aria-ease 6.5.0 → 6.6.0

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 (42) hide show
  1. package/README.md +14 -10
  2. package/bin/{chunk-AUJAN4RK.js → chunk-LKN5PRYD.js} +0 -5
  3. package/bin/cli.cjs +50 -33
  4. package/bin/cli.js +1 -1
  5. package/{dist/contractTestRunnerPlaywright-7F756CFB.js → bin/contractTestRunnerPlaywright-PC6JOYYV.js} +51 -29
  6. package/bin/{test-C3CMRHSI.js → test-LP723IXM.js} +2 -2
  7. package/dist/{chunk-AUJAN4RK.js → chunk-LKN5PRYD.js} +0 -5
  8. package/{bin/contractTestRunnerPlaywright-7F756CFB.js → dist/contractTestRunnerPlaywright-PC6JOYYV.js} +51 -29
  9. package/dist/index.cjs +165 -100
  10. package/dist/index.js +117 -69
  11. package/dist/src/{Types.d-yGC2bBaB.d.cts → Types.d-DYfYR3Vc.d.cts} +1 -1
  12. package/dist/src/{Types.d-yGC2bBaB.d.ts → Types.d-DYfYR3Vc.d.ts} +1 -1
  13. package/dist/src/accordion/index.d.cts +1 -1
  14. package/dist/src/accordion/index.d.ts +1 -1
  15. package/dist/src/block/index.cjs +1 -6
  16. package/dist/src/block/index.d.cts +1 -1
  17. package/dist/src/block/index.d.ts +1 -1
  18. package/dist/src/block/index.js +72 -1
  19. package/dist/src/checkbox/index.d.cts +1 -1
  20. package/dist/src/checkbox/index.d.ts +1 -1
  21. package/dist/src/combobox/index.d.cts +1 -1
  22. package/dist/src/combobox/index.d.ts +1 -1
  23. package/dist/src/menu/index.cjs +112 -142
  24. package/dist/src/menu/index.d.cts +1 -1
  25. package/dist/src/menu/index.d.ts +1 -1
  26. package/dist/src/menu/index.js +112 -18
  27. package/dist/src/radio/index.d.cts +1 -1
  28. package/dist/src/radio/index.d.ts +1 -1
  29. package/dist/src/tabs/index.d.cts +1 -1
  30. package/dist/src/tabs/index.d.ts +1 -1
  31. package/dist/src/toggle/index.d.cts +1 -1
  32. package/dist/src/toggle/index.d.ts +1 -1
  33. package/dist/src/utils/test/aria-contracts/accordion/accordion.contract.json +1 -1
  34. package/dist/src/utils/test/aria-contracts/combobox/combobox.listbox.contract.json +1 -1
  35. package/dist/src/utils/test/aria-contracts/menu/menu.contract.json +143 -30
  36. package/dist/src/utils/test/aria-contracts/tabs/tabs.contract.json +8 -8
  37. package/dist/src/utils/test/{chunk-AUJAN4RK.js → chunk-LKN5PRYD.js} +0 -5
  38. package/dist/src/utils/test/{contractTestRunnerPlaywright-HL73FADJ.js → contractTestRunnerPlaywright-RGKMGXND.js} +51 -29
  39. package/dist/src/utils/test/index.cjs +50 -33
  40. package/dist/src/utils/test/index.js +2 -2
  41. package/package.json +1 -1
  42. package/dist/src/chunk-ZJXZZDUR.js +0 -127
package/README.md CHANGED
@@ -18,7 +18,7 @@ Aria-Ease isn't a utility library. **It's an accessibility infrastructure** that
18
18
  | **🔧 Development** | Component utilities for accessible patterns | ✅ Available | Build it right from the start |
19
19
  | **⚡ Linting** | ESLint rules to enforce accessible coding | 🚧 Roadmap | Catch mistakes as you type |
20
20
  | **🔍 Pre-Deploy** | Axe-core powered static accessibility audit | ✅ Available | Verify before it ships |
21
- | **🧪 Testing** | WAI-ARIA APG contract testing with Playwright | ✅ Available | 26 combobox assertions in ~4 seconds |
21
+ | **🧪 Testing** | WAI-ARIA APG contract testing with Playwright | ✅ Available | 26 combobox assertions in ~2 seconds |
22
22
  | **🚀 CI/CD** | Accessibility as deployment gatekeeper | ✅ Available | Block inaccessible code from production |
