1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
|
import XCTest
/// One accessibility audit per primary screen, plus a Dynamic Type sweep.
///
/// See `AccessibilityAuditHarness` for why these report rather than fail by
/// default, and how to make them enforcing.
@MainActor
final class AccessibilityAuditTests: XCTestCase {
override func setUp() {
// Keep going after a failure so an enforced audit still collects and
// attaches every finding. With this off, XCTest aborts at the first
// reported issue and the burndown list is lost precisely when a category
// is being enforced.
continueAfterFailure = true
}
// MARK: Per-screen audits
func testInspectScreen() throws {
let app = AccessibilityAuditHarness.launch()
app.selectRootTab("Inspect")
// Type a domain so the Run button is enabled. A disabled control has no
// contrast requirement under WCAG 1.4.3, but the audit still flags it,
// so auditing the empty state would report a false positive forever.
let field = app.textFields.firstMatch
if field.waitForExistence(timeout: 5) {
field.tap()
field.typeText("example.com")
}
let audited = try AccessibilityAuditHarness.audit(app, screen: "inspect", test: self)
try XCTSkipUnless(audited, "Audit did not complete in time for Inspect")
}
func testDashboardScreen() throws {
try auditRootTab("Dashboard")
}
func testAuditScreen() throws {
try auditRootTab("Audit")
}
func testHistoryScreen() throws {
try auditRootTab("History")
}
func testSettingsScreen() throws {
try auditRootTab("Settings")
}
func testTrackedDomainsScreen() throws {
let app = AccessibilityAuditHarness.launch()
app.selectRootTab("Settings")
let trackedDomains = app.buttons["Tracked Domains"]
XCTAssertTrue(
trackedDomains.waitForExistence(timeout: 5),
"Settings no longer offers a Tracked Domains row"
)
trackedDomains.tap()
let audited = try AccessibilityAuditHarness.audit(app, screen: "tracked-domains", test: self)
try XCTSkipUnless(audited, "Tracked Domains audit did not complete in time")
}
// MARK: Dynamic Type
/// Re-audits every root screen at the largest accessibility content size.
///
/// This is where clipped text and fixed-height containers surface — the
/// `.accessibility5`-class failures that the fixed geometry in
/// `AppDensityMetrics` is expected to produce until phase 3 of #21 lands.
///
/// Every screen is attempted even if an earlier one times out, so one slow
/// screen cannot silently drop the rest; the skip is reported at the end.
func testAllScreensAtLargestAccessibilitySize() throws {
let app = AccessibilityAuditHarness.launch(
contentSizeCategory: "UICTContentSizeCategoryAccessibilityXXXL"
)
var unaudited: [String] = []
for tab in ["Inspect", "Dashboard", "Audit", "History", "Settings"] {
app.selectRootTab(tab)
let screen = "\(tab.lowercased())-accessibilityXXXL"
if try !AccessibilityAuditHarness.audit(app, screen: screen, test: self) {
unaudited.append(tab)
}
}
try XCTSkipUnless(
unaudited.isEmpty,
"Audit did not complete in time for: \(unaudited.joined(separator: ", "))"
)
}
// MARK: Helpers
private func auditRootTab(_ tab: String) throws {
let app = AccessibilityAuditHarness.launch()
app.selectRootTab(tab)
let audited = try AccessibilityAuditHarness.audit(app, screen: tab.lowercased(), test: self)
try XCTSkipUnless(audited, "Audit did not complete in time for \(tab)")
}
}
|