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:
@@ -98,7 +98,7 @@ final class CartCheckoutFlowTests: XCTestCase {
|
|||||||
func testAddProductToCartAndReachCheckout() throws {
|
func testAddProductToCartAndReachCheckout() throws {
|
||||||
let app = XCUIApplication()
|
let app = XCUIApplication()
|
||||||
app.launch()
|
app.launch()
|
||||||
try reachCheckoutWithOneItem(app)
|
try reachCheckoutWithProducts(app)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Exercises CheckoutView's payment-method selection and the address
|
/// Exercises CheckoutView's payment-method selection and the address
|
||||||
@@ -108,7 +108,7 @@ final class CartCheckoutFlowTests: XCTestCase {
|
|||||||
func testCheckoutPaymentMethodSelectionAndAddressAlterar() throws {
|
func testCheckoutPaymentMethodSelectionAndAddressAlterar() throws {
|
||||||
let app = XCUIApplication()
|
let app = XCUIApplication()
|
||||||
app.launch()
|
app.launch()
|
||||||
try reachCheckoutWithOneItem(app)
|
try reachCheckoutWithProducts(app)
|
||||||
|
|
||||||
XCTAssertTrue(app.staticTexts["MÉTODO DE PAGAMENTO"].waitForExistence(timeout: 5))
|
XCTAssertTrue(app.staticTexts["MÉTODO DE PAGAMENTO"].waitForExistence(timeout: 5))
|
||||||
|
|
||||||
@@ -167,7 +167,7 @@ final class CartCheckoutFlowTests: XCTestCase {
|
|||||||
func testConfirmarEPagarWithCreditCardOpensCardSelectionWithoutSubmitting() throws {
|
func testConfirmarEPagarWithCreditCardOpensCardSelectionWithoutSubmitting() throws {
|
||||||
let app = XCUIApplication()
|
let app = XCUIApplication()
|
||||||
app.launch()
|
app.launch()
|
||||||
try reachCheckoutWithOneItem(app)
|
try reachCheckoutWithProducts(app)
|
||||||
|
|
||||||
let creditCardRow = app.buttons.matching(NSPredicate(format: "label CONTAINS[c] %@", "Cartão de Crédito")).firstMatch
|
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")
|
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()
|
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")
|
XCTAssertTrue(app.ensureLoggedIn(), "Login never completed")
|
||||||
|
|
||||||
// ensureLoggedIn can land on whichever tab it detected the
|
// 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:
|
// clearly rendered on screen while `images.matching(identifier:
|
||||||
// "plus")` still found nothing, and the same "plus" systemName is
|
// "plus")` still found nothing, and the same "plus" systemName is
|
||||||
// separately reused by CartView's quantity stepper, making it an
|
// separately reused by CartView's quantity stepper, making it an
|
||||||
// ambiguous identifier to search by in the first place. Matching
|
// ambiguous identifier to search by in the first place.
|
||||||
// the button itself (not a nested image) also sidesteps the
|
let addButtons = app.buttons.matching(identifier: "storeDetailProductAddButton")
|
||||||
// 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
|
|
||||||
XCTAssertTrue(
|
XCTAssertTrue(
|
||||||
addButton.waitForExistence(timeout: 25),
|
addButtons.firstMatch.waitForExistence(timeout: 25),
|
||||||
"Store Detail's product list never loaded. \(quickDiagnostics(app))"
|
"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
|
// Once the cart has items, the tab bar button's accessible label
|
||||||
// becomes just the badge count ("4") instead of "Shopping Cart" -
|
// becomes just the badge count ("4") instead of "Shopping Cart" -
|
||||||
@@ -259,7 +302,7 @@ final class CartCheckoutFlowTests: XCTestCase {
|
|||||||
XCTAssertTrue(cartTab.waitForExistence(timeout: 5))
|
XCTAssertTrue(cartTab.waitForExistence(timeout: 5))
|
||||||
cartTab.tap()
|
cartTab.tap()
|
||||||
XCTAssertTrue(app.staticTexts["Meu Carrinho"].waitForExistence(timeout: 10))
|
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()
|
app.buttons["Ir para o Pagamento"].tap()
|
||||||
XCTAssertTrue(app.staticTexts["Finalizar Pedido"].waitForExistence(timeout: 10), "Never reached Checkout")
|
XCTAssertTrue(app.staticTexts["Finalizar Pedido"].waitForExistence(timeout: 10), "Never reached Checkout")
|
||||||
|
|||||||
Reference in New Issue
Block a user