23
23
  | **📊 Production** | Real user signal monitoring and replay | 🚧 Roadmap | Understand how users actually interact |
24
24
  | **📈 Insights** | Dashboard for reporting and analytics | 🚧 Roadmap | Visualize accessibility health |
@@ -70,11 +70,11 @@ This is the game-changer. We encoded the WAI-ARIA APG into deterministic JSON "c
70
70
 
71
71
  ```bash
72
72
  npx aria-ease test
73
- # ✓ 26 assertions in ~4 seconds
73
+ # ✓ 26 assertions in ~2 seconds
74
74
  # ✓ 26 assertions in ~1 second in CI
75
75
  ```
76
76
 
77
- **Why this matters:** Before, verifying a combobox meant manual keyboard testing across browsers. Now, it's automated, fast, and repeatable. You can boast about executing 26 combobox interaction assertions in ~4 seconds.
77
+ **Why this matters:** Before, verifying a combobox meant manual keyboard testing across browsers. Now, it's automated, fast, and repeatable. You can boast about executing 26 combobox interaction assertions in ~2 seconds.
78
78
 
79
79
  #### 4. **CI/CD Integration** (Available Now)
80
80
 
@@ -134,7 +134,7 @@ pnpm add aria-ease
134
134
 
135
135
  ### Automated Accessibility Audits (CLI)
136
136
 
137
- Run automated accessibility audits on your website with one command:
137
+ Run axe-core powered automated accessibility audits on your website with one command:
138
138
 
139
139
  ```bash
140
140
  npx aria-ease audit --url https://yoursite.com
@@ -754,7 +754,7 @@ For older browser support, use a polyfill service or transpile with appropriate
754
754
 
755
755
  ---
756
756
 
757
- ## CI/CD Integration: Accessibility as a Deployment Gatekeeper
757
+ ## 🚀 CI/CD Integration: Accessibility as a Deployment Gatekeeper
758
758
 
759
759
  **The game-changer:** Turn accessibility into a deployment invariant. Code that fails accessibility checks cannot reach production.
760
760
 
@@ -876,10 +876,14 @@ Create `ariaease.config.js` in your project root:
876
876
  ```javascript
877
877
  export default {
878
878
  audit: {
879
- urls: ["http://localhost:5173/", "http://localhost:5173/changelog"],
879
+ urls: [
880
+ "http://localhost:5173", // Homepage
881
+ "http://localhost:5173/docs", // Docs
882
+ "http://localhost:5173/examples", // Examples
883
+ ],
880
884
  output: {
881
- format: "html",
882
- out: "./accessibility-reports/audit",
885
+ format: "all", // Generate JSON, CSV, and HTML reports
886
+ out: "./accessibility-reports",
883
887
  },
884
888
  },
885
889
  };
@@ -993,11 +997,11 @@ The first time you see that green check mark in your CI/CD pipeline—knowing th
993
997
 
994
998
  **No one has any excuse to ship inaccessible code anymore.**
995
999
 
996
- You've shifted accessibility left (into development), automated the verification, and made it a deployment gatekeeper. By the time code reaches manual testing, there should only be minute, non-automatable aspects left to verify.
1000
+ You've shifted accessibility left (into development), automated the verification, and made it a deployment gatekeeper.
997
1001
 
998
1002
  ---
999
1003
 
1000
- ## �📖 More Resources
1004
+ ## 📖 More Resources
1001
1005
 
