test(uitests): write failure diagnostics to a file, not print()

Confirmed (pasted full CI log around a timeout, searched for the
print()'d marker string, found nothing) that plain print() from inside
a UI test never reaches fastlane's xcodebuild log output on this
runner - its formatter only relays lines matching its own known
patterns and drops everything else.

CartCheckoutFlowTests now writes app.debugDescription to
NSTemporaryDirectory()+'pedifoods_uitest_diag.log' on each of its three
failure points (shared dumpDiagnostics helper, appending), clearing any
stale copy in class setUp(). test.yml gained a step after 'Run tests
with coverage' (if: always()) that cats that file when present - TMPDIR
is stable for the whole CI job, unlike the per-run-hashed workspace
path that broke the original screenshot attempt.

See decisions/2026-09-11-ui-test-shared-login-session.md follow-up.
This commit is contained in:
2026-09-11 10:44:02 -03:00
parent 1a7d5c3d51
commit 3f99ebcba1
2 changed files with 43 additions and 13 deletions

View File

@@ -6,6 +6,33 @@ import XCTest
/// so Home shows a real store list - see
/// decisions/2026-08-06-ui-test-account-and-app-attest-bypass.md.
final class CartCheckoutFlowTests: XCTestCase {
/// Plain `print()` from inside a UI test does not reach the CI log on
/// this runner - fastlane's xcodebuild log formatter only relays
/// lines matching its own known patterns (Test Suite/Test Case/etc.)
/// and silently drops anything else, confirmed by pasting the full
/// log around a timeout and finding no trace of a print()'d marker
/// string. Writing to a file `cat`'d by a later CI step
/// (`.gitea/workflows/test.yml`) works regardless. `NSTemporaryDirectory()`
/// resolves to the CI job's `$TMPDIR`, consistent for the whole job
/// (both the xcodebuild-spawned test process and the later shell step
/// run under the same user session) - unlike the fixed scratch path
/// this replaced, which was hardcoded to a different machine/session
/// and silently no-op'd here.
static let diagnosticLogPath = NSTemporaryDirectory() + "pedifoods_uitest_diag.log"
private static func dumpDiagnostics(_ title: String, app: XCUIApplication) {
let entry = "\n=== \(title) ===\n\(app.debugDescription)\n"
guard let data = entry.data(using: .utf8) else { return }
if FileManager.default.fileExists(atPath: diagnosticLogPath),
let handle = FileHandle(forWritingAtPath: diagnosticLogPath) {
handle.seekToEndOfFile()
handle.write(data)
handle.closeFile()
} else {
FileManager.default.createFile(atPath: diagnosticLogPath, contents: data)
}
}
/// The cart is on-device state (`CartState`/`SessionStateStore`, see
/// `AppState.swift`), not server-side - it persists across app
/// relaunches on this simulator just like the login session (see
@@ -22,6 +49,10 @@ final class CartCheckoutFlowTests: XCTestCase {
/// a known-empty cart instead of an ever-growing one.
override class func setUp() {
super.setUp()
// Start each run with a clean diagnostic log, not a leftover from
// a previous one.
try? FileManager.default.removeItem(atPath: diagnosticLogPath)
let app = XCUIApplication()
app.launch()
app.ensureLoggedIn()
@@ -106,8 +137,7 @@ final class CartCheckoutFlowTests: XCTestCase {
app.swipeDown()
}
if app.staticTexts["Finalizar Pedido"].waitForExistence(timeout: 10) == false {
print("=== Never returned to Checkout after Alterar dismiss - accessibility hierarchy ===")
print(app.debugDescription)
Self.dumpDiagnostics("Never returned to Checkout after Alterar dismiss", app: app)
}
XCTAssertTrue(app.staticTexts["Finalizar Pedido"].exists, "Never returned to Checkout")
@@ -180,8 +210,7 @@ final class CartCheckoutFlowTests: XCTestCase {
// a plain static text.
let storeCard = app.buttons.matching(NSPredicate(format: "label CONTAINS[c] %@", "MARIBA")).firstMatch
if storeCard.waitForExistence(timeout: 15) == false {
print("=== No MARIBA store card on Home - accessibility hierarchy ===")
print(app.debugDescription)
Self.dumpDiagnostics("No MARIBA store card on Home", app: app)
}
XCTAssertTrue(storeCard.exists, "Expected store card never appeared on Home")
storeCard.tap()
@@ -201,15 +230,7 @@ final class CartCheckoutFlowTests: XCTestCase {
// directly instead - same fix pattern as the cart-tab icon.
let addButton = app.images.matching(identifier: "plus").firstMatch
if addButton.waitForExistence(timeout: 25) == false {
// A hardcoded scratch-path screenshot write here was silently
// no-oping on the CI runner (try? swallowed a write failure
// into a directory that doesn't exist on this machine's user
// account) - printing the accessibility hierarchy instead
// shows up directly in the CI log with no extra file access
// needed, same technique already used in
// decisions/2026-08-06-ui-test-account-and-app-attest-bypass.md.
print("=== Store Detail timeout - accessibility hierarchy ===")
print(app.debugDescription)
Self.dumpDiagnostics("Store Detail timeout - product list never loaded", app: app)
}
XCTAssertTrue(addButton.exists, "Store Detail's product list never loaded")
addButton.tap()