diff --git a/README.md b/README.md index 846d446..4e8e540 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,19 @@ A sample application built solely to showcase axe DevTools Mobile implementation ------ +## Two ways to test + +This sample demonstrates both ways to run accessibility scans with axe DevTools Mobile. Each lives in its own folder under `axe-devtools-ios-sample-appUITests/` so you can find the pattern you need at a glance. + +| Approach | What it does | Where to look | Credentials | +| --- | --- | --- | --- | +| **Targeted scanning** | You choose exactly which screens to scan with `axe.run(onElement:)`, then report via an HTML report and/or upload to the dashboard. | [`TargetedScan/TargetedScanUITests.swift`](axe-devtools-ios-sample-appUITests/TargetedScan/TargetedScanUITests.swift) | `Login.swift` | +| **AutoScan** | Zero-code: your test just drives the app (no imports, no scan calls). The framework automatically captures, de-duplicates, and scans each unique screen, then writes one HTML report. | [`AutoScan/AutoScanUITests.swift`](axe-devtools-ios-sample-appUITests/AutoScan/AutoScanUITests.swift) | [`axe_config.json`](axe-devtools-ios-sample-appUITests/axe_config.json) | + +Both approaches produce a self-contained **HTML report** locally. With targeted scanning you call `generateHtmlReportAndSummary()` yourself; with AutoScan it happens automatically when the test bundle finishes — controlled entirely by `axe_config.json`. AutoScan ships **disabled** (`"axeAutoScanMode": false`); set it to `true` and add your credentials in that file to try it. + +------ + ## Get Started: > Prerequisites: @@ -18,9 +31,9 @@ A sample application built solely to showcase axe DevTools Mobile implementation This project uses Swift Package Manager to pull in the frameworks from [axe-devtools-ios](https://github.com/dequelabs/axe-devtools-ios/). -1. Add your Deque API Key and your axe Developer Hub Project Id to `Login.swift`. +1. Add your Deque API Key and your axe Developer Hub Project Id to `Login.swift` (for the targeted-scanning tests) and to `axe-devtools-ios-sample-appUITests/axe_config.json` (for the AutoScan tests). -2. Once you have a device or simulator ready to go, open `SampleUITests.swift`, and tap on the diamond to run this suite. The simulator will launch, and the tests will run. +2. Once you have a device or simulator ready to go, open either `TargetedScan/TargetedScanUITests.swift` or `AutoScan/AutoScanUITests.swift`, and tap on the diamond to run the suite. The simulator will launch, and the tests will run. Shows the click area for running the UI test. @@ -30,8 +43,8 @@ _Note: This project requires a device on iOS 16.0 or later. Additionally, Virtua To run a test with this project, or to setup your own project, follow our online guide to get started with [XCUITesting on SauceLabs.](https://docs.deque.com/devtools-mobile/ios-example-sauce-labs-xcui) -Assuming you have already downloaded the sample project to your computer: -1. Add your Deque API Key and your axe Developer Hub Project Id to `Login.swift`. +These steps run the **targeted-scanning** tests. Assuming you have already downloaded the sample project to your computer: +1. Add your Deque API Key and your axe Developer Hub Project Id to `Login.swift`. (To exercise the AutoScan path instead, also enable it in `axe-devtools-ios-sample-appUITests/axe_config.json` as described above.) 2. Open a Terminal window, enter `cd `, then drag and drop the `axe-devtools-ios-sample-app` from Finder into the Terminal window. Hit return/enter. To run on **Real Devices**, type `sh prepareForSauceRealDevice.sh` in the same window. Hit return/enter. diff --git a/RegressionUITests/RegressionUITests.swift b/RegressionUITests/RegressionUITests.swift index 0e155f5..3137d45 100644 --- a/RegressionUITests/RegressionUITests.swift +++ b/RegressionUITests/RegressionUITests.swift @@ -14,7 +14,7 @@ class RegressionUITests: XCTestCase { var lastResult: AxeResult? override func setUp() { - axe = try? AxeDevTools.startSession(apiKey: Login.APIKey, projectId: Login.projectId) + axe = try? AxeDevTools.startScanSession(apiKey: Login.APIKey, projectId: Login.projectId) app.launch() sleep(2) // allow app to fully load, Github actions needed a moment. diff --git a/axe-devtools-ios-sample-app.xcodeproj/project.pbxproj b/axe-devtools-ios-sample-app.xcodeproj/project.pbxproj index 4c538fc..625fe86 100644 --- a/axe-devtools-ios-sample-app.xcodeproj/project.pbxproj +++ b/axe-devtools-ios-sample-app.xcodeproj/project.pbxproj @@ -7,11 +7,14 @@ objects = { /* Begin PBXBuildFile section */ + 30CF7AC9E4AF9F05A27959C6 /* TargetedScanUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5930CB6C34FD68A1DF754BBE /* TargetedScanUITests.swift */; }; + 3399D4BF2D58349249B12104 /* axe_config.json in Resources */ = {isa = PBXBuildFile; fileRef = E91A725F8F38EF78E0303331 /* axe_config.json */; }; 6A3B4C992A5D8EC20078BB09 /* RegressionUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A3B4C982A5D8EC20078BB09 /* RegressionUITests.swift */; }; 6A3B4CA12A5D8F6B0078BB09 /* Login.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AEDD8512886FD4D0017DCF1 /* Login.swift */; }; 6AEDD8522886FD4D0017DCF1 /* Login.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AEDD8512886FD4D0017DCF1 /* Login.swift */; }; 6AEDD8542886FD500017DCF1 /* Login.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AEDD8512886FD4D0017DCF1 /* Login.swift */; }; - 6AF13275282E848C00A64994 /* SampleUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AF13274282E848C00A64994 /* SampleUITests.swift */; }; + 805E4C1E5E82317B9E2A3B89 /* SampleAppNavigation.swift in Sources */ = {isa = PBXBuildFile; fileRef = B17D6D36DF84C738F14736B7 /* SampleAppNavigation.swift */; }; + 845EEC6DCB3471149282DB01 /* AutoScanUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C23D938F193549E1C9DE7418 /* AutoScanUITests.swift */; }; 9E38FE2F2E31209F000DAC24 /* NamedTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E38FE2E2E312097000DAC24 /* NamedTextField.swift */; }; 9E38FE312E3121D0000DAC24 /* ContactUsTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E38FE302E3121C8000DAC24 /* ContactUsTableViewCell.swift */; }; 9E38FE332E312271000DAC24 /* Route.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E38FE322E31226E000DAC24 /* Route.swift */; }; @@ -125,15 +128,18 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 5930CB6C34FD68A1DF754BBE /* TargetedScanUITests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = TargetedScanUITests.swift; sourceTree = ""; }; 6A3B4C962A5D8EC20078BB09 /* RegressionUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RegressionUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 6A3B4C982A5D8EC20078BB09 /* RegressionUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegressionUITests.swift; sourceTree = ""; }; 6AEDD8512886FD4D0017DCF1 /* Login.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Login.swift; sourceTree = ""; }; - 6AF13274282E848C00A64994 /* SampleUITests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SampleUITests.swift; sourceTree = ""; }; 9E38FE2E2E312097000DAC24 /* NamedTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NamedTextField.swift; sourceTree = ""; }; 9E38FE302E3121C8000DAC24 /* ContactUsTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactUsTableViewCell.swift; sourceTree = ""; }; 9E38FE322E31226E000DAC24 /* Route.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Route.swift; sourceTree = ""; }; 9E38FE362E3169FA000DAC24 /* UIFont+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIFont+Extensions.swift"; sourceTree = ""; }; 9E76EC592E3004BC0023917A /* ContactUsTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactUsTableViewController.swift; sourceTree = ""; }; + B17D6D36DF84C738F14736B7 /* SampleAppNavigation.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = SampleAppNavigation.swift; sourceTree = ""; }; + C23D938F193549E1C9DE7418 /* AutoScanUITests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AutoScanUITests.swift; sourceTree = ""; }; + E91A725F8F38EF78E0303331 /* axe_config.json */ = {isa = PBXFileReference; includeInIndex = 1; path = axe_config.json; sourceTree = ""; }; F3799A7227E4D0C5009CFC2E /* ItemCollectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemCollectionView.swift; sourceTree = ""; }; F3799A7427E4D0DC009CFC2E /* ItemCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemCollectionViewCell.swift; sourceTree = ""; }; F3799A7627E4D7ED009CFC2E /* ItemCellViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemCellViewModel.swift; sourceTree = ""; }; @@ -239,6 +245,15 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 21B84018A6A78D27990475E2 /* AutoScan */ = { + isa = PBXGroup; + children = ( + C23D938F193549E1C9DE7418 /* AutoScanUITests.swift */, + ); + name = AutoScan; + path = AutoScan; + sourceTree = ""; + }; 6A3B4C972A5D8EC20078BB09 /* RegressionUITests */ = { isa = PBXGroup; children = ( @@ -247,6 +262,15 @@ path = RegressionUITests; sourceTree = ""; }; + 86674B529DE75ED599ED20CF /* TargetedScan */ = { + isa = PBXGroup; + children = ( + 5930CB6C34FD68A1DF754BBE /* TargetedScanUITests.swift */, + ); + name = TargetedScan; + path = TargetedScan; + sourceTree = ""; + }; 9E38FE2D2E312081000DAC24 /* Views */ = { isa = PBXGroup; children = ( @@ -282,6 +306,15 @@ path = ContactUs; sourceTree = ""; }; + B19CEFD283E99B98B85E565F /* Shared */ = { + isa = PBXGroup; + children = ( + B17D6D36DF84C738F14736B7 /* SampleAppNavigation.swift */, + ); + name = Shared; + path = Shared; + sourceTree = ""; + }; F3133A4628086B0A00389A4C /* Frameworks */ = { isa = PBXGroup; children = ( @@ -528,7 +561,10 @@ F3F31DAE27DFD32500D87988 /* axe-devtools-ios-sample-appUITests */ = { isa = PBXGroup; children = ( - 6AF13274282E848C00A64994 /* SampleUITests.swift */, + 86674B529DE75ED599ED20CF /* TargetedScan */, + 21B84018A6A78D27990475E2 /* AutoScan */, + B19CEFD283E99B98B85E565F /* Shared */, + E91A725F8F38EF78E0303331 /* axe_config.json */, ); path = "axe-devtools-ios-sample-appUITests"; sourceTree = ""; @@ -608,8 +644,6 @@ dependencies = ( ); name = "axe-devtools-ios-sample-app"; - packageProductDependencies = ( - ); productName = "axe-devtools-ios-sample-app"; productReference = F3F31D8B27DFD32500D87988 /* axe-devtools-ios-sample-app.app */; productType = "com.apple.product-type.application"; @@ -707,6 +741,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 3399D4BF2D58349249B12104 /* axe_config.json in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -806,7 +841,9 @@ buildActionMask = 2147483647; files = ( 6AEDD8542886FD500017DCF1 /* Login.swift in Sources */, - 6AF13275282E848C00A64994 /* SampleUITests.swift in Sources */, + 30CF7AC9E4AF9F05A27959C6 /* TargetedScanUITests.swift in Sources */, + 845EEC6DCB3471149282DB01 /* AutoScanUITests.swift in Sources */, + 805E4C1E5E82317B9E2A3B89 /* SampleAppNavigation.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/axe-devtools-ios-sample-app.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/axe-devtools-ios-sample-app.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 0dfedce..3e91f8a 100644 --- a/axe-devtools-ios-sample-app.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/axe-devtools-ios-sample-app.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -7,7 +7,7 @@ "location" : "https://github.com/dequelabs/axe-devtools-ios", "state" : { "branch" : "main", - "revision" : "474fcacb158ccc6e4ae1f175649e8f30d535e9d2" + "revision" : "162c924e0f91beb08cd047704380e78c09fd4bf2" } } ], diff --git a/axe-devtools-ios-sample-app/Login.swift b/axe-devtools-ios-sample-app/Login.swift index 9de1b9e..e9fb3b5 100644 --- a/axe-devtools-ios-sample-app/Login.swift +++ b/axe-devtools-ios-sample-app/Login.swift @@ -6,6 +6,14 @@ // import Foundation + +/// Credentials for the **targeted scanning** examples (TargetedScanUITests). +/// +/// - API key: https://axe.deque.com/settings +/// - Project ID: https://axe.deque.com/axe-watcher/projects +/// +/// Note: the **AutoScan** example reads its credentials from +/// `axe-devtools-ios-sample-appUITests/axe_config.json` instead, not from here. struct Login { static let APIKey="" static let projectId="" diff --git a/axe-devtools-ios-sample-appUITests/AutoScan/AutoScanUITests.swift b/axe-devtools-ios-sample-appUITests/AutoScan/AutoScanUITests.swift new file mode 100644 index 0000000..04d69c6 --- /dev/null +++ b/axe-devtools-ios-sample-appUITests/AutoScan/AutoScanUITests.swift @@ -0,0 +1,53 @@ +// +// AutoScanUITests.swift +// axe-devtools-ios-sample-appUITests +// +// AUTOSCAN — the zero-code path. +// +// Notice what's NOT here: no `import axeDevToolsXCUI`, no session setup, no +// scan calls, no report calls. That's the whole point of AutoScan. +// +// As long as the axeDevToolsXCUI framework is linked to this test target and +// `axe_config.json` has `"axeAutoScanMode": true`, the framework automatically: +// 1. Starts AutoScan when the test bundle begins. +// 2. Captures and de-duplicates each unique screen as you tap and swipe. +// 3. Scans everything and writes an HTML report when the test bundle finishes. +// +// AutoScan ships DISABLED in this sample. To try it, open axe_config.json, +// set `"axeAutoScanMode": true`, and fill in your axeMobileApiKey/axeProjectId. +// Then just drive the app the way a user would — no other code required. +// +// Full guide: https://docs.deque.com/devtools-mobile/ +// + +import XCTest + +final class AutoScanUITests: XCTestCase { + private let app = XCUIApplication() + + override func setUpWithError() throws { + continueAfterFailure = false + app.launch() + } + + /// A plain user journey across the app, recorded with Xcode's UI test recorder + /// and then tidied up. There is no accessibility code here — AutoScan captures + /// and scans each screen automatically as these gestures run. + func testUserJourney() throws { + // Home — scroll through the featured content. + app.collectionViews.containing(.other, identifier: "Vertical scroll bar, 4 pages").firstMatch.swipeUp() + app.otherElements.matching(identifier: "Horizontal scroll bar, 1 page").element(boundBy: 0).swipeUp() + + // Catalog — browse the categories. + app.images["Category"].firstMatch.tap() + app.scrollViews.firstMatch.swipeUp() + + // Cart — add an item. + app.images["Bag"].firstMatch.tap() + app.buttons.matching(identifier: "+").element(boundBy: 1).tap() + + // Profile — open and scroll to the help section. + app.images["Menu"].firstMatch.tap() + app.cells.containing(.image, identifier: "Info Square").firstMatch.swipeUp() + } +} diff --git a/axe-devtools-ios-sample-appUITests/SampleUITests.swift b/axe-devtools-ios-sample-appUITests/SampleUITests.swift deleted file mode 100644 index 1570a1b..0000000 --- a/axe-devtools-ios-sample-appUITests/SampleUITests.swift +++ /dev/null @@ -1,122 +0,0 @@ -// -// SampleUITests.swift -// axe-devtools-ios-sample-appUITests -// -// Created by Kate Owens on 3/14/22. -// - -import axe_devtools_ios_sample_app -import axeDevToolsXCUI -import XCTest - -/* - Full Getting Started guide is available here: https://docs.deque.com/devtools-mobile/ios-setup - - Quick Start Guide - - To get started: - Set up axeDevTools automation for UITests in the 2 easy steps outlined below. - Don't forget to save this file when finished. File > Save or Command + S - - To run an accessibility test: - Select a simulator or device in Xcode and press cmd+U to begin running tests. - Review your test results from the Mobile Dashboard: https://axe-mobile.deque.com/ - */ - -class SampleUITests: XCTestCase { - var axe: AxeDevTools? - var app = XCUIApplication() - - override func setUp() { - // 1. - // Enter your axe DevTools Mobile API key and axe Developer Hub project ID in the "" marks in the APIKey and projectId constants defined in the Login file - // Get your API key here: https://axe.deque.com/settings - // Get your axe Developer Hub Project Id here: https://axe.deque.com/axe-watcher/projects - axe = try? AxeDevTools.startSession(apiKey: Login.APIKey, projectId: Login.projectId) - - // 1a (Optional). Experimental rules are rulesets that are still in testing and development. Results for the - // experimental rules can be IGNORED with the ignoreExperimental method, and this way they will not run. - axe?.configuration.ignoreExperimental() - - // 2. - // Launch the sample app - app.launch() - sleep(1) - } - - /* - This test runs an accessibility scan on each tab in the sample application, then posts to the dashboard. - This example contains a few different options for implementing -- feel free to play around with it! - */ - func testAccessibilityAndPostResults() throws { - - // Run a scan on the first page of the sample app - // For more information about running an accessibility scan visit: - // https://docs.deque.com/devtools-mobile/xcui-setup#automated-testing - guard let result = try axe?.run(onElement: app) else { - XCTFail("\n\n🦮 axe DevTools didn't run - Did you add your API key in Login.swift?\n\n") - return - } - - // Post scan results to the mobile dashboard - // For more information on this feature please visit: - // https://docs.deque.com/devtools-mobile/ios-upload - try axe?.postResult(result) - - let tabBar = XCUIApplication().tabBars["Tab Bar"] - - tabBar.buttons["Catalog"].tap() - - guard let result = try axe?.run(onElement: app) else { - XCTFail("\n\n🦮 axe DevTools didn't run - Did you add your API key in Login.swift?\n\n") - return - } - - // Add a custom scan name when posting result. By default, the screen title is the scan name. - // For more information on renaming a scan, check out: - // https://docs.deque.com/devtools-mobile/ios-scan-name - let newScanName = "Catalog Tab" - - try axe?.postResult(result, withScanName: newScanName) - - tabBar.buttons["Cart"].tap() - guard let result = try axe?.run(onElement: app) else { - XCTFail("\n\n🦮 axe DevTools didn't run - Did you add your API key in Login.swift?\n\n") - return - } - - // Add tags to your scan to easily share with your coworkers when it's pushed to the dashboard. - // For more infromation on tagging a scan please see: - // https://docs.deque.com/devtools-mobile/ios-tag-result - let tags = ["Cart Tab, iPhone 15 Pro"] - - try axe?.postResult(result, withTags: tags) - - tabBar.buttons["Profile"].tap() - guard let result = try axe?.run(onElement: app) else { - XCTFail("\n\n🦮 axe DevTools didn't run - Did you add your API key in Login.swift?\n\n") - return - } - try axe?.postResult(result, withScanName: "Profile Tab") - } - - // This test runs an accessibility scan on the first page of the sample application and saves the scan results locally. By default, the results will be saved to a folder called 'AxeDevToolsResults' within your local User folder - // For more information on saving results locally, please visit: - // https://docs.deque.com/devtools-mobile/ios-save-result - func testAccessibilityAndSaveResultsLocally() throws { - var lastResult: AxeResult? - guard let result = try axe?.run(onElement: app) else { - XCTFail("\n\n🦮 axe DevTools didn't run - Did you add your API key in Login.swift?\n\n") - return - } - - // Save scan results locally - _ = try axe?.saveResult(result) - - lastResult = result - let critical = lastResult?.failures.filter { $0.impact == .CRITICAL }.count - - // Assert that there are no critical impact accessibility issues in scan results - XCTAssertTrue(critical == 0, "Critical Accessibility Results were found." ) - } -} diff --git a/axe-devtools-ios-sample-appUITests/Shared/SampleAppNavigation.swift b/axe-devtools-ios-sample-appUITests/Shared/SampleAppNavigation.swift new file mode 100644 index 0000000..a5afd39 --- /dev/null +++ b/axe-devtools-ios-sample-appUITests/Shared/SampleAppNavigation.swift @@ -0,0 +1,27 @@ +// +// SampleAppNavigation.swift +// axe-devtools-ios-sample-appUITests +// +// Small helpers that keep the example tests terse by centralizing +// the sample app's tab-bar navigation. The four tabs (Home, Catalog, +// Cart, Profile) are defined in MainTabBarViewController. +// + +import XCTest + +extension XCUIApplication { + /// The sample app's main tab bar. + var sampleTabBar: XCUIElement { + tabBars["Tab Bar"] + } + + /// The titles of every tab in the sample app, in display order. + static let sampleTabs = ["Home", "Catalog", "Cart", "Profile"] + + /// Waits for the tab with the given title to appear, then taps it. + func goToTab(_ title: String) { + let button = sampleTabBar.buttons[title] + XCTAssertTrue(button.waitForExistence(timeout: 5), "Tab '\(title)' did not appear.") + button.tap() + } +} diff --git a/axe-devtools-ios-sample-appUITests/TargetedScan/TargetedScanUITests.swift b/axe-devtools-ios-sample-appUITests/TargetedScan/TargetedScanUITests.swift new file mode 100644 index 0000000..60c85d4 --- /dev/null +++ b/axe-devtools-ios-sample-appUITests/TargetedScan/TargetedScanUITests.swift @@ -0,0 +1,82 @@ +// +// TargetedScanUITests.swift +// axe-devtools-ios-sample-appUITests +// +// TARGETED SCANNING — the on-demand path. +// +// You decide exactly which screens to scan and when, by calling +// `axe.run(onElement:)`. This example walks the four tabs of the sample +// app, then shows the two ways to report the results: +// 1. Generate a self-contained HTML report + summary locally. +// 2. Upload each scan to the axe Developer Hub dashboard. +// +// Full guide: https://docs.deque.com/devtools-mobile/ +// + +import axe_devtools_ios_sample_app +import axeDevToolsXCUI +import XCTest + +final class TargetedScanUITests: XCTestCase { + private var axe: AxeDevTools? + private let app = XCUIApplication() + + override func setUpWithError() throws { + continueAfterFailure = false + + // Start a scan session against the axe Developer Hub. + // Add your API key and project ID to Login.swift first: + // • API key: https://axe.deque.com/settings + // • Project ID: https://axe.deque.com/axe-watcher/projects + // `try?` so a missing/invalid key leaves `axe` nil and each test fails with the + // friendly guidance below, rather than aborting setUp with an opaque error. + axe = try? AxeDevTools.startScanSession(apiKey: Login.APIKey, projectId: Login.projectId) + + // Experimental rules are still in development; ignore them so they don't run. + axe?.configuration.ignoreExperimental() + + app.launch() + } + + /// Scans every tab on demand, then produces ONE HTML report covering all of them. + /// + /// `run(onElement:)` accumulates each result on the `axe` instance, so a single + /// `generateHtmlReportAndSummary()` call at the end bundles every scan into one + /// self-contained HTML file plus a printed summary. + func testScanAllTabsAndGenerateHtmlReport() throws { + let axe = try XCTUnwrap(axe, "axe DevTools didn't start — did you add your API key to Login.swift?") + + for tab in XCUIApplication.sampleTabs { + app.goToTab(tab) + _ = try axe.run(onElement: app) + } + + // One report for the whole journey. Defaults to ~/AxeDevToolsMobileResults. + let report = try axe.generateHtmlReportAndSummary() + XCTAssertFalse(report.htmlReportPath.isEmpty, "Expected an HTML report to be written.") + print("📄 HTML report: \(report.htmlReportPath)") + } + + /// Scans a couple of screens and uploads each result to the dashboard, showing the + /// common reporting options: a custom scan name and tags. + func testScanAndUploadToDashboard() throws { + let axe = try XCTUnwrap(axe, "axe DevTools didn't start — did you add your API key to Login.swift?") + + app.goToTab("Home") + try axe.postResult(try axe.run(onElement: app), withScanName: "Home Tab") + + app.goToTab("Catalog") + // Tags make a scan easy to find and share on the dashboard. + try axe.postResult(try axe.run(onElement: app), withTags: ["smoke", "catalog"], withScanName: "Catalog Tab") + } + + /// Demonstrates gating a build on accessibility: fail if any CRITICAL issues are found. + func testHomeScreenHasNoCriticalIssues() throws { + let axe = try XCTUnwrap(axe, "axe DevTools didn't start — did you add your API key to Login.swift?") + + app.goToTab("Home") + let result = try axe.run(onElement: app) + let criticalCount = result.failures.filter { $0.impact == .CRITICAL }.count + XCTAssertEqual(criticalCount, 0, "Found \(criticalCount) critical accessibility issue(s) on the Home screen.") + } +} diff --git a/axe-devtools-ios-sample-appUITests/axe_config.json b/axe-devtools-ios-sample-appUITests/axe_config.json new file mode 100644 index 0000000..9e1fa3f --- /dev/null +++ b/axe-devtools-ios-sample-appUITests/axe_config.json @@ -0,0 +1,8 @@ +{ + "axeAutoScanMode": false, + "axeAppBundleId": "com.dequesystems.axe-devtools-ios-sample-app", + "axeMobileApiKey": "", + "axeProjectId": "", + "axeAccountUrl": "", + "axeUploadResults": true +}