aria-ease 6.5.1 → 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.
- package/README.md +14 -10
- package/bin/{chunk-AUJAN4RK.js → chunk-LKN5PRYD.js} +0 -5
- package/bin/cli.cjs +50 -33
- package/bin/cli.js +1 -1
- package/{dist/contractTestRunnerPlaywright-7F756CFB.js → bin/contractTestRunnerPlaywright-PC6JOYYV.js} +51 -29
- package/bin/{test-C3CMRHSI.js → test-LP723IXM.js} +2 -2
- package/dist/{chunk-AUJAN4RK.js → chunk-LKN5PRYD.js} +0 -5
- package/{bin/contractTestRunnerPlaywright-7F756CFB.js → dist/contractTestRunnerPlaywright-PC6JOYYV.js} +51 -29
- package/dist/index.cjs +94 -39
- package/dist/index.js +46 -8
- package/dist/src/menu/index.cjs +44 -6
- package/dist/src/menu/index.js +44 -6
- package/dist/src/utils/test/aria-contracts/menu/menu.contract.json +143 -30
- package/dist/src/utils/test/aria-contracts/tabs/tabs.contract.json +7 -7
- package/dist/src/utils/test/{chunk-AUJAN4RK.js → chunk-LKN5PRYD.js} +0 -5
- package/dist/src/utils/test/{contractTestRunnerPlaywright-HL73FADJ.js → contractTestRunnerPlaywright-RGKMGXND.js} +51 -29
- package/dist/src/utils/test/index.cjs +50 -33
- package/dist/src/utils/test/index.js +2 -2
- package/package.json +1 -1
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 ~
|
|
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 ~
|
|
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 ~
|
|
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
|
-
##
|
|
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: [
|
|
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: "
|
|
882
|
-
out: "./accessibility-reports
|
|
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.
|
|
1000
|
+
You've shifted accessibility left (into development), automated the verification, and made it a deployment gatekeeper.
|
|
997
1001
|
|
|
998
1002
|
---
|
|
999
1003
|
|
|
1000
|
-
##
|
|
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
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
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
|
-
|
|
982
|
-
|
|
983
|
-
|
|
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
|
-
|
|
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
|
|
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-
|
|
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-
|
|
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
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
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
|
-
|
|
181
|
-
|
|
182
|
-
|
|
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
|
-
|
|
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
|
|
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-
|
|
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-
|
|
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`);
|