1002
1006
  - [Full Documentation](https://ariaease.site/docs)
1003
1007
  - [GitHub Repository](https://github.com/aria-ease/aria-ease)
@@ -195,11 +195,6 @@ ${"\u2500".repeat(60)}`);
195
195
  ${"\u2550".repeat(60)}`);
196
196
  this.log(`\u{1F4CA} Summary
197
197
  `);
198
- const staticIcon = this.staticFailures === 0 ? "\u2705" : "\u274C";
199
- const staticStatus = this.staticFailures === 0 ? "PASS" : "FAIL";
200
- this.log(`${staticIcon} Static ARIA Tests: ${staticStatus}`);
201
- this.log(` ${this.staticPasses}/${this.staticPasses + this.staticFailures} required attributes present`);
202
- this.log("");
203
198
  if (totalFailures === 0 && this.skipped === 0 && this.optionalSuggestions === 0) {
204
199
  this.log(`\u2705 All ${totalRun} tests passed!`);
205
200
  this.log(` ${this.componentName.charAt(0).toUpperCase()}${this.componentName.slice(1)} component meets WAI-ARIA expectations for Roles, States, Properties, and Keyboard Interactions \u2713`);
package/bin/cli.cjs CHANGED
@@ -603,11 +603,6 @@ ${"\u2500".repeat(60)}`);
603
603
  ${"\u2550".repeat(60)}`);
604
604
  this.log(`\u{1F4CA} Summary
605
605
  `);
606
- const staticIcon = this.staticFailures === 0 ? "\u2705" : "\u274C";
607
- const staticStatus = this.staticFailures === 0 ? "PASS" : "FAIL";
608
- this.log(`${staticIcon} Static ARIA Tests: ${staticStatus}`);
609
- this.log(` ${this.staticPasses}/${this.staticPasses + this.staticFailures} required attributes present`);
610
- this.log("");
611
606
  if (totalFailures === 0 && this.skipped === 0 && this.optionalSuggestions === 0) {
612
607
  this.log(`\u2705 All ${totalRun} tests passed!`);
613
608
  this.log(` ${this.componentName.charAt(0).toUpperCase()}${this.componentName.slice(1)} component meets WAI-ARIA expectations for Roles, States, Properties, and Keyboard Interactions \u2713`);
@@ -967,29 +962,20 @@ This indicates a problem with the menu component's close functionality.`
967
962
  }
968
963
  }
