Major infrastructure changes: - BaseUITestCase: per-suite app termination via class setUp() prevents stale state when parallel clones share simulators - relaunchBetweenTests override for suites that modify login/onboarding state - focusAndType: dedicated SecureTextField path handles iOS strong password autofill suggestions (Choose My Own Password / Not Now dialogs) - LoginScreenObject: tapSignUp/tapForgotPassword use scrollIntoView for offscreen buttons instead of simple swipeUp - Removed all coordinate taps from ForgotPasswordScreen, VerifyResetCodeScreen, ResetPasswordScreen (Rule 3 compliance) - Removed all usleep calls from screen objects (Rule 14 compliance) App fixes exposed by tests: - ContractorsListView: added onDismiss to sheet for list refresh after save - AllTasksView: added Task.RefreshButton accessibility identifier - AccessibilityIdentifiers: added Task.refreshButton - DocumentsWarrantiesView: onDismiss handler for document list refresh - Various form views: textContentType, submitLabel, onSubmit for keyboard flow Test fixes: - PasswordResetTests: handle auto-login after reset (app skips success screen) - AuthenticatedUITestCase: refreshTasks() helper for kanban toolbar button - All pre-login suites use relaunchBetweenTests for test independence - Deleted dead code: AuthenticatedTestCase, SeededTestData, SeedTests, CleanupTests, old Suite0/2/3, Suite1_RegistrationRebuildTests 10 remaining failures: 5 iOS strong password autofill (simulator env), 3 pull-to-refresh gesture on empty lists, 2 feature coverage edge cases. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
857 lines
32 KiB
Swift
857 lines
32 KiB
Swift
import XCTest
|
|
|
|
/// Comprehensive documents and warranties testing suite covering all scenarios, edge cases, and variations
|
|
/// Tests both document types (permits, receipts, etc.) and warranties with filtering, searching, and CRUD operations
|
|
final class Suite8_DocumentWarrantyTests: AuthenticatedUITestCase {
|
|
|
|
override var needsAPISession: Bool { true }
|
|
|
|
// Test data tracking
|
|
var createdDocumentTitles: [String] = []
|
|
var currentResidenceId: Int32?
|
|
|
|
override func setUpWithError() throws {
|
|
try super.setUpWithError()
|
|
|
|
// Ensure at least one residence exists via API (required for property picker)
|
|
ensureResidenceExists()
|
|
|
|
// Dismiss any form left open by a previous test
|
|
let cancelBtn = app.buttons[AccessibilityIdentifiers.Document.formCancelButton]
|
|
if cancelBtn.exists { cancelBtn.tap() }
|
|
|
|
// 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()
|
|
}
|
|
}
|
|
|
|
override func tearDownWithError() throws {
|
|
// Track all created documents for API cleanup before super.tearDown runs cleaner.cleanAll()
|
|
if !createdDocumentTitles.isEmpty,
|
|
let allDocs = TestAccountAPIClient.listDocuments(token: session.token) {
|
|
for title in createdDocumentTitles {
|
|
if let doc = allDocs.first(where: { $0.title.contains(title) }) {
|
|
cleaner.trackDocument(doc.id)
|
|
}
|
|
}
|
|
}
|
|
createdDocumentTitles.removeAll()
|
|
currentResidenceId = nil
|
|
try super.tearDownWithError()
|
|
}
|
|
|
|
// MARK: - Page Objects
|
|
|
|
private var docList: DocumentListScreen { DocumentListScreen(app: app) }
|
|
private var docForm: DocumentFormScreen { DocumentFormScreen(app: app) }
|
|
|
|
// MARK: - Helper Methods
|
|
|
|
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) {
|
|
let pickerButton = app.buttons[AccessibilityIdentifiers.Document.residencePicker].firstMatch
|
|
pickerButton.waitForExistenceOrFail(timeout: navigationTimeout, message: "Property picker should exist", file: file, line: line)
|
|
|
|
pickerButton.tap()
|
|
|
|
// SwiftUI Picker in Form pushes a selection list — find any row to select
|
|
// Try menu items first (menu style), then static texts (list style)
|
|
let menuItem = app.menuItems.firstMatch
|
|
if menuItem.waitForExistence(timeout: navigationTimeout) {
|
|
let allItems = app.menuItems.allElementsBoundByIndex
|
|
allItems[max(allItems.count - 1, 0)].tap()
|
|
} 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
|
|
}
|
|
// Tap the first non-placeholder cell
|
|
if cells.count > 1 {
|
|
cells.element(boundBy: 1).tap()
|
|
} else {
|
|
cells.element(boundBy: 0).tap()
|
|
}
|
|
}
|
|
|
|
// Wait for picker to dismiss and return to form
|
|
_ = docForm.titleField.waitForExistence(timeout: navigationTimeout)
|
|
}
|
|
|
|
private func selectDocumentType(type: String) {
|
|
let typePicker = app.buttons[AccessibilityIdentifiers.Document.typePicker].firstMatch
|
|
if typePicker.exists {
|
|
typePicker.tap()
|
|
|
|
let typeButton = app.buttons[type]
|
|
if typeButton.waitForExistence(timeout: defaultTimeout) {
|
|
typeButton.tap()
|
|
} else {
|
|
// Try cells if it's a navigation style picker
|
|
let cells = app.cells
|
|
for i in 0..<cells.count {
|
|
let cell = cells.element(boundBy: i)
|
|
if cell.staticTexts[type].exists {
|
|
cell.tap()
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private func submitForm(file: StaticString = #filePath, line: UInt = #line) {
|
|
// Dismiss keyboard so submit button is visible
|
|
dismissKeyboard()
|
|
|
|
let submitButton = docForm.saveButton
|
|
if !submitButton.exists || !submitButton.isHittable {
|
|
app.swipeUp()
|
|
_ = submitButton.waitForExistence(timeout: defaultTimeout)
|
|
}
|
|
XCTAssertTrue(submitButton.exists && submitButton.isEnabled, "Submit button should exist and be enabled", file: file, line: line)
|
|
submitButton.tap()
|
|
// Wait for form to dismiss after submit
|
|
submitButton.waitForNonExistence(timeout: navigationTimeout, file: file, line: line)
|
|
}
|
|
|
|
/// 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 switchToDocumentsTab() {
|
|
let documentsButton = app.buttons["Documents"].firstMatch
|
|
if documentsButton.waitForExistence(timeout: navigationTimeout) {
|
|
documentsButton.tap()
|
|
return
|
|
}
|
|
// Fallback: segmented control button
|
|
app.segmentedControls.buttons["Documents"].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: - Test Cases
|
|
|
|
// MARK: Navigation Tests
|
|
|
|
func test01_NavigateToDocumentsScreen() {
|
|
navigateToDocuments()
|
|
|
|
// Verify we're on documents screen by checking for the segmented control tabs
|
|
let warrantiesTab = app.buttons["Warranties"]
|
|
let documentsTab = app.buttons["Documents"]
|
|
let warrantiesExists = warrantiesTab.waitForExistence(timeout: navigationTimeout)
|
|
let documentsExists = documentsTab.waitForExistence(timeout: defaultTimeout)
|
|
XCTAssertTrue(warrantiesExists || documentsExists, "Should see tab switcher on Documents screen")
|
|
}
|
|
|
|
func test02_SwitchBetweenWarrantiesAndDocuments() {
|
|
navigateToDocuments()
|
|
|
|
// Start on warranties tab
|
|
switchToWarrantiesTab()
|
|
|
|
// Switch to documents tab
|
|
switchToDocumentsTab()
|
|
|
|
// Switch back to warranties
|
|
switchToWarrantiesTab()
|
|
|
|
// Should not crash and tabs should still exist
|
|
let warrantiesTab = app.buttons["Warranties"]
|
|
XCTAssertTrue(warrantiesTab.exists, "Tabs should remain functional after switching")
|
|
}
|
|
|
|
// MARK: Document Creation Tests
|
|
|
|
func test03_CreateDocumentWithAllFields() {
|
|
navigateToDocuments()
|
|
switchToDocumentsTab()
|
|
|
|
openDocumentForm()
|
|
|
|
let testTitle = "Test Permit \(UUID().uuidString.prefix(8))"
|
|
createdDocumentTitles.append(testTitle)
|
|
|
|
// Fill required fields (document type — no warranty fields needed)
|
|
selectProperty()
|
|
docForm.titleField.focusAndType(testTitle, app: app)
|
|
|
|
submitForm()
|
|
|
|
// Verify document appears in list
|
|
let documentCard = app.staticTexts[testTitle]
|
|
XCTAssertTrue(documentCard.waitForExistence(timeout: navigationTimeout), "Created document should appear in list")
|
|
}
|
|
|
|
func test04_CreateDocumentWithMinimalFields() {
|
|
navigateToDocuments()
|
|
switchToDocumentsTab()
|
|
|
|
openDocumentForm()
|
|
|
|
let testTitle = "Min Doc \(UUID().uuidString.prefix(8))"
|
|
createdDocumentTitles.append(testTitle)
|
|
|
|
// Fill required fields (document type — title + property)
|
|
selectProperty()
|
|
docForm.titleField.focusAndType(testTitle, app: app)
|
|
|
|
submitForm()
|
|
|
|
// Verify document appears
|
|
let documentCard = app.staticTexts[testTitle]
|
|
XCTAssertTrue(documentCard.waitForExistence(timeout: navigationTimeout), "Document with minimal fields should appear")
|
|
}
|
|
|
|
func test05_CreateDocumentWithEmptyTitle_ShouldFail() {
|
|
navigateToDocuments()
|
|
switchToDocumentsTab()
|
|
|
|
openDocumentForm()
|
|
|
|
// Try to submit without title
|
|
selectProperty()
|
|
selectDocumentType(type: "Insurance")
|
|
|
|
let submitButton = app.buttons[AccessibilityIdentifiers.Document.saveButton].firstMatch
|
|
|
|
// Submit button should be disabled or show error
|
|
if submitButton.exists && submitButton.isEnabled {
|
|
submitButton.tap()
|
|
|
|
// Should show error message
|
|
let errorMessage = app.staticTexts.containing(NSPredicate(format: "label CONTAINS[c] 'required' OR label CONTAINS[c] 'title'")).firstMatch
|
|
XCTAssertTrue(errorMessage.waitForExistence(timeout: defaultTimeout), "Should show validation error for missing title")
|
|
}
|
|
|
|
cancelForm()
|
|
}
|
|
|
|
// MARK: Warranty Creation Tests
|
|
|
|
func test06_CreateWarrantyWithAllFields() {
|
|
navigateToDocuments()
|
|
switchToWarrantiesTab()
|
|
|
|
openDocumentForm()
|
|
|
|
let testTitle = "Test Warranty \(UUID().uuidString.prefix(8))"
|
|
createdDocumentTitles.append(testTitle)
|
|
|
|
// 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() {
|
|
navigateToDocuments()
|
|
switchToWarrantiesTab()
|
|
|
|
openDocumentForm()
|
|
|
|
let testTitle = "Future Warranty \(UUID().uuidString.prefix(8))"
|
|
createdDocumentTitles.append(testTitle)
|
|
|
|
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() {
|
|
navigateToDocuments()
|
|
switchToWarrantiesTab()
|
|
|
|
openDocumentForm()
|
|
|
|
let testTitle = "Expired Warranty \(UUID().uuidString.prefix(8))"
|
|
createdDocumentTitles.append(testTitle)
|
|
|
|
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
|
|
|
|
func test09_SearchDocumentsByTitle() {
|
|
navigateToDocuments()
|
|
switchToDocumentsTab()
|
|
|
|
// Create a test document first
|
|
openDocumentForm()
|
|
let searchableTitle = "Searchable Doc \(UUID().uuidString.prefix(8))"
|
|
createdDocumentTitles.append(searchableTitle)
|
|
selectProperty()
|
|
docForm.titleField.focusAndType(searchableTitle, app: app)
|
|
selectDocumentType(type: "Insurance")
|
|
submitForm()
|
|
|
|
// Search for it
|
|
searchFor(text: String(searchableTitle.prefix(15)))
|
|
|
|
// Should find the document
|
|
let foundDocument = app.staticTexts[searchableTitle]
|
|
XCTAssertTrue(foundDocument.exists, "Should find document by search")
|
|
|
|
clearSearch()
|
|
}
|
|
|
|
func test10_FilterWarrantiesByCategory() {
|
|
navigateToDocuments()
|
|
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 test11_FilterDocumentsByType() {
|
|
navigateToDocuments()
|
|
switchToDocumentsTab()
|
|
|
|
// Apply type 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: "Permit")
|
|
|
|
if filterApplied {
|
|
// Should show filter indication
|
|
let filterChip = app.staticTexts["Permit"]
|
|
XCTAssertTrue(filterChip.exists || app.buttons["Permit"].exists, "Should show active type filter")
|
|
|
|
// Clear filter
|
|
applyFilter(filterName: "All Types")
|
|
}
|
|
// If filter was not applied (button not found), test passes — no crash happened
|
|
}
|
|
|
|
func test12_ToggleActiveWarrantiesFilter() {
|
|
navigateToDocuments()
|
|
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: Document Detail Tests
|
|
|
|
func test13_ViewDocumentDetail() {
|
|
navigateToDocuments()
|
|
switchToDocumentsTab()
|
|
|
|
// Create a document
|
|
openDocumentForm()
|
|
let testTitle = "Detail Test Doc \(UUID().uuidString.prefix(8))"
|
|
createdDocumentTitles.append(testTitle)
|
|
selectProperty()
|
|
docForm.titleField.focusAndType(testTitle, app: app)
|
|
selectDocumentType(type: "Insurance")
|
|
fillTextEditor(text: "This is a test receipt with details")
|
|
submitForm()
|
|
|
|
// Tap on the document card
|
|
let documentCard = app.staticTexts[testTitle]
|
|
XCTAssertTrue(documentCard.waitForExistence(timeout: navigationTimeout), "Document should exist in list")
|
|
documentCard.tap()
|
|
|
|
// Should show detail screen
|
|
let detailTitle = app.staticTexts[testTitle]
|
|
XCTAssertTrue(detailTitle.waitForExistence(timeout: defaultTimeout), "Should show document detail screen")
|
|
|
|
// Go back
|
|
let backButton = app.navigationBars.buttons.firstMatch
|
|
backButton.tap()
|
|
}
|
|
|
|
func test14_ViewWarrantyDetailWithDates() {
|
|
navigateToDocuments()
|
|
switchToWarrantiesTab()
|
|
|
|
// Create a warranty
|
|
openDocumentForm()
|
|
let testTitle = "Warranty Detail Test \(UUID().uuidString.prefix(8))"
|
|
createdDocumentTitles.append(testTitle)
|
|
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
|
|
|
|
func test15_EditDocumentTitle() {
|
|
navigateToDocuments()
|
|
switchToDocumentsTab()
|
|
|
|
// Create document
|
|
openDocumentForm()
|
|
let originalTitle = "Edit Test \(UUID().uuidString.prefix(8))"
|
|
createdDocumentTitles.append(originalTitle)
|
|
selectProperty()
|
|
docForm.titleField.focusAndType(originalTitle, app: app)
|
|
selectDocumentType(type: "Insurance")
|
|
submitForm()
|
|
|
|
// Open detail
|
|
let documentCard = app.staticTexts[originalTitle]
|
|
XCTAssertTrue(documentCard.waitForExistence(timeout: navigationTimeout), "Document should exist")
|
|
documentCard.tap()
|
|
|
|
// Tap edit button
|
|
let editButton = app.buttons[AccessibilityIdentifiers.Document.editButton].firstMatch
|
|
if editButton.waitForExistence(timeout: defaultTimeout) {
|
|
editButton.tap()
|
|
|
|
// Change title using the accessibility identifier
|
|
let titleField = app.textFields[AccessibilityIdentifiers.Document.titleField].firstMatch
|
|
if titleField.waitForExistence(timeout: defaultTimeout) {
|
|
let newTitle = "Edited \(originalTitle)"
|
|
titleField.clearAndEnterText(newTitle, app: app)
|
|
createdDocumentTitles.append(newTitle)
|
|
|
|
submitForm()
|
|
|
|
// Verify new title appears
|
|
let updatedTitle = app.staticTexts[newTitle]
|
|
XCTAssertTrue(updatedTitle.waitForExistence(timeout: navigationTimeout), "Updated title should appear")
|
|
}
|
|
}
|
|
|
|
// Go back to list
|
|
app.navigationBars.buttons.element(boundBy: 0).tap()
|
|
}
|
|
|
|
func test16_EditWarrantyDates() {
|
|
navigateToDocuments()
|
|
switchToWarrantiesTab()
|
|
|
|
// Create warranty
|
|
openDocumentForm()
|
|
let testTitle = "Edit Dates Warranty \(UUID().uuidString.prefix(8))"
|
|
createdDocumentTitles.append(testTitle)
|
|
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
|
|
|
|
func test17_DeleteDocument() {
|
|
navigateToDocuments()
|
|
switchToDocumentsTab()
|
|
|
|
// Create document to delete
|
|
openDocumentForm()
|
|
let deleteTitle = "To Delete \(UUID().uuidString.prefix(8))"
|
|
selectProperty()
|
|
docForm.titleField.focusAndType(deleteTitle, app: app)
|
|
selectDocumentType(type: "Insurance")
|
|
submitForm()
|
|
|
|
// Open detail
|
|
let documentCard = app.staticTexts[deleteTitle]
|
|
XCTAssertTrue(documentCard.waitForExistence(timeout: navigationTimeout), "Document should exist")
|
|
documentCard.tap()
|
|
|
|
// Find and tap delete button
|
|
let deleteButton = app.buttons[AccessibilityIdentifiers.Document.deleteButton].firstMatch
|
|
if deleteButton.waitForExistence(timeout: defaultTimeout) {
|
|
deleteButton.tap()
|
|
|
|
// Confirm deletion
|
|
let confirmButton = app.alerts.buttons["Delete"].firstMatch
|
|
if confirmButton.waitForExistence(timeout: defaultTimeout) {
|
|
confirmButton.tap()
|
|
}
|
|
|
|
// Wait for navigation back to list
|
|
_ = app.navigationBars.firstMatch.waitForExistence(timeout: defaultTimeout)
|
|
|
|
// Verify document no longer exists
|
|
let deletedCard = app.staticTexts[deleteTitle]
|
|
XCTAssertTrue(deletedCard.waitForNonExistence(timeout: defaultTimeout), "Deleted document should not appear in list")
|
|
}
|
|
}
|
|
|
|
func test18_DeleteWarranty() {
|
|
navigateToDocuments()
|
|
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
|
|
|
|
func test19_CancelDocumentCreation() {
|
|
navigateToDocuments()
|
|
switchToDocumentsTab()
|
|
|
|
openDocumentForm()
|
|
|
|
// Fill some fields
|
|
selectProperty()
|
|
docForm.titleField.focusAndType("Cancelled Document", app: app)
|
|
selectDocumentType(type: "Insurance")
|
|
|
|
// Cancel instead of save
|
|
cancelForm()
|
|
|
|
// Should not appear in list
|
|
let cancelledDoc = app.staticTexts["Cancelled Document"]
|
|
XCTAssertFalse(cancelledDoc.exists, "Cancelled document should not be created")
|
|
}
|
|
|
|
func test20_HandleEmptyDocumentsList() {
|
|
navigateToDocuments()
|
|
switchToDocumentsTab()
|
|
|
|
// Apply very specific filter to get empty list
|
|
searchFor(text: "NONEXISTENT_DOCUMENT_12345")
|
|
|
|
// Should show empty state or no items
|
|
let emptyState = app.otherElements[AccessibilityIdentifiers.Document.emptyStateView]
|
|
_ = emptyState.waitForExistence(timeout: defaultTimeout)
|
|
|
|
let hasNoItems = app.cells.count == 0
|
|
XCTAssertTrue(emptyState.exists || hasNoItems, "Should handle empty documents list gracefully")
|
|
|
|
clearSearch()
|
|
}
|
|
|
|
func test21_HandleEmptyWarrantiesList() {
|
|
navigateToDocuments()
|
|
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 test22_CreateDocumentWithLongTitle() {
|
|
navigateToDocuments()
|
|
switchToDocumentsTab()
|
|
|
|
openDocumentForm()
|
|
|
|
let longTitle = "This is a very long document title that exceeds normal length expectations to test how the UI handles lengthy text input " + UUID().uuidString
|
|
createdDocumentTitles.append(longTitle)
|
|
|
|
selectProperty()
|
|
docForm.titleField.focusAndType(longTitle, app: app)
|
|
selectDocumentType(type: "Insurance")
|
|
|
|
submitForm()
|
|
|
|
// Just verify it was created (partial match)
|
|
let partialTitle = String(longTitle.prefix(30))
|
|
let documentExists = app.staticTexts.containing(NSPredicate(format: "label CONTAINS[c] '\(partialTitle)'")).firstMatch.exists
|
|
XCTAssertTrue(documentExists, "Document with long title should be created")
|
|
}
|
|
|
|
func test23_CreateWarrantyWithSpecialCharacters() {
|
|
navigateToDocuments()
|
|
switchToWarrantiesTab()
|
|
|
|
openDocumentForm()
|
|
|
|
let specialTitle = "Warranty w/ Special #Chars: @ & $ % \(UUID().uuidString.prefix(8))"
|
|
createdDocumentTitles.append(specialTitle)
|
|
|
|
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()
|
|
|
|
let partialTitle = String(specialTitle.prefix(20))
|
|
let warrantyExists = app.staticTexts.containing(NSPredicate(format: "label CONTAINS '\(partialTitle)'")).firstMatch.exists
|
|
XCTAssertTrue(warrantyExists, "Warranty with special characters should be created")
|
|
}
|
|
|
|
func test24_RapidTabSwitching() {
|
|
navigateToDocuments()
|
|
|
|
// Rapidly switch between tabs
|
|
for _ in 0..<5 {
|
|
switchToWarrantiesTab()
|
|
switchToDocumentsTab()
|
|
}
|
|
|
|
// Should remain stable
|
|
let warrantiesTab = app.buttons["Warranties"]
|
|
let documentsTab = app.buttons["Documents"]
|
|
XCTAssertTrue(warrantiesTab.exists && documentsTab.exists, "Should handle rapid tab switching without crashing")
|
|
}
|
|
|
|
func test25_MultipleFiltersCombined() {
|
|
navigateToDocuments()
|
|
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
|
|
}
|
|
}
|
|
|
|
// MARK: - XCUIElement Extension for Clearing Text
|
|
|
|
extension XCUIElement {
|
|
func clearText() {
|
|
guard let stringValue = self.value as? String else {
|
|
return
|
|
}
|
|
|
|
self.tap()
|
|
|
|
let deleteString = String(repeating: XCUIKeyboardKey.delete.rawValue, count: stringValue.count)
|
|
self.typeText(deleteString)
|
|
}
|
|
}
|