aria-ease 6.5.1 → 6.7.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 +88 -24
- package/bin/{chunk-AUJAN4RK.js → chunk-2TOYEY5L.js} +87 -40
- package/bin/chunk-VPBHLMAS.js +127 -0
- package/bin/cli.cjs +403 -237
- package/bin/cli.js +8 -123
- package/bin/configLoader-XRF6VM4J.js +7 -0
- package/{dist/contractTestRunnerPlaywright-7F756CFB.js → bin/contractTestRunnerPlaywright-UAOFNS7Z.js} +121 -60
- package/bin/{test-C3CMRHSI.js → test-WRIJHN6H.js} +65 -24
- package/dist/{chunk-AUJAN4RK.js → chunk-2TOYEY5L.js} +87 -40
- package/dist/configLoader-IT4PWCJB.js +128 -0
- package/{bin/contractTestRunnerPlaywright-7F756CFB.js → dist/contractTestRunnerPlaywright-UAOFNS7Z.js} +121 -60
- package/dist/index.cjs +471 -137
- package/dist/index.d.cts +6 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.js +127 -35
- package/dist/src/menu/index.cjs +62 -11
- package/dist/src/menu/index.js +62 -11
- package/dist/src/utils/test/aria-contracts/accordion/accordion.contract.json +8 -8
- package/dist/src/utils/test/aria-contracts/combobox/combobox.listbox.contract.json +4 -4
- package/dist/src/utils/test/aria-contracts/menu/menu.contract.json +172 -34
- package/dist/src/utils/test/aria-contracts/tabs/tabs.contract.json +10 -10
- package/dist/src/utils/test/{chunk-AUJAN4RK.js → chunk-2TOYEY5L.js} +85 -41
- package/dist/src/utils/test/configLoader-LD4RV2WQ.js +126 -0
- package/dist/src/utils/test/{contractTestRunnerPlaywright-HL73FADJ.js → contractTestRunnerPlaywright-IRJOAEMT.js} +117 -59
- package/dist/src/utils/test/index.cjs +403 -125
- package/dist/src/utils/test/index.d.cts +7 -1
- package/dist/src/utils/test/index.d.ts +7 -1
- package/dist/src/utils/test/index.js +61 -23
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -13,19 +13,15 @@ Stop treating accessibility as an afterthought. Aria-Ease engineers accessibilit
|
|
|
13
13
|
|
|
14
14
|
Aria-Ease isn't a utility library. **It's an accessibility infrastructure** that integrates into every phase of your frontend engineering lifecycle:
|
|
15
15
|
|
|
16
|
-
| Phase | Feature | Status | Impact
|
|
17
|
-
| ------------------ | --------------------------------------------- | ------------ |
|
|
18
|
-
| **🔧 Development** | Component utilities for accessible patterns | ✅ Available | Build it right from the start
|
|
19
|
-
| **⚡ Linting** | ESLint rules to enforce accessible coding | 🚧 Roadmap | Catch mistakes as you type
|
|
20
|
-
| **🔍 Pre-Deploy** | Axe-core powered static accessibility audit | ✅ Available | Verify before it ships
|
|
21
|
-
| **🧪 Testing** | WAI-ARIA APG contract testing with Playwright | ✅ Available |
|
|
22
|
-
| **🚀 CI/CD** | Accessibility as deployment gatekeeper | ✅ Available | Block inaccessible code from production
|
|
23
|
-
| **📊 Production** | Real user signal monitoring and replay | 🚧 Roadmap | Understand how users actually interact
|
|
24
|
-
| **📈 Insights** | Dashboard for reporting and analytics | 🚧 Roadmap | Visualize accessibility health
|
|
25
|
-
|
|
26
|
-
**The philosophy:** By the time your app reaches manual testing, there should only be minute, non-automatable aspects left to verify.
|
|
27
|
-
|
|
28
|
-
**The reality:** Code that fails accessibility checks cannot reach production. Period.
|
|
16
|
+
| Phase | Feature | Status | Impact |
|
|
17
|
+
| ------------------ | --------------------------------------------- | ------------ | --------------------------------------------- |
|
|
18
|
+
| **🔧 Development** | Component utilities for accessible patterns | ✅ Available | Build it right from the start |
|
|
19
|
+
| **⚡ Linting** | ESLint rules to enforce accessible coding | 🚧 Roadmap | Catch mistakes as you type |
|
|
20
|
+
| **🔍 Pre-Deploy** | Axe-core powered static accessibility audit | ✅ Available | Verify before it ships |
|
|
21
|
+
| **🧪 Testing** | WAI-ARIA APG contract testing with Playwright | ✅ Available | Fast, determinic component accessibility test |
|
|
22
|
+
| **🚀 CI/CD** | Accessibility as deployment gatekeeper | ✅ Available | Block inaccessible code from production |
|
|
23
|
+
| **📊 Production** | Real user signal monitoring and replay | 🚧 Roadmap | Understand how users actually interact |
|
|
24
|
+
| **📈 Insights** | Dashboard for reporting and analytics | 🚧 Roadmap | Visualize accessibility health |
|
|
29
25
|
|
|
30
26
|
---
|
|
31
27
|
|
|
@@ -35,7 +31,7 @@ Aria-Ease isn't a utility library. **It's an accessibility infrastructure** that
|
|
|
35
31
|
|
|
36
32
|
**Traditional approach:** Build features → Manual testing → Find accessibility issues → Fix them → Manual testing again → Ship (maybe)
|
|
37
33
|
|
|
38
|
-
**Aria-Ease approach:** Build with accessible utilities → Automated audits catch issues → Contract tests verify
|
|
34
|
+
**Aria-Ease approach:** Build with accessible utilities → Automated audits catch issues → Contract tests verify deterministic component behaviors → CI/CD gates deployment → Ship with confidence
|
|
39
35
|
|
|
40
36
|
### What Makes This Different?
|
|
41
37
|
|
|
@@ -70,11 +66,11 @@ This is the game-changer. We encoded the WAI-ARIA APG into deterministic JSON "c
|
|
|
70
66
|
|
|
71
67
|
```bash
|
|
72
68
|
npx aria-ease test
|
|
73
|
-
# ✓ 26 assertions in ~
|
|
69
|
+
# ✓ 26 assertions in ~2 seconds
|
|
74
70
|
# ✓ 26 assertions in ~1 second in CI
|
|
75
71
|
```
|
|
76
72
|
|
|
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 ~
|
|
73
|
+
**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
74
|
|
|
79
75
|
#### 4. **CI/CD Integration** (Available Now)
|
|
80
76
|
|
|
@@ -134,7 +130,7 @@ pnpm add aria-ease
|
|
|
134
130
|
|
|
135
131
|
### Automated Accessibility Audits (CLI)
|
|
136
132
|
|
|
137
|
-
Run automated accessibility audits on your website with one command:
|
|
133
|
+
Run axe-core powered automated accessibility audits on your website with one command:
|
|
138
134
|
|
|
139
135
|
```bash
|
|
140
136
|
npx aria-ease audit --url https://yoursite.com
|
|
@@ -158,6 +154,9 @@ export default {
|
|
|
158
154
|
out: "./accessibility-reports",
|
|
159
155
|
},
|
|
160
156
|
},
|
|
157
|
+
test: {
|
|
158
|
+
strictness: "balanced", // 'minimal' | 'balanced' | 'strict' | 'paranoid'
|
|
159
|
+
},
|
|
161
160
|
};
|
|
162
161
|
```
|
|
163
162
|
|
|
@@ -662,6 +661,67 @@ describe("Shopify User Menu Accessibility Test", () => {
|
|
|
662
661
|
});
|
|
663
662
|
```
|
|
664
663
|
|
|
664
|
+
### Strictness Modes
|
|
665
|
+
|
|
666
|
+
Aria-Ease supports strictness policies to define how contract levels are enforced.
|
|
667
|
+
|
|
668
|
+
- `minimal`
|
|
669
|
+
- `required` -> error
|
|
670
|
+
- `recommended` -> ignore
|
|
671
|
+
- `optional` -> ignore
|
|
672
|
+
- `balanced` (default)
|
|
673
|
+
- `required` -> error
|
|
674
|
+
- `recommended` -> warning
|
|
675
|
+
- `optional` -> ignore
|
|
676
|
+
- `strict`
|
|
677
|
+
- `required` -> error
|
|
678
|
+
- `recommended` -> error
|
|
679
|
+
- `optional` -> warning
|
|
680
|
+
- `paranoid`
|
|
681
|
+
- `required` -> error
|
|
682
|
+
- `recommended` -> error
|
|
683
|
+
- `optional` -> error
|
|
684
|
+
|
|
685
|
+
Configure strictness globally in `ariaease.config.*`:
|
|
686
|
+
|
|
687
|
+
```javascript
|
|
688
|
+
export default {
|
|
689
|
+
test: {
|
|
690
|
+
strictness: "balanced",
|
|
691
|
+
},
|
|
692
|
+
};
|
|
693
|
+
```
|
|
694
|
+
|
|
695
|
+
Or define strictness per component (recommended when components have different maturity levels):
|
|
696
|
+
|
|
697
|
+
```javascript
|
|
698
|
+
export default {
|
|
699
|
+
test: {
|
|
700
|
+
strictness: "balanced", // fallback
|
|
701
|
+
components: [
|
|
702
|
+
{
|
|
703
|
+
name: "menu",
|
|
704
|
+
strictness: "strict",
|
|
705
|
+
},
|
|
706
|
+
{
|
|
707
|
+
name: "accordion",
|
|
708
|
+
strictness: "minimal",
|
|
709
|
+
},
|
|
710
|
+
],
|
|
711
|
+
},
|
|
712
|
+
};
|
|
713
|
+
```
|
|
714
|
+
|
|
715
|
+
`path` is optional and not required for strictness resolution.
|
|
716
|
+
|
|
717
|
+
Or override per test call:
|
|
718
|
+
|
|
719
|
+
```javascript
|
|
720
|
+
await testUiComponent("menu", null, "http://localhost:5173/test-harness?component=menu", {
|
|
721
|
+
strictness: "strict",
|
|
722
|
+
});
|
|
723
|
+
```
|
|
724
|
+
|
|
665
725
|
---
|
|
666
726
|
|
|
667
727
|
## 📦 Bundle Size
|
|
@@ -754,7 +814,7 @@ For older browser support, use a polyfill service or transpile with appropriate
|
|
|
754
814
|
|
|
755
815
|
---
|
|
756
816
|
|
|
757
|
-
##
|
|
817
|
+
## 🚀 CI/CD Integration: Accessibility as a Deployment Gatekeeper
|
|
758
818
|
|
|
759
819
|
**The game-changer:** Turn accessibility into a deployment invariant. Code that fails accessibility checks cannot reach production.
|
|
760
820
|
|
|
@@ -876,10 +936,14 @@ Create `ariaease.config.js` in your project root:
|
|
|
876
936
|
```javascript
|
|
877
937
|
export default {
|
|
878
938
|
audit: {
|
|
879
|
-
urls: [
|
|
939
|
+
urls: [
|
|
940
|
+
"http://localhost:5173", // Homepage
|
|
941
|
+
"http://localhost:5173/docs", // Docs
|
|
942
|
+
"http://localhost:5173/examples", // Examples
|
|
943
|
+
],
|
|
880
944
|
output: {
|
|
881
|
-
format: "
|
|
882
|
-
out: "./accessibility-reports
|
|
945
|
+
format: "all", // Generate JSON, CSV, and HTML reports
|
|
946
|
+
out: "./accessibility-reports",
|
|
883
947
|
},
|
|
884
948
|
},
|
|
885
949
|
};
|
|
@@ -985,7 +1049,7 @@ npx aria-ease test
|
|
|
985
1049
|
- Deterministic JSON contracts (no flaky selectors)
|
|
986
1050
|
- Runs headless in CI
|
|
987
1051
|
|
|
988
|
-
**Compare to manual testing:** Manually verifying keyboard interactions for a combobox (arrow keys, typing, Enter, Escape, Home/End, etc.) across browsers, verifying WAI-ARIA roles, states and properties = 20-30 minutes. Aria-Ease =
|
|
1052
|
+
**Compare to manual testing:** Manually verifying keyboard interactions for a combobox listbox (arrow keys, typing, Enter, Escape, Home/End, etc.) across browsers, verifying WAI-ARIA roles, states and properties = 20-30 minutes. Aria-Ease = ~2 seconds.
|
|
989
1053
|
|
|
990
1054
|
### The Moment of Truth
|
|
991
1055
|
|
|
@@ -993,11 +1057,11 @@ The first time you see that green check mark in your CI/CD pipeline—knowing th
|
|
|
993
1057
|
|
|
994
1058
|
**No one has any excuse to ship inaccessible code anymore.**
|
|
995
1059
|
|
|
996
|
-
You've shifted accessibility left (into development), automated the verification, and made it a deployment gatekeeper.
|
|
1060
|
+
You've shifted accessibility left (into development), automated the verification, and made it a deployment gatekeeper.
|
|
997
1061
|
|
|
998
1062
|
---
|
|
999
1063
|
|
|
1000
|
-
##
|
|
1064
|
+
## 📖 More Resources
|
|
1001
1065
|
|
|
1002
1066
|
- [Full Documentation](https://ariaease.site/docs)
|
|
1003
1067
|
- [GitHub Repository](https://github.com/aria-ease/aria-ease)
|
|
@@ -24,10 +24,11 @@ var ContractReporter = class {
|
|
|
24
24
|
componentName = "";
|
|
25
25
|
staticPasses = 0;
|
|
26
26
|
staticFailures = 0;
|
|
27
|
+
staticWarnings = 0;
|
|
27
28
|
dynamicResults = [];
|
|
28
29
|
totalTests = 0;
|
|
29
30
|
skipped = 0;
|
|
30
|
-
|
|
31
|
+
warnings = 0;
|
|
31
32
|
isPlaywright = false;
|
|
32
33
|
apgUrl = "https://www.w3.org/WAI/ARIA/apg/";
|
|
33
34
|
hasPrintedStaticSection = false;
|
|
@@ -54,23 +55,27 @@ ${"\u2550".repeat(60)}`);
|
|
|
54
55
|
this.log(`${"\u2550".repeat(60)}
|
|
55
56
|
`);
|
|
56
57
|
}
|
|
57
|
-
reportStatic(passes, failures) {
|
|
58
|
+
reportStatic(passes, failures, warnings = 0) {
|
|
58
59
|
this.staticPasses = passes;
|
|
59
60
|
this.staticFailures = failures;
|
|
61
|
+
this.staticWarnings = warnings;
|
|
60
62
|
}
|
|
61
63
|
/**
|
|
62
64
|
* Report individual static test pass
|
|
63
65
|
*/
|
|
64
|
-
reportStaticTest(description,
|
|
66
|
+
reportStaticTest(description, status, failureMessage, level) {
|
|
65
67
|
if (!this.hasPrintedStaticSection) {
|
|
66
68
|
this.log(`${"\u2500".repeat(60)}`);
|
|
67
69
|
this.log(`\u{1F9EA} Static Assertions`);
|
|
68
70
|
this.log(`${"\u2500".repeat(60)}`);
|
|
69
71
|
this.hasPrintedStaticSection = true;
|
|
70
72
|
}
|
|
71
|
-
const icon =
|
|
73
|
+
const icon = status === "pass" ? "\u2713" : status === "warn" ? "\u26A0" : status === "skip" ? "\u25CB" : "\u2717";
|
|
72
74
|
this.log(` ${icon} ${description}`);
|
|
73
|
-
if (
|
|
75
|
+
if (level) {
|
|
76
|
+
this.log(` \u21B3 level=${level}`);
|
|
77
|
+
}
|
|
78
|
+
if ((status === "fail" || status === "warn" || status === "skip") && failureMessage) {
|
|
74
79
|
this.log(` \u21B3 ${failureMessage}`);
|
|
75
80
|
}
|
|
76
81
|
}
|
|
@@ -89,23 +94,26 @@ ${"\u2550".repeat(60)}`);
|
|
|
89
94
|
description: test.description,
|
|
90
95
|
status,
|
|
91
96
|
failureMessage,
|
|
92
|
-
|
|
97
|
+
level: test.level
|
|
93
98
|
};
|
|
94
99
|
if (status === "skip") {
|
|
95
100
|
result.skipReason = "Requires real browser (addEventListener events)";
|
|
96
101
|
}
|
|
97
102
|
this.dynamicResults.push(result);
|
|
98
|
-
const icons = { pass: "\u2713", fail: "\u2717",
|
|
99
|
-
const
|
|
100
|
-
this.log(` ${icons[status]} ${
|
|
103
|
+
const icons = { pass: "\u2713", fail: "\u2717", warn: "\u26A0", skip: "\u25CB" };
|
|
104
|
+
const levelPrefix = test.level ? `[${test.level.toUpperCase()}] ` : "";
|
|
105
|
+
this.log(` ${icons[status]} ${levelPrefix}${test.description}`);
|
|
101
106
|
if (status === "skip" && !this.isPlaywright) {
|
|
102
107
|
this.log(` \u21B3 Skipped in jsdom (runs in Playwright)`);
|
|
103
108
|
}
|
|
104
|
-
if (status === "fail" && failureMessage
|
|
109
|
+
if (status === "fail" && failureMessage) {
|
|
105
110
|
this.log(` \u21B3 ${failureMessage}`);
|
|
106
111
|
}
|
|
107
|
-
if (status === "
|
|
108
|
-
this.log(` \u21B3
|
|
112
|
+
if (status === "warn" && failureMessage) {
|
|
113
|
+
this.log(` \u21B3 ${failureMessage}`);
|
|
114
|
+
}
|
|
115
|
+
if (status === "skip" && failureMessage) {
|
|
116
|
+
this.log(` \u21B3 ${failureMessage}`);
|
|
109
117
|
}
|
|
110
118
|
}
|
|
111
119
|
/**
|
|
@@ -129,29 +137,29 @@ ${"\u2500".repeat(60)}`);
|
|
|
129
137
|
this.log("");
|
|
130
138
|
});
|
|
131
139
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
reportOptionalSuggestions() {
|
|
136
|
-
const suggestions = this.dynamicResults.filter((r) => r.status === "optional-fail");
|
|
137
|
-
if (suggestions.length === 0) return;
|
|
140
|
+
reportWarnings() {
|
|
141
|
+
const warnings = this.dynamicResults.filter((r) => r.status === "warn");
|
|
142
|
+
if (warnings.length === 0 && this.staticWarnings === 0) return;
|
|
138
143
|
this.log(`
|
|
139
144
|
${"\u2500".repeat(60)}`);
|
|
140
|
-
this.log(`\
|
|
145
|
+
this.log(`\u26A0\uFE0F Warnings (${this.staticWarnings + warnings.length}):
|
|
141
146
|
`);
|
|
142
|
-
this.log(`These
|
|
143
|
-
this.log(`for improved user experience and keyboard interaction:
|
|
147
|
+
this.log(`These checks are failing but treated as warnings under the active strictness mode.
|
|
144
148
|
`);
|
|
145
|
-
|
|
149
|
+
warnings.forEach((test, index) => {
|
|
146
150
|
this.log(`${index + 1}. ${test.description}`);
|
|
147
151
|
if (test.failureMessage) {
|
|
148
152
|
this.log(` \u21B3 ${test.failureMessage}`);
|
|
149
153
|
}
|
|
154
|
+
if (test.level) {
|
|
155
|
+
this.log(` \u21B3 level=${test.level}`);
|
|
156
|
+
}
|
|
150
157
|
});
|
|
151
|
-
this.
|
|
152
|
-
|
|
153
|
-
|
|
158
|
+
if (this.apgUrl) {
|
|
159
|
+
this.log(`
|
|
160
|
+
Reference: ${this.apgUrl}
|
|
154
161
|
`);
|
|
162
|
+
}
|
|
155
163
|
}
|
|
156
164
|
/**
|
|
157
165
|
* Report skipped tests with helpful context
|
|
@@ -181,46 +189,42 @@ ${"\u2500".repeat(60)}`);
|
|
|
181
189
|
const duration = Date.now() - this.startTime;
|
|
182
190
|
const dynamicPasses = this.dynamicResults.filter((r) => r.status === "pass").length;
|
|
183
191
|
const dynamicFailures = this.dynamicResults.filter((r) => r.status === "fail").length;
|
|
192
|
+
const dynamicWarnings = this.dynamicResults.filter((r) => r.status === "warn").length;
|
|
184
193
|
this.skipped = this.dynamicResults.filter((r) => r.status === "skip").length;
|
|
185
|
-
this.
|
|
194
|
+
this.warnings = this.staticWarnings + dynamicWarnings;
|
|
186
195
|
const totalPasses = this.staticPasses + dynamicPasses;
|
|
187
196
|
const totalFailures = this.staticFailures + dynamicFailures;
|
|
188
|
-
const totalRun = totalPasses + totalFailures;
|
|
197
|
+
const totalRun = totalPasses + totalFailures + this.warnings;
|
|
189
198
|
if (failures.length > 0) {
|
|
190
199
|
this.reportFailures(failures);
|
|
191
200
|
}
|
|
192
|
-
this.
|
|
201
|
+
this.reportWarnings();
|
|
193
202
|
this.reportSkipped();
|
|
194
203
|
this.log(`
|
|
195
204
|
${"\u2550".repeat(60)}`);
|
|
196
205
|
this.log(`\u{1F4CA} Summary
|
|
197
206
|
`);
|
|
198
|
-
|
|
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
|
-
if (totalFailures === 0 && this.skipped === 0 && this.optionalSuggestions === 0) {
|
|
207
|
+
if (totalFailures === 0 && this.skipped === 0 && this.warnings === 0) {
|
|
204
208
|
this.log(`\u2705 All ${totalRun} tests passed!`);
|
|
205
209
|
this.log(` ${this.componentName.charAt(0).toUpperCase()}${this.componentName.slice(1)} component meets WAI-ARIA expectations for Roles, States, Properties, and Keyboard Interactions \u2713`);
|
|
206
210
|
} else if (totalFailures === 0) {
|
|
207
|
-
this.log(`\u2705 ${totalPasses}/${totalRun}
|
|
211
|
+
this.log(`\u2705 ${totalPasses}/${totalRun} tests passed`);
|
|
208
212
|
if (this.skipped > 0) {
|
|
209
213
|
this.log(`\u25CB ${this.skipped} tests skipped`);
|
|
210
214
|
}
|
|
211
|
-
if (this.
|
|
212
|
-
this.log(`\
|
|
215
|
+
if (this.warnings > 0) {
|
|
216
|
+
this.log(`\u26A0\uFE0F ${this.warnings} warning${this.warnings > 1 ? "s" : ""}`);
|
|
213
217
|
}
|
|
214
218
|
this.log(` ${this.componentName.charAt(0).toUpperCase()}${this.componentName.slice(1)} component meets WAI-ARIA expectations for Roles, States, Properties, and Keyboard Interactions \u2713`);
|
|
215
219
|
} else {
|
|
216
220
|
this.log(`\u274C ${totalFailures} test${totalFailures > 1 ? "s" : ""} failed`);
|
|
217
221
|
this.log(`\u2705 ${totalPasses} test${totalPasses > 1 ? "s" : ""} passed`);
|
|
222
|
+
if (this.warnings > 0) {
|
|
223
|
+
this.log(`\u26A0\uFE0F ${this.warnings} warning${this.warnings > 1 ? "s" : ""}`);
|
|
224
|
+
}
|
|
218
225
|
if (this.skipped > 0) {
|
|
219
226
|
this.log(`\u25CB ${this.skipped} test${this.skipped > 1 ? "s" : ""} skipped`);
|
|
220
227
|
}
|
|
221
|
-
if (this.optionalSuggestions > 0) {
|
|
222
|
-
this.log(`\u{1F4A1} ${this.optionalSuggestions} optional enhancement${this.optionalSuggestions > 1 ? "s" : ""} suggested`);
|
|
223
|
-
}
|
|
224
228
|
}
|
|
225
229
|
this.log(`\u23F1\uFE0F Duration: ${duration}ms`);
|
|
226
230
|
this.log(`${"\u2550".repeat(60)}
|
|
@@ -255,6 +259,46 @@ ${"\u2550".repeat(60)}`);
|
|
|
255
259
|
}
|
|
256
260
|
};
|
|
257
261
|
|
|
262
|
+
// src/utils/test/src/strictness.ts
|
|
263
|
+
var FALLBACK_LEVEL = "required";
|
|
264
|
+
function normalizeLevel(level) {
|
|
265
|
+
if (level === "required" || level === "recommended" || level === "optional") {
|
|
266
|
+
return level;
|
|
267
|
+
}
|
|
268
|
+
return FALLBACK_LEVEL;
|
|
269
|
+
}
|
|
270
|
+
function normalizeStrictness(strictness) {
|
|
271
|
+
if (strictness === "minimal" || strictness === "balanced" || strictness === "strict" || strictness === "paranoid") {
|
|
272
|
+
return strictness;
|
|
273
|
+
}
|
|
274
|
+
return "balanced";
|
|
275
|
+
}
|
|
276
|
+
function resolveEnforcement(level, strictness) {
|
|
277
|
+
const matrix = {
|
|
278
|
+
minimal: {
|
|
279
|
+
required: "error",
|
|
280
|
+
recommended: "ignore",
|
|
281
|
+
optional: "ignore"
|
|
282
|
+
},
|
|
283
|
+
balanced: {
|
|
284
|
+
required: "error",
|
|
285
|
+
recommended: "warning",
|
|
286
|
+
optional: "ignore"
|
|
287
|
+
},
|
|
288
|
+
strict: {
|
|
289
|
+
required: "error",
|
|
290
|
+
recommended: "error",
|
|
291
|
+
optional: "warning"
|
|
292
|
+
},
|
|
293
|
+
paranoid: {
|
|
294
|
+
required: "error",
|
|
295
|
+
recommended: "error",
|
|
296
|
+
optional: "error"
|
|
297
|
+
}
|
|
298
|
+
};
|
|
299
|
+
return matrix[strictness][level];
|
|
300
|
+
}
|
|
301
|
+
|
|
258
302
|
// src/utils/test/src/playwrightTestHarness.ts
|
|
259
303
|
import { chromium } from "playwright";
|
|
260
304
|
var sharedBrowser = null;
|
|
@@ -302,6 +346,9 @@ async function closeSharedBrowser() {
|
|
|
302
346
|
export {
|
|
303
347
|
contract_default,
|
|
304
348
|
ContractReporter,
|
|
349
|
+
normalizeLevel,
|
|
350
|
+
normalizeStrictness,
|
|
351
|
+
resolveEnforcement,
|
|
305
352
|
createTestPage,
|
|
306
353
|
closeSharedBrowser
|
|
307
354
|
};
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
// src/utils/cli/configLoader.ts
|
|
2
|
+
import path from "path";
|
|
3
|
+
import fs from "fs-extra";
|
|
4
|
+
function validateConfig(config) {
|
|
5
|
+
const errors = [];
|
|
6
|
+
if (!config || typeof config !== "object") {
|
|
7
|
+
errors.push("Config must be an object");
|
|
8
|
+
return { valid: false, errors };
|
|
9
|
+
}
|
|
10
|
+
const cfg = config;
|
|
11
|
+
if (cfg.audit !== void 0) {
|
|
12
|
+
if (typeof cfg.audit !== "object" || cfg.audit === null) {
|
|
13
|
+
errors.push("audit must be an object");
|
|
14
|
+
} else {
|
|
15
|
+
if (cfg.audit.urls !== void 0) {
|
|
16
|
+
if (!Array.isArray(cfg.audit.urls)) {
|
|
17
|
+
errors.push("audit.urls must be an array");
|
|
18
|
+
} else if (cfg.audit.urls.some((url) => typeof url !== "string")) {
|
|
19
|
+
errors.push("audit.urls must contain only strings");
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
if (cfg.audit.output !== void 0) {
|
|
23
|
+
if (typeof cfg.audit.output !== "object") {
|
|
24
|
+
errors.push("audit.output must be an object");
|
|
25
|
+
} else {
|
|
26
|
+
const output = cfg.audit.output;
|
|
27
|
+
if (output.format !== void 0) {
|
|
28
|
+
if (!["json", "csv", "html", "all"].includes(output.format)) {
|
|
29
|
+
errors.push("audit.output.format must be one of: json, csv, html, all");
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
if (output.out !== void 0 && typeof output.out !== "string") {
|
|
33
|
+
errors.push("audit.output.out must be a string");
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (cfg.test !== void 0) {
|
|
40
|
+
if (typeof cfg.test !== "object" || cfg.test === null) {
|
|
41
|
+
errors.push("test must be an object");
|
|
42
|
+
} else {
|
|
43
|
+
if (cfg.test.components !== void 0) {
|
|
44
|
+
if (!Array.isArray(cfg.test.components)) {
|
|
45
|
+
errors.push("test.components must be an array");
|
|
46
|
+
} else {
|
|
47
|
+
cfg.test.components.forEach((comp, idx) => {
|
|
48
|
+
if (typeof comp !== "object" || comp === null) {
|
|
49
|
+
errors.push(`test.components[${idx}] must be an object`);
|
|
50
|
+
} else {
|
|
51
|
+
if (typeof comp.name !== "string") {
|
|
52
|
+
errors.push(`test.components[${idx}].name must be a string`);
|
|
53
|
+
}
|
|
54
|
+
if (comp.path !== void 0 && typeof comp.path !== "string") {
|
|
55
|
+
errors.push(`test.components[${idx}].path must be a string when provided`);
|
|
56
|
+
}
|
|
57
|
+
if (comp.strictness !== void 0 && !["minimal", "balanced", "strict", "paranoid"].includes(comp.strictness)) {
|
|
58
|
+
errors.push(`test.components[${idx}].strictness must be one of: minimal, balanced, strict, paranoid`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (cfg.test.strictness !== void 0) {
|
|
65
|
+
if (!["minimal", "balanced", "strict", "paranoid"].includes(cfg.test.strictness)) {
|
|
66
|
+
errors.push("test.strictness must be one of: minimal, balanced, strict, paranoid");
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return { valid: errors.length === 0, errors };
|
|
72
|
+
}
|
|
73
|
+
async function loadConfigFile(filePath) {
|
|
74
|
+
try {
|
|
75
|
+
const ext = path.extname(filePath);
|
|
76
|
+
if (ext === ".json") {
|
|
77
|
+
const content = await fs.readFile(filePath, "utf-8");
|
|
78
|
+
return JSON.parse(content);
|
|
79
|
+
} else if ([".js", ".mjs", ".cjs", ".ts"].includes(ext)) {
|
|
80
|
+
const imported = await import(filePath);
|
|
81
|
+
return imported.default || imported;
|
|
82
|
+
}
|
|
83
|
+
return null;
|
|
84
|
+
} catch {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
async function loadConfig(cwd = process.cwd()) {
|
|
89
|
+
const configNames = [
|
|
90
|
+
"ariaease.config.js",
|
|
91
|
+
"ariaease.config.mjs",
|
|
92
|
+
"ariaease.config.cjs",
|
|
93
|
+
"ariaease.config.json",
|
|
94
|
+
"ariaease.config.ts"
|
|
95
|
+
];
|
|
96
|
+
let loadedConfig = null;
|
|
97
|
+
let foundPath = null;
|
|
98
|
+
const errors = [];
|
|
99
|
+
for (const name of configNames) {
|
|
100
|
+
const configPath = path.resolve(cwd, name);
|
|
101
|
+
if (await fs.pathExists(configPath)) {
|
|
102
|
+
foundPath = configPath;
|
|
103
|
+
loadedConfig = await loadConfigFile(configPath);
|
|
104
|
+
if (loadedConfig === null) {
|
|
105
|
+
errors.push(`Found config at ${name} but failed to load it. Check for syntax errors.`);
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
const validation = validateConfig(loadedConfig);
|
|
109
|
+
if (!validation.valid) {
|
|
110
|
+
errors.push(`Config validation failed in ${name}:`);
|
|
111
|
+
errors.push(...validation.errors.map((err) => ` - ${err}`));
|
|
112
|
+
loadedConfig = null;
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return {
|
|
119
|
+
config: loadedConfig || {},
|
|
120
|
+
configPath: loadedConfig ? foundPath : null,
|
|
121
|
+
errors
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export {
|
|
126
|
+
loadConfig
|
|
127
|
+
};
|