@sun-asterisk/sungen 3.2.30 → 3.2.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (177) hide show
  1. package/dist/cli/commands/audit.d.ts.map +1 -1
  2. package/dist/cli/commands/audit.js +3 -1
  3. package/dist/cli/commands/audit.js.map +1 -1
  4. package/dist/cli/commands/capability.d.ts.map +1 -1
  5. package/dist/cli/commands/capability.js +11 -0
  6. package/dist/cli/commands/capability.js.map +1 -1
  7. package/dist/cli/commands/init.d.ts.map +1 -1
  8. package/dist/cli/commands/init.js +2 -0
  9. package/dist/cli/commands/init.js.map +1 -1
  10. package/dist/cli/commands/setup-mobile-env.d.ts +15 -0
  11. package/dist/cli/commands/setup-mobile-env.d.ts.map +1 -0
  12. package/dist/cli/commands/setup-mobile-env.js +65 -0
  13. package/dist/cli/commands/setup-mobile-env.js.map +1 -0
  14. package/dist/cli/index.js +2 -0
  15. package/dist/cli/index.js.map +1 -1
  16. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/all-contain-assertion.hbs +1 -2
  17. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/all-contain-element.hbs +8 -5
  18. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/count-assertion.hbs +5 -1
  19. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/disabled-with-filter-assertion.hbs +1 -1
  20. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/disabled-with-role-variable-assertion.hbs +1 -1
  21. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/hidden-with-filter-assertion.hbs +1 -1
  22. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/hidden-with-role-variable-assertion.hbs +1 -1
  23. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/list-item-count-assertion.hbs +5 -2
  24. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/state-with-filter-assertion.hbs +1 -1
  25. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/visible-filtered-assertion.hbs +1 -1
  26. package/dist/generators/test-generator/adapters/appium/templates/steps/partials/await-elements.hbs +12 -0
  27. package/dist/generators/test-generator/adapters/appium/templates/test-file.hbs +26 -0
  28. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/capture-variable.hbs +7 -1
  29. package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/row-scoped-column-assertion.hbs +23 -1
  30. package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/locator-base.hbs +4 -4
  31. package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/locator-strategies/default.hbs +1 -1
  32. package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/locator-strategies/id.hbs +1 -1
  33. package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/locator.hbs +4 -4
  34. package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/url-assertion.hbs +13 -2
  35. package/dist/generators/test-generator/adapters/playwright/templates/test-file.hbs +14 -0
  36. package/dist/generators/test-generator/code-generator.d.ts.map +1 -1
  37. package/dist/generators/test-generator/code-generator.js +56 -13
  38. package/dist/generators/test-generator/code-generator.js.map +1 -1
  39. package/dist/generators/test-generator/diagnostics.d.ts +39 -2
  40. package/dist/generators/test-generator/diagnostics.d.ts.map +1 -1
  41. package/dist/generators/test-generator/diagnostics.js +38 -1
  42. package/dist/generators/test-generator/diagnostics.js.map +1 -1
  43. package/dist/generators/test-generator/patterns/index.d.ts +1 -1
  44. package/dist/generators/test-generator/patterns/index.js +1 -1
  45. package/dist/generators/test-generator/step-mapper.d.ts +35 -0
  46. package/dist/generators/test-generator/step-mapper.d.ts.map +1 -1
  47. package/dist/generators/test-generator/step-mapper.js +103 -4
  48. package/dist/generators/test-generator/step-mapper.js.map +1 -1
  49. package/dist/generators/test-generator/template-engine.d.ts.map +1 -1
  50. package/dist/generators/test-generator/template-engine.js +18 -0
  51. package/dist/generators/test-generator/template-engine.js.map +1 -1
  52. package/dist/generators/test-generator/utils/selector-resolver.d.ts +47 -0
  53. package/dist/generators/test-generator/utils/selector-resolver.d.ts.map +1 -1
  54. package/dist/generators/test-generator/utils/selector-resolver.js +17 -3
  55. package/dist/generators/test-generator/utils/selector-resolver.js.map +1 -1
  56. package/dist/harness/audit.d.ts.map +1 -1
  57. package/dist/harness/audit.js +23 -4
  58. package/dist/harness/audit.js.map +1 -1
  59. package/dist/harness/next-step.d.ts.map +1 -1
  60. package/dist/harness/next-step.js +2 -1
  61. package/dist/harness/next-step.js.map +1 -1
  62. package/dist/harness/parse.d.ts.map +1 -1
  63. package/dist/harness/parse.js +42 -7
  64. package/dist/harness/parse.js.map +1 -1
  65. package/dist/harness/spec-branches.d.ts +7 -2
  66. package/dist/harness/spec-branches.d.ts.map +1 -1
  67. package/dist/harness/spec-branches.js +62 -11
  68. package/dist/harness/spec-branches.js.map +1 -1
  69. package/dist/harness/spec-coverage.d.ts +16 -0
  70. package/dist/harness/spec-coverage.d.ts.map +1 -1
  71. package/dist/harness/spec-coverage.js +119 -10
  72. package/dist/harness/spec-coverage.js.map +1 -1
  73. package/dist/orchestrator/appium-environment-setup.d.ts +24 -0
  74. package/dist/orchestrator/appium-environment-setup.d.ts.map +1 -0
  75. package/dist/orchestrator/appium-environment-setup.js +157 -0
  76. package/dist/orchestrator/appium-environment-setup.js.map +1 -0
  77. package/dist/orchestrator/mobile-runtime-scaffolder.d.ts +2 -0
  78. package/dist/orchestrator/mobile-runtime-scaffolder.d.ts.map +1 -1
  79. package/dist/orchestrator/mobile-runtime-scaffolder.js.map +1 -1
  80. package/dist/orchestrator/project-initializer.d.ts +1 -0
  81. package/dist/orchestrator/project-initializer.d.ts.map +1 -1
  82. package/dist/orchestrator/project-initializer.js +14 -0
  83. package/dist/orchestrator/project-initializer.js.map +1 -1
  84. package/dist/orchestrator/templates/ai-src/skills/sungen-error-mapping/SKILL.md +5 -1
  85. package/dist/orchestrator/templates/ai-src/skills/sungen-gherkin-syntax/SKILL.md +55 -2
  86. package/dist/orchestrator/templates/ai-src/skills/sungen-tc-generation/SKILL.md +20 -5
  87. package/dist/orchestrator/templates/ai-src/skills/sungen-tc-review/SKILL.md +3 -3
  88. package/dist/orchestrator/templates/appium-environment/README.md +83 -0
  89. package/dist/orchestrator/templates/appium-environment/scripts/common/install-appium.ps1 +44 -0
  90. package/dist/orchestrator/templates/appium-environment/scripts/common/install-appium.sh +50 -0
  91. package/dist/orchestrator/templates/appium-environment/scripts/common/lib.ps1 +108 -0
  92. package/dist/orchestrator/templates/appium-environment/scripts/common/lib.sh +141 -0
  93. package/dist/orchestrator/templates/appium-environment/scripts/common/verify-appium.ps1 +39 -0
  94. package/dist/orchestrator/templates/appium-environment/scripts/common/verify-appium.sh +43 -0
  95. package/dist/orchestrator/templates/appium-environment/scripts/common/verify-node.ps1 +13 -0
  96. package/dist/orchestrator/templates/appium-environment/scripts/common/verify-node.sh +16 -0
  97. package/dist/orchestrator/templates/appium-environment/scripts/macos/install-android.sh +110 -0
  98. package/dist/orchestrator/templates/appium-environment/scripts/macos/install-ios.sh +32 -0
  99. package/dist/orchestrator/templates/appium-environment/scripts/macos/verify-android.sh +32 -0
  100. package/dist/orchestrator/templates/appium-environment/scripts/macos/verify-ios.sh +15 -0
  101. package/dist/orchestrator/templates/appium-environment/scripts/windows/install-android.ps1 +153 -0
  102. package/dist/orchestrator/templates/appium-environment/scripts/windows/verify-android.ps1 +26 -0
  103. package/dist/orchestrator/templates/appium-environment/setup.ps1 +58 -0
  104. package/dist/orchestrator/templates/appium-environment/setup.sh +88 -0
  105. package/dist/orchestrator/templates/specs-checked-state.d.ts +1 -1
  106. package/dist/orchestrator/templates/specs-checked-state.d.ts.map +1 -1
  107. package/dist/orchestrator/templates/specs-checked-state.js +26 -3
  108. package/dist/orchestrator/templates/specs-checked-state.js.map +1 -1
  109. package/dist/orchestrator/templates/specs-checked-state.ts +31 -6
  110. package/dist/orchestrator/templates/specs-mock.d.ts +2 -1
  111. package/dist/orchestrator/templates/specs-mock.d.ts.map +1 -1
  112. package/dist/orchestrator/templates/specs-mock.js +10 -3
  113. package/dist/orchestrator/templates/specs-mock.js.map +1 -1
  114. package/dist/orchestrator/templates/specs-mock.ts +12 -4
  115. package/package.json +3 -3
  116. package/src/cli/commands/audit.ts +2 -1
  117. package/src/cli/commands/capability.ts +12 -1
  118. package/src/cli/commands/init.ts +3 -0
  119. package/src/cli/commands/setup-mobile-env.ts +64 -0
  120. package/src/cli/index.ts +2 -0
  121. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/all-contain-assertion.hbs +1 -2
  122. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/all-contain-element.hbs +8 -5
  123. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/count-assertion.hbs +5 -1
  124. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/disabled-with-filter-assertion.hbs +1 -1
  125. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/disabled-with-role-variable-assertion.hbs +1 -1
  126. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/hidden-with-filter-assertion.hbs +1 -1
  127. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/hidden-with-role-variable-assertion.hbs +1 -1
  128. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/list-item-count-assertion.hbs +5 -2
  129. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/state-with-filter-assertion.hbs +1 -1
  130. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/visible-filtered-assertion.hbs +1 -1
  131. package/src/generators/test-generator/adapters/appium/templates/steps/partials/await-elements.hbs +12 -0
  132. package/src/generators/test-generator/adapters/appium/templates/test-file.hbs +26 -0
  133. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/capture-variable.hbs +7 -1
  134. package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/row-scoped-column-assertion.hbs +23 -1
  135. package/src/generators/test-generator/adapters/playwright/templates/steps/partials/locator-base.hbs +4 -4
  136. package/src/generators/test-generator/adapters/playwright/templates/steps/partials/locator-strategies/default.hbs +1 -1
  137. package/src/generators/test-generator/adapters/playwright/templates/steps/partials/locator-strategies/id.hbs +1 -1
  138. package/src/generators/test-generator/adapters/playwright/templates/steps/partials/locator.hbs +4 -4
  139. package/src/generators/test-generator/adapters/playwright/templates/steps/partials/url-assertion.hbs +13 -2
  140. package/src/generators/test-generator/adapters/playwright/templates/test-file.hbs +14 -0
  141. package/src/generators/test-generator/code-generator.ts +66 -17
  142. package/src/generators/test-generator/diagnostics.ts +39 -2
  143. package/src/generators/test-generator/patterns/index.ts +1 -1
  144. package/src/generators/test-generator/step-mapper.ts +106 -5
  145. package/src/generators/test-generator/template-engine.ts +18 -0
  146. package/src/generators/test-generator/utils/selector-resolver.ts +18 -3
  147. package/src/harness/audit.ts +24 -5
  148. package/src/harness/next-step.ts +2 -1
  149. package/src/harness/parse.ts +42 -7
  150. package/src/harness/spec-branches.ts +67 -12
  151. package/src/harness/spec-coverage.ts +121 -11
  152. package/src/orchestrator/appium-environment-setup.ts +127 -0
  153. package/src/orchestrator/mobile-runtime-scaffolder.ts +2 -0
  154. package/src/orchestrator/project-initializer.ts +15 -1
  155. package/src/orchestrator/templates/ai-src/skills/sungen-error-mapping/SKILL.md +5 -1
  156. package/src/orchestrator/templates/ai-src/skills/sungen-gherkin-syntax/SKILL.md +55 -2
  157. package/src/orchestrator/templates/ai-src/skills/sungen-tc-generation/SKILL.md +20 -5
  158. package/src/orchestrator/templates/ai-src/skills/sungen-tc-review/SKILL.md +3 -3
  159. package/src/orchestrator/templates/appium-environment/README.md +83 -0
  160. package/src/orchestrator/templates/appium-environment/scripts/common/install-appium.ps1 +44 -0
  161. package/src/orchestrator/templates/appium-environment/scripts/common/install-appium.sh +50 -0
  162. package/src/orchestrator/templates/appium-environment/scripts/common/lib.ps1 +108 -0
  163. package/src/orchestrator/templates/appium-environment/scripts/common/lib.sh +141 -0
  164. package/src/orchestrator/templates/appium-environment/scripts/common/verify-appium.ps1 +39 -0
  165. package/src/orchestrator/templates/appium-environment/scripts/common/verify-appium.sh +43 -0
  166. package/src/orchestrator/templates/appium-environment/scripts/common/verify-node.ps1 +13 -0
  167. package/src/orchestrator/templates/appium-environment/scripts/common/verify-node.sh +16 -0
  168. package/src/orchestrator/templates/appium-environment/scripts/macos/install-android.sh +110 -0
  169. package/src/orchestrator/templates/appium-environment/scripts/macos/install-ios.sh +32 -0
  170. package/src/orchestrator/templates/appium-environment/scripts/macos/verify-android.sh +32 -0
  171. package/src/orchestrator/templates/appium-environment/scripts/macos/verify-ios.sh +15 -0
  172. package/src/orchestrator/templates/appium-environment/scripts/windows/install-android.ps1 +153 -0
  173. package/src/orchestrator/templates/appium-environment/scripts/windows/verify-android.ps1 +26 -0
  174. package/src/orchestrator/templates/appium-environment/setup.ps1 +58 -0
  175. package/src/orchestrator/templates/appium-environment/setup.sh +88 -0
  176. package/src/orchestrator/templates/specs-checked-state.ts +31 -6
  177. package/src/orchestrator/templates/specs-mock.ts +12 -4
