Re-architect iOS XCUITest suite: per-test isolation + domain organization
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>
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,536 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user