969
964
  async shouldSkipTest(test, page) {
970
- for (const act of test.action) {
971
- if (act.type === "keypress" && (act.target === "submenuTrigger" || act.target === "submenu")) {
972
- const submenuSelector = this.selectors[act.target];
973
- if (submenuSelector) {
974
- const submenuCount = await page.locator(submenuSelector).count();
975
- if (submenuCount === 0) {
976
- return true;
977
- }
978
- }
979
- }
965
+ const requiresSubmenu = test.action.some(
966
+ (act) => act.target === "submenu" || act.target === "submenuTrigger" || act.target === "submenuItems"
967
+ ) || test.assertions.some(
968
+ (assertion) => assertion.target === "submenu" || assertion.target === "submenuTrigger" || assertion.target === "submenuItems"
969
+ );
970
+ if (!requiresSubmenu) {
971
+ return false;
980
972
  }
981
- for (const assertion of test.assertions) {
982
- if (assertion.target === "submenu" || assertion.target === "submenuTrigger") {
983
- const submenuSelector = this.selectors[assertion.target];
984
- if (submenuSelector) {
985
- const submenuCount = await page.locator(submenuSelector).count();
986
- if (submenuCount === 0) {
987
- return true;
988
- }
989
- }
990
- }
973
+ const submenuTriggerSelector = this.selectors.submenuTrigger;
974
+ if (!submenuTriggerSelector) {
975
+ return true;
991
976
  }
992
- return false;
977
+ const submenuTriggerCount = await page.locator(submenuTriggerSelector).count();
978
+ return submenuTriggerCount === 0;
993
979
  }
994
980
  getMainSelector() {
995
981
  return this.mainSelector;
@@ -1132,6 +1118,9 @@ var init_ActionExecutor = __esm({
1132
1118
  this.selectors = selectors;
1133
1119
  this.timeoutMs = timeoutMs;
1134
1120
  }
1121
+ isOptionalMenuTarget(target) {
1122
+ return ["submenu", "submenuTrigger", "submenuItems"].includes(target);
1123
+ }
1135
1124
  /**
1136
1125
  * Check if error is due to browser/page being closed
1137
1126
  */
@@ -1252,7 +1241,7 @@ var init_ActionExecutor = __esm({
1252
1241
  } else if (keyValue.includes(" ")) {
1253
1242
  keyValue = keyValue.replace(/ /g, "");
1254
1243
  }
1255
- if (target === "focusable" && ["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight", "Escape"].includes(keyValue)) {
1244
+ if (target === "focusable" && ["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight", "Escape", "Home", "End", "Tab", "Shift+Tab"].includes(keyValue)) {
1256
1245
  await this.page.keyboard.press(keyValue);
1257
1246
  return { success: true };
1258
1247
  }
@@ -1263,9 +1252,10 @@ var init_ActionExecutor = __esm({
1263
1252
  const locator = this.page.locator(selector).first();
1264
1253
  const elementCount = await locator.count();
1265
1254
  if (elementCount === 0) {
1255
+ const optionalMenuTarget = this.isOptionalMenuTarget(target);
1266
1256
  return {
1267
1257
  success: false,
1268
- error: `${target} element not found (optional submenu test)`,
1258
+ error: optionalMenuTarget ? `${target} element not found (optional submenu test)` : `${target} element not found.`,
1269
1259
  shouldBreak: true
1270
1260
  // Signal to skip this test
1271
1261
  };
@@ -1638,23 +1628,35 @@ This usually means:
1638
1628
  }).catch(() => {
1639
1629
  });
1640
1630
  }
1641
- const failuresBeforeStatic = failures.length;
1631
+ const hasSubmenuCapability = componentName === "menu" && !!componentContract.selectors.submenuTrigger ? await page.locator(componentContract.selectors.submenuTrigger).count() > 0 : false;
1632
+ let staticFailed = 0;
1642
1633
  const staticAssertionRunner = new AssertionRunner(page, componentContract.selectors, assertionTimeoutMs);
1643
1634
  for (const test of componentContract.static[0]?.assertions || []) {
1644
1635
  if (test.target === "relative") continue;
1645
1636
  const staticDescription = `${test.target}${test.attribute ? ` (${test.attribute})` : ""}`;
1637
+ if (componentName === "menu" && test.target === "submenuTrigger" && !hasSubmenuCapability) {
1638
+ passes.push(`Skipping submenu static assertion for ${test.target}: no submenu capability detected in rendered component.`);
1639
+ reporter.reportStaticTest(staticDescription, true);
1640
+ continue;
1641
+ }
1646
1642
  const targetSelector = componentContract.selectors[test.target];
1647
1643
  if (!targetSelector) {
1648
1644
  const failure = `Selector for target ${test.target} not found.`;
1649
1645
  failures.push(failure);
1646
+ staticFailed += 1;
1650
1647
  reporter.reportStaticTest(staticDescription, false, failure);
1651
1648
  continue;
1652
1649
  }
1653
1650
  const target = page.locator(targetSelector).first();
1654
1651
  const exists = await target.count() > 0;
1655
1652
  if (!exists) {
1653
+ if (test.isOptional === true) {
1654
+ reporter.reportStaticTest(staticDescription, true);
1655
+ continue;
1656
+ }
1656
1657
  const failure = `Target ${test.target} not found.`;
1657
1658
  failures.push(failure);
1659
+ staticFailed += 1;
1658
1660
  reporter.reportStaticTest(staticDescription, false, failure);
1659
1661
  continue;
1660
1662
  }
@@ -1691,6 +1693,7 @@ This usually means:
1691
1693
  if (!hasAny && !allRedundant) {
1692
1694
  const failure = test.failureMessage + ` None of the attributes "${test.attribute}" are present.`;
1693
1695
  failures.push(failure);
1696
+ staticFailed += 1;
1694
1697
  reporter.reportStaticTest(staticDescription, false, failure);
1695
1698
  } else if (!allRedundant && hasAny) {
1696
1699
  passes.push(`At least one of the attributes "${test.attribute}" exists on the element.`);
@@ -1716,6 +1719,7 @@ This usually means:
1716
1719
  reporter.reportStaticTest(staticDescription, true);
1717
1720
  } else if (!result.success && result.failMessage) {
1718
1721
  failures.push(result.failMessage);
1722
+ staticFailed += 1;
1719
1723
  reporter.reportStaticTest(staticDescription, false, result.failMessage);
1720
1724
  }
1721
1725
  }
@@ -1745,9 +1749,12 @@ This usually means:
1745
1749
  }
1746
1750
  const actionExecutor = new ActionExecutor(page, componentContract.selectors, actionTimeoutMs);
1747
1751
  const assertionRunner = new AssertionRunner(page, componentContract.selectors, assertionTimeoutMs);
1752
+ let shouldSkipCurrentTest = false;
1753
+ let shouldAbortCurrentTest = false;
1748
1754
  for (const act of action) {
1749
1755
  if (!page || page.isClosed()) {
1750
1756
  failures.push(`CRITICAL: Browser/page closed during test execution. Remaining actions skipped.`);
1757
+ shouldAbortCurrentTest = true;
1751
1758
  break;
1752
1759
  }
1753
1760
  let result;
@@ -1765,18 +1772,29 @@ This usually means:
1765
1772
  continue;
1766
1773
  }
1767
1774
  if (!result.success) {
1768
- if (result.error) {
1769
- failures.push(result.error);
1770
- }
1771
1775
  if (result.shouldBreak) {
1772
1776
  if (result.error?.includes("optional submenu test")) {
1773
1777
  reporter.reportTest(dynamicTest, "skip", result.error);
1778
+ shouldSkipCurrentTest = true;
1779
+ } else if (result.error) {
1780
+ failures.push(result.error);
1781
+ shouldAbortCurrentTest = true;
1774
1782
  }
1775
1783
  break;
1776
1784
  }
1785
+ if (result.error) {
1786
+ failures.push(result.error);
1787
+ }
1777
1788
  continue;
1778
1789
  }
1779
1790
  }
1791
+ if (shouldSkipCurrentTest) {
1792
+ continue;
1793
+ }
1794
+ if (shouldAbortCurrentTest) {
1795
+ reporter.reportTest(dynamicTest, "fail", failures[failures.length - 1]);
1796
+ continue;
1797
+ }
1780
1798
  for (const assertion of assertions) {
1781
1799
  const result = await assertionRunner.validate(assertion, dynamicTest.description);
1782
1800
  if (result.success && result.passMessage) {
@@ -1796,7 +1814,6 @@ This usually means:
1796
1814
  }
1797
1815
  }
1798
1816
  const staticTotal = componentContract.static[0].assertions.length;
1799
- const staticFailed = failures.length - failuresBeforeStatic;
1800
1817
  const staticPassed = Math.max(0, staticTotal - staticFailed);
1801
1818
  reporter.reportStatic(staticPassed, staticFailed);
1802
1819
  reporter.summary(failures);
package/bin/cli.js CHANGED
@@ -237,7 +237,7 @@ program.command("audit").description("Run axe-core powered accessibility audit o
237
237
  process.exit(1);
238
238
  });
239
239
  program.command("test").description("Run core a11y accessibility standard tests on UI components").action(async () => {
240
- const { runTest } = await import("./test-C3CMRHSI.js");
240
+ const { runTest } = await import("./test-LP723IXM.js");
241
241
  runTest();
242
242
  });
243
243
  program.command("help").description("Display help information").action(() => {
@@ -2,7 +2,7 @@ import {
2
2
  ContractReporter,
3
3
  contract_default,
4
4
  createTestPage
5
- } from "./chunk-AUJAN4RK.js";
5
+ } from "./chunk-LKN5PRYD.js";
6
6
  import {
7
7
  __export,
8
8
  __reExport
@@ -166,29 +166,20 @@ This indicates a problem with the menu component's close functionality.`
166
166
  }
167
167
  }
168
168
  async shouldSkipTest(test, page) {
169
- for (const act of test.action) {
170
- if (act.type === "keypress" && (act.target === "submenuTrigger" || act.target === "submenu")) {
171
- const submenuSelector = this.selectors[act.target];
172
- if (submenuSelector) {
173
- const submenuCount = await page.locator(submenuSelector).count();
174
- if (submenuCount === 0) {
175
- return true;
176
- }
177
- }
178
- }
169
+ const requiresSubmenu = test.action.some(
170
+ (act) => act.target === "submenu" || act.target === "submenuTrigger" || act.target === "submenuItems"
171
+ ) || test.assertions.some(
172
+ (assertion) => assertion.target === "submenu" || assertion.target === "submenuTrigger" || assertion.target === "submenuItems"
173
+ );
174
+ if (!requiresSubmenu) {
175
+ return false;
179
176
  }
180
- for (const assertion of test.assertions) {
181
- if (assertion.target === "submenu" || assertion.target === "submenuTrigger") {
182
- const submenuSelector = this.selectors[assertion.target];
183
- if (submenuSelector) {
184
- const submenuCount = await page.locator(submenuSelector).count();
185
- if (submenuCount === 0) {
186
- return true;
187
- }
188
- }
189
- }
177
+ const submenuTriggerSelector = this.selectors.submenuTrigger;
178
+ if (!submenuTriggerSelector) {
179
+ return true;
190
180
  }
191
- return false;
181
+ const submenuTriggerCount = await page.locator(submenuTriggerSelector).count();
182
+ return submenuTriggerCount === 0;
192
183
  }
193
184
  getMainSelector() {
194
185
  return this.mainSelector;
@@ -300,6 +291,9 @@ var ActionExecutor = class {
300
291
  this.selectors = selectors;
301
292
  this.timeoutMs = timeoutMs;
302
293
  }
294
+ isOptionalMenuTarget(target) {
295
+ return ["submenu", "submenuTrigger", "submenuItems"].includes(target);
296
+ }
303
297
  /**
304
298
  * Check if error is due to browser/page being closed
305
299
  */
@@ -420,7 +414,7 @@ var ActionExecutor = class {
420
414
  } else if (keyValue.includes(" ")) {
421
415
  keyValue = keyValue.replace(/ /g, "");
422
416
  }
423
- if (target === "focusable" && ["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight", "Escape"].includes(keyValue)) {
417
+ if (target === "focusable" && ["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight", "Escape", "Home", "End", "Tab", "Shift+Tab"].includes(keyValue)) {
424
418
  await this.page.keyboard.press(keyValue);
425
419
  return { success: true };
426
420
  }
@@ -431,9 +425,10 @@ var ActionExecutor = class {
431
425
  const locator = this.page.locator(selector).first();
432
426
  const elementCount = await locator.count();
433
427
  if (elementCount === 0) {
428
+ const optionalMenuTarget = this.isOptionalMenuTarget(target);
434
429
  return {
435
430
  success: false,
436
- error: `${target} element not found (optional submenu test)`,
431
+ error: optionalMenuTarget ? `${target} element not found (optional submenu test)` : `${target} element not found.`,
437
432
  shouldBreak: true
438
433
  // Signal to skip this test
439
434
  };
@@ -792,23 +787,35 @@ This usually means:
792
787
  }).catch(() => {
793
788
  });
794
789
  }
795
- const failuresBeforeStatic = failures.length;
790
+ const hasSubmenuCapability = componentName === "menu" && !!componentContract.selectors.submenuTrigger ? await page.locator(componentContract.selectors.submenuTrigger).count() > 0 : false;
791
+ let staticFailed = 0;
796
792
  const staticAssertionRunner = new AssertionRunner(page, componentContract.selectors, assertionTimeoutMs);
797
793
  for (const test of componentContract.static[0]?.assertions || []) {
798
794
  if (test.target === "relative") continue;
799
795
  const staticDescription = `${test.target}${test.attribute ? ` (${test.attribute})` : ""}`;
796
+ if (componentName === "menu" && test.target === "submenuTrigger" && !hasSubmenuCapability) {
797
+ passes.push(`Skipping submenu static assertion for ${test.target}: no submenu capability detected in rendered component.`);
798
+ reporter.reportStaticTest(staticDescription, true);
799
+ continue;
800
+ }
800
801
  const targetSelector = componentContract.selectors[test.target];
801
802
  if (!targetSelector) {
802
803
  const failure = `Selector for target ${test.target} not found.`;
803
804
  failures.push(failure);
805
+ staticFailed += 1;
804
806
  reporter.reportStaticTest(staticDescription, false, failure);
805
807
  continue;
806
808
  }
807
809
  const target = page.locator(targetSelector).first();
808
810
  const exists = await target.count() > 0;
809
811
  if (!exists) {
812
+ if (test.isOptional === true) {
813
+ reporter.reportStaticTest(staticDescription, true);
814
+ continue;
815
+ }
810
816
  const failure = `Target ${test.target} not found.`;
811
817
  failures.push(failure);
818
+ staticFailed += 1;
812
819
  reporter.reportStaticTest(staticDescription, false, failure);
813
820
  continue;
814
821
  }
@@ -845,6 +852,7 @@ This usually means:
845
852
  if (!hasAny && !allRedundant) {
846
853
  const failure = test.failureMessage + ` None of the attributes "${test.attribute}" are present.`;
847
854
  failures.push(failure);
855
+ staticFailed += 1;
848
856
  reporter.reportStaticTest(staticDescription, false, failure);
849
857
  } else if (!allRedundant && hasAny) {
850
858
  passes.push(`At least one of the attributes "${test.attribute}" exists on the element.`);
@@ -870,6 +878,7 @@ This usually means:
870
878
  reporter.reportStaticTest(staticDescription, true);
871
879
  } else if (!result.success && result.failMessage) {
872
880
  failures.push(result.failMessage);
881
+ staticFailed += 1;
873
882
  reporter.reportStaticTest(staticDescription, false, result.failMessage);
874
883
  }
875
884
  }
@@ -899,9 +908,12 @@ This usually means:
899
908
  }
900
909
  const actionExecutor = new ActionExecutor(page, componentContract.selectors, actionTimeoutMs);
901
910
  const assertionRunner = new AssertionRunner(page, componentContract.selectors, assertionTimeoutMs);
911
+ let shouldSkipCurrentTest = false;
912
+ let shouldAbortCurrentTest = false;
902
913
  for (const act of action) {
903
914
  if (!page || page.isClosed()) {
904
915
  failures.push(`CRITICAL: Browser/page closed during test execution. Remaining actions skipped.`);
916
+ shouldAbortCurrentTest = true;
905
917
  break;
906
918
  }
907
919
  let result;
@@ -919,18 +931,29 @@ This usually means:
919
931
  continue;
920
932
  }
921
933
  if (!result.success) {
922
- if (result.error) {
923
- failures.push(result.error);
924
- }
925
934
  if (result.shouldBreak) {
926
935
  if (result.error?.includes("optional submenu test")) {
927
936
  reporter.reportTest(dynamicTest, "skip", result.error);
937
+ shouldSkipCurrentTest = true;
938
+ } else if (result.error) {
939
+ failures.push(result.error);
940
+ shouldAbortCurrentTest = true;
928
941
  }
929
942
  break;
930
943
  }
944
+ if (result.error) {
945
+ failures.push(result.error);
946
+ }
931
947
  continue;
932
948
  }
933
949
  }
950
+ if (shouldSkipCurrentTest) {
951
+ continue;
952
+ }
953
+ if (shouldAbortCurrentTest) {
954
+ reporter.reportTest(dynamicTest, "fail", failures[failures.length - 1]);
955
+ continue;
956
+ }
934
957
  for (const assertion of assertions) {
935
958
  const result = await assertionRunner.validate(assertion, dynamicTest.description);
936
959
  if (result.success && result.passMessage) {
@@ -950,7 +973,6 @@ This usually means:
950
973
  }
951
974
  }
952
975
  const staticTotal = componentContract.static[0].assertions.length;
953
- const staticFailed = failures.length - failuresBeforeStatic;
954
976
  const staticPassed = Math.max(0, staticTotal - staticFailed);
955
977
  reporter.reportStatic(staticPassed, staticFailed);
956
978
  reporter.summary(failures);
@@ -2,7 +2,7 @@ import {
2
2
  ContractReporter,
3
3
  closeSharedBrowser,
4
4
  contract_default
5
- } from "./chunk-AUJAN4RK.js";
5
+ } from "./chunk-LKN5PRYD.js";
6
6
  import "./chunk-I2KLQ2HA.js";
7
7
 
8
8
  // src/utils/test/src/test.ts
@@ -116,7 +116,7 @@ Error: ${error instanceof Error ? error.message : String(error)}`
116
116
  const devServerUrl = await checkDevServer(url);
117
117
  if (devServerUrl) {
118
118
  console.log(`\u{1F3AD} Running Playwright tests on ${devServerUrl}`);
119
- const { runContractTestsPlaywright } = await import("./contractTestRunnerPlaywright-7F756CFB.js");
119
+ const { runContractTestsPlaywright } = await import("./contractTestRunnerPlaywright-PC6JOYYV.js");
120
120
  contract = await runContractTestsPlaywright(componentName, devServerUrl);
121
121
  } else {
122
122
  throw new Error(
@@ -195,11 +195,6 @@ ${"\u2500".repeat(60)}`);
195
195
  ${"\u2550".repeat(60)}`);
196
196
  this.log(`\u{1F4CA} Summary
197
197
  `);
198
- const staticIcon = this.staticFailures === 0 ? "\u2705" : "\u274C";
199
- const staticStatus = this.staticFailures === 0 ? "PASS" : "FAIL";
200
- this.log(`${staticIcon} Static ARIA Tests: ${staticStatus}`);
201
- this.log(` ${this.staticPasses}/${this.staticPasses + this.staticFailures} required attributes present`);
202
- this.log("");
203
198
  if (totalFailures === 0 && this.skipped === 0 && this.optionalSuggestions === 0) {
204
199
  this.log(`\u2705 All ${totalRun} tests passed!`);
205
200
  this.log(` ${this.componentName.charAt(0).toUpperCase()}${this.componentName.slice(1)} component meets WAI-ARIA expectations for Roles, States, Properties, and Keyboard Interactions \u2713`);