@@ -0,0 +1,88 @@
1
+ #!/usr/bin/env bash
2
+ # setup.sh — macOS entry point of the sungen Appium environment bundle.
3
+ #
4
+ # ./appium-environment/setup.sh [--check] [--skip-android] [--skip-ios] [--no-profile]
5
+ #
6
+ # Emitted by `sungen init --mobile` / `sungen capability add mobile`, which also run it once.
7
+ # Re-run it any time: every step is check → install → verify and a satisfied step is skipped.
8
+ # --check report what is missing, install nothing (side-effect free)
9
+ # --skip-android skip JDK / Android SDK / uiautomator2
10
+ # --skip-ios skip Xcode CLT / applesimutils / xcuitest
11
+ # --no-profile do not write the ANDROID_HOME / JAVA_HOME / PATH block to your shell profile
12
+ # Exit: 0 ready (or only warnings) · 1 something failed · 2 unsupported OS / bad flag
13
+ set -u
14
+
15
+ BUNDLE_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
16
+ PROJECT_DIR=$(cd "$BUNDLE_DIR/.." && pwd)
17
+ export PROJECT_DIR
18
+ . "$BUNDLE_DIR/scripts/common/lib.sh"
19
+
20
+ usage() { sed -n '4,13p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'; }
21
+
22
+ for arg in "$@"; do
23
+ case "$arg" in
24
+ --check) SUNGEN_ENV_CHECK_ONLY=1 ;;
25
+ --skip-android) SUNGEN_ENV_SKIP_ANDROID=1 ;;
26
+ --skip-ios) SUNGEN_ENV_SKIP_IOS=1 ;;
27
+ --no-profile) SUNGEN_ENV_NO_PROFILE=1 ;;
28
+ -h|--help) usage; exit 0 ;;
29
+ *) printf 'unknown flag: %s\n\n' "$arg" >&2; usage >&2; exit 2 ;;
30
+ esac
31
+ done
32
+
33
+ case "$(uname -s)" in
34
+ Darwin) ;;
35
+ Linux)
36
+ printf 'ℹ Linux detected — this bundle provisions macOS and Windows.\n'
37
+ printf ' Android on Linux: see README.md → "Manual fallback" (JDK + Android SDK via your package manager,\n'
38
+ printf ' then `sungen capability add mobile` for the project-local Appium + uiautomator2).\n'
39
+ exit 0 ;;
40
+ *) printf '✗ unsupported OS: %s (macOS: setup.sh · Windows: setup.ps1)\n' "$(uname -s)" >&2; exit 2 ;;
41
+ esac
42
+
43
+ cd "$PROJECT_DIR"
44
+ if [ "$SUNGEN_ENV_CHECK_ONLY" = 1 ]; then section "sungen · Appium environment — check only (macOS)"
45
+ else section "sungen · Appium environment setup (macOS)"; fi
46
+ printf '%sproject: %s%s\n' "$C_DIM" "$PROJECT_DIR" "$C_RESET"
47
+
48
+ # ── 1/5 prerequisites ──────────────────────────────────────────────────────────────────────────
49
+ section "1/5 Prerequisites"
50
+ . "$BUNDLE_DIR/scripts/common/verify-node.sh"
51
+
52
+ brew_check() { have brew && printf 'brew at %s' "$(command -v brew)"; }
53
+ brew_install() {
54
+ NONINTERACTIVE=1 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" || return 1
55
+ # The installer does not touch the running shell — pick brew up for the rest of this run.
56
+ if [ -x /opt/homebrew/bin/brew ]; then eval "$(/opt/homebrew/bin/brew shellenv)"
57
+ elif [ -x /usr/local/bin/brew ]; then eval "$(/usr/local/bin/brew shellenv)"; fi
58
+ }
59
+ brew_verify() { brew_check || { printf 'Homebrew not found after install — https://brew.sh, then re-run'; return 1; }; }
60
+ run_step "Homebrew" brew_check brew_install brew_verify
61
+
62
+ # ── 2/5 Android ────────────────────────────────────────────────────────────────────────────────
63
+ if [ "$SUNGEN_ENV_SKIP_ANDROID" != 1 ]; then
64
+ section "2/5 Android (JDK, SDK, adb, emulator)"
65
+ . "$BUNDLE_DIR/scripts/macos/install-android.sh"
66
+ else
67
+ section "2/5 Android"; log_skip "Android toolchain" "--skip-android"
68
+ fi
69
+
70
+ # ── 3/5 iOS ────────────────────────────────────────────────────────────────────────────────────
71
+ if [ "$SUNGEN_ENV_SKIP_IOS" != 1 ]; then
72
+ section "3/5 iOS (Xcode, applesimutils)"
73
+ . "$BUNDLE_DIR/scripts/macos/install-ios.sh"
74
+ else
75
+ section "3/5 iOS"; log_skip "iOS toolchain" "--skip-ios"
76
+ fi
77
+
78
+ # ── 4/5 Appium ─────────────────────────────────────────────────────────────────────────────────
79
+ section "4/5 Appium (project-local) + platform drivers"
80
+ . "$BUNDLE_DIR/scripts/common/install-appium.sh"
81
+
82
+ # ── 5/5 health ─────────────────────────────────────────────────────────────────────────────────
83
+ section "5/5 Health check"
84
+ [ "$SUNGEN_ENV_SKIP_ANDROID" != 1 ] && . "$BUNDLE_DIR/scripts/macos/verify-android.sh"
85
+ [ "$SUNGEN_ENV_SKIP_IOS" != 1 ] && . "$BUNDLE_DIR/scripts/macos/verify-ios.sh"
86
+ . "$BUNDLE_DIR/scripts/common/verify-appium.sh"
87
+
88
+ print_summary
@@ -38,7 +38,7 @@ const POLL_INTERVAL_MS = 100;
38
38
  export interface DeclaredSignal { class?: string; attribute?: string }
