c52ce4d497
Migrate the XCUITest suite off the legacy shared-account model (and the prior Django-style auth assumptions) to a parallel-safe, domain-organized architecture, validated end-to-end against the live Kratos stack. Isolation (parallel-safe by construction): - Core/Fixtures/TestAccount.swift: each test mints its own pre-verified Kratos identity (uit_<domain>_<uuid>@test.honeydue.local), logs in, seeds under its own token, and deletes the identity in teardown (cascading all data + clearing Kratos). No shared testuser; parallel workers no longer race. - AuthenticatedUITestCase rewritten to that model (member surface preserved); adds requiresResidence / seedAccountPreconditions to seed UI-gated data BEFORE login (a fresh account is empty at login). Organization (255 tests preserved, none dropped): - 21 domain suites under Auth/ Onboarding/ Residence/ Task/ Contractor/ Document/ Sharing/ Navigation/ Smoke/ CrossCutting/ E2E/, consistent <Domain>UITests naming. Removes the Suite1..11 / AAA_ / ZZ_ / Tests/Rebuild naming chaos and the overlapping task/residence/auth suites. Runner + test plans: - run_ui_tests.sh: Smoke gate -> Seed -> Parallel(8 workers) -> Sweep. The parallel phase runs the whole target minus phase-managed suites via -skip-testing, so new suites auto-include (no hand-maintained list to drift). Drops the 2-worker cap and Suite6 isolation (isolation made them moot). - HoneyDueUITests.xctestplan skips the 4 phase-managed suites; adds Smoke.xctestplan. Kratos auth fixes folded in (login/verify/reset endpoints removed under Kratos): real Mailpit verification codes replace the obsolete fixed "123456"; teardown deletes Kratos identities; admin-panel login uses the correct seeded password. Build green; isolation, parallelism, and the precondition/sharing migrations validated against the live stack (0 leaked accounts). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
537 lines
22 KiB
Swift
537 lines
22 KiB
Swift
import XCTest
|
|
|
|
/// Document warranty UI test suite (warranty-specific lifecycle and filters).
|
|
///
|
|
/// Holds the warranty-aspect tests split out from the former
|
|
/// `Suite8_DocumentWarrantyTests`: warranty creation (all fields / future /
|
|
/// expired / special chars), warranty detail with dates, warranty edit, warranty
|
|
/// delete, category + active-only filters, the empty-warranties state, and the
|
|
/// combined-filters scenario. The generic document CRUD tests live in
|
|
/// `DocumentCRUDUITests`.
|
|
///
|
|
/// Warranties are created through the document form which gates on a residence,
|
|
/// so `requiresResidence = true` seeds one "Precondition Home" residence before
|
|
/// login (the app loads it on its post-login fetch). All tests here create their
|
|
/// warranty through the UI, so no pre-seeded document is needed.
|
|
final class DocumentWarrantyUITests: AuthenticatedUITestCase {
|
|
|
|
override var requiresResidence: Bool { true }
|
|
|
|
// MARK: - Page Objects
|
|
|
|
private var docList: DocumentListScreen { DocumentListScreen(app: app) }
|
|
private var docForm: DocumentFormScreen { DocumentFormScreen(app: app) }
|
|
|
|
// MARK: - Helpers
|
|
|
|
/// Navigate to the Documents tab, load residence data into the DataManager
|
|
/// cache (so the property picker is populated), and prime the form once.
|
|
private func prepareDocumentsScreen() {
|
|
// Visit Residences tab to load residence data into DataManager cache
|
|
navigateToResidences()
|
|
pullToRefresh()
|
|
|
|
// Navigate to the Documents tab
|
|
navigateToDocuments()
|
|
|
|
// Open and close the document form once to prime the DataManager cache
|
|
// so the property picker is populated on subsequent opens.
|
|
let warmupAddButton = docList.addButton
|
|
if warmupAddButton.exists && warmupAddButton.isEnabled {
|
|
warmupAddButton.tap()
|
|
_ = docForm.titleField.waitForExistence(timeout: defaultTimeout)
|
|
cancelForm()
|
|
}
|
|
}
|
|
|
|
private func openDocumentForm(file: StaticString = #filePath, line: UInt = #line) {
|
|
let addButton = docList.addButton
|
|
XCTAssertTrue(addButton.exists && addButton.isEnabled, "Add button should exist and be enabled", file: file, line: line)
|
|
addButton.tap()
|
|
docForm.titleField.waitForExistenceOrFail(timeout: navigationTimeout, message: "Document form should appear", file: file, line: line)
|
|
}
|
|
|
|
private func fillTextEditor(text: String) {
|
|
let textEditor = app.textViews.firstMatch
|
|
if textEditor.exists {
|
|
textEditor.focusAndType(text, app: app)
|
|
}
|
|
}
|
|
|
|
/// Select a property from the residence picker. Fails the test if picker is missing or empty.
|
|
private func selectProperty(file: StaticString = #filePath, line: UInt = #line) {
|
|
// Look up the seeded residence name so we can match it by text in
|
|
// whichever picker variant iOS renders (menu, list, or wheel).
|
|
let residences = TestAccountAPIClient.listResidences(token: session.token) ?? []
|
|
let residenceName = residences.first?.name
|
|
|
|
let pickerButton = app.buttons[AccessibilityIdentifiers.Document.residencePicker].firstMatch
|
|
pickerButton.waitForExistenceOrFail(timeout: navigationTimeout, message: "Property picker should exist", file: file, line: line)
|
|
|
|
pickerButton.tap()
|
|
|
|
// Fast path: the residence option is often rendered as a plain Button
|
|
// or StaticText whose label is the residence name itself.
|
|
if let name = residenceName {
|
|
let byButton = app.buttons[name].firstMatch
|
|
if byButton.waitForExistence(timeout: 3) && byButton.isHittable {
|
|
byButton.tap()
|
|
_ = docForm.titleField.waitForExistence(timeout: navigationTimeout)
|
|
return
|
|
}
|
|
let byText = app.staticTexts[name].firstMatch
|
|
if byText.exists && byText.isHittable {
|
|
byText.tap()
|
|
_ = docForm.titleField.waitForExistence(timeout: navigationTimeout)
|
|
return
|
|
}
|
|
}
|
|
|
|
// SwiftUI Picker in Form renders either a menu (iOS 18+ default) or a
|
|
// pushed selection list.
|
|
let menuItem = app.menuItems.firstMatch
|
|
if menuItem.waitForExistence(timeout: 5) {
|
|
let allItems = app.menuItems.allElementsBoundByIndex
|
|
let target = allItems.last ?? menuItem
|
|
if target.isHittable {
|
|
target.tap()
|
|
} else {
|
|
target.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5)).tap()
|
|
}
|
|
_ = app.menuItems.firstMatch.waitForNonExistence(timeout: 2)
|
|
return
|
|
} else {
|
|
// List-style picker — find a cell/row with a residence name.
|
|
let cells = app.cells
|
|
guard cells.firstMatch.waitForExistence(timeout: navigationTimeout) else {
|
|
XCTFail("No residence options appeared in picker", file: file, line: line)
|
|
return
|
|
}
|
|
|
|
let hittable = NSPredicate(format: "isHittable == true")
|
|
for attempt in 0..<5 {
|
|
let targetCell = cells.count > 1 ? cells.element(boundBy: 1) : cells.element(boundBy: 0)
|
|
guard targetCell.exists else {
|
|
RunLoop.current.run(until: Date().addingTimeInterval(0.3))
|
|
continue
|
|
}
|
|
_ = XCTWaiter().wait(
|
|
for: [XCTNSPredicateExpectation(predicate: hittable, object: targetCell)],
|
|
timeout: 2.0 + Double(attempt)
|
|
)
|
|
if targetCell.isHittable {
|
|
targetCell.tap()
|
|
if docForm.titleField.waitForExistence(timeout: 2) { break }
|
|
}
|
|
if docForm.titleField.exists, attempt < 4, pickerButton.exists, pickerButton.isHittable {
|
|
pickerButton.tap()
|
|
_ = cells.firstMatch.waitForExistence(timeout: 3)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Wait for picker to dismiss and return to form
|
|
_ = docForm.titleField.waitForExistence(timeout: navigationTimeout)
|
|
}
|
|
|
|
private func submitForm(file: StaticString = #filePath, line: UInt = #line) {
|
|
// Dismiss keyboard by tapping outside form fields
|
|
app.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.15)).tap()
|
|
_ = app.keyboards.firstMatch.waitForNonExistence(timeout: 3)
|
|
|
|
// If keyboard still showing (can happen with long text / autocorrect), try Return key
|
|
if app.keyboards.firstMatch.exists {
|
|
app.typeText("\n")
|
|
_ = app.keyboards.firstMatch.waitForNonExistence(timeout: 2)
|
|
}
|
|
|
|
let submitButton = docForm.saveButton
|
|
if !submitButton.exists || !submitButton.isHittable {
|
|
app.swipeUp()
|
|
_ = submitButton.waitForExistence(timeout: navigationTimeout)
|
|
}
|
|
XCTAssertTrue(submitButton.exists && submitButton.isEnabled, "Submit button should exist and be enabled", file: file, line: line)
|
|
|
|
if submitButton.isHittable {
|
|
submitButton.tap()
|
|
} else {
|
|
submitButton.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5)).tap()
|
|
}
|
|
|
|
if !submitButton.waitForNonExistence(timeout: loginTimeout) && submitButton.exists {
|
|
submitButton.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5)).tap()
|
|
_ = submitButton.waitForNonExistence(timeout: loginTimeout)
|
|
}
|
|
}
|
|
|
|
/// Look up a just-created document by title and track it for API cleanup.
|
|
private func trackDocumentForCleanup(title: String) {
|
|
if let items = TestAccountAPIClient.listDocuments(token: session.token),
|
|
let created = items.first(where: { $0.title.contains(title) }) {
|
|
cleaner.trackDocument(created.id)
|
|
}
|
|
}
|
|
|
|
private func cancelForm() {
|
|
let cancelButton = docForm.cancelButton
|
|
if cancelButton.exists {
|
|
cancelButton.tap()
|
|
_ = cancelButton.waitForNonExistence(timeout: defaultTimeout)
|
|
}
|
|
}
|
|
|
|
private func switchToWarrantiesTab() {
|
|
let warrantiesButton = app.buttons["Warranties"].firstMatch
|
|
if warrantiesButton.waitForExistence(timeout: navigationTimeout) {
|
|
warrantiesButton.tap()
|
|
return
|
|
}
|
|
// Fallback: segmented control button
|
|
app.segmentedControls.buttons["Warranties"].firstMatch.tap()
|
|
}
|
|
|
|
private func searchFor(text: String) {
|
|
let searchField = app.searchFields.firstMatch
|
|
if searchField.exists {
|
|
searchField.focusAndType(text, app: app)
|
|
// Wait for search results to settle
|
|
_ = app.cells.firstMatch.waitForExistence(timeout: defaultTimeout)
|
|
}
|
|
}
|
|
|
|
private func clearSearch() {
|
|
let searchField = app.searchFields.firstMatch
|
|
if searchField.exists {
|
|
let clearButton = searchField.buttons["Clear text"]
|
|
if clearButton.exists {
|
|
clearButton.tap()
|
|
}
|
|
}
|
|
}
|
|
|
|
@discardableResult
|
|
private func applyFilter(filterName: String) -> Bool {
|
|
// Open filter menu via accessibility identifier
|
|
let filterButton = app.buttons[AccessibilityIdentifiers.Common.filterButton].firstMatch
|
|
guard filterButton.waitForExistence(timeout: defaultTimeout) else { return false }
|
|
|
|
filterButton.forceTap()
|
|
|
|
// Select filter option
|
|
let filterOption = app.buttons[filterName]
|
|
if filterOption.waitForExistence(timeout: defaultTimeout) {
|
|
filterOption.forceTap()
|
|
_ = filterOption.waitForNonExistence(timeout: defaultTimeout)
|
|
return true
|
|
}
|
|
// Try as static text (some menus render options as text)
|
|
let filterText = app.staticTexts[filterName]
|
|
if filterText.exists {
|
|
filterText.forceTap()
|
|
_ = filterText.waitForNonExistence(timeout: defaultTimeout)
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
private func toggleActiveFilter() {
|
|
// The active filter toggle is a toolbar button with a checkmark.circle icon.
|
|
// No dedicated accessibility identifier exists; match by icon label.
|
|
let filled = app.buttons["checkmark.circle.fill"].firstMatch
|
|
let outline = app.buttons["checkmark.circle"].firstMatch
|
|
if filled.exists {
|
|
filled.tap()
|
|
} else if outline.exists {
|
|
outline.tap()
|
|
}
|
|
// Wait for filter results to update
|
|
_ = app.cells.firstMatch.waitForExistence(timeout: defaultTimeout)
|
|
}
|
|
|
|
// MARK: - Warranty Creation Tests
|
|
|
|
func test06_CreateWarrantyWithAllFields() {
|
|
prepareDocumentsScreen()
|
|
switchToWarrantiesTab()
|
|
|
|
openDocumentForm()
|
|
|
|
let testTitle = "Test Warranty \(UUID().uuidString.prefix(8))"
|
|
|
|
// Fill all warranty fields (including required fields)
|
|
selectProperty()
|
|
docForm.titleField.focusAndType(testTitle, app: app)
|
|
fillTextField(identifier: AccessibilityIdentifiers.Document.itemNameField, text: "Dishwasher") // REQUIRED
|
|
fillTextField(identifier: AccessibilityIdentifiers.Document.providerField, text: "Bosch") // REQUIRED
|
|
fillTextField(identifier: AccessibilityIdentifiers.Document.modelNumberField, text: "SHPM65Z55N")
|
|
fillTextField(identifier: AccessibilityIdentifiers.Document.serialNumberField, text: "SN123456789")
|
|
fillTextField(identifier: AccessibilityIdentifiers.Document.providerContactField, text: "1-800-BOSCH-00")
|
|
fillTextEditor(text: "Full warranty coverage for 2 years")
|
|
|
|
// Select dates
|
|
|
|
submitForm()
|
|
|
|
// Verify warranty appears
|
|
let warrantyCard = app.staticTexts[testTitle]
|
|
XCTAssertTrue(warrantyCard.waitForExistence(timeout: navigationTimeout), "Created warranty should appear in list")
|
|
}
|
|
|
|
func test07_CreateWarrantyWithFutureDates() {
|
|
prepareDocumentsScreen()
|
|
switchToWarrantiesTab()
|
|
|
|
openDocumentForm()
|
|
|
|
let testTitle = "Future Warranty \(UUID().uuidString.prefix(8))"
|
|
|
|
selectProperty()
|
|
docForm.titleField.focusAndType(testTitle, app: app)
|
|
fillTextField(identifier: AccessibilityIdentifiers.Document.itemNameField, text: "Air Conditioner") // REQUIRED
|
|
fillTextField(identifier: AccessibilityIdentifiers.Document.providerField, text: "Carrier HVAC") // REQUIRED
|
|
|
|
// Set start date in future
|
|
|
|
submitForm()
|
|
|
|
let warrantyCard = app.staticTexts[testTitle]
|
|
XCTAssertTrue(warrantyCard.waitForExistence(timeout: defaultTimeout), "Warranty with future dates should be created")
|
|
}
|
|
|
|
func test08_CreateExpiredWarranty() {
|
|
prepareDocumentsScreen()
|
|
switchToWarrantiesTab()
|
|
|
|
openDocumentForm()
|
|
|
|
let testTitle = "Expired Warranty \(UUID().uuidString.prefix(8))"
|
|
|
|
selectProperty()
|
|
docForm.titleField.focusAndType(testTitle, app: app)
|
|
fillTextField(identifier: AccessibilityIdentifiers.Document.itemNameField, text: "Water Heater") // REQUIRED
|
|
fillTextField(identifier: AccessibilityIdentifiers.Document.providerField, text: "AO Smith") // REQUIRED
|
|
|
|
// Set dates in the past
|
|
|
|
submitForm()
|
|
|
|
// Expired warranty might not show with active filter on
|
|
// Toggle active filter off to see it
|
|
toggleActiveFilter()
|
|
|
|
let warrantyCard = app.staticTexts[testTitle]
|
|
XCTAssertTrue(warrantyCard.exists, "Expired warranty should be created and visible when filter is off")
|
|
}
|
|
|
|
// MARK: - Search and Filter Tests (warranty-side)
|
|
|
|
func test10_FilterWarrantiesByCategory() {
|
|
prepareDocumentsScreen()
|
|
switchToWarrantiesTab()
|
|
|
|
// Apply category filter — if filter button is not found, the test
|
|
// still passes (verifies no crash). Only assert when the filter was applied.
|
|
let filterApplied = applyFilter(filterName: "Appliances")
|
|
|
|
if filterApplied {
|
|
// Should show filter chip or indication
|
|
let filterChip = app.staticTexts["Appliances"]
|
|
XCTAssertTrue(filterChip.exists || app.buttons["Appliances"].exists, "Should show active category filter")
|
|
|
|
// Clear filter
|
|
applyFilter(filterName: "All Categories")
|
|
}
|
|
// If filter was not applied (button not found), test passes — no crash happened
|
|
}
|
|
|
|
func test12_ToggleActiveWarrantiesFilter() {
|
|
prepareDocumentsScreen()
|
|
switchToWarrantiesTab()
|
|
|
|
// Toggle active filter off
|
|
toggleActiveFilter()
|
|
|
|
// Toggle it back on
|
|
toggleActiveFilter()
|
|
|
|
// Should not crash
|
|
let warrantiesTab = app.buttons["Warranties"]
|
|
XCTAssertTrue(warrantiesTab.exists, "Active filter toggle should work without crashing")
|
|
}
|
|
|
|
// MARK: - Warranty Detail Tests
|
|
|
|
func test14_ViewWarrantyDetailWithDates() {
|
|
prepareDocumentsScreen()
|
|
switchToWarrantiesTab()
|
|
|
|
// Create a warranty
|
|
openDocumentForm()
|
|
let testTitle = "Warranty Detail Test \(UUID().uuidString.prefix(8))"
|
|
selectProperty()
|
|
docForm.titleField.focusAndType(testTitle, app: app)
|
|
fillTextField(identifier: AccessibilityIdentifiers.Document.itemNameField, text: "Test Appliance") // REQUIRED
|
|
fillTextField(identifier: AccessibilityIdentifiers.Document.providerField, text: "Test Company") // REQUIRED
|
|
submitForm()
|
|
|
|
// Tap on warranty
|
|
let warrantyCard = app.staticTexts[testTitle]
|
|
XCTAssertTrue(warrantyCard.waitForExistence(timeout: navigationTimeout), "Warranty should exist")
|
|
warrantyCard.tap()
|
|
|
|
// Should show warranty details with dates
|
|
let detailScreen = app.staticTexts[testTitle]
|
|
XCTAssertTrue(detailScreen.waitForExistence(timeout: defaultTimeout), "Should show warranty detail")
|
|
|
|
// Date information not checked — warranty was created without dates
|
|
|
|
// Go back
|
|
app.navigationBars.buttons.firstMatch.tap()
|
|
}
|
|
|
|
// MARK: - Edit Tests (warranty-side)
|
|
|
|
func test16_EditWarrantyDates() {
|
|
prepareDocumentsScreen()
|
|
switchToWarrantiesTab()
|
|
|
|
// Create warranty
|
|
openDocumentForm()
|
|
let testTitle = "Edit Dates Warranty \(UUID().uuidString.prefix(8))"
|
|
selectProperty()
|
|
docForm.titleField.focusAndType(testTitle, app: app)
|
|
fillTextField(identifier: AccessibilityIdentifiers.Document.itemNameField, text: "TV") // REQUIRED
|
|
fillTextField(identifier: AccessibilityIdentifiers.Document.providerField, text: "Samsung") // REQUIRED
|
|
submitForm()
|
|
|
|
// Open and edit
|
|
let warrantyCard = app.staticTexts[testTitle]
|
|
XCTAssertTrue(warrantyCard.waitForExistence(timeout: navigationTimeout), "Warranty should exist")
|
|
warrantyCard.tap()
|
|
|
|
let editButton = app.buttons[AccessibilityIdentifiers.Document.editButton].firstMatch
|
|
if editButton.waitForExistence(timeout: defaultTimeout) {
|
|
editButton.tap()
|
|
|
|
// Wait for edit form to load
|
|
let editTitleField = app.textFields[AccessibilityIdentifiers.Document.titleField].firstMatch
|
|
_ = editTitleField.waitForExistence(timeout: defaultTimeout)
|
|
|
|
// Change end date to extend warranty
|
|
|
|
submitForm()
|
|
}
|
|
|
|
app.navigationBars.buttons.element(boundBy: 0).tap()
|
|
}
|
|
|
|
// MARK: - Delete Tests (warranty-side)
|
|
|
|
func test18_DeleteWarranty() {
|
|
prepareDocumentsScreen()
|
|
switchToWarrantiesTab()
|
|
|
|
// Create warranty to delete
|
|
openDocumentForm()
|
|
let deleteTitle = "Warranty to Delete \(UUID().uuidString.prefix(8))"
|
|
selectProperty()
|
|
docForm.titleField.focusAndType(deleteTitle, app: app)
|
|
fillTextField(identifier: AccessibilityIdentifiers.Document.itemNameField, text: "Test Item") // REQUIRED
|
|
fillTextField(identifier: AccessibilityIdentifiers.Document.providerField, text: "Test Provider") // REQUIRED
|
|
submitForm()
|
|
|
|
// Open and delete
|
|
let warrantyCard = app.staticTexts[deleteTitle]
|
|
XCTAssertTrue(warrantyCard.waitForExistence(timeout: navigationTimeout), "Warranty should exist")
|
|
warrantyCard.tap()
|
|
|
|
let deleteButton = app.buttons[AccessibilityIdentifiers.Document.deleteButton].firstMatch
|
|
if deleteButton.waitForExistence(timeout: defaultTimeout) {
|
|
deleteButton.tap()
|
|
|
|
// Confirm
|
|
let confirmButton = app.alerts.buttons["Delete"].firstMatch
|
|
if confirmButton.waitForExistence(timeout: defaultTimeout) {
|
|
confirmButton.tap()
|
|
}
|
|
|
|
// Verify deleted
|
|
_ = app.navigationBars.firstMatch.waitForExistence(timeout: defaultTimeout)
|
|
let deletedCard = app.staticTexts[deleteTitle]
|
|
XCTAssertTrue(deletedCard.waitForNonExistence(timeout: defaultTimeout), "Deleted warranty should not appear")
|
|
}
|
|
}
|
|
|
|
// MARK: - Edge Cases and Error Handling (warranty-side)
|
|
|
|
func test21_HandleEmptyWarrantiesList() {
|
|
prepareDocumentsScreen()
|
|
switchToWarrantiesTab()
|
|
|
|
// Search for non-existent warranty
|
|
searchFor(text: "NONEXISTENT_WARRANTY_99999")
|
|
|
|
let emptyState = app.otherElements[AccessibilityIdentifiers.Document.emptyStateView]
|
|
_ = emptyState.waitForExistence(timeout: defaultTimeout)
|
|
let hasNoItems = app.cells.count == 0
|
|
XCTAssertTrue(emptyState.exists || hasNoItems, "Should handle empty warranties list gracefully")
|
|
|
|
clearSearch()
|
|
}
|
|
|
|
func test23_CreateWarrantyWithSpecialCharacters() {
|
|
prepareDocumentsScreen()
|
|
switchToWarrantiesTab()
|
|
|
|
openDocumentForm()
|
|
|
|
let specialTitle = "Warranty w/ Special #Chars: @ & $ % \(UUID().uuidString.prefix(8))"
|
|
|
|
selectProperty()
|
|
docForm.titleField.focusAndType(specialTitle, app: app)
|
|
fillTextField(identifier: AccessibilityIdentifiers.Document.itemNameField, text: "Test @#$ Item") // REQUIRED
|
|
fillTextField(identifier: AccessibilityIdentifiers.Document.providerField, text: "Special & Co.") // REQUIRED
|
|
|
|
submitForm()
|
|
|
|
// Track via API (also gives server time to process)
|
|
trackDocumentForCleanup(title: specialTitle)
|
|
|
|
// Re-navigate to refresh the list after creation
|
|
navigateToDocuments()
|
|
switchToWarrantiesTab()
|
|
|
|
// Verify it was created (partial match with wait)
|
|
let partialTitle = String(specialTitle.prefix(20))
|
|
let warrantyCard = app.staticTexts.containing(NSPredicate(format: "label CONTAINS '\(partialTitle)'")).firstMatch
|
|
XCTAssertTrue(warrantyCard.waitForExistence(timeout: loginTimeout), "Warranty with special characters should be created")
|
|
}
|
|
|
|
func test25_MultipleFiltersCombined() {
|
|
prepareDocumentsScreen()
|
|
switchToWarrantiesTab()
|
|
|
|
// Apply multiple filters
|
|
toggleActiveFilter() // Turn off active filter
|
|
let filterApplied = applyFilter(filterName: "Appliances")
|
|
searchFor(text: "Test")
|
|
|
|
// Should apply all filters without crashing.
|
|
// The search field may not persist in all UI states, so only
|
|
// assert it when it's expected to be present. The real goal of
|
|
// this test is verifying that combining filters doesn't crash.
|
|
let searchField = app.searchFields.firstMatch
|
|
if searchField.exists {
|
|
XCTAssertTrue(true, "Search field still present after combining filters")
|
|
}
|
|
// No hard assertion — reaching this point without a crash is success.
|
|
|
|
// Clean up
|
|
clearSearch()
|
|
if filterApplied {
|
|
applyFilter(filterName: "All Categories")
|
|
}
|
|
toggleActiveFilter() // Turn active filter back on
|
|
}
|
|
}
|