test(uitests): embed live-state diagnostics in assertion messages, not a file

The file-based diagnostic dump never worked: PediFoodsUITests-Runner
runs as an app inside the simulator, sandboxed to that device's own
container filesystem. NSTemporaryDirectory() called from test code
resolves inside the simulator's data container, invisible to the host
shell's $TMPDIR a later CI step tried to cat - confirmed by the new
step printing 'No UI test diagnostic log was written.' every time.

Replaced with quickDiagnostics(_:), which builds a short live-state
summary (closed-store snackbar, spinning activity indicator, any open
alert, visible 'erro' text, first ~15 visible static texts) and
interpolates it directly into each of the three XCTAssertTrue failure
messages - the one channel already confirmed working end to end on
every run. message: is @autoclosure, so this costs nothing when the
assertion passes. Removed the dead file-write helper and the now-unused
CI step.

See decisions/2026-09-11-ui-test-shared-login-session.md follow-up.
This commit is contained in:
2026-09-11 10:57:48 -03:00
parent 3f99ebcba1
commit 6da8b05cfb
2 changed files with 50 additions and 49 deletions

View File

@@ -6,31 +6,45 @@ 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)
/// Two earlier diagnostic attempts for the timeouts below both failed
/// silently: plain `print()` never reached fastlane's xcodebuild log
/// (its formatter only relays lines matching its own known patterns),
/// and a file written via `NSTemporaryDirectory()` was never found by
/// a later CI step's `cat` either - because a UI test target's code
/// runs as `PediFoodsUITests-Runner`, an app *inside the simulator*,
/// sandboxed to that simulator device's own container filesystem;
/// `NSTemporaryDirectory()` there resolves to a path under
/// `.../CoreSimulator/Devices/<UDID>/data/...`, invisible to the
/// host shell's `$TMPDIR` a later workflow step reads from.
///
/// A `XCTAssertTrue` failure *message* is the one diagnostic channel
/// already confirmed working end to end every single run (the
/// "Store Detail's product list never loaded" text itself always
/// showed up). Its `message` parameter is `@autoclosure`, evaluated
/// only on actual failure - so building a short live-state summary
/// and interpolating it into the message costs nothing when the
/// assertion passes, and needs no file access or CI plumbing at all.
private func quickDiagnostics(_ app: XCUIApplication) -> String {
var signals: [String] = []
if app.staticTexts["Loja fechada no momento."].exists {
signals.append("closed-store snackbar visible")
}
if app.activityIndicators.firstMatch.exists {
signals.append("activity indicator still spinning")
}
if app.alerts.firstMatch.exists {
signals.append("alert showing: \(app.alerts.firstMatch.label)")
}
let errorText = app.staticTexts.matching(NSPredicate(format: "label CONTAINS[c] %@", "erro")).firstMatch
if errorText.exists {
signals.append("error text visible: \(errorText.label)")
}
let visibleTexts = app.staticTexts.allElementsBoundByIndex
.prefix(15)
.map(\.label)
.joined(separator: " | ")
signals.append("visible static texts: [\(visibleTexts)]")
return signals.joined(separator: "; ")
}
/// The cart is on-device state (`CartState`/`SessionStateStore`, see
@@ -49,10 +63,6 @@ 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()
@@ -136,10 +146,10 @@ final class CartCheckoutFlowTests: XCTestCase {
if dismissed == false {
app.swipeDown()
}
if app.staticTexts["Finalizar Pedido"].waitForExistence(timeout: 10) == false {
Self.dumpDiagnostics("Never returned to Checkout after Alterar dismiss", app: app)
}
XCTAssertTrue(app.staticTexts["Finalizar Pedido"].exists, "Never returned to Checkout")
XCTAssertTrue(
app.staticTexts["Finalizar Pedido"].waitForExistence(timeout: 10),
"Never returned to Checkout. \(quickDiagnostics(app))"
)
// Deliberately not tapping "Confirmar e Pagar" - see doc comment.
}
@@ -209,10 +219,10 @@ final class CartCheckoutFlowTests: XCTestCase {
// distance + status all combine into its accessibility label), not
// a plain static text.
let storeCard = app.buttons.matching(NSPredicate(format: "label CONTAINS[c] %@", "MARIBA")).firstMatch
if storeCard.waitForExistence(timeout: 15) == false {
Self.dumpDiagnostics("No MARIBA store card on Home", app: app)
}
XCTAssertTrue(storeCard.exists, "Expected store card never appeared on Home")
XCTAssertTrue(
storeCard.waitForExistence(timeout: 15),
"Expected store card never appeared on Home. \(quickDiagnostics(app))"
)
storeCard.tap()
// A plain (non-addon, non-pizza) product row's "+" control adds
@@ -229,10 +239,10 @@ final class CartCheckoutFlowTests: XCTestCase {
// keeps identifier "plus" regardless of quantity, so target that
// directly instead - same fix pattern as the cart-tab icon.
let addButton = app.images.matching(identifier: "plus").firstMatch
if addButton.waitForExistence(timeout: 25) == false {
Self.dumpDiagnostics("Store Detail timeout - product list never loaded", app: app)
}
XCTAssertTrue(addButton.exists, "Store Detail's product list never loaded")
XCTAssertTrue(
addButton.waitForExistence(timeout: 25),
"Store Detail's product list never loaded. \(quickDiagnostics(app))"
)
addButton.tap()
// Once the cart has items, the tab bar button's accessible label