test(uitests): cover both add-on and plain-add product flows

The original helper only ever tapped the *first* product's add button
and assumed it went straight to Cart. That's the same single-path
assumption that produced the wrong 'product list never loaded'
diagnosis - the first product on this catalog ('Alcatra') has add-ons
and opens ProductDetailSheet ('Detalhes') instead of adding directly.
Fixing just the button selector would have hidden this and failed
differently on the next run.

Renamed reachCheckoutWithOneItem to reachCheckoutWithProducts(_:count:
Int = 3) and added addProduct(atIndex:in:), which taps the Nth
product's add button and branches on the real outcome: if
ProductDetailSheet appears, confirms via its own main action button
('Atualizar...') and waits for it to dismiss; otherwise the product
already added directly. Loops over the first 3 products (capped by the
actual button count) so both flows are exercised on every run,
regardless of which specific products the real catalog happens to give
add-ons to.

See decisions/2026-09-11-ui-test-shared-login-session.md follow-up.
This commit is contained in:
2026-09-11 11:21:30 -03:00
parent 2ebe97e078
commit 6fead38ad7

View File

@@ -98,7 +98,7 @@ final class CartCheckoutFlowTests: XCTestCase {
func testAddProductToCartAndReachCheckout() throws {
let app = XCUIApplication()
app.launch()
try reachCheckoutWithOneItem(app)
try reachCheckoutWithProducts(app)
}
/// Exercises CheckoutView's payment-method selection and the address
@@ -108,7 +108,7 @@ final class CartCheckoutFlowTests: XCTestCase {
func testCheckoutPaymentMethodSelectionAndAddressAlterar() throws {
let app = XCUIApplication()
app.launch()
try reachCheckoutWithOneItem(app)
try reachCheckoutWithProducts(app)
XCTAssertTrue(app.staticTexts["MÉTODO DE PAGAMENTO"].waitForExistence(timeout: 5))
@@ -167,7 +167,7 @@ final class CartCheckoutFlowTests: XCTestCase {
func testConfirmarEPagarWithCreditCardOpensCardSelectionWithoutSubmitting() throws {
let app = XCUIApplication()
app.launch()
try reachCheckoutWithOneItem(app)
try reachCheckoutWithProducts(app)
let creditCardRow = app.buttons.matching(NSPredicate(format: "label CONTAINS[c] %@", "Cartão de Crédito")).firstMatch
XCTAssertTrue(creditCardRow.waitForExistence(timeout: 5), "This store is expected to offer Cartão de Crédito")
@@ -209,7 +209,49 @@ final class CartCheckoutFlowTests: XCTestCase {
app.swipeDown()
}
private func reachCheckoutWithOneItem(_ app: XCUIApplication) throws {
/// Taps the Nth product row's add button (0-based, top to bottom) and
/// handles both real outcomes on this catalog: a product with add-ons
/// (e.g. "Alcatra", the first product) opens `ProductDetailSheet`
/// ("Detalhes") and needs its own confirm tap; a plain product (no
/// `addonGroups`) adds directly with no sheet at all
/// (`StoreDetailView+Components.swift`). Which products have add-ons
/// is real catalog data, not something this suite controls, so both
/// paths must be handled rather than assumed - asserting "no sheet
/// appeared" for a product that genuinely has add-ons is exactly the
/// wrong-diagnosis mistake this suite already made once for this same
/// screen (see decisions/2026-09-11-ui-test-shared-login-session.md).
/// `ProductDetailSheet` requires no addon selection to confirm -
/// `quantity` defaults to 1 and addons default to none, so its main
/// action button ("Atualizar <price>") is always tappable as-is.
private func addProduct(atIndex index: Int, in app: XCUIApplication) {
let addButtons = app.buttons.matching(identifier: "storeDetailProductAddButton")
let button = addButtons.element(boundBy: index)
XCTAssertTrue(
button.waitForExistence(timeout: 25),
"Product add button at index \(index) never appeared. \(quickDiagnostics(app))"
)
button.tap()
if app.staticTexts["Detalhes"].waitForExistence(timeout: 3) {
let confirmButton = app.buttons.matching(NSPredicate(format: "label BEGINSWITH %@", "Atualizar")).firstMatch
XCTAssertTrue(
confirmButton.waitForExistence(timeout: 5),
"ProductDetailSheet's confirm button never appeared for product at index \(index). \(quickDiagnostics(app))"
)
confirmButton.tap()
XCTAssertFalse(
app.staticTexts["Detalhes"].waitForExistence(timeout: 3),
"ProductDetailSheet never dismissed after confirming product at index \(index)"
)
}
}
/// Adds the first `count` products from Store Detail's list, top to
/// bottom, then reaches Checkout. Defaults to 3 so both the add-on
/// flow (first product) and the plain-add flow (later products) are
/// both exercised on every run, not just whichever one the first
/// product happens to be.
private func reachCheckoutWithProducts(_ app: XCUIApplication, count: Int = 3) throws {
XCTAssertTrue(app.ensureLoggedIn(), "Login never completed")
// ensureLoggedIn can land on whichever tab it detected the
@@ -237,16 +279,17 @@ final class CartCheckoutFlowTests: XCTestCase {
// clearly rendered on screen while `images.matching(identifier:
// "plus")` still found nothing, and the same "plus" systemName is
// separately reused by CartView's quantity stepper, making it an
// ambiguous identifier to search by in the first place. Matching
// the button itself (not a nested image) also sidesteps the
// quantity-badge accessibility-label-merging issue that affected
// the tab bar's cart icon elsewhere in this suite.
let addButton = app.buttons.matching(identifier: "storeDetailProductAddButton").firstMatch
// ambiguous identifier to search by in the first place.
let addButtons = app.buttons.matching(identifier: "storeDetailProductAddButton")
XCTAssertTrue(
addButton.waitForExistence(timeout: 25),
addButtons.firstMatch.waitForExistence(timeout: 25),
"Store Detail's product list never loaded. \(quickDiagnostics(app))"
)
addButton.tap()
let actualCount = min(count, addButtons.count)
for index in 0..<actualCount {
addProduct(atIndex: index, in: app)
}
// Once the cart has items, the tab bar button's accessible label
// becomes just the badge count ("4") instead of "Shopping Cart" -
@@ -259,7 +302,7 @@ final class CartCheckoutFlowTests: XCTestCase {
XCTAssertTrue(cartTab.waitForExistence(timeout: 5))
cartTab.tap()
XCTAssertTrue(app.staticTexts["Meu Carrinho"].waitForExistence(timeout: 10))
XCTAssertFalse(app.staticTexts["Seu carrinho está vazio"].exists, "Cart still shows empty after adding a product")
XCTAssertFalse(app.staticTexts["Seu carrinho está vazio"].exists, "Cart still shows empty after adding products")
app.buttons["Ir para o Pagamento"].tap()
XCTAssertTrue(app.staticTexts["Finalizar Pedido"].waitForExistence(timeout: 10), "Never reached Checkout")