import XCTest final class CableUITestsScreenshot: XCTestCase { private let springboard = XCUIApplication(bundleIdentifier: "com.apple.springboard") private enum UIStringKey: String { case addLoad case browseLibrary case library case overviewTab case componentsTab case batteriesTab case chargersTab case close case cancel case settings case defaultLoadName case billOfMaterials case systemEditorTitle case systemsTitle } private let translations: [UIStringKey: [String: String]] = [ .addLoad: [ "en": "Add Load", "de": "Verbraucher hinzufügen", "es": "Añadir carga", "fr": "Ajouter une charge", "nl": "Belasting toevoegen", ], .browseLibrary: [ "en": "Browse Library", "de": "Bibliothek durchsuchen", "es": "Explorar biblioteca", "fr": "Parcourir la bibliothèque", "nl": "Bibliotheek bekijken", ], .library: [ "en": "Library", "de": "Bibliothek", "es": "Biblioteca", "fr": "Bibliothèque", "nl": "Bibliotheek", ], .overviewTab: [ "en": "Overview", "de": "Übersicht", "es": "Resumen", "fr": "Aperçu", "nl": "Overzicht", ], .componentsTab: [ "en": "Components", "de": "Verbraucher", "es": "Componentes", "fr": "Composants", "nl": "Componenten", ], .batteriesTab: [ "en": "Batteries", "de": "Batterien", "es": "Baterías", "fr": "Batteries", "nl": "Batterijen", ], .chargersTab: [ "en": "Chargers", "de": "Ladegeräte", "es": "Cargadores", "fr": "Chargeurs", "nl": "Laders", ], .close: [ "en": "Close", "de": "Schließen", "es": "Cerrar", "fr": "Fermer", "nl": "Sluiten", ], .cancel: [ "en": "Cancel", "de": "Abbrechen", "es": "Cancelar", "fr": "Annuler", "nl": "Annuleren", ], .settings: [ "en": "Settings", "de": "Einstellungen", "es": "Configuración", "fr": "Réglages", "nl": "Instellingen", ], .defaultLoadName: [ "en": "New Load", "de": "Neuer Verbraucher", "es": "Carga nueva", "fr": "Nouvelle charge", "nl": "Nieuwe last", ], .billOfMaterials: [ "en": "Bill of Materials", "de": "Stückliste", "es": "Lista de materiales", "fr": "Liste de matériel", "nl": "Stuklijst", ], .systemEditorTitle: [ "en": "Edit System", "de": "System bearbeiten", "es": "Editar sistema", "fr": "Modifier le système", "nl": "Systeem bewerken", ], .systemsTitle: [ "en": "Systems", "de": "Systeme", "es": "Sistemas", "fr": "Systèmes", "nl": "Systemen", ], ] override func setUpWithError() throws { try super.setUpWithError() continueAfterFailure = false XCUIDevice.shared.orientation = .portrait //ensureDoNotDisturbEnabled() //dismissSystemOverlays() } override func tearDownWithError() throws { try super.tearDownWithError() //dismissSystemOverlays() } @MainActor func testOnboardingScreenshots() throws { let app = launchApp(arguments: ["--uitest-reset-data"]) waitForStability(long: true) dismissNotificationBannersIfNeeded() waitForStability(long: true) dismissNotificationBannersIfNeeded() waitForStability(long: true) dismissNotificationBannersIfNeeded() waitForStability(long: true) dismissNotificationBannersIfNeeded() let createSystemButton = app.buttons["create-system-button"] XCTAssertTrue(createSystemButton.waitForExistence(timeout: 8)) waitForStability(long: true) dismissNotificationBannersIfNeeded() waitForStability(long: true) takeScreenshot(named: "01-OnboardingSystemsView") createSystemButton.tap() let addLoadButton = button(in: app.buttons, for: .addLoad) XCTAssertTrue(addLoadButton.waitForExistence(timeout: 8)) let browseLibraryButton = button(in: app.buttons, for: .browseLibrary) XCTAssertTrue(browseLibraryButton.waitForExistence(timeout: 4)) waitForStability() takeScreenshot(named: "02-OnboardingSystemView") browseLibraryButton.tap() let libraryCloseButton = app.buttons["library-view-close-button"] XCTAssertTrue(libraryCloseButton.waitForExistence(timeout: 8)) waitForStability(long: true) takeScreenshot(named: "04-ComponentSelectorView") libraryCloseButton.tap() XCTAssertTrue(addLoadButton.waitForExistence(timeout: 4)) addLoadButton.tap() let newLoadButton = button(in: app.buttons, for: .defaultLoadName) XCTAssertTrue(newLoadButton.waitForExistence(timeout: 8)) waitForStability(long: true) takeScreenshot(named: "03-LoadEditorView") } @MainActor func testSampleDataScreenshots() throws { let app = XCUIApplication() app.launchArguments = ["--uitest-reset-data", "--uitest-sample-data"] app.launch() let systemsList = resolvedSystemsList(in: app) XCTAssertTrue(systemsList.waitForExistence(timeout: 4)) takeScreenshot(named: "05-SystemsWithSampleData") let firstSystemCell = systemsList.cells.element(boundBy: 0) XCTAssertTrue(firstSystemCell.waitForExistence(timeout: 2)) let systemName = firstSystemCell.staticTexts.firstMatch.label let systemButton = firstSystemCell.buttons.firstMatch if systemButton.exists { systemButton.tap() } else { firstSystemCell.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5)).tap() } var detailVisible = waitForSystemDetail(named: systemName, in: app) if !detailVisible { firstSystemCell.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5)).tap() detailVisible = waitForSystemDetail(named: systemName, in: app) } XCTAssertTrue(detailVisible) takeScreenshot(named: "06-AdventureVanOverview") // let overviewTab = app.buttons["overview-tab"] // XCTAssertTrue(overviewTab.waitForExistence(timeout: 3)) // overviewTab.tap() waitForStability(long: false) let bomElement = resolveBillOfMaterialsElement(in: app) if !bomElement.waitForExistence(timeout: 6) { bringElementIntoView(bomElement, in: app) } XCTAssertTrue(bomElement.exists) if !bomElement.isHittable { bringElementIntoView(bomElement, in: app, requireHittable: true) } if bomElement.isHittable { bomElement.tap() } else { bomElement.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5)).tap() } waitForStability(long: true) takeScreenshot(named: "08-BillOfMaterials") let closeButton = app.buttons["system-bom-close-button"] XCTAssertTrue(closeButton.waitForExistence(timeout: 6)) closeButton.tap() let componentsTab = componentsTabButton(in: app) XCTAssertTrue(componentsTab.waitForExistence(timeout: 3)) if componentsTab.isHittable { componentsTab.tap() } else { componentsTab.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5)).tap() } let loadsList = resolvedLoadsList(in: app) XCTAssertTrue(loadsList.waitForExistence(timeout: 4)) takeScreenshot(named: "07-AdventureVanLoads") waitForStability() let firstLoad = loadsList.cells.element(boundBy: 0) XCTAssertTrue(firstLoad.waitForExistence(timeout: 2)) let loadName = firstLoad.staticTexts.firstMatch.label firstLoad.tap() let loadNavButton = app.navigationBars.buttons[loadName] XCTAssertTrue(loadNavButton.waitForExistence(timeout: 3)) takeScreenshot(named: "09-AdventureVanCalculator") } private func launchApp(arguments: [String]) -> XCUIApplication { let app = XCUIApplication() var launchArguments = ["--uitest-reset-data"] launchArguments.append(contentsOf: arguments) app.launchArguments = launchArguments app.launch() //dismissSystemOverlays() return app } private func resolvedSystemsList(in app: XCUIApplication) -> XCUIElement { let collection = app.collectionViews["systems-list"] if collection.waitForExistence(timeout: 6) { return collection } if app.collectionViews.firstMatch.waitForExistence(timeout: 2) { return app.collectionViews.firstMatch } let table = app.tables["systems-list"] if table.waitForExistence(timeout: 6) { return table } XCTAssertTrue(app.tables.firstMatch.waitForExistence(timeout: 2)) return app.tables.firstMatch } private func resolvedLoadsList(in app: XCUIApplication) -> XCUIElement { let collection = app.collectionViews["loads-list"] if collection.waitForExistence(timeout: 6) { return collection } let table = app.tables["loads-list"] XCTAssertTrue(table.waitForExistence(timeout: 6)) return table } private func takeScreenshot(named name: String) { let screenshot = XCUIScreen.main.screenshot() let attachment = XCTAttachment(screenshot: screenshot) attachment.name = name attachment.lifetime = .keepAlways add(attachment) } private func waitForStability(long: Bool = false) { RunLoop.current.run(until: Date().addingTimeInterval(long ? 5.0 : 0.5)) } private func componentsTabButton(in app: XCUIApplication) -> XCUIElement { let identifierMatch = app.descendants(matching: .any) .matching(identifier: "components-tab").firstMatch if identifierMatch.exists { return identifierMatch } let localizedLabels = [ "Components", "Verbraucher", "Componentes", "Composants", "Componenten" ] for label in localizedLabels { let button = app.buttons[label] if button.exists { return button } let tabBarButton = app.tabBars.buttons[label] if tabBarButton.exists { return tabBarButton } let segmentedButton = app.segmentedControls.buttons[label] if segmentedButton.exists { return segmentedButton } let segmentedOther = app.segmentedControls.otherElements[label] if segmentedOther.exists { return segmentedOther } } let fallbackSegmented = app.segmentedControls.buttons.element(boundBy: 1) if fallbackSegmented.exists { return fallbackSegmented } let tabBarButton = app.tabBars.buttons.element(boundBy: 1) if tabBarButton.exists { return tabBarButton } return app.tabBars.descendants(matching: .any).firstMatch } private func waitForSystemDetail(named systemName: String, in app: XCUIApplication, timeout: TimeInterval = 6) -> Bool { let deadline = Date().addingTimeInterval(timeout) while Date() < deadline { if app.otherElements["system-overview"].exists { return true } let navBar = app.navigationBars.firstMatch if navBar.buttons[systemName].exists || navBar.staticTexts[systemName].exists { return true } RunLoop.current.run(until: Date().addingTimeInterval(0.25)) } return app.otherElements["system-overview"].exists } private func bringElementIntoView( _ element: XCUIElement, in app: XCUIApplication, requireHittable: Bool = false, attempts: Int = 8 ) { let scrollContainer = app.scrollViews.firstMatch.exists ? app.scrollViews.firstMatch : app.tables.firstMatch for _ in 0.. XCUIElement { let identifier = "system-bom-button" let buttonByIdentifier = app.buttons.matching(identifier: identifier).firstMatch if buttonByIdentifier.exists { return buttonByIdentifier } let elementByIdentifier = app.otherElements.matching(identifier: identifier).firstMatch if elementByIdentifier.exists { return elementByIdentifier } let candidates = candidateStrings(for: .billOfMaterials) for candidate in candidates { let button = app.buttons[candidate] if button.exists { return button } let other = app.otherElements[candidate] if other.exists { return other } } return buttonByIdentifier } private func dismissNotificationBannersIfNeeded() { let banner = springboard.otherElements.matching(identifier: "NotificationShortLookView").firstMatch if banner.waitForExistence(timeout: 1) { if banner.isHittable { banner.swipeUp() } else { let start = banner.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5)) let end = banner.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: -1.5)) start.press(forDuration: 0.05, thenDragTo: end) } waitForStability() } } private func candidateStrings(for key: UIStringKey) -> [String] { var values = Set() if let languageCode = Locale.preferredLanguages.first.flatMap({ String($0.prefix(2)) }), let localized = translations[key]?[languageCode] { values.insert(localized) } if let english = translations[key]?["en"] { values.insert(english) } if let others = translations[key]?.values { values.formUnion(others) } if key == .settings { values.insert("gearshape") } return Array(values) } private func button(in query: XCUIElementQuery, for key: UIStringKey) -> XCUIElement { let candidates = candidateStrings(for: key) for candidate in candidates { let element = query[candidate] if element.exists { return element } } let predicate = NSPredicate( format: "label IN %@ OR identifier IN %@", NSArray(array: candidates), NSArray(array: candidates) ) return query.matching(predicate).firstMatch } private func optionalButton(in query: XCUIElementQuery, for key: UIStringKey) -> XCUIElement? { let element = button(in: query, for: key) return element.exists ? element : nil } private func tabButton(in app: XCUIApplication, key: UIStringKey) -> XCUIElement { let tabSpecific = button(in: app.tabBars.buttons, for: key) if tabSpecific.exists { return tabSpecific } return button(in: app.buttons, for: key) } private func navigationBar(in app: XCUIApplication, key: UIStringKey) -> XCUIElement { let candidates = candidateStrings(for: key) for candidate in candidates { let bar = app.navigationBars[candidate] if bar.exists { return bar } } return app.navigationBars.element(boundBy: 0) } private func tapButtonIfPresent(app: XCUIApplication, key: UIStringKey) { let candidates = candidateStrings(for: key) for candidate in candidates { let button = app.buttons[candidate] if button.waitForExistence(timeout: 2) { button.tap() return } } } private func openBillOfMaterials(app: XCUIApplication) { let bomButton = button(in: app.buttons, for: .billOfMaterials) XCTAssertTrue(bomButton.waitForExistence(timeout: 6)) bomButton.tap() let bomView = app.otherElements["system-bom-view"] XCTAssertTrue(bomView.waitForExistence(timeout: 8)) waitForStability(long: true) } private func closeBillOfMaterials(app: XCUIApplication) { tapButtonIfPresent(app: app, key: .close) } private func navigateBack(app: XCUIApplication) { let backButton = app.navigationBars.buttons.element(boundBy: 0) if backButton.exists { backButton.tap() } else { app.swipeRight() } } private func openSettings(app: XCUIApplication) { let systemsBar = navigationBar(in: app, key: .systemsTitle) let settingsButton = button(in: systemsBar.buttons, for: .settings) if settingsButton.exists { settingsButton.tap() } else { systemsBar.buttons.element(boundBy: 0).tap() } } private func ensureDoNotDisturbEnabled() { springboard.activate() let pullStart = springboard.coordinate(withNormalizedOffset: CGVector(dx: 0.98, dy: 0.02)) let pullEnd = springboard.coordinate(withNormalizedOffset: CGVector(dx: 0.98, dy: 0.30)) pullStart.press(forDuration: 0.1, thenDragTo: pullEnd) let focusTile = springboard.otherElements["Focus"] let focusButton = springboard.buttons["Focus"] if focusTile.waitForExistence(timeout: 2) { focusTile.press(forDuration: 1.0) } else if focusButton.waitForExistence(timeout: 2) { focusButton.press(forDuration: 1.0) } else { return } let dndButton = springboard.buttons["Do Not Disturb"] if dndButton.waitForExistence(timeout: 1) { if !dndButton.isSelected { dndButton.tap() } } else { let dndCell = springboard.cells["Do Not Disturb"] if dndCell.waitForExistence(timeout: 1) && !dndCell.isSelected { dndCell.tap() } else { let dndLabel = springboard.staticTexts["Do Not Disturb"] if dndLabel.waitForExistence(timeout: 1) { dndLabel.tap() } } } let dismissStart = springboard.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.85)) let dismissEnd = springboard.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.15)) dismissStart.press(forDuration: 0.1, thenDragTo: dismissEnd) } private func dismissSystemOverlays() { let app = XCUIApplication() let alertButtons = [ "OK", "Allow", "Later", "Not Now", "Close", "Continue", "Remind Me Later", "Maybe Later", ] if app.alerts.firstMatch.exists { handleAlerts(in: app, buttons: alertButtons) } if springboard.alerts.firstMatch.exists || springboard.scrollViews.firstMatch.exists { handleAlerts(in: springboard, buttons: alertButtons + ["Enable Later"]) } } private func handleAlerts(in application: XCUIApplication, buttons: [String]) { for buttonLabel in buttons { let button = application.buttons[buttonLabel] if button.waitForExistence(timeout: 0.5) { button.tap() return } } let closeButton = application.buttons.matching(NSPredicate(format: "identifier CONTAINS[c] %@", "Close")).firstMatch if closeButton.exists { closeButton.tap() } } }