39
39
 
40
40
  /** Where the control publishes its on/off state — decided in the page, from the real element. */
41
- export type StateSource = 'native' | 'aria-pressed' | 'aria-checked' | 'class' | 'attribute' | 'none';
41
+ export type StateSource = 'native' | 'aria-pressed' | 'aria-checked' | 'aria-selected' | 'aria-current' | 'class' | 'attribute' | 'none';
42
42
  export interface ControlProbe {
43
43
  source: StateSource;
44
44
  /** Current state, already normalised. Meaningless when `source` is 'native' (Playwright reads it). */
@@ -110,6 +110,24 @@ export function probeControl(node: unknown, declared?: DeclaredSignal): ControlP
110
110
  }
111
111
  // Role claims checkable but nothing is published — let Playwright raise its own, precise error.
112
112
  if (ariaCheckableRole) return { source: 'native', on: false, buttonLike };
113
+ // `is selected` on a tab, an option, a chip or a nav item. These publish `aria-selected` or
114
+ // `aria-current`, which nothing above reads: the probe fell straight through to 'none' and the
115
+ // step failed saying the control has no on/off state to set, on a control that publishes one in
116
+ // the standard place. Read here rather than in the templates so a suite already generated in the
117
+ // field picks the fix up without regenerating — the same reasoning the URL predicate is a runtime
118
+ // helper for.
119
+ //
120
+ // `aria-current` is not a boolean: the spec allows 'page', 'step', 'location', 'date', 'time' and
121
+ // 'true', and only the literal 'false' (or the attribute's absence) means off. Read that way
122
+ // round, so `aria-current="page"` on the active nav item reads as ON rather than as a stray
123
+ // non-'true' string.
124
+ if (el.hasAttribute('aria-selected')) {
125
+ return { source: 'aria-selected', on: String(el.getAttribute('aria-selected') ?? '').trim().toLowerCase() === 'true', buttonLike };
126
+ }
127
+ if (el.hasAttribute('aria-current')) {
128
+ const raw = String(el.getAttribute('aria-current') ?? '').trim().toLowerCase();
129
+ return { source: 'aria-current', on: raw !== '' && raw !== 'false', buttonLike };
130
+ }
113
131
  return { source: 'none', on: false, buttonLike };
114
132
  }
115
133
 
@@ -132,11 +150,18 @@ const sleep = (ms: number): Promise<void> => new Promise((r) => setTimeout(r, ms
132
150
  */
133
151
  const left = (deadline: number): number => Math.max(250, deadline - Date.now());
134
152
 
153
+ /** The sources that name an ATTRIBUTE the live state can be re-read from. */
154
+ type AttributeSource = 'aria-pressed' | 'aria-checked' | 'aria-selected' | 'aria-current';
155
+
135
156
  /** Read the live state. A REMOVED attribute means off: `aria-pressed={on || undefined}` is a common
136
157
  * React/Vue idiom, so requiring a literal "false" would fail a control that is correctly off. */
137
- async function readOn(target: Locator, attr: 'aria-pressed' | 'aria-checked', deadline: number): Promise<boolean> {
138
- const raw = await target.getAttribute(attr, { timeout: left(deadline) });
139
- return String(raw ?? '').trim().toLowerCase() === 'true';
158
+ async function readOn(target: Locator, attr: AttributeSource, deadline: number): Promise<boolean> {
159
+ const raw = String(await target.getAttribute(attr, { timeout: left(deadline) }) ?? '').trim().toLowerCase();
160
+ // `aria-current` is the one non-boolean of the four: the spec allows 'page', 'step', 'location',
161
+ // 'date', 'time' and 'true', so only the literal 'false' (or absence) means off. Read the other
162
+ // way round it would report the active nav item as OFF.
163
+ if (attr === 'aria-current') return raw !== '' && raw !== 'false';
164
+ return raw === 'true';
140
165
  }
141
166
 
142
167
  /**
@@ -238,12 +263,12 @@ export async function setCheckedState(
238
263
  // Not pressable and publishes nothing — there is no reading of this that makes the step meaningful.
239
264
  throw new Error(
240
265
  `Cannot ${want ? 'check' : 'uncheck'} [${label}] — it is not a checkbox/radio/switch, is not a `
241
- + `button, and publishes no aria-pressed/aria-checked, so it has no on/off state to set. Point the `
266
+ + `button, and publishes no aria-pressed/aria-checked/aria-selected/aria-current, so it has no on/off state to set. Point the `
242
267
  + `selector at the control itself, or give it a real ARIA toggle role.`,
243
268
  );
244
269
  }
245
270
 
246
- const attr = probe.source;
271
+ const attr: AttributeSource = probe.source as AttributeSource;
247
272
  // Already in the requested state → nothing to do. This is the idempotence `check` promises.
248
273
  if (probe.on === want) return;
249
274
  await target.click({ timeout });
@@ -25,7 +25,8 @@ export interface MockConfig {
25
25
  name: string;
26
26
  method: string; // HTTP verb or '*'
27
27
  url: string; // page.route glob, or 're:<pattern>' for a regex
28
- status: number;
28
+ status?: number; // the server ANSWERED — absent exactly when `abort` is set
29
+ abort?: string; // the request NEVER ARRIVED — a route.abort() errorCode
29
30
  headers?: Record<string, string>;
30
31
  body?: string; // pre-serialized by the compiler (JSON string)
31
32
  delay_ms?: number;
@@ -104,9 +105,16 @@ class MockRouter {
104
105
  if (cfg.method !== '*' && route.request().method() !== cfg.method) return route.fallback();
105
106
  st.counts.set(cfg.name, (st.counts.get(cfg.name) ?? 0) + 1);
106
107
  if (cfg.delay_ms) await new Promise((r) => setTimeout(r, cfg.delay_ms));
107
- // A delayed fulfill can race a page close / reset at teardown — that race must
108
- // not surface as an unhandled rejection from inside a route handler.
109
- try { await route.fulfill({ status: cfg.status, headers: cfg.headers, body: cfg.body }); } catch { /* page closing */ }
108
+ // A delayed response can race a page close / reset at teardown — that race must
109
+ // not surface as an unhandled rejection from inside a route handler. It wraps BOTH
110
+ // outcomes: an abort races teardown exactly like a fulfill does.
111
+ try {
112
+ // `abort` is the request that never reaches the server; `status` is the one the
113
+ // server answered. The catalog refuses an entry carrying both, so this is a real
114
+ // either/or, not a precedence rule (#676).
115
+ if (cfg.abort) await route.abort(cfg.abort);
116
+ else await route.fulfill({ status: cfg.status, headers: cfg.headers, body: cfg.body });
117
+ } catch { /* page closing */ }
110
118
  };
111
119
  await page.route(pattern, handler);
112
120
  st.routes.push({ pattern, handler });