krz/domain-dig

an ios app for DNS & SSL analysis

clone: git clone https://gitbay.org/krz/domain-dig.git

349bec0e99f330fc1172ca9847c58f019e861f61

unsigned

author: Christian Cleberg <hello@cleberg.net> · 2026-07-20T23:29:18Z

fix(a11y): suppress disabled-control contrast findings on the rule

WCAG 1.4.3 exempts inactive components from contrast requirements, so
Inspect's Run button — disabled until a domain is typed — was reporting a
contrast failure that was never a real defect.

The first attempt typed a domain to enable the button. That worked for
the single-screen test but raised the keyboard, which then followed the
audit onto every later screen in the Dynamic Type sweep and reported nine
phantom hit-region findings per screen against the system emoji picker's
category buttons. Suppressing on the rule instead — drop contrast
findings whose element reports isEnabled == false — fixes it everywhere
with no UI manipulation.

Also documents that simulator keyboard state persists across runs, so a
dirty simulator inflates the burndown with system-UI findings. Erase and
re-run before believing anything that names system UI.
 Docs/ACCESSIBILITY.md                            | 19 +++++++++++++------
 DomainDigUITests/AccessibilityAuditHarness.swift | 12 ++++++++++++
 DomainDigUITests/AccessibilityAuditTests.swift   | 15 +--------------
 3 files changed, 26 insertions(+), 20 deletions(-)

diff --git a/Docs/ACCESSIBILITY.md b/Docs/ACCESSIBILITY.md
index 128bf3f..7e28d89 100644
--- a/Docs/ACCESSIBILITY.md
+++ b/Docs/ACCESSIBILITY.md
@@ -137,12 +137,19 @@ pre-commit that blocks every commit. A hook routinely bypassed with
 
 ## Notes
 
-- **Disabled controls are a false positive.** WCAG 1.4.3 exempts inactive
-  components from contrast requirements, but the audit flags them anyway. The
-  Inspect screen's Run button is disabled until a domain is typed, and auditing
-  the empty state reported a contrast failure that was never a real defect —
-  which is why `testInspectScreen` types a domain before auditing. Watch for
-  this before "fixing" a contrast finding on a disabled control.
+- **Disabled controls are a false positive, and are suppressed.** WCAG 1.4.3
+  exempts inactive components from contrast requirements, but the audit flags
+  them anyway — Inspect's Run button is disabled until a domain is typed, and
+  auditing the empty state reported a contrast failure that was never a real
+  defect. The harness now drops contrast findings whose element reports
+  `isEnabled == false`. Suppressing on the rule beats driving the UI to enable
+  the control: typing raises the keyboard, which then follows the audit onto
+  later screens and flags the system emoji picker's category buttons.
+- **A dirty simulator inflates the burndown.** Keyboard state persists across
+  runs, so a simulator left with the emoji picker open reports ~9 phantom
+  hit-region findings per screen. If findings appear that name system UI
+  ("Flags category", "Frequently Used category"), erase the simulator
+  (`xcrun simctl erase <udid>`) and re-run before believing them.
 - Audits retry up to three times. Slower machines can miss the audit's internal
   deadline (`Audit failed to complete in time`, code `-56`), which is a tooling
   timeout, not an app defect. A screen that still cannot be audited is reported
diff --git a/DomainDigUITests/AccessibilityAuditHarness.swift b/DomainDigUITests/AccessibilityAuditHarness.swift
index ca4d12b..08ddedd 100644
--- a/DomainDigUITests/AccessibilityAuditHarness.swift
+++ b/DomainDigUITests/AccessibilityAuditHarness.swift
@@ -85,6 +85,18 @@ enum AccessibilityAuditHarness {
             timeout = nil
             do {
                 try app.performAccessibilityAudit { issue in
+                    // WCAG 1.4.3 exempts inactive components from contrast
+                    // requirements, but the audit flags them anyway. Inspect's
+                    // Run button is disabled until a domain is typed, so the
+                    // empty state reported a contrast failure that was never a
+                    // real defect. Suppressing on the rule beats driving the UI
+                    // to enable the control: typing raises the keyboard, which
+                    // then follows the audit onto later screens and flags the
+                    // system emoji picker's category buttons.
+                    if issue.auditType.contains(.contrast), issue.element?.isEnabled == false {
+                        return true
+                    }
+
                     let isEnforced = !enforcedAuditTypes.intersection(issue.auditType).isEmpty
                     let marker = isEnforced ? "FAIL" : "report"
                     // Include the element so the burndown says *what* to fix, not
diff --git a/DomainDigUITests/AccessibilityAuditTests.swift b/DomainDigUITests/AccessibilityAuditTests.swift
index 63ae584..6a90bac 100644
--- a/DomainDigUITests/AccessibilityAuditTests.swift
+++ b/DomainDigUITests/AccessibilityAuditTests.swift
@@ -17,20 +17,7 @@ final class AccessibilityAuditTests: XCTestCase {
     // 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")
+        try auditRootTab("Inspect")
     }
 
     func testDashboardScreen() throws {