fix(uitests): move forced logout to class setUp, fix ambiguous Ver Detalhes match
Previous commit's per-test logoutIfAuthenticated() in HomeGuestFlowTests
and ProfileLoggedOutFlowTests ran the guest address-picker navigation
dance (reachProfileTabRegardlessOfAuthState) twice back-to-back per test
- once in the new launch helper, once again inside reachGuestHome/
reachLoggedOutProfile - doubling exposure to that dance's known race
and regressing 5 previously-stable tests ('Login never completed' was
fixed, but new failures appeared in its place).
Moved the forced logout into override class func setUp() so it runs
once per class instead of once per test; test bodies are back to the
plain XCUIApplication()+launch() pattern.
Also fixed OrderDetailsFlowTests: 'Ver Detalhes' now matches multiple
elements once the QA account has more than one real order (previously
masked because login itself was failing first). Switched to a label
predicate + .firstMatch.
See decisions/2026-09-11-ui-test-shared-login-session.md follow-ups.
This commit is contained in:
@@ -6,23 +6,31 @@ import XCTest
|
|||||||
/// blocked by a separate login-navigation issue documented there and are
|
/// blocked by a separate login-navigation issue documented there and are
|
||||||
/// not covered here.
|
/// not covered here.
|
||||||
final class HomeGuestFlowTests: XCTestCase {
|
final class HomeGuestFlowTests: XCTestCase {
|
||||||
|
/// Authenticated-flow tests no longer log out after themselves (see
|
||||||
|
/// decisions/2026-09-11-ui-test-shared-login-session.md), so this
|
||||||
|
/// class must force a clean logged-out state before its tests run,
|
||||||
|
/// rather than assume one. Done once per class, not once per test:
|
||||||
|
/// the underlying navigate-to-Profile-tab dance
|
||||||
|
/// (`reachProfileTabRegardlessOfAuthState`) is a known race against
|
||||||
|
/// the guest address-picker sheet (see
|
||||||
|
/// decisions/2026-08-06-ui-test-account-and-app-attest-bypass.md) -
|
||||||
|
/// running it a second time back-to-back inside every test (once
|
||||||
|
/// here, once again inside `reachGuestHome`'s own callers) doubled
|
||||||
|
/// exposure to that race and broke tests that were previously stable.
|
||||||
|
override class func setUp() {
|
||||||
|
super.setUp()
|
||||||
|
let app = XCUIApplication()
|
||||||
|
app.launch()
|
||||||
|
app.logoutIfAuthenticated()
|
||||||
|
}
|
||||||
|
|
||||||
override func setUpWithError() throws {
|
override func setUpWithError() throws {
|
||||||
continueAfterFailure = false
|
continueAfterFailure = false
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Authenticated-flow tests no longer log out after themselves (see
|
func testHomeRendersSearchAndGreeting() throws {
|
||||||
/// decisions/2026-09-11-ui-test-shared-login-session.md), so guest
|
|
||||||
/// tests must force a clean logged-out state themselves right after
|
|
||||||
/// launch, rather than assume one.
|
|
||||||
private func launchGuestApp() -> XCUIApplication {
|
|
||||||
let app = XCUIApplication()
|
let app = XCUIApplication()
|
||||||
app.launch()
|
app.launch()
|
||||||
app.logoutIfAuthenticated()
|
|
||||||
return app
|
|
||||||
}
|
|
||||||
|
|
||||||
func testHomeRendersSearchAndGreeting() throws {
|
|
||||||
let app = launchGuestApp()
|
|
||||||
XCTAssertTrue(app.reachGuestHome(), "Never reached Home's search field")
|
XCTAssertTrue(app.reachGuestHome(), "Never reached Home's search field")
|
||||||
|
|
||||||
XCTAssertTrue(app.staticTexts["ENTREGAR EM:"].exists)
|
XCTAssertTrue(app.staticTexts["ENTREGAR EM:"].exists)
|
||||||
@@ -31,7 +39,8 @@ final class HomeGuestFlowTests: XCTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testHomeShowsDefaultCategoryChip() throws {
|
func testHomeShowsDefaultCategoryChip() throws {
|
||||||
let app = launchGuestApp()
|
let app = XCUIApplication()
|
||||||
|
app.launch()
|
||||||
XCTAssertTrue(app.reachGuestHome())
|
XCTAssertTrue(app.reachGuestHome())
|
||||||
|
|
||||||
XCTAssertTrue(app.staticTexts["Categories"].exists)
|
XCTAssertTrue(app.staticTexts["Categories"].exists)
|
||||||
@@ -39,7 +48,8 @@ final class HomeGuestFlowTests: XCTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testHomeShowsGuestLocationPromptWhenNoLocationChosen() throws {
|
func testHomeShowsGuestLocationPromptWhenNoLocationChosen() throws {
|
||||||
let app = launchGuestApp()
|
let app = XCUIApplication()
|
||||||
|
app.launch()
|
||||||
XCTAssertTrue(app.reachGuestHome())
|
XCTAssertTrue(app.reachGuestHome())
|
||||||
|
|
||||||
// No guest state/city selected -> Home's own inline message, not
|
// No guest state/city selected -> Home's own inline message, not
|
||||||
@@ -48,7 +58,8 @@ final class HomeGuestFlowTests: XCTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testTappingAddressChipReopensLocationPicker() throws {
|
func testTappingAddressChipReopensLocationPicker() throws {
|
||||||
let app = launchGuestApp()
|
let app = XCUIApplication()
|
||||||
|
app.launch()
|
||||||
XCTAssertTrue(app.reachGuestHome())
|
XCTAssertTrue(app.reachGuestHome())
|
||||||
|
|
||||||
// The "ENTREGAR EM:" address button re-opens the same address
|
// The "ENTREGAR EM:" address button re-opens the same address
|
||||||
|
|||||||
@@ -21,8 +21,14 @@ final class OrderDetailsFlowTests: XCTestCase {
|
|||||||
XCTAssertTrue(app.staticTexts["Meus Pedidos"].waitForExistence(timeout: 10))
|
XCTAssertTrue(app.staticTexts["Meus Pedidos"].waitForExistence(timeout: 10))
|
||||||
|
|
||||||
// "Ver Detalhes" appears once per order card; this account has real
|
// "Ver Detalhes" appears once per order card; this account has real
|
||||||
// order history (confirmed in AuthenticatedProfileNavigationTests).
|
// order history (confirmed in AuthenticatedProfileNavigationTests)
|
||||||
let detailsButton = app.buttons["Ver Detalhes"]
|
// which has grown to more than one order across many manual and
|
||||||
|
// automated runs, so an exact-match subscript now resolves to
|
||||||
|
// multiple elements and throws on .tap() ("Find single matching
|
||||||
|
// element"). .firstMatch tolerates any count - the test only
|
||||||
|
// needs *an* order's details screen to be reachable, not a
|
||||||
|
// specific one.
|
||||||
|
let detailsButton = app.buttons.matching(NSPredicate(format: "label == %@", "Ver Detalhes")).firstMatch
|
||||||
XCTAssertTrue(detailsButton.waitForExistence(timeout: 10), "No orders with a 'Ver Detalhes' action found")
|
XCTAssertTrue(detailsButton.waitForExistence(timeout: 10), "No orders with a 'Ver Detalhes' action found")
|
||||||
detailsButton.tap()
|
detailsButton.tap()
|
||||||
|
|
||||||
|
|||||||
@@ -3,23 +3,32 @@ import XCTest
|
|||||||
/// Covers the Profile tab's logged-out state (`ProfileLoggedOutView`) and
|
/// Covers the Profile tab's logged-out state (`ProfileLoggedOutView`) and
|
||||||
/// the auth intro screen (`LoginView`) it opens.
|
/// the auth intro screen (`LoginView`) it opens.
|
||||||
final class ProfileLoggedOutFlowTests: XCTestCase {
|
final class ProfileLoggedOutFlowTests: XCTestCase {
|
||||||
|
/// Authenticated-flow tests no longer log out after themselves (see
|
||||||
|
/// decisions/2026-09-11-ui-test-shared-login-session.md), so this
|
||||||
|
/// class must force a clean logged-out state before its tests run,
|
||||||
|
/// rather than assume one. Done once per class, not once per test:
|
||||||
|
/// the underlying navigate-to-Profile-tab dance
|
||||||
|
/// (`reachProfileTabRegardlessOfAuthState`) is a known race against
|
||||||
|
/// the guest address-picker sheet (see
|
||||||
|
/// decisions/2026-08-06-ui-test-account-and-app-attest-bypass.md) -
|
||||||
|
/// running it a second time back-to-back inside every test (once
|
||||||
|
/// here, once again inside `reachLoggedOutProfile`'s own callers)
|
||||||
|
/// doubled exposure to that race and broke tests that were
|
||||||
|
/// previously stable.
|
||||||
|
override class func setUp() {
|
||||||
|
super.setUp()
|
||||||
|
let app = XCUIApplication()
|
||||||
|
app.launch()
|
||||||
|
app.logoutIfAuthenticated()
|
||||||
|
}
|
||||||
|
|
||||||
override func setUpWithError() throws {
|
override func setUpWithError() throws {
|
||||||
continueAfterFailure = false
|
continueAfterFailure = false
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Authenticated-flow tests no longer log out after themselves (see
|
func testLoggedOutProfileShowsLoginPrompt() throws {
|
||||||
/// decisions/2026-09-11-ui-test-shared-login-session.md), so
|
|
||||||
/// logged-out tests must force a clean state themselves right after
|
|
||||||
/// launch, rather than assume one.
|
|
||||||
private func launchLoggedOutApp() -> XCUIApplication {
|
|
||||||
let app = XCUIApplication()
|
let app = XCUIApplication()
|
||||||
app.launch()
|
app.launch()
|
||||||
app.logoutIfAuthenticated()
|
|
||||||
return app
|
|
||||||
}
|
|
||||||
|
|
||||||
func testLoggedOutProfileShowsLoginPrompt() throws {
|
|
||||||
let app = launchLoggedOutApp()
|
|
||||||
XCTAssertTrue(app.reachLoggedOutProfile(), "Never reached the logged-out profile screen")
|
XCTAssertTrue(app.reachLoggedOutProfile(), "Never reached the logged-out profile screen")
|
||||||
|
|
||||||
XCTAssertTrue(app.staticTexts["Entre na sua conta"].exists)
|
XCTAssertTrue(app.staticTexts["Entre na sua conta"].exists)
|
||||||
@@ -32,7 +41,8 @@ final class ProfileLoggedOutFlowTests: XCTestCase {
|
|||||||
/// a working way back out (its `LCENavigationView` back button, which
|
/// a working way back out (its `LCENavigationView` back button, which
|
||||||
/// sets `root = .main`).
|
/// sets `root = .main`).
|
||||||
func testAuthIntroScreenShowsChoicesAndCanGoBack() throws {
|
func testAuthIntroScreenShowsChoicesAndCanGoBack() throws {
|
||||||
let app = launchLoggedOutApp()
|
let app = XCUIApplication()
|
||||||
|
app.launch()
|
||||||
XCTAssertTrue(app.reachLoggedOutProfile())
|
XCTAssertTrue(app.reachLoggedOutProfile())
|
||||||
|
|
||||||
app.buttons["Entrar ou Cadastrar"].tap()
|
app.buttons["Entrar ou Cadastrar"].tap()
|
||||||
@@ -54,7 +64,8 @@ final class ProfileLoggedOutFlowTests: XCTestCase {
|
|||||||
/// app's standard `AppBackButtonIcon` via `LCENavigationView`, not the
|
/// app's standard `AppBackButtonIcon` via `LCENavigationView`, not the
|
||||||
/// oversized iOS 26 system glass back button.
|
/// oversized iOS 26 system glass back button.
|
||||||
func testPushedAuthScreensUseAppBackButton() throws {
|
func testPushedAuthScreensUseAppBackButton() throws {
|
||||||
let app = launchLoggedOutApp()
|
let app = XCUIApplication()
|
||||||
|
app.launch()
|
||||||
XCTAssertTrue(app.reachLoggedOutProfile())
|
XCTAssertTrue(app.reachLoggedOutProfile())
|
||||||
app.buttons["Entrar ou Cadastrar"].tap()
|
app.buttons["Entrar ou Cadastrar"].tap()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user