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:
@@ -24,12 +24,3 @@ jobs:
|
|||||||
|
|
||||||
- name: Run tests with coverage
|
- name: Run tests with coverage
|
||||||
run: fastlane tests
|
run: fastlane tests
|
||||||
|
|
||||||
- name: Show UI test failure diagnostics (if any)
|
|
||||||
if: always()
|
|
||||||
run: |
|
|
||||||
if [ -f "$TMPDIR/pedifoods_uitest_diag.log" ]; then
|
|
||||||
cat "$TMPDIR/pedifoods_uitest_diag.log"
|
|
||||||
else
|
|
||||||
echo "No UI test diagnostic log was written."
|
|
||||||
fi
|
|
||||||
|
|||||||
@@ -6,31 +6,45 @@ import XCTest
|
|||||||
/// so Home shows a real store list - see
|
/// so Home shows a real store list - see
|
||||||
/// decisions/2026-08-06-ui-test-account-and-app-attest-bypass.md.
|
/// decisions/2026-08-06-ui-test-account-and-app-attest-bypass.md.
|
||||||
final class CartCheckoutFlowTests: XCTestCase {
|
final class CartCheckoutFlowTests: XCTestCase {
|
||||||
/// Plain `print()` from inside a UI test does not reach the CI log on
|
/// Two earlier diagnostic attempts for the timeouts below both failed
|
||||||
/// this runner - fastlane's xcodebuild log formatter only relays
|
/// silently: plain `print()` never reached fastlane's xcodebuild log
|
||||||
/// lines matching its own known patterns (Test Suite/Test Case/etc.)
|
/// (its formatter only relays lines matching its own known patterns),
|
||||||
/// and silently drops anything else, confirmed by pasting the full
|
/// and a file written via `NSTemporaryDirectory()` was never found by
|
||||||
/// log around a timeout and finding no trace of a print()'d marker
|
/// a later CI step's `cat` either - because a UI test target's code
|
||||||
/// string. Writing to a file `cat`'d by a later CI step
|
/// runs as `PediFoodsUITests-Runner`, an app *inside the simulator*,
|
||||||
/// (`.gitea/workflows/test.yml`) works regardless. `NSTemporaryDirectory()`
|
/// sandboxed to that simulator device's own container filesystem;
|
||||||
/// resolves to the CI job's `$TMPDIR`, consistent for the whole job
|
/// `NSTemporaryDirectory()` there resolves to a path under
|
||||||
/// (both the xcodebuild-spawned test process and the later shell step
|
/// `.../CoreSimulator/Devices/<UDID>/data/...`, invisible to the
|
||||||
/// run under the same user session) - unlike the fixed scratch path
|
/// host shell's `$TMPDIR` a later workflow step reads from.
|
||||||
/// this replaced, which was hardcoded to a different machine/session
|
///
|
||||||
/// and silently no-op'd here.
|
/// A `XCTAssertTrue` failure *message* is the one diagnostic channel
|
||||||
static let diagnosticLogPath = NSTemporaryDirectory() + "pedifoods_uitest_diag.log"
|
/// already confirmed working end to end every single run (the
|
||||||
|
/// "Store Detail's product list never loaded" text itself always
|
||||||
private static func dumpDiagnostics(_ title: String, app: XCUIApplication) {
|
/// showed up). Its `message` parameter is `@autoclosure`, evaluated
|
||||||
let entry = "\n=== \(title) ===\n\(app.debugDescription)\n"
|
/// only on actual failure - so building a short live-state summary
|
||||||
guard let data = entry.data(using: .utf8) else { return }
|
/// and interpolating it into the message costs nothing when the
|
||||||
if FileManager.default.fileExists(atPath: diagnosticLogPath),
|
/// assertion passes, and needs no file access or CI plumbing at all.
|
||||||
let handle = FileHandle(forWritingAtPath: diagnosticLogPath) {
|
private func quickDiagnostics(_ app: XCUIApplication) -> String {
|
||||||
handle.seekToEndOfFile()
|
var signals: [String] = []
|
||||||
handle.write(data)
|
if app.staticTexts["Loja fechada no momento."].exists {
|
||||||
handle.closeFile()
|
signals.append("closed-store snackbar visible")
|
||||||
} else {
|
|
||||||
FileManager.default.createFile(atPath: diagnosticLogPath, contents: data)
|
|
||||||
}
|
}
|
||||||
|
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
|
/// 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.
|
/// a known-empty cart instead of an ever-growing one.
|
||||||
override class func setUp() {
|
override class func setUp() {
|
||||||
super.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()
|
let app = XCUIApplication()
|
||||||
app.launch()
|
app.launch()
|
||||||
app.ensureLoggedIn()
|
app.ensureLoggedIn()
|
||||||
@@ -136,10 +146,10 @@ final class CartCheckoutFlowTests: XCTestCase {
|
|||||||
if dismissed == false {
|
if dismissed == false {
|
||||||
app.swipeDown()
|
app.swipeDown()
|
||||||
}
|
}
|
||||||
if app.staticTexts["Finalizar Pedido"].waitForExistence(timeout: 10) == false {
|
XCTAssertTrue(
|
||||||
Self.dumpDiagnostics("Never returned to Checkout after Alterar dismiss", app: app)
|
app.staticTexts["Finalizar Pedido"].waitForExistence(timeout: 10),
|
||||||
}
|
"Never returned to Checkout. \(quickDiagnostics(app))"
|
||||||
XCTAssertTrue(app.staticTexts["Finalizar Pedido"].exists, "Never returned to Checkout")
|
)
|
||||||
|
|
||||||
// Deliberately not tapping "Confirmar e Pagar" - see doc comment.
|
// 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
|
// distance + status all combine into its accessibility label), not
|
||||||
// a plain static text.
|
// a plain static text.
|
||||||
let storeCard = app.buttons.matching(NSPredicate(format: "label CONTAINS[c] %@", "MARIBA")).firstMatch
|
let storeCard = app.buttons.matching(NSPredicate(format: "label CONTAINS[c] %@", "MARIBA")).firstMatch
|
||||||
if storeCard.waitForExistence(timeout: 15) == false {
|
XCTAssertTrue(
|
||||||
Self.dumpDiagnostics("No MARIBA store card on Home", app: app)
|
storeCard.waitForExistence(timeout: 15),
|
||||||
}
|
"Expected store card never appeared on Home. \(quickDiagnostics(app))"
|
||||||
XCTAssertTrue(storeCard.exists, "Expected store card never appeared on Home")
|
)
|
||||||
storeCard.tap()
|
storeCard.tap()
|
||||||
|
|
||||||
// A plain (non-addon, non-pizza) product row's "+" control adds
|
// 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
|
// keeps identifier "plus" regardless of quantity, so target that
|
||||||
// directly instead - same fix pattern as the cart-tab icon.
|
// directly instead - same fix pattern as the cart-tab icon.
|
||||||
let addButton = app.images.matching(identifier: "plus").firstMatch
|
let addButton = app.images.matching(identifier: "plus").firstMatch
|
||||||
if addButton.waitForExistence(timeout: 25) == false {
|
XCTAssertTrue(
|
||||||
Self.dumpDiagnostics("Store Detail timeout - product list never loaded", app: app)
|
addButton.waitForExistence(timeout: 25),
|
||||||
}
|
"Store Detail's product list never loaded. \(quickDiagnostics(app))"
|
||||||
XCTAssertTrue(addButton.exists, "Store Detail's product list never loaded")
|
)
|
||||||
addButton.tap()
|
addButton.tap()
|
||||||
|
|
||||||
// Once the cart has items, the tab bar button's accessible label
|
// Once the cart has items, the tab bar button's accessible label
|
||||||
|
|||||||
Reference in New Issue
Block a user