aria-ease 6.14.0 → 7.0.1
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 +1 -1
- package/dist/AccordionComponentStrategy-2SWMNUR6.js +1 -0
- package/dist/ComboboxComponentStrategy-YSYLR2U5.js +5 -0
- package/dist/MenuComponentStrategy-C22BZEBH.js +5 -0
- package/dist/RelativeTargetResolver-T4P25J2M.js +1 -0
- package/dist/TabsComponentStrategy-ADEEFJXM.js +1 -0
- package/dist/audit-WBKVW7H6.js +9 -0
- package/dist/badgeHelper-IB5RTMAG.js +11 -0
- package/dist/badgeHelper-JSROP5ML.js +1 -0
- package/dist/buildContracts-T4XQZBDU.js +13 -0
- package/dist/chunk-52I3INNG.js +11 -0
- package/dist/chunk-APUMBDOT.js +1 -0
- package/dist/chunk-BHNO4ZI3.js +1 -0
- package/dist/chunk-CNU4N4AY.js +1 -0
- package/dist/chunk-SM6ZKEDR.js +1 -0
- package/dist/chunk-ZNQ5BXVJ.js +1 -0
- package/dist/cli.cjs +132 -3560
- package/dist/cli.js +19 -161
- package/dist/configLoader-ZEJVXLX7.js +1 -0
- package/dist/configLoader-ZXTSCIP6.js +1 -0
- package/dist/contractTestRunnerPlaywright-FOCQTM4L.js +46 -0
- package/dist/contractTestRunnerPlaywright-QPU6HZXG.js +46 -0
- package/dist/formatters-H3CPDLG5.js +87 -0
- package/dist/index.cjs +64 -5103
- package/dist/index.d.cts +4 -6
- package/dist/index.d.ts +4 -6
- package/dist/index.js +17 -2703
- package/dist/src/accordion/index.cjs +1 -183
- package/dist/src/accordion/index.js +1 -181
- package/dist/src/block/index.cjs +1 -124
- package/dist/src/block/index.js +1 -122
- package/dist/src/checkbox/index.cjs +1 -109
- package/dist/src/checkbox/index.js +1 -107
- package/dist/src/combobox/index.cjs +1 -265
- package/dist/src/combobox/index.js +1 -263
- package/dist/src/menu/index.cjs +1 -339
- package/dist/src/menu/index.js +1 -337
- package/dist/src/radio/index.cjs +1 -117
- package/dist/src/radio/index.js +1 -115
- package/dist/src/tabs/index.cjs +1 -265
- package/dist/src/tabs/index.js +1 -263
- package/dist/src/toggle/index.cjs +1 -119
- package/dist/src/toggle/index.js +1 -117
- package/dist/src/utils/test/AccordionComponentStrategy-X2GSQ5KT.js +1 -0
- package/dist/src/utils/test/ComboboxComponentStrategy-SICWLI27.js +5 -0
- package/dist/src/utils/test/MenuComponentStrategy-R4VPAHDE.js +5 -0
- package/dist/src/utils/test/RelativeTargetResolver-UQQMZHI6.js +1 -0
- package/dist/src/utils/test/TabsComponentStrategy-L2PYNEW6.js +1 -0
- package/dist/src/utils/test/badgeHelper-ER5ZOHWF.js +11 -0
- package/dist/src/utils/test/chunk-APUMBDOT.js +1 -0
- package/dist/src/utils/test/chunk-BHNO4ZI3.js +1 -0
- package/dist/src/utils/test/configLoader-NCYRL2O6.js +1 -0
- package/dist/src/utils/test/contractTestRunnerPlaywright-YZCMF64Q.js +46 -0
- package/dist/src/utils/test/dsl/index.cjs +1 -838
- package/dist/src/utils/test/dsl/index.d.cts +2 -4
- package/dist/src/utils/test/dsl/index.d.ts +2 -4
- package/dist/src/utils/test/dsl/index.js +1 -836
- package/dist/src/utils/test/index.cjs +64 -2672
- package/dist/src/utils/test/index.d.cts +2 -2
- package/dist/src/utils/test/index.d.ts +2 -2
- package/dist/src/utils/test/index.js +16 -340
- package/dist/test-VXSCSKV5.js +19 -0
- package/package.json +8 -10
- package/dist/AccordionComponentStrategy-4ZEIQ2V6.js +0 -42
- package/dist/ComboboxComponentStrategy-DU342VMB.js +0 -64
- package/dist/MenuComponentStrategy-VYCC2XOM.js +0 -81
- package/dist/RelativeTargetResolver-DJAITO6D.js +0 -7
- package/dist/TabsComponentStrategy-3SQURPMX.js +0 -29
- package/dist/audit-JYEPKLHR.js +0 -63
- package/dist/badgeHelper-JOWO6RQG.js +0 -15
- package/dist/badgeHelper-RDOMCC6E.js +0 -108
- package/dist/buildContracts-VIV6GM56.js +0 -437
- package/dist/chunk-4DU5Z5BR.js +0 -340
- package/dist/chunk-GJGUY643.js +0 -182
- package/dist/chunk-GLT43UVH.js +0 -43
- package/dist/chunk-I2KLQ2HA.js +0 -22
- package/dist/chunk-JJEPLK7L.js +0 -107
- package/dist/chunk-PK5L2SAF.js +0 -17
- package/dist/configLoader-Q7N5XV4P.js +0 -183
- package/dist/configLoader-REHK3S3Q.js +0 -7
- package/dist/contractTestRunnerPlaywright-B2HLZKKK.js +0 -1394
- package/dist/contractTestRunnerPlaywright-RWK52C7S.js +0 -1394
- package/dist/formatters-32KQIIYS.js +0 -183
- package/dist/src/utils/test/AccordionComponentStrategy-WRHZOEN6.js +0 -38
- package/dist/src/utils/test/ComboboxComponentStrategy-XKQ72RFD.js +0 -60
- package/dist/src/utils/test/MenuComponentStrategy-6XWU5KLW.js +0 -77
- package/dist/src/utils/test/RelativeTargetResolver-G2XDN2VV.js +0 -1
- package/dist/src/utils/test/TabsComponentStrategy-BKG53SEV.js +0 -26
- package/dist/src/utils/test/badgeHelper-HZKGOPB4.js +0 -102
- package/dist/src/utils/test/chunk-4DU5Z5BR.js +0 -332
- package/dist/src/utils/test/chunk-GLT43UVH.js +0 -41
- package/dist/src/utils/test/configLoader-NA7IBCS3.js +0 -181
- package/dist/src/utils/test/contractTestRunnerPlaywright-5FIGA5G4.js +0 -1372
- package/dist/test-WDBS5JWO.js +0 -358
package/dist/cli.js
CHANGED
|
@@ -1,162 +1,20 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const { formatResults } = await import("./formatters-32KQIIYS.js");
|
|
22
|
-
const needsConfig = !opts.url;
|
|
23
|
-
const { config, configPath, errors } = await loadConfig(process.cwd());
|
|
24
|
-
if (configPath) {
|
|
25
|
-
console.log(chalk.green(`\u2705 Loaded config from ${path.basename(configPath)}
|
|
26
|
-
`));
|
|
27
|
-
} else if (errors.length > 0 && needsConfig) {
|
|
28
|
-
console.log(chalk.red("\u274C Config file errors:\n"));
|
|
29
|
-
errors.forEach((err) => console.log(chalk.red(` ${err}`)));
|
|
30
|
-
console.log("");
|
|
31
|
-
process.exit(1);
|
|
32
|
-
} else if (errors.length > 0) {
|
|
33
|
-
console.log(chalk.yellow("\u26A0\uFE0F Config file has errors (ignored, using CLI options)\n"));
|
|
34
|
-
} else if (!configPath && needsConfig) {
|
|
35
|
-
console.log(chalk.yellow("\u2139\uFE0F No config file found, using CLI options.\n"));
|
|
36
|
-
}
|
|
37
|
-
const urls = [];
|
|
38
|
-
if (opts.url) {
|
|
39
|
-
urls.push(opts.url);
|
|
40
|
-
} else if (config.audit?.urls && Array.isArray(config.audit.urls)) {
|
|
41
|
-
urls.push(...config.audit.urls);
|
|
42
|
-
}
|
|
43
|
-
const format = config.audit?.output && config.audit.output.format || opts.format;
|
|
44
|
-
if (!["json", "csv", "html", "all"].includes(format)) {
|
|
45
|
-
console.log(chalk.red('\u274C Invalid format. Use "json", "csv", "html" or "all".'));
|
|
46
|
-
process.exit(1);
|
|
47
|
-
}
|
|
48
|
-
if (urls.length === 0) {
|
|
49
|
-
console.log(chalk.red('\u274C No URLs provided. Use --url option or add "urls" in config file'));
|
|
50
|
-
process.exit(1);
|
|
51
|
-
}
|
|
52
|
-
const allResults = [];
|
|
53
|
-
const { createAuditBrowser } = await import("./audit-JYEPKLHR.js");
|
|
54
|
-
const browser = await createAuditBrowser();
|
|
55
|
-
try {
|
|
56
|
-
const auditOptions = { browser };
|
|
57
|
-
for (const url of urls) {
|
|
58
|
-
console.log(chalk.yellow(`\u{1F50E} Auditing: ${url}`));
|
|
59
|
-
try {
|
|
60
|
-
const result = await runAudit(url, auditOptions);
|
|
61
|
-
allResults.push({ url, result });
|
|
62
|
-
console.log(chalk.green(`\u2705 Completed audit for ${url}
|
|
63
|
-
`));
|
|
64
|
-
} catch (error) {
|
|
65
|
-
if (error instanceof Error && error.message) {
|
|
66
|
-
console.log(chalk.red(`\u274C Failed auditing ${url}: ${error.message}`));
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
} finally {
|
|
71
|
-
await browser.close();
|
|
72
|
-
}
|
|
73
|
-
const hasResults = allResults.some((r) => r.result && r.result.violations && r.result.violations.length > 0);
|
|
74
|
-
if (!hasResults) {
|
|
75
|
-
const auditedCount = allResults.filter((r) => r.result).length;
|
|
76
|
-
if (auditedCount === 0) {
|
|
77
|
-
console.log(chalk.red("\u274C No pages were successfully audited."));
|
|
78
|
-
process.exit(1);
|
|
79
|
-
}
|
|
80
|
-
console.log(chalk.green("\n\u{1F389} Great news! No static accessibility violations found!"));
|
|
81
|
-
console.log(chalk.gray(` Audited ${auditedCount} page${auditedCount > 1 ? "s" : ""} successfully.
|
|
82
|
-
`));
|
|
83
|
-
displayBadgeInfo("audit");
|
|
84
|
-
await promptAddBadge("audit", process.cwd());
|
|
85
|
-
console.log(chalk.dim("\n" + "\u2500".repeat(60)));
|
|
86
|
-
console.log(chalk.cyan("\u{1F499} Found aria-ease helpful?"));
|
|
87
|
-
console.log(chalk.white(" \u2022 Star us on GitHub: ") + chalk.blue.underline("https://github.com/aria-ease/aria-ease"));
|
|
88
|
-
console.log(chalk.white(" \u2022 Share feedback: ") + chalk.blue.underline("https://github.com/aria-ease/aria-ease/discussions"));
|
|
89
|
-
console.log(chalk.dim("\u2500".repeat(60) + "\n"));
|
|
90
|
-
process.exit(0);
|
|
91
|
-
}
|
|
92
|
-
async function createReport(format2) {
|
|
93
|
-
const formatted = formatResults(allResults, format2);
|
|
94
|
-
const out = config.audit?.output && config.audit.output.out || opts.out;
|
|
95
|
-
await fs.ensureDir(out);
|
|
96
|
-
const d = /* @__PURE__ */ new Date();
|
|
97
|
-
const pad = (n) => String(n).padStart(2, "0");
|
|
98
|
-
const timestamp = `${pad(d.getDate())}-${pad(d.getMonth() + 1)}-${d.getFullYear()} ${pad(d.getHours())}h ${pad(d.getMinutes())}m ${pad(d.getSeconds())}s`;
|
|
99
|
-
const fileName = `ariaease-report-${timestamp}.${format2}`;
|
|
100
|
-
const filePath = path.join(out, fileName);
|
|
101
|
-
await fs.writeFile(filePath, formatted, "utf-8");
|
|
102
|
-
console.log(chalk.magentaBright(`\u{1F4C1} Report saved to ${filePath}`));
|
|
103
|
-
}
|
|
104
|
-
if (["json", "csv", "html"].includes(format)) {
|
|
105
|
-
await createReport(format);
|
|
106
|
-
} else if (format === "all") {
|
|
107
|
-
await Promise.all(["json", "csv", "html"].map((format2) => createReport(format2)));
|
|
108
|
-
}
|
|
109
|
-
const totalViolations = allResults.reduce((sum, r) => {
|
|
110
|
-
return sum + (r.result?.violations?.length || 0);
|
|
111
|
-
}, 0);
|
|
112
|
-
console.log(chalk.red(`
|
|
113
|
-
\u274C Accessibility violations found!`));
|
|
114
|
-
console.log(chalk.yellow(` ${totalViolations} violation${totalViolations !== 1 ? "s" : ""} detected across ${allResults.length} page${allResults.length !== 1 ? "s" : ""}.`));
|
|
115
|
-
console.log(chalk.gray(` Review the generated report for details.
|
|
116
|
-
`));
|
|
117
|
-
console.log(chalk.dim("\n" + "\u2500".repeat(60)));
|
|
118
|
-
console.log(chalk.cyan("\u{1F499} Found aria-ease helpful?"));
|
|
119
|
-
console.log(chalk.white(" \u2022 Star us on GitHub: ") + chalk.blue.underline("https://github.com/aria-ease/aria-ease"));
|
|
120
|
-
console.log(chalk.white(" \u2022 Share feedback: ") + chalk.blue.underline("https://github.com/aria-ease/aria-ease/discussions"));
|
|
121
|
-
console.log(chalk.dim("\u2500".repeat(60) + "\n"));
|
|
122
|
-
process.exit(1);
|
|
123
|
-
});
|
|
124
|
-
program.command("test").description("Run core a11y accessibility standard tests on UI components").action(async () => {
|
|
125
|
-
const { runTest } = await import("./test-WDBS5JWO.js");
|
|
126
|
-
runTest();
|
|
127
|
-
});
|
|
128
|
-
program.command("build").description("Build accessibility artifacts").addCommand(
|
|
129
|
-
new Command("contracts").description("Build DSL contracts to JSON").action(async () => {
|
|
130
|
-
const { buildContracts } = await import("./buildContracts-VIV6GM56.js");
|
|
131
|
-
const { loadConfig: loadConfig2 } = await import("./configLoader-REHK3S3Q.js");
|
|
132
|
-
const cwd = process.cwd();
|
|
133
|
-
const { config, configPath, errors } = await loadConfig2(cwd);
|
|
134
|
-
if (configPath) {
|
|
135
|
-
console.log(chalk.green(`\u2705 Loaded config from ${path.basename(configPath)}
|
|
136
|
-
`));
|
|
137
|
-
} else if (errors.length > 0) {
|
|
138
|
-
console.log(chalk.red("\u274C Config file has errors:\n"));
|
|
139
|
-
errors.forEach((err) => console.log(chalk.red(` ${err}`)));
|
|
140
|
-
console.log("");
|
|
141
|
-
process.exit(1);
|
|
142
|
-
}
|
|
143
|
-
const result = await buildContracts(cwd, config);
|
|
144
|
-
if (!result.success && result.errors.length > 0) {
|
|
145
|
-
console.log(chalk.red(`\u274C ${result.errors.length} error${result.errors.length !== 1 ? "s" : ""} occurred during build
|
|
146
|
-
`));
|
|
147
|
-
process.exit(1);
|
|
148
|
-
}
|
|
149
|
-
if (result.built.length === 0 && (!config.contracts || config.contracts.length === 0)) {
|
|
150
|
-
process.exit(0);
|
|
151
|
-
}
|
|
152
|
-
if (result.built.length === 0) {
|
|
153
|
-
console.log(chalk.yellow("\u26A0\uFE0F No contracts were built\n"));
|
|
154
|
-
process.exit(1);
|
|
155
|
-
}
|
|
156
|
-
process.exit(0);
|
|
157
|
-
})
|
|
158
|
-
);
|
|
159
|
-
program.command("help").description("Display help information").action(() => {
|
|
160
|
-
program.outputHelp();
|
|
161
|
-
});
|
|
162
|
-
program.parse(process.argv);
|
|
2
|
+
import{a as x}from"./chunk-ZNQ5BXVJ.js";import{c as R,d as C}from"./chunk-52I3INNG.js";import"./chunk-CNU4N4AY.js";import{Command as S}from"commander";import o from"chalk";import w from"path";import A from"fs-extra";var l=new S;l.name("aria-ease").description("Run accessibility tests and audits").version("2.2.3");l.command("audit").description("Run axe-core powered accessibility audit on webpages").option("-u, --url <url>","Single URL to audit").option("-f, --format <format>","Output format for the audit report: json | csv | html | all","all").option("-o, --out <path>","Directory to save the audit report","./accessibility-reports/audit").action(async n=>{console.log(o.cyanBright(`\u{1F680} Starting accessibility audit...
|
|
3
|
+
`));let{runAudit:h}=await import("./audit-WBKVW7H6.js"),{formatResults:f}=await import("./formatters-H3CPDLG5.js"),c=!n.url,{config:s,configPath:u,errors:i}=await x(process.cwd());u?console.log(o.green(`\u2705 Loaded config from ${w.basename(u)}
|
|
4
|
+
`)):i.length>0&&c?(console.log(o.red(`\u274C Config file errors:
|
|
5
|
+
`)),i.forEach(e=>console.log(o.red(` ${e}`))),console.log(""),process.exit(1)):i.length>0?console.log(o.yellow(`\u26A0\uFE0F Config file has errors (ignored, using CLI options)
|
|
6
|
+
`)):!u&&c&&console.log(o.yellow(`\u2139\uFE0F No config file found, using CLI options.
|
|
7
|
+
`));let d=[];n.url?d.push(n.url):s.audit?.urls&&Array.isArray(s.audit.urls)&&d.push(...s.audit.urls);let m=s.audit?.output&&s.audit.output.format||n.format;["json","csv","html","all"].includes(m)||(console.log(o.red('\u274C Invalid format. Use "json", "csv", "html" or "all".')),process.exit(1)),d.length===0&&(console.log(o.red('\u274C No URLs provided. Use --url option or add "urls" in config file')),process.exit(1));let r=[],{createAuditBrowser:B}=await import("./audit-WBKVW7H6.js"),y=await B();try{let e={browser:y};for(let t of d){console.log(o.yellow(`\u{1F50E} Auditing: ${t}`));try{let a=await h(t,e);r.push({url:t,result:a}),console.log(o.green(`\u2705 Completed audit for ${t}
|
|
8
|
+
`))}catch(a){a instanceof Error&&a.message&&console.log(o.red(`\u274C Failed auditing ${t}: ${a.message}`))}}}finally{await y.close()}if(!r.some(e=>e.result&&e.result.violations&&e.result.violations.length>0)){let e=r.filter(t=>t.result).length;e===0&&(console.log(o.red("\u274C No pages were successfully audited.")),process.exit(1)),console.log(o.green(`
|
|
9
|
+
\u{1F389} Great news! No static accessibility violations found!`)),console.log(o.gray(` Audited ${e} page${e>1?"s":""} successfully.
|
|
10
|
+
`)),R("audit"),await C("audit",process.cwd()),console.log(o.dim(`
|
|
11
|
+
`+"\u2500".repeat(60))),console.log(o.cyan("\u{1F499} Found aria-ease helpful?")),console.log(o.white(" \u2022 Star us on GitHub: ")+o.blue.underline("https://github.com/aria-ease/aria-ease")),console.log(o.white(" \u2022 Share feedback: ")+o.blue.underline("https://github.com/aria-ease/aria-ease/discussions")),console.log(o.dim("\u2500".repeat(60)+`
|
|
12
|
+
`)),process.exit(0)}async function b(e){let t=f(r,e),a=s.audit?.output&&s.audit.output.out||n.out;await A.ensureDir(a);let g=new Date,p=N=>String(N).padStart(2,"0"),L=`ariaease-report-${`${p(g.getDate())}-${p(g.getMonth()+1)}-${g.getFullYear()} ${p(g.getHours())}h ${p(g.getMinutes())}m ${p(g.getSeconds())}s`}.${e}`,v=w.join(a,L);await A.writeFile(v,t,"utf-8"),console.log(o.magentaBright(`\u{1F4C1} Report saved to ${v}`))}["json","csv","html"].includes(m)?await b(m):m==="all"&&await Promise.all(["json","csv","html"].map(e=>b(e)));let $=r.reduce((e,t)=>e+(t.result?.violations?.length||0),0);console.log(o.red(`
|
|
13
|
+
\u274C Accessibility violations found!`)),console.log(o.yellow(` ${$} violation${$!==1?"s":""} detected across ${r.length} page${r.length!==1?"s":""}.`)),console.log(o.gray(` Review the generated report for details.
|
|
14
|
+
`)),console.log(o.dim(`
|
|
15
|
+
`+"\u2500".repeat(60))),console.log(o.cyan("\u{1F499} Found aria-ease helpful?")),console.log(o.white(" \u2022 Star us on GitHub: ")+o.blue.underline("https://github.com/aria-ease/aria-ease")),console.log(o.white(" \u2022 Share feedback: ")+o.blue.underline("https://github.com/aria-ease/aria-ease/discussions")),console.log(o.dim("\u2500".repeat(60)+`
|
|
16
|
+
`)),process.exit(1)});l.command("test").description("Run core a11y accessibility standard tests on UI components").action(async()=>{let{runTest:n}=await import("./test-VXSCSKV5.js");n()});l.command("build").description("Build accessibility artifacts").addCommand(new S("contracts").description("Build DSL contracts to JSON").action(async()=>{let{buildContracts:n}=await import("./buildContracts-T4XQZBDU.js"),{loadConfig:h}=await import("./configLoader-ZXTSCIP6.js"),f=process.cwd(),{config:c,configPath:s,errors:u}=await h(f);s?console.log(o.green(`\u2705 Loaded config from ${w.basename(s)}
|
|
17
|
+
`)):u.length>0&&(console.log(o.red(`\u274C Config file has errors:
|
|
18
|
+
`)),u.forEach(d=>console.log(o.red(` ${d}`))),console.log(""),process.exit(1));let i=await n(f,c);!i.success&&i.errors.length>0&&(console.log(o.red(`\u274C ${i.errors.length} error${i.errors.length!==1?"s":""} occurred during build
|
|
19
|
+
`)),process.exit(1)),i.built.length===0&&(!c.contracts||c.contracts.length===0)&&process.exit(0),i.built.length===0&&(console.log(o.yellow(`\u26A0\uFE0F No contracts were built
|
|
20
|
+
`)),process.exit(1)),process.exit(0)}));l.command("help").description("Display help information").action(()=>{l.outputHelp()});l.parse(process.argv);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"./chunk-CNU4N4AY.js";import f from"path";import c from"fs-extra";function l(o){let e=[];if(!o||typeof o!="object")return e.push("Config must be an object"),{valid:!1,errors:e};let t=o;if(t.audit!==void 0){if(typeof t.audit!="object"||t.audit===null)e.push("audit must be an object");else if(t.audit.urls!==void 0&&(Array.isArray(t.audit.urls)?t.audit.urls.some(n=>typeof n!="string")&&e.push("audit.urls must contain only strings"):e.push("audit.urls must be an array")),t.audit.output!==void 0)if(typeof t.audit.output!="object")e.push("audit.output must be an object");else{let n=t.audit.output;n.format!==void 0&&(["json","csv","html","all"].includes(n.format)||e.push("audit.output.format must be one of: json, csv, html, all")),n.out!==void 0&&typeof n.out!="string"&&e.push("audit.output.out must be a string")}}if(t.test!==void 0)if(typeof t.test!="object"||t.test===null)e.push("test must be an object");else{t.test.disableTimeouts!==void 0&&typeof t.test.disableTimeouts!="boolean"&&e.push("test.disableTimeouts must be a boolean when provided");let n=["actionTimeoutMs","assertionTimeoutMs","navigationTimeoutMs","componentReadyTimeoutMs"];for(let s of n){let i=t.test[s];i!==void 0&&(typeof i!="number"||!Number.isFinite(i)||i<0)&&e.push(`test.${s} must be a non-negative number when provided`)}t.test.components!==void 0&&(Array.isArray(t.test.components)?t.test.components.forEach((s,i)=>{if(typeof s!="object"||s===null)e.push(`test.components[${i}] must be an object`);else{typeof s.name!="string"&&e.push(`test.components[${i}].name must be a string`),s.contractPath!==void 0&&typeof s.contractPath!="string"&&e.push(`test.components[${i}].contractPath must be a string when provided`),s.strategyPath!==void 0&&typeof s.strategyPath!="string"&&e.push(`test.components[${i}].strategyPath must be a string when provided`),s.strictness!==void 0&&!["minimal","balanced","strict","paranoid"].includes(s.strictness)&&e.push(`test.components[${i}].strictness must be one of: minimal, balanced, strict, paranoid`),s.disableTimeouts!==void 0&&typeof s.disableTimeouts!="boolean"&&e.push(`test.components[${i}].disableTimeouts must be a boolean when provided`);let u=["actionTimeoutMs","assertionTimeoutMs","navigationTimeoutMs","componentReadyTimeoutMs"];for(let r of u){let a=s[r];a!==void 0&&(typeof a!="number"||!Number.isFinite(a)||a<0)&&e.push(`test.components[${i}].${r} must be a non-negative number when provided`)}}}):e.push("test.components must be an array")),t.test.strictness!==void 0&&(["minimal","balanced","strict","paranoid"].includes(t.test.strictness)||e.push("test.strictness must be one of: minimal, balanced, strict, paranoid"))}return t.contracts!==void 0&&(Array.isArray(t.contracts)?t.contracts.forEach((n,s)=>{typeof n!="object"||n===null?e.push(`contracts[${s}] must be an object`):(typeof n.src!="string"&&e.push(`contracts[${s}].src is required and must be a string`),n.out!==void 0&&typeof n.out!="string"&&e.push(`contracts[${s}].out must be a string`))}):e.push("contracts must be an array")),{valid:e.length===0,errors:e}}async function d(o){try{let e=f.extname(o);if(e===".json"){let t=await c.readFile(o,"utf-8");return JSON.parse(t)}else if([".js",".mjs",".cjs",".ts"].includes(e)){let t=await import(o);return t.default||t}return null}catch{return null}}async function b(o=process.cwd()){let e=["ariaease.config.js","ariaease.config.mjs","ariaease.config.cjs","ariaease.config.json","ariaease.config.ts"],t=null,n=null,s=[];for(let i of e){let u=f.resolve(o,i);if(await c.pathExists(u)){if(n=u,t=await d(u),t===null){s.push(`Found config at ${i} but failed to load it. Check for syntax errors.`);continue}let r=l(t);if(!r.valid){s.push(`Config validation failed in ${i}:`),s.push(...r.errors.map(a=>` - ${a}`)),t=null;continue}break}}return{config:t||{},configPath:t?n:null,errors:s}}export{b as loadConfig};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{a}from"./chunk-ZNQ5BXVJ.js";import"./chunk-CNU4N4AY.js";export{a as loadConfig};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import{a as q}from"./chunk-BHNO4ZI3.js";import{a as Te,c as X,d as Se,e as ke}from"./chunk-APUMBDOT.js";import{a as Y}from"./chunk-SM6ZKEDR.js";import"./chunk-CNU4N4AY.js";import{readFileSync as de}from"fs";import he from"path";import{readFileSync as fe}from"fs";import ge from"path";import Ce from"path";import{pathToFileURL as Pe}from"url";var ie=class{builtInStrategies=new Map;constructor(){this.registerBuiltInStrategies()}registerBuiltInStrategies(){this.builtInStrategies.set("menu",()=>import("./MenuComponentStrategy-C22BZEBH.js").then(t=>t.MenuComponentStrategy)),this.builtInStrategies.set("accordion",()=>import("./AccordionComponentStrategy-2SWMNUR6.js").then(t=>t.AccordionComponentStrategy)),this.builtInStrategies.set("combobox",()=>import("./ComboboxComponentStrategy-YSYLR2U5.js").then(t=>t.ComboboxComponentStrategy)),this.builtInStrategies.set("tabs",()=>import("./TabsComponentStrategy-ADEEFJXM.js").then(t=>t.TabsComponentStrategy))}async loadStrategy(t,s,r){try{if(s)try{let a=Ce.isAbsolute(s)?s:Ce.resolve(r||process.cwd(),s),l=await import(Pe(a).href),n=l.default||l;if(!n)throw new Error(`No default export found in ${s}`);return n}catch(a){throw new Error(`Failed to load custom strategy from ${s}: ${a instanceof Error?a.message:String(a)}`)}let i=this.builtInStrategies.get(t);return i?i():null}catch(i){throw new Error(`Strategy loading failed for ${t}: ${i instanceof Error?i.message:String(i)}`)}}has(t,s){return!!s||this.builtInStrategies.has(t)}};var ne=class{static strategyRegistry=new ie;static isComponentConfig(t){return typeof t=="object"&&t!==null}static async detect(t,s,r=400,i=400,a){let l=this.isComponentConfig(s)?s:void 0,n=l?.contractPath;if(!n)throw new Error(`Contract path not found for component: ${t}`);let R=(()=>{if(ge.isAbsolute(n))return n;if(a){let se=ge.resolve(a,n);try{return fe(se,"utf-8"),se}catch{}}let z=ge.resolve(process.cwd(),n);try{return fe(z,"utf-8"),z}catch{return new URL(n,import.meta.url).pathname}})(),A=fe(R,"utf-8"),N=JSON.parse(A).selectors,_=await this.strategyRegistry.loadStrategy(t,l?.strategyPath,a);if(!_)return null;let J=N.main;return t==="tabs"?new _(J,N):new _(J,N,r,i)}};var oe=class{startTime=0;componentName="";staticPasses=0;staticFailures=0;staticWarnings=0;dynamicResults=[];totalTests=0;skipped=0;warnings=0;isPlaywright=!1;isCustomContract=!1;apgUrl="https://www.w3.org/WAI/ARIA/apg/";hasPrintedStaticSection=!1;hasPrintedDynamicSection=!1;constructor(t=!1,s=!1){this.isPlaywright=t,this.isCustomContract=s}log(t){process.stderr.write(t+`
|
|
2
|
+
`)}start(t,s,r){this.startTime=Date.now(),this.componentName=t,this.totalTests=s,this.hasPrintedStaticSection=!1,this.hasPrintedDynamicSection=!1,r&&(this.apgUrl=r);let i="Playwright (Real Browser)";this.log(`
|
|
3
|
+
${"\u2550".repeat(60)}`),this.log(`\u{1F50D} Testing ${t.charAt(0).toUpperCase()+t.slice(1)} Component - ${i}`),this.log(`${"\u2550".repeat(60)}
|
|
4
|
+
`)}reportStatic(t,s,r=0){this.staticPasses=t,this.staticFailures=s,this.staticWarnings=r}reportStaticTest(t,s,r,i){this.hasPrintedStaticSection||(this.log(`${"\u2500".repeat(60)}`),this.log("\u{1F9EA} Static Assertions"),this.log(`${"\u2500".repeat(60)}`),this.hasPrintedStaticSection=!0);let a=s==="pass"?"\u2713":s==="warn"?"\u26A0":s==="skip"?"\u25CB":"\u2717";this.log(` ${a} ${t}`),i&&this.log(` \u21B3 level=${i}`),(s==="fail"||s==="warn"||s==="skip")&&r&&this.log(` \u21B3 ${r}`)}reportTest(t,s,r){this.hasPrintedDynamicSection||(this.log(""),this.log(`${"\u2500".repeat(60)}`),this.log("\u2328\uFE0F Dynamic Interaction Tests"),this.log(`${"\u2500".repeat(60)}`),this.hasPrintedDynamicSection=!0);let i={description:t.description,status:s,failureMessage:r,level:t.level};s==="skip"&&(i.skipReason="Requires real browser (addEventListener events)"),this.dynamicResults.push(i);let a={pass:"\u2713",fail:"\u2717",warn:"\u26A0",skip:"\u25CB"},l=t.level?`[${t.level.toUpperCase()}] `:"";this.log(` ${a[s]} ${l}${t.description}`),s==="skip"&&!this.isPlaywright&&this.log(" \u21B3 Skipped (runs only in Playwright)"),s==="fail"&&r&&this.log(` \u21B3 ${r}`),s==="warn"&&r&&this.log(` \u21B3 ${r}`),s==="skip"&&r&&this.log(` \u21B3 ${r}`)}reportFailures(t){t.length!==0&&(this.log(`
|
|
5
|
+
${"\u2500".repeat(60)}`),this.log(`\u274C Failures (${t.length}):
|
|
6
|
+
`),t.forEach((s,r)=>{this.log(`${r+1}. ${s}`),s.includes("aria-")?this.log(" \u{1F4A1} Add the missing ARIA attribute to improve screen reader support"):s.includes("focus")?this.log(" \u{1F4A1} Check keyboard event handlers and focus management"):s.includes("visible")&&this.log(" \u{1F4A1} Verify display/visibility styles and state management"),this.log("")}))}reportWarnings(){let t=this.dynamicResults.filter(s=>s.status==="warn");t.length===0&&this.staticWarnings===0||(this.log(`
|
|
7
|
+
${"\u2500".repeat(60)}`),this.log(`\u26A0\uFE0F Warnings (${this.staticWarnings+t.length}):
|
|
8
|
+
`),this.log(`These checks are failing but treated as warnings under the active strictness mode.
|
|
9
|
+
`),t.forEach((s,r)=>{this.log(`${r+1}. ${s.description}`),s.failureMessage&&this.log(` \u21B3 ${s.failureMessage}`),s.level&&this.log(` \u21B3 level=${s.level}`)}),this.apgUrl&&this.log(`
|
|
10
|
+
Reference: ${this.apgUrl}
|
|
11
|
+
`))}reportSkipped(){if(this.skipped===0||this.isPlaywright)return;let t=this.dynamicResults.filter(s=>s.status==="skip");this.log(`
|
|
12
|
+
${"\u2500".repeat(60)}`),this.log(`\u2139\uFE0F Skipped Tests (${this.skipped}):
|
|
13
|
+
`),this.log("These tests use native keyboard events via addEventListener,"),this.log(`which only run in Playwright (real browser).
|
|
14
|
+
`),t.forEach((s,r)=>{this.log(`${r+1}. ${s.description}`)}),this.log(`
|
|
15
|
+
\u{1F4A1} Run with Playwright for full validation:`),this.log(` testUiComponent('${this.componentName}', null, 'http://localhost:5173/test-harness?component=component_name')
|
|
16
|
+
`)}summary(t){let s=Date.now()-this.startTime,r=this.dynamicResults.filter(M=>M.status==="pass").length,i=this.dynamicResults.filter(M=>M.status==="fail").length,a=this.dynamicResults.filter(M=>M.status==="warn").length;this.skipped=this.dynamicResults.filter(M=>M.status==="skip").length,this.warnings=this.staticWarnings+a;let l=this.staticPasses+r,n=this.staticFailures+i,R=l+n+this.warnings,A=()=>{let M=`${this.componentName.charAt(0).toUpperCase()}${this.componentName.slice(1)}`;return this.isCustomContract?`${M} component validates against your custom accessibility policy \u2713`:`${M} component meets Aria-Ease baseline WAI-ARIA expectations \u2713`};return t.length>0&&this.reportFailures(t),this.reportWarnings(),this.reportSkipped(),this.log(`
|
|
17
|
+
${"\u2550".repeat(60)}`),this.log(`\u{1F4CA} Summary
|
|
18
|
+
`),n===0&&this.skipped===0&&this.warnings===0?(this.log(`\u2705 All ${R} tests passed!`),this.log(` ${A()}`)):n===0?(this.log(`\u2705 ${l}/${R} tests passed`),this.skipped>0&&this.log(`\u25CB ${this.skipped} tests skipped`),this.warnings>0&&this.log(`\u26A0\uFE0F ${this.warnings} warning${this.warnings>1?"s":""}`),this.log(` ${A()}`)):(this.log(`\u274C ${n} test${n>1?"s":""} failed`),this.log(`\u2705 ${l} test${l>1?"s":""} passed`),this.warnings>0&&this.log(`\u26A0\uFE0F ${this.warnings} warning${this.warnings>1?"s":""}`),this.skipped>0&&this.log(`\u25CB ${this.skipped} test${this.skipped>1?"s":""} skipped`)),this.log(`\u23F1\uFE0F Duration: ${s}ms`),this.log(`${"\u2550".repeat(60)}
|
|
19
|
+
`),n>0?(this.log("\u{1F527} Next Steps:"),this.log(" 1. Review the failures above"),this.log(" 2. Fix ARIA attributes and keyboard handlers"),this.log(` 3. Re-run tests to verify fixes
|
|
20
|
+
`)):!this.isPlaywright&&this.skipped>0&&this.log(`\u2728 Optional: Run Playwright tests for complete validation
|
|
21
|
+
`),{passes:l,failures:n,skipped:this.skipped,duration:s}}error(t,s){this.log(`
|
|
22
|
+
\u274C Error: ${t}`),s&&this.log(` Context: ${s}`),this.log("")}};var Z=class{constructor(t,s,r=400){this.page=t;this.selectors=s;this.timeoutMs=r}isBrowserClosedError(t){return t instanceof Error&&t.message.includes("Target page, context or browser has been closed")}async focus(t,s,r){try{if(t==="virtual"&&r){let a=this.selectors.main;if(!a)return{success:!1,error:"Main selector not defined for virtual focus."};let l=this.page.locator(a).first();return await l.count()?(await l.evaluate((R,A)=>{R.setAttribute("aria-activedescendant",A)},r),{success:!0}):{success:!1,error:"Main element not found for virtual focus."}}if(t==="relative"&&s){let a=this.selectors.relative;if(!a)return{success:!1,error:"Relative selector not defined for focus action."};let l=await q.resolve(this.page,a,s);return l?(await l.focus({timeout:this.timeoutMs}),{success:!0}):{success:!1,error:`Could not resolve relative target ${s} for focus.`}}let i=this.selectors[t];return i?(await this.page.locator(i).first().focus({timeout:this.timeoutMs}),{success:!0}):{success:!1,error:`Selector for focus target ${t} not found.`}}catch(i){return this.isBrowserClosedError(i)?{success:!1,error:"CRITICAL: Browser/page closed during test execution. Remaining actions skipped.",shouldBreak:!0}:{success:!1,error:`Failed to focus ${t}: ${i instanceof Error?i.message:String(i)}`}}}async type(t,s){try{let r=this.selectors[t];return r?(await this.page.locator(r).first().fill(s,{timeout:this.timeoutMs}),{success:!0}):{success:!1,error:`Selector for type target ${t} not found.`}}catch(r){return this.isBrowserClosedError(r)?{success:!1,error:"CRITICAL: Browser/page closed during test execution. Remaining actions skipped.",shouldBreak:!0}:{success:!1,error:`Failed to type into ${t}: ${r instanceof Error?r.message:String(r)}`}}}async click(t,s){try{if(t==="document")return await this.page.mouse.click(10,10),{success:!0};if(t==="relative"&&s){let i=this.selectors.relative;if(!i)return{success:!1,error:"Relative selector not defined for click action."};let a=await q.resolve(this.page,i,s);return a?(await a.click({timeout:this.timeoutMs}),{success:!0}):{success:!1,error:`Could not resolve relative target ${s} for click.`}}let r=this.selectors[t];return r?(await this.page.locator(r).first().click({timeout:this.timeoutMs}),{success:!0}):{success:!1,error:`Selector for action target ${t} not found.`}}catch(r){return this.isBrowserClosedError(r)?{success:!1,error:"CRITICAL: Browser/page closed during test execution. Remaining actions skipped.",shouldBreak:!0}:{success:!1,error:`Failed to click ${t}: ${r instanceof Error?r.message:String(r)}`}}}async keypress(t,s){try{let i={Space:"Space",Enter:"Enter",Escape:"Escape","Arrow Up":"ArrowUp","Arrow Down":"ArrowDown","Arrow Left":"ArrowLeft","Arrow Right":"ArrowRight",Home:"Home",End:"End",Tab:"Tab"}[s]||s;if(i==="Space"?i=" ":i.includes(" ")&&(i=i.replace(/ /g,"")),t==="focusable"&&["ArrowUp","ArrowDown","ArrowLeft","ArrowRight","Escape","Home","End","Tab","Shift+Tab"].includes(i))return await this.page.keyboard.press(i),{success:!0};let a=this.selectors[t];if(!a)return{success:!1,error:`Selector for keypress target ${t} not found.`};let l=this.page.locator(a).first();return await l.count()===0?{success:!1,error:`${t} element not found.`,shouldBreak:!0}:(await l.press(i,{timeout:this.timeoutMs}),{success:!0})}catch(r){return this.isBrowserClosedError(r)?{success:!1,error:"CRITICAL: Browser/page closed during test execution. Remaining actions skipped.",shouldBreak:!0}:{success:!1,error:`Failed to press ${s} on ${t}: ${r instanceof Error?r.message:String(r)}`}}}async hover(t,s){try{if(t==="relative"&&s){let i=this.selectors.relative;if(!i)return{success:!1,error:"Relative selector not defined for hover action."};let a=await q.resolve(this.page,i,s);return a?(await a.hover({timeout:this.timeoutMs}),{success:!0}):{success:!1,error:`Could not resolve relative target ${s} for hover.`}}let r=this.selectors[t];return r?(await this.page.locator(r).first().hover({timeout:this.timeoutMs}),{success:!0}):{success:!1,error:`Selector for hover target ${t} not found.`}}catch(r){return this.isBrowserClosedError(r)?{success:!1,error:"CRITICAL: Browser/page closed during test execution. Remaining actions skipped.",shouldBreak:!0}:{success:!1,error:`Failed to hover ${t}: ${r instanceof Error?r.message:String(r)}`}}}};var te=class{constructor(t,s,r=400){this.page=t;this.selectors=s;this.timeoutMs=r}async resolveTarget(t,s,r){try{if(t==="relative"){let a=r?this.selectors[r]:this.selectors.relative;if(!a)return{target:null,error:"Relative selector is not defined in the contract."};if(!s)return{target:null,error:"Relative target or expected value is not defined."};let l=await q.resolve(this.page,a,s);return l?{target:l}:{target:null,error:`Target ${t} not found.`}}let i=this.selectors[t];return i?{target:this.page.locator(i).first()}:{target:null,error:`Selector for assertion target ${t} not found.`}}catch(i){return{target:null,error:`Failed to resolve target ${t}: ${i instanceof Error?i.message:String(i)}`}}}async validateVisibility(t,s,r,i,a){try{return r?(await(0,Y.expect)(t).toBeVisible({timeout:this.timeoutMs}),{success:!0,passMessage:`${s} is visible as expected. Test: "${a}".`}):(await(0,Y.expect)(t).toBeHidden({timeout:this.timeoutMs}),{success:!0,passMessage:`${s} is not visible as expected. Test: "${a}".`})}catch{let l=this.selectors[s]||"",n=await this.page.evaluate(R=>{let A=R?document.querySelector(R):null;if(!A)return"element not found";let M=window.getComputedStyle(A);return`display:${M.display}, visibility:${M.visibility}, opacity:${M.opacity}`},l);return r?{success:!1,failMessage:`${i} (actual: ${n})`}:{success:!1,failMessage:`${i} ${s} is still visible (actual: ${n}).`}}}async validateAttribute(t,s,r,i,a,l){if(i==="!empty"){let A=await t.getAttribute(r);return A&&A.trim()!==""?{success:!0,passMessage:`${s} has non-empty "${r}". Test: "${l}".`}:{success:!1,failMessage:`${a} ${s} "${r}" should not be empty, found "${A}".`}}if(typeof i!="string")throw console.error("[AssertionRunner] expectedValue is not a string:",i),new Error(`AssertionRunner: expectedValue for attribute assertion must be a string, but got: ${JSON.stringify(i)}`);let n=i.split(" | ").map(A=>A.trim()),R=await t.getAttribute(r);return R!==null&&n.includes(R)?{success:!0,passMessage:`${s} has expected "${r}". Test: "${l}".`}:{success:!1,failMessage:`${a} ${s} "${r}" should be "${i}", found "${R}".`}}async validateValue(t,s,r,i,a){let l=await t.inputValue().catch(()=>"");return r==="!empty"?l&&l.trim()!==""?{success:!0,passMessage:`${s} has non-empty value. Test: "${a}".`}:{success:!1,failMessage:`${i} ${s} value should not be empty, found "${l}".`}:r===""?l===""?{success:!0,passMessage:`${s} has empty value. Test: "${a}".`}:{success:!1,failMessage:`${i} ${s} value should be empty, found "${l}".`}:l===r?{success:!0,passMessage:`${s} has expected value. Test: "${a}".`}:{success:!1,failMessage:`${i} ${s} value should be "${r}", found "${l}".`}}async validateFocus(t,s,r,i,a){try{return r?(await(0,Y.expect)(t).toBeFocused({timeout:this.timeoutMs}),{success:!0,passMessage:`${s} has focus as expected. Test: "${a}".`}):(await(0,Y.expect)(t).not.toBeFocused({timeout:this.timeoutMs}),{success:!0,passMessage:`${s} does not have focus as expected. Test: "${a}".`})}catch{let l=await this.page.evaluate(()=>{let n=document.activeElement;return n?`${n.tagName}#${n.id||"no-id"}.${n.className||"no-class"}`:"no element focused"});return{success:!1,failMessage:`${i} (actual focus: ${l})`}}}async validateRole(t,s,r,i,a){let l=await t.getAttribute("role");return l===r?{success:!0,passMessage:`${s} has role "${r}". Test: "${a}".`}:{success:!1,failMessage:`${i} Expected role "${r}", found "${l}".`}}async validate(t,s){if(this.page.isClosed())return{success:!1,failMessage:"CRITICAL: Browser/page closed before completing all tests. Increase test timeout or reduce test complexity."};let{target:r,error:i}=await this.resolveTarget(t.target,t.relativeTarget||t.expectedValue,t.selectorKey);if(i||!r)return{success:!1,failMessage:i||`Target ${t.target} not found.`,target:null};if(t.target==="input"&&t.attribute==="aria-activedescendant"&&t.expectedValue==="!empty"&&t.relativeTarget&&t.selectorKey){let a=await q.resolve(this.page,this.selectors[t.selectorKey],t.relativeTarget),l=a?await a.getAttribute("id"):null,n=await r.getAttribute("aria-activedescendant");return l&&n===l?{success:!0,passMessage:`input[aria-activedescendant] matches id of ${t.relativeTarget}(${t.selectorKey}). Test: "${s}".`}:{success:!1,failMessage:`input[aria-activedescendant] should match id of ${t.relativeTarget}(${t.selectorKey}), found "${n}".`}}switch(t.assertion){case"toBeVisible":return this.validateVisibility(r,t.target,!0,t.failureMessage||"",s);case"notToBeVisible":return this.validateVisibility(r,t.target,!1,t.failureMessage||"",s);case"toHaveAttribute":return t.attribute&&t.expectedValue!==void 0?this.validateAttribute(r,t.target,t.attribute,t.expectedValue,t.failureMessage||"",s):{success:!1,failMessage:"Missing attribute or expectedValue for toHaveAttribute assertion"};case"toHaveValue":return t.expectedValue!==void 0?this.validateValue(r,t.target,t.expectedValue,t.failureMessage||"",s):{success:!1,failMessage:"Missing expectedValue for toHaveValue assertion"};case"toHaveFocus":return this.validateFocus(r,t.target,!0,t.failureMessage||"",s);case"notToHaveFocus":return this.validateFocus(r,t.target,!1,t.failureMessage||"",s);case"toHaveRole":return t.expectedValue!==void 0?this.validateRole(r,t.target,t.expectedValue,t.failureMessage||"",s):{success:!1,failMessage:"Missing expectedValue for toHaveRole assertion"};default:return{success:!1,failMessage:`Unknown assertion type: ${t.assertion}`}}}};async function tt(C,t,s,r,i){let a=r?.test?.components?.find(d=>d.name===C),l=!!a?.contractPath,n=new oe(!0,l),R={actionTimeoutMs:400,assertionTimeoutMs:400,navigationTimeoutMs:3e4,componentReadyTimeoutMs:5e3},A=r?.test?.disableTimeouts===!0,N=a?.disableTimeouts===!0||A,_=(d,W,O)=>{if(N)return 0;let L=d??W;return typeof L!="number"||!Number.isFinite(L)||L<0?O:L},J=_(a?.actionTimeoutMs,r?.test?.actionTimeoutMs,R.actionTimeoutMs),z=_(a?.assertionTimeoutMs,r?.test?.assertionTimeoutMs,R.assertionTimeoutMs),se=_(a?.navigationTimeoutMs,r?.test?.navigationTimeoutMs,R.navigationTimeoutMs),ae=_(a?.componentReadyTimeoutMs,r?.test?.componentReadyTimeoutMs,R.componentReadyTimeoutMs),me=Se(s),G=a?.contractPath;if(!G)throw new Error(`Contract path not found for component: ${C}`);let Re=(()=>{if(he.isAbsolute(G))return G;if(i){let W=he.resolve(i,G);try{return de(W,"utf-8"),W}catch{}}let d=he.resolve(process.cwd(),G);try{return de(d,"utf-8"),d}catch{return new URL(G,import.meta.url).pathname}})(),Ae=de(Re,"utf-8"),$=JSON.parse(Ae),Me=($.relationships?.length||0)+($.static[0]?.assertions.length||0)+$.dynamic.length,Ee=$.meta?.source?.apg,U=[],j=[],K=[],x=[],w=null,E=(d,W)=>{let O=X(W),L=ke(O,me);if(L==="error")return U.push(d),{status:"fail",level:O,detail:d};if(L==="warning")return j.push(d),{status:"warn",level:O,detail:d};let H=`${d} (ignored by strictness=${me}, level=${O})`;return x.push(H),{status:"skip",level:O,detail:H}};try{if(w=await Te(),t){try{await w.goto(t,{waitUntil:"domcontentloaded",timeout:se})}catch(e){throw new Error(`Failed to navigate to ${t}. Ensure dev server is running and accessible. Original error: ${e instanceof Error?e.message:String(e)}`)}await w.addStyleTag({content:"* { transition: none !important; animation: none !important; }"})}let d=await ne.detect(C,a,J,z,i);if(!d)throw new Error(`Unsupported component: ${C}`);let W=d.getMainSelector();if(!W)throw new Error(`CRITICAL: No selector found in contract for ${C}`);try{await w.locator(W).first().waitFor({state:"attached",timeout:ae})}catch(e){throw new Error(`
|
|
23
|
+
\u274C CRITICAL: Component not found on page!
|
|
24
|
+
This usually means:
|
|
25
|
+
- The component didn't render
|
|
26
|
+
- The URL is incorrect
|
|
27
|
+
- The component selector '${W}' in the contract is wrong
|
|
28
|
+
- Original error: ${e}`)}n.start(C,Me,Ee),C==="menu"&&$.selectors.main&&await w.locator($.selectors.main).first().waitFor({state:"visible",timeout:ae}).catch(()=>{});let O=C==="menu"&&$.selectors.submenuTrigger?await w.locator($.selectors.submenuTrigger).count()>0:!1,L=e=>e.type==="aria-reference"&&[e.from,e.to].some(u=>["submenu","submenuTrigger","submenuItems"].includes(u||""))||e.type==="contains"&&[e.parent,e.child].some(u=>["submenu","submenuTrigger","submenuItems"].includes(u||"")),H=0,P=0,I=0;for(let e of $.relationships||[]){if(d&&typeof d.resetState=="function")try{await d.resetState(w)}catch(g){j.push(`Warning: resetState failed before relationship test: ${g instanceof Error?g.message:String(g)}`)}let u=X(e.level);if(Array.isArray(e.setup)&&e.setup.length>0){let b=function(o){return v.includes(o)};var ye=b;let g=new Z(w,$.selectors,J),T=e.type==="aria-reference"?`${e.from}.${e.attribute} references ${e.to}`:`${e.parent} contains ${e.child}`,v=["focus","type","click","keypress","hover"],m=o=>({...o,type:b(o.type)?o.type:"click"}),y=e.setup.map(m),p=await ce(y,g,d,w,T,["submenu","submenuTrigger","submenuItems"]);if(p.skip){x.push(p.message||"Setup action skipped"),n.reportStaticTest(T,"skip",p.message,u);continue}if(!p.success){let o=`Relationship setup failed: ${p.error}`,f=E(o,e.level);f.status==="fail"&&(P+=1),f.status==="warn"&&(I+=1),n.reportStaticTest(T,f.status,f.detail,f.level);continue}}if(C==="menu"&&!O&&L(e)){let T=e.type==="aria-reference"?`${e.from}.${e.attribute} references ${e.to}`:`${e.parent} contains ${e.child}`,v="Skipping submenu relationship assertion: no submenu capability detected in rendered component.";x.push(v),n.reportStaticTest(T,"skip",v,u);continue}if(e.type==="aria-reference"){let g=`${e.from}.${e.attribute} references ${e.to}`,T=$.selectors[e.from],v=$.selectors[e.to];if(!T||!v){let h=E(`Relationship selector missing: from="${e.from}" or to="${e.to}" not found in selectors.`,e.level);h.status==="fail"&&(P+=1),h.status==="warn"&&(I+=1),n.reportStaticTest(g,h.status,h.detail,h.level);continue}let b=w.locator(T).first(),m=w.locator(v).first(),y=await b.count()>0,p=await m.count()>0;if(!y||!p){if(C==="menu"&&L(e)){let ee="Skipping submenu relationship assertion in static phase: submenu elements are not present until submenu is opened.";x.push(ee),n.reportStaticTest(g,"skip",ee,u);continue}let h=E(`Relationship target not found: ${y?e.to:e.from}.`,e.level);h.status==="fail"&&(P+=1),h.status==="warn"&&(I+=1),n.reportStaticTest(g,h.status,h.detail,h.level);continue}let o=await b.getAttribute(e.attribute),f=await m.getAttribute("id");if(!f){let h=E(`Relationship target "${e.to}" must have an id for ${e.attribute} validation.`,e.level);h.status==="fail"&&(P+=1),h.status==="warn"&&(I+=1),n.reportStaticTest(g,h.status,h.detail,h.level);continue}if(!(o||"").split(/\s+/).filter(Boolean).includes(f)){let h=E(`Expected ${e.from} ${e.attribute} to reference id "${f}", found "${o||""}".`,e.level);h.status==="fail"&&(P+=1),h.status==="warn"&&(I+=1),n.reportStaticTest(g,h.status,h.detail,h.level);continue}K.push(`Relationship valid: ${e.from}.${e.attribute} -> ${e.to} (id=${f}).`),H+=1,n.reportStaticTest(g,"pass",void 0,u);continue}if(e.type==="contains"){let g=`${e.parent} contains ${e.child}`,T=$.selectors[e.parent],v=$.selectors[e.child];if(!T||!v){let o=E(`Relationship selector missing: parent="${e.parent}" or child="${e.child}" not found in selectors.`,e.level);o.status==="fail"&&(P+=1),o.status==="warn"&&(I+=1),n.reportStaticTest(g,o.status,o.detail,o.level);continue}let b=w.locator(T).first();if(!(await b.count()>0)){if(C==="menu"&&L(e)){let f="Skipping submenu relationship assertion in static phase: submenu container is not present until submenu is opened.";x.push(f),n.reportStaticTest(g,"skip",f,u);continue}let o=E(`Relationship parent target not found: ${e.parent}.`,e.level);o.status==="fail"&&(P+=1),o.status==="warn"&&(I+=1),n.reportStaticTest(g,o.status,o.detail,o.level);continue}if(await b.locator(v).count()<1){if(C==="menu"&&L(e)){let f="Skipping submenu relationship assertion in static phase: submenu descendants are not present until submenu is opened.";x.push(f),n.reportStaticTest(g,"skip",f,u);continue}let o=E(`Expected ${e.parent} to contain descendant matching selector for ${e.child}.`,e.level);o.status==="fail"&&(P+=1),o.status==="warn"&&(I+=1),n.reportStaticTest(g,o.status,o.detail,o.level);continue}K.push(`Relationship valid: ${e.parent} contains ${e.child}.`),H+=1,n.reportStaticTest(g,"pass",void 0,u)}}async function ve(e,u,g,T={}){if(!e||typeof e!="object"||!("ref"in e))return e;let v;if(e.ref==="relative"){if(!e.relativeTarget||!T.relativeBaseSelector)return;let b=g.locator(T.relativeBaseSelector),m=await b.count(),y=0;if(e.relativeTarget==="first"?y=0:e.relativeTarget==="second"?y=1:e.relativeTarget==="last"?y=m-1:isNaN(Number(e.relativeTarget))?y=0:y=Number(e.relativeTarget),y<0||y>=m)return;let p=b.nth(y);return await we(p,e.property||e.attribute)}else{if(v=u[e.ref],!v)throw new Error(`Selector for ref '${e.ref}' not found in contract selectors.`);let b=g.locator(v).first();return await we(b,e.property||e.attribute)}}async function we(e,u){if(e)return!u||u==="id"?await e.getAttribute("id")??void 0:u==="class"?await e.getAttribute("class")??void 0:u==="textContent"?await e.evaluate(g=>g.textContent??void 0):u.startsWith("aria-")?await e.getAttribute(u)??void 0:u.endsWith("*")?await e.evaluate(T=>{let v=[];for(let b of Array.from(T.attributes))b.name.startsWith("aria-")&&v.push(`${b.name}=${b.value}`);return v.join(";")}):await e.getAttribute(u)??void 0}let xe=new te(w,$.selectors,z);async function ce(e,u,g,T,v,b=[]){if(!Array.isArray(e)||e.length===0)return{success:!0};g&&typeof g.resetState=="function"&&await g.resetState(T);for(let m of e){let y={success:!0};try{if(m.type==="focus")m.target==="relative"&&m.relativeTarget?y=await u.focus("relative",m.relativeTarget):y=await u.focus(m.target);else if(m.type==="type"&&m.value)y=await u.type(m.target,m.value);else if(m.type==="click")y=await u.click(m.target,m.relativeTarget);else if(m.type==="keypress"&&m.key)y=await u.keypress(m.target,m.key);else if(m.type==="hover")y=await u.hover(m.target,m.relativeTarget);else continue}catch(p){y={success:!1,error:p instanceof Error?p.message:String(p)}}if(!y.success){let p=y.error||"Setup action failed";return b.some(f=>v.includes(f)||p.includes(f))?{success:!1,skip:!0,message:`Skipping test - capability not present: ${p}`}:{success:!1,error:p}}}return{success:!0}}for(let e of $.static[0]?.assertions||[]){if(d&&typeof d.resetState=="function")try{await d.resetState(w)}catch(p){j.push(`Warning: resetState failed before static test: ${p instanceof Error?p.message:String(p)}`)}if(e.target==="relative")continue;let u=`${e.target}${e.attribute?` (${e.attribute})`:""}`,g=X(e.level);if(C==="menu"&&e.target==="submenuTrigger"&&!O){let p=`Skipping submenu static assertion for ${e.target}: no submenu capability detected in rendered component.`;x.push(p),n.reportStaticTest(u,"skip",p,g);continue}if(Array.isArray(e.setup)&&e.setup.length>0){let f=function(D){return o.includes(D)};var ye=f;let p=new Z(w,$.selectors,J),o=["focus","type","click","keypress","hover"],B=D=>({...D,type:f(D.type)?D.type:"click"}),k=e.setup.map(B),h=await ce(k,p,d,w,u,["submenu","submenuTrigger","submenuItems"]);if(h.skip){x.push(h.message||"Setup action skipped"),n.reportStaticTest(u,"skip",h.message,g);continue}if(!h.success){let D=`Static setup failed: ${h.error}`,c=E(D,e.level);c.status==="fail"&&(P+=1),c.status==="warn"&&(I+=1),n.reportStaticTest(u,c.status,c.detail,c.level);continue}}let T=$.selectors[e.target];if(!T){let p=`Selector for target ${e.target} not found.`,o=E(p,e.level);o.status==="fail"&&(P+=1),o.status==="warn"&&(I+=1),n.reportStaticTest(u,o.status,o.detail,o.level);continue}let v=w.locator(T).first();if(!(await v.count()>0)){let p=`Target ${e.target} not found.`,o=E(p,e.level);o.status==="fail"&&(P+=1),o.status==="warn"&&(I+=1),n.reportStaticTest(u,o.status,o.detail,o.level);continue}let m=(p,o,f)=>{let B=new RegExp(`\\[${o}(?:=["']?([^\\]"']+)["']?)?\\]`),k=p.match(B);if(!k)return!1;if(!f)return!0;let h=k[1];return h?f.split(" | ").includes(h):!1},y=e.expectedValue;if(e.expectedValue&&typeof e.expectedValue=="object"&&"ref"in e.expectedValue){let p={},o=e.relativeTarget;if(e.expectedValue.ref==="relative"&&e.target==="relative"&&o){let f=$.selectors[o];if(!f)throw new Error(`Selector for relativeTarget '${o}' not found in contract selectors.`);p.relativeBaseSelector=f}else if(e.expectedValue.ref==="relative"&&o){let f=$.selectors[o];if(!f)throw new Error(`Selector for relativeTarget '${o}' not found in contract selectors.`);p.relativeBaseSelector=f}y=await ve(e.expectedValue,$.selectors,w,p),console.log("Expected value in static check",y)}if(e.expectedValue)if(m(T,e.attribute,typeof y=="string"?y:void 0))K.push(`${e.attribute}="${y}" on ${e.target} verified by selector (already present in: ${T}).`),H+=1,n.reportStaticTest(u,"pass",void 0,g);else{let p=y??"",o=await xe.validateAttribute(v,e.target,e.attribute,p,e.failureMessage,"Static ARIA Test");if(o.success&&o.passMessage)K.push(o.passMessage),H+=1,n.reportStaticTest(u,"pass",void 0,g);else if(!o.success&&o.failMessage){let f=E(o.failMessage,e.level);f.status==="fail"&&(P+=1),f.status==="warn"&&(I+=1),n.reportStaticTest(u,f.status,f.detail,f.level)}}else{let p=e.attribute.split(" | "),o=!1,f=!0;for(let B of p){let k=B.trim();if(m(T,k)){K.push(`${k} on ${e.target} verified by selector (already present in: ${T}).`),o=!0;continue}if(f=!1,await v.getAttribute(k)!==null){o=!0;break}}if(!o&&!f){let B=e.failureMessage+` None of the attributes "${e.attribute}" are present.`,k=E(B,e.level);k.status==="fail"&&(P+=1),k.status==="warn"&&(I+=1),n.reportStaticTest(u,k.status,k.detail,k.level)}else!f&&o?(K.push(`At least one of the attributes "${e.attribute}" exists on the element.`),H+=1,n.reportStaticTest(u,"pass",void 0,g)):(H+=1,n.reportStaticTest(u,"pass",void 0,g))}}for(let e of $.dynamic||[]){if(!w||w.isClosed()){console.warn(`
|
|
29
|
+
\u26A0\uFE0F Browser closed - skipping remaining ${$.dynamic.length-$.dynamic.indexOf(e)} tests
|
|
30
|
+
`),U.push(`CRITICAL: Browser/page closed before completing all tests. ${$.dynamic.length-$.dynamic.indexOf(e)} tests skipped.`);break}try{await d.resetState(w)}catch(c){let S=c instanceof Error?c.message:String(c);throw n.error(S),c}let{setup:u=[],action:g,assertions:T}=e,v=X(e.level),b=new Z(w,$.selectors,J);if(Array.isArray(u)&&u.length>0){let S=function(V){return c.includes(V)};var ye=S;let c=["focus","type","click","keypress","hover"],Q=V=>({...V,type:S(V.type)?V.type:"click"}),le=u.map(Q),F=await ce(le,b,d,w,e.description,["submenu","submenuTrigger","submenuItems"]);if(F.skip){x.push(F.message||"Setup action skipped"),n.reportTest({description:e.description,level:v},"skip",F.message);continue}if(!F.success){let V=E(`Setup failed: ${F.error}`,e.level);n.reportTest({description:e.description,level:v},V.status,V.detail);continue}}let m=U.length,y=j.length,p=x.length;if(await d.shouldSkipTest(e,w)){let c="Skipping test - component-specific conditions not met";x.push(c),n.reportTest({description:e.description,level:v},"skip",c);continue}let f=new te(w,$.selectors,z),B=!1,k=null;for(let c of g){if(!w||w.isClosed()){U.push("CRITICAL: Browser/page closed during test execution. Remaining actions skipped."),B=!0;break}let S;if(c.type==="focus")c.target==="relative"&&c.relativeTarget?S=await b.focus("relative",c.relativeTarget):S=await b.focus(c.target);else if(c.type==="type"&&c.value)S=await b.type(c.target,c.value);else if(c.type==="click")S=await b.click(c.target,c.relativeTarget);else if(c.type==="keypress"&&c.key)S=await b.keypress(c.target,c.key);else if(c.type==="hover")S=await b.hover(c.target,c.relativeTarget);else continue;if(!S.success){if(S.error){let Q=E(S.error,e.level);k={status:Q.status,detail:Q.detail}}B=!0;break}}if(B){n.reportTest({description:e.description,level:v},k?.status||"fail",k?.detail||U[U.length-1]);continue}for(let c of T){let S;if(c.expectedValue&&typeof c.expectedValue=="object"&&"ref"in c.expectedValue)if(c.expectedValue.ref==="relative"){let{RelativeTargetResolver:ue}=await import("./RelativeTargetResolver-T4P25J2M.js"),V=$.selectors.relative;if(!V)throw new Error("Relative selector not defined in contract selectors.");let $e=c.relativeTarget||"first",pe=await ue.resolve(w,V,$e);if(!pe)throw new Error(`Could not resolve relative target '${$e}' for expectedValue.`);let be=c.expectedValue.property||c.expectedValue.attribute||"id";if(be==="textContent")S=await pe.evaluate(re=>re.textContent??void 0);else{let re=await pe.getAttribute(be);S=re===null?void 0:re}}else S=await ve(c.expectedValue,$.selectors,w,{});else typeof c.expectedValue=="string"||typeof c.expectedValue>"u"?S=c.expectedValue:S="";let Q={...c,expectedValue:S},le=S??"",F=await f.validate({...Q,expectedValue:le},e.description);if(F.success&&F.passMessage)K.push(F.passMessage);else if(!F.success&&F.failMessage){let ue=X(c.level||e.level);if(E(F.failMessage,ue).status==="skip")continue}}let h=U.length,ee=j.length,D=x.length;h>m?n.reportTest({description:e.description,level:v},"fail",U[U.length-1]):ee>y?n.reportTest({description:e.description,level:v},"warn",j[j.length-1]):D>p?n.reportTest({description:e.description,level:v},"skip",x[x.length-1]):n.reportTest({description:e.description,level:v},"pass")}n.reportStatic(H,P,I),n.summary(U)}catch(d){if(d instanceof Error)throw d.message.includes("Executable doesn't exist")||d.message.includes("browserType.launch")?new Error(`
|
|
31
|
+
\u274C CRITICAL: Playwright browsers not found!
|
|
32
|
+
\u{1F4E6} Run: npx playwright install chromium`):d.message.includes("net::ERR_CONNECTION_REFUSED")||d.message.includes("NS_ERROR_CONNECTION_REFUSED")?new Error(`
|
|
33
|
+
\u274C CRITICAL: Cannot connect to dev server!
|
|
34
|
+
Make sure your dev server is running at ${t}`):d.message.includes("Timeout")&&d.message.includes("waitFor")?new Error(`
|
|
35
|
+
\u274C CRITICAL: Component not found on page!
|
|
36
|
+
The component selector could not be found within ${ae}ms.
|
|
37
|
+
This usually means:
|
|
38
|
+
- The component didn't render
|
|
39
|
+
- The URL is incorrect
|
|
40
|
+
- The component selector was not provided to the component utility, or a wrong selector was used
|
|
41
|
+
`):d.message.includes("Target page, context or browser has been closed")?new Error(`
|
|
42
|
+
\u274C CRITICAL: Browser/page was closed unexpectedly!
|
|
43
|
+
This usually means:
|
|
44
|
+
- The test timeout was too short
|
|
45
|
+
- The browser crashed
|
|
46
|
+
- An external process killed the browser`):d}finally{w&&await w.close()}return{passes:K,failures:U,skipped:x,warnings:j}}export{tt as runContractTestsPlaywright};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import{a as Te,c as X,d as Se,e as ke}from"./chunk-APUMBDOT.js";import{a as Y}from"./chunk-SM6ZKEDR.js";import{a as q}from"./chunk-BHNO4ZI3.js";import"./chunk-CNU4N4AY.js";import{readFileSync as de}from"fs";import he from"path";import{readFileSync as fe}from"fs";import ge from"path";import Ce from"path";import{pathToFileURL as Pe}from"url";var ie=class{builtInStrategies=new Map;constructor(){this.registerBuiltInStrategies()}registerBuiltInStrategies(){this.builtInStrategies.set("menu",()=>import("./MenuComponentStrategy-C22BZEBH.js").then(t=>t.MenuComponentStrategy)),this.builtInStrategies.set("accordion",()=>import("./AccordionComponentStrategy-2SWMNUR6.js").then(t=>t.AccordionComponentStrategy)),this.builtInStrategies.set("combobox",()=>import("./ComboboxComponentStrategy-YSYLR2U5.js").then(t=>t.ComboboxComponentStrategy)),this.builtInStrategies.set("tabs",()=>import("./TabsComponentStrategy-ADEEFJXM.js").then(t=>t.TabsComponentStrategy))}async loadStrategy(t,s,r){try{if(s)try{let a=Ce.isAbsolute(s)?s:Ce.resolve(r||process.cwd(),s),l=await import(Pe(a).href),n=l.default||l;if(!n)throw new Error(`No default export found in ${s}`);return n}catch(a){throw new Error(`Failed to load custom strategy from ${s}: ${a instanceof Error?a.message:String(a)}`)}let i=this.builtInStrategies.get(t);return i?i():null}catch(i){throw new Error(`Strategy loading failed for ${t}: ${i instanceof Error?i.message:String(i)}`)}}has(t,s){return!!s||this.builtInStrategies.has(t)}};var ne=class{static strategyRegistry=new ie;static isComponentConfig(t){return typeof t=="object"&&t!==null}static async detect(t,s,r=400,i=400,a){let l=this.isComponentConfig(s)?s:void 0,n=l?.contractPath;if(!n)throw new Error(`Contract path not found for component: ${t}`);let R=(()=>{if(ge.isAbsolute(n))return n;if(a){let se=ge.resolve(a,n);try{return fe(se,"utf-8"),se}catch{}}let z=ge.resolve(process.cwd(),n);try{return fe(z,"utf-8"),z}catch{return new URL(n,import.meta.url).pathname}})(),A=fe(R,"utf-8"),N=JSON.parse(A).selectors,_=await this.strategyRegistry.loadStrategy(t,l?.strategyPath,a);if(!_)return null;let J=N.main;return t==="tabs"?new _(J,N):new _(J,N,r,i)}};var oe=class{startTime=0;componentName="";staticPasses=0;staticFailures=0;staticWarnings=0;dynamicResults=[];totalTests=0;skipped=0;warnings=0;isPlaywright=!1;isCustomContract=!1;apgUrl="https://www.w3.org/WAI/ARIA/apg/";hasPrintedStaticSection=!1;hasPrintedDynamicSection=!1;constructor(t=!1,s=!1){this.isPlaywright=t,this.isCustomContract=s}log(t){process.stderr.write(t+`
|
|
2
|
+
`)}start(t,s,r){this.startTime=Date.now(),this.componentName=t,this.totalTests=s,this.hasPrintedStaticSection=!1,this.hasPrintedDynamicSection=!1,r&&(this.apgUrl=r);let i="Playwright (Real Browser)";this.log(`
|
|
3
|
+
${"\u2550".repeat(60)}`),this.log(`\u{1F50D} Testing ${t.charAt(0).toUpperCase()+t.slice(1)} Component - ${i}`),this.log(`${"\u2550".repeat(60)}
|
|
4
|
+
`)}reportStatic(t,s,r=0){this.staticPasses=t,this.staticFailures=s,this.staticWarnings=r}reportStaticTest(t,s,r,i){this.hasPrintedStaticSection||(this.log(`${"\u2500".repeat(60)}`),this.log("\u{1F9EA} Static Assertions"),this.log(`${"\u2500".repeat(60)}`),this.hasPrintedStaticSection=!0);let a=s==="pass"?"\u2713":s==="warn"?"\u26A0":s==="skip"?"\u25CB":"\u2717";this.log(` ${a} ${t}`),i&&this.log(` \u21B3 level=${i}`),(s==="fail"||s==="warn"||s==="skip")&&r&&this.log(` \u21B3 ${r}`)}reportTest(t,s,r){this.hasPrintedDynamicSection||(this.log(""),this.log(`${"\u2500".repeat(60)}`),this.log("\u2328\uFE0F Dynamic Interaction Tests"),this.log(`${"\u2500".repeat(60)}`),this.hasPrintedDynamicSection=!0);let i={description:t.description,status:s,failureMessage:r,level:t.level};s==="skip"&&(i.skipReason="Requires real browser (addEventListener events)"),this.dynamicResults.push(i);let a={pass:"\u2713",fail:"\u2717",warn:"\u26A0",skip:"\u25CB"},l=t.level?`[${t.level.toUpperCase()}] `:"";this.log(` ${a[s]} ${l}${t.description}`),s==="skip"&&!this.isPlaywright&&this.log(" \u21B3 Skipped (runs only in Playwright)"),s==="fail"&&r&&this.log(` \u21B3 ${r}`),s==="warn"&&r&&this.log(` \u21B3 ${r}`),s==="skip"&&r&&this.log(` \u21B3 ${r}`)}reportFailures(t){t.length!==0&&(this.log(`
|
|
5
|
+
${"\u2500".repeat(60)}`),this.log(`\u274C Failures (${t.length}):
|
|
6
|
+
`),t.forEach((s,r)=>{this.log(`${r+1}. ${s}`),s.includes("aria-")?this.log(" \u{1F4A1} Add the missing ARIA attribute to improve screen reader support"):s.includes("focus")?this.log(" \u{1F4A1} Check keyboard event handlers and focus management"):s.includes("visible")&&this.log(" \u{1F4A1} Verify display/visibility styles and state management"),this.log("")}))}reportWarnings(){let t=this.dynamicResults.filter(s=>s.status==="warn");t.length===0&&this.staticWarnings===0||(this.log(`
|
|
7
|
+
${"\u2500".repeat(60)}`),this.log(`\u26A0\uFE0F Warnings (${this.staticWarnings+t.length}):
|
|
8
|
+
`),this.log(`These checks are failing but treated as warnings under the active strictness mode.
|
|
9
|
+
`),t.forEach((s,r)=>{this.log(`${r+1}. ${s.description}`),s.failureMessage&&this.log(` \u21B3 ${s.failureMessage}`),s.level&&this.log(` \u21B3 level=${s.level}`)}),this.apgUrl&&this.log(`
|
|
10
|
+
Reference: ${this.apgUrl}
|
|
11
|
+
`))}reportSkipped(){if(this.skipped===0||this.isPlaywright)return;let t=this.dynamicResults.filter(s=>s.status==="skip");this.log(`
|
|
12
|
+
${"\u2500".repeat(60)}`),this.log(`\u2139\uFE0F Skipped Tests (${this.skipped}):
|
|
13
|
+
`),this.log("These tests use native keyboard events via addEventListener,"),this.log(`which only run in Playwright (real browser).
|
|
14
|
+
`),t.forEach((s,r)=>{this.log(`${r+1}. ${s.description}`)}),this.log(`
|
|
15
|
+
\u{1F4A1} Run with Playwright for full validation:`),this.log(` testUiComponent('${this.componentName}', null, 'http://localhost:5173/test-harness?component=component_name')
|
|
16
|
+
`)}summary(t){let s=Date.now()-this.startTime,r=this.dynamicResults.filter(M=>M.status==="pass").length,i=this.dynamicResults.filter(M=>M.status==="fail").length,a=this.dynamicResults.filter(M=>M.status==="warn").length;this.skipped=this.dynamicResults.filter(M=>M.status==="skip").length,this.warnings=this.staticWarnings+a;let l=this.staticPasses+r,n=this.staticFailures+i,R=l+n+this.warnings,A=()=>{let M=`${this.componentName.charAt(0).toUpperCase()}${this.componentName.slice(1)}`;return this.isCustomContract?`${M} component validates against your custom accessibility policy \u2713`:`${M} component meets Aria-Ease baseline WAI-ARIA expectations \u2713`};return t.length>0&&this.reportFailures(t),this.reportWarnings(),this.reportSkipped(),this.log(`
|
|
17
|
+
${"\u2550".repeat(60)}`),this.log(`\u{1F4CA} Summary
|
|
18
|
+
`),n===0&&this.skipped===0&&this.warnings===0?(this.log(`\u2705 All ${R} tests passed!`),this.log(` ${A()}`)):n===0?(this.log(`\u2705 ${l}/${R} tests passed`),this.skipped>0&&this.log(`\u25CB ${this.skipped} tests skipped`),this.warnings>0&&this.log(`\u26A0\uFE0F ${this.warnings} warning${this.warnings>1?"s":""}`),this.log(` ${A()}`)):(this.log(`\u274C ${n} test${n>1?"s":""} failed`),this.log(`\u2705 ${l} test${l>1?"s":""} passed`),this.warnings>0&&this.log(`\u26A0\uFE0F ${this.warnings} warning${this.warnings>1?"s":""}`),this.skipped>0&&this.log(`\u25CB ${this.skipped} test${this.skipped>1?"s":""} skipped`)),this.log(`\u23F1\uFE0F Duration: ${s}ms`),this.log(`${"\u2550".repeat(60)}
|
|
19
|
+
`),n>0?(this.log("\u{1F527} Next Steps:"),this.log(" 1. Review the failures above"),this.log(" 2. Fix ARIA attributes and keyboard handlers"),this.log(` 3. Re-run tests to verify fixes
|
|
20
|
+
`)):!this.isPlaywright&&this.skipped>0&&this.log(`\u2728 Optional: Run Playwright tests for complete validation
|
|
21
|
+
`),{passes:l,failures:n,skipped:this.skipped,duration:s}}error(t,s){this.log(`
|
|
22
|
+
\u274C Error: ${t}`),s&&this.log(` Context: ${s}`),this.log("")}};var Z=class{constructor(t,s,r=400){this.page=t;this.selectors=s;this.timeoutMs=r}isBrowserClosedError(t){return t instanceof Error&&t.message.includes("Target page, context or browser has been closed")}async focus(t,s,r){try{if(t==="virtual"&&r){let a=this.selectors.main;if(!a)return{success:!1,error:"Main selector not defined for virtual focus."};let l=this.page.locator(a).first();return await l.count()?(await l.evaluate((R,A)=>{R.setAttribute("aria-activedescendant",A)},r),{success:!0}):{success:!1,error:"Main element not found for virtual focus."}}if(t==="relative"&&s){let a=this.selectors.relative;if(!a)return{success:!1,error:"Relative selector not defined for focus action."};let l=await q.resolve(this.page,a,s);return l?(await l.focus({timeout:this.timeoutMs}),{success:!0}):{success:!1,error:`Could not resolve relative target ${s} for focus.`}}let i=this.selectors[t];return i?(await this.page.locator(i).first().focus({timeout:this.timeoutMs}),{success:!0}):{success:!1,error:`Selector for focus target ${t} not found.`}}catch(i){return this.isBrowserClosedError(i)?{success:!1,error:"CRITICAL: Browser/page closed during test execution. Remaining actions skipped.",shouldBreak:!0}:{success:!1,error:`Failed to focus ${t}: ${i instanceof Error?i.message:String(i)}`}}}async type(t,s){try{let r=this.selectors[t];return r?(await this.page.locator(r).first().fill(s,{timeout:this.timeoutMs}),{success:!0}):{success:!1,error:`Selector for type target ${t} not found.`}}catch(r){return this.isBrowserClosedError(r)?{success:!1,error:"CRITICAL: Browser/page closed during test execution. Remaining actions skipped.",shouldBreak:!0}:{success:!1,error:`Failed to type into ${t}: ${r instanceof Error?r.message:String(r)}`}}}async click(t,s){try{if(t==="document")return await this.page.mouse.click(10,10),{success:!0};if(t==="relative"&&s){let i=this.selectors.relative;if(!i)return{success:!1,error:"Relative selector not defined for click action."};let a=await q.resolve(this.page,i,s);return a?(await a.click({timeout:this.timeoutMs}),{success:!0}):{success:!1,error:`Could not resolve relative target ${s} for click.`}}let r=this.selectors[t];return r?(await this.page.locator(r).first().click({timeout:this.timeoutMs}),{success:!0}):{success:!1,error:`Selector for action target ${t} not found.`}}catch(r){return this.isBrowserClosedError(r)?{success:!1,error:"CRITICAL: Browser/page closed during test execution. Remaining actions skipped.",shouldBreak:!0}:{success:!1,error:`Failed to click ${t}: ${r instanceof Error?r.message:String(r)}`}}}async keypress(t,s){try{let i={Space:"Space",Enter:"Enter",Escape:"Escape","Arrow Up":"ArrowUp","Arrow Down":"ArrowDown","Arrow Left":"ArrowLeft","Arrow Right":"ArrowRight",Home:"Home",End:"End",Tab:"Tab"}[s]||s;if(i==="Space"?i=" ":i.includes(" ")&&(i=i.replace(/ /g,"")),t==="focusable"&&["ArrowUp","ArrowDown","ArrowLeft","ArrowRight","Escape","Home","End","Tab","Shift+Tab"].includes(i))return await this.page.keyboard.press(i),{success:!0};let a=this.selectors[t];if(!a)return{success:!1,error:`Selector for keypress target ${t} not found.`};let l=this.page.locator(a).first();return await l.count()===0?{success:!1,error:`${t} element not found.`,shouldBreak:!0}:(await l.press(i,{timeout:this.timeoutMs}),{success:!0})}catch(r){return this.isBrowserClosedError(r)?{success:!1,error:"CRITICAL: Browser/page closed during test execution. Remaining actions skipped.",shouldBreak:!0}:{success:!1,error:`Failed to press ${s} on ${t}: ${r instanceof Error?r.message:String(r)}`}}}async hover(t,s){try{if(t==="relative"&&s){let i=this.selectors.relative;if(!i)return{success:!1,error:"Relative selector not defined for hover action."};let a=await q.resolve(this.page,i,s);return a?(await a.hover({timeout:this.timeoutMs}),{success:!0}):{success:!1,error:`Could not resolve relative target ${s} for hover.`}}let r=this.selectors[t];return r?(await this.page.locator(r).first().hover({timeout:this.timeoutMs}),{success:!0}):{success:!1,error:`Selector for hover target ${t} not found.`}}catch(r){return this.isBrowserClosedError(r)?{success:!1,error:"CRITICAL: Browser/page closed during test execution. Remaining actions skipped.",shouldBreak:!0}:{success:!1,error:`Failed to hover ${t}: ${r instanceof Error?r.message:String(r)}`}}}};var te=class{constructor(t,s,r=400){this.page=t;this.selectors=s;this.timeoutMs=r}async resolveTarget(t,s,r){try{if(t==="relative"){let a=r?this.selectors[r]:this.selectors.relative;if(!a)return{target:null,error:"Relative selector is not defined in the contract."};if(!s)return{target:null,error:"Relative target or expected value is not defined."};let l=await q.resolve(this.page,a,s);return l?{target:l}:{target:null,error:`Target ${t} not found.`}}let i=this.selectors[t];return i?{target:this.page.locator(i).first()}:{target:null,error:`Selector for assertion target ${t} not found.`}}catch(i){return{target:null,error:`Failed to resolve target ${t}: ${i instanceof Error?i.message:String(i)}`}}}async validateVisibility(t,s,r,i,a){try{return r?(await(0,Y.expect)(t).toBeVisible({timeout:this.timeoutMs}),{success:!0,passMessage:`${s} is visible as expected. Test: "${a}".`}):(await(0,Y.expect)(t).toBeHidden({timeout:this.timeoutMs}),{success:!0,passMessage:`${s} is not visible as expected. Test: "${a}".`})}catch{let l=this.selectors[s]||"",n=await this.page.evaluate(R=>{let A=R?document.querySelector(R):null;if(!A)return"element not found";let M=window.getComputedStyle(A);return`display:${M.display}, visibility:${M.visibility}, opacity:${M.opacity}`},l);return r?{success:!1,failMessage:`${i} (actual: ${n})`}:{success:!1,failMessage:`${i} ${s} is still visible (actual: ${n}).`}}}async validateAttribute(t,s,r,i,a,l){if(i==="!empty"){let A=await t.getAttribute(r);return A&&A.trim()!==""?{success:!0,passMessage:`${s} has non-empty "${r}". Test: "${l}".`}:{success:!1,failMessage:`${a} ${s} "${r}" should not be empty, found "${A}".`}}if(typeof i!="string")throw console.error("[AssertionRunner] expectedValue is not a string:",i),new Error(`AssertionRunner: expectedValue for attribute assertion must be a string, but got: ${JSON.stringify(i)}`);let n=i.split(" | ").map(A=>A.trim()),R=await t.getAttribute(r);return R!==null&&n.includes(R)?{success:!0,passMessage:`${s} has expected "${r}". Test: "${l}".`}:{success:!1,failMessage:`${a} ${s} "${r}" should be "${i}", found "${R}".`}}async validateValue(t,s,r,i,a){let l=await t.inputValue().catch(()=>"");return r==="!empty"?l&&l.trim()!==""?{success:!0,passMessage:`${s} has non-empty value. Test: "${a}".`}:{success:!1,failMessage:`${i} ${s} value should not be empty, found "${l}".`}:r===""?l===""?{success:!0,passMessage:`${s} has empty value. Test: "${a}".`}:{success:!1,failMessage:`${i} ${s} value should be empty, found "${l}".`}:l===r?{success:!0,passMessage:`${s} has expected value. Test: "${a}".`}:{success:!1,failMessage:`${i} ${s} value should be "${r}", found "${l}".`}}async validateFocus(t,s,r,i,a){try{return r?(await(0,Y.expect)(t).toBeFocused({timeout:this.timeoutMs}),{success:!0,passMessage:`${s} has focus as expected. Test: "${a}".`}):(await(0,Y.expect)(t).not.toBeFocused({timeout:this.timeoutMs}),{success:!0,passMessage:`${s} does not have focus as expected. Test: "${a}".`})}catch{let l=await this.page.evaluate(()=>{let n=document.activeElement;return n?`${n.tagName}#${n.id||"no-id"}.${n.className||"no-class"}`:"no element focused"});return{success:!1,failMessage:`${i} (actual focus: ${l})`}}}async validateRole(t,s,r,i,a){let l=await t.getAttribute("role");return l===r?{success:!0,passMessage:`${s} has role "${r}". Test: "${a}".`}:{success:!1,failMessage:`${i} Expected role "${r}", found "${l}".`}}async validate(t,s){if(this.page.isClosed())return{success:!1,failMessage:"CRITICAL: Browser/page closed before completing all tests. Increase test timeout or reduce test complexity."};let{target:r,error:i}=await this.resolveTarget(t.target,t.relativeTarget||t.expectedValue,t.selectorKey);if(i||!r)return{success:!1,failMessage:i||`Target ${t.target} not found.`,target:null};if(t.target==="input"&&t.attribute==="aria-activedescendant"&&t.expectedValue==="!empty"&&t.relativeTarget&&t.selectorKey){let a=await q.resolve(this.page,this.selectors[t.selectorKey],t.relativeTarget),l=a?await a.getAttribute("id"):null,n=await r.getAttribute("aria-activedescendant");return l&&n===l?{success:!0,passMessage:`input[aria-activedescendant] matches id of ${t.relativeTarget}(${t.selectorKey}). Test: "${s}".`}:{success:!1,failMessage:`input[aria-activedescendant] should match id of ${t.relativeTarget}(${t.selectorKey}), found "${n}".`}}switch(t.assertion){case"toBeVisible":return this.validateVisibility(r,t.target,!0,t.failureMessage||"",s);case"notToBeVisible":return this.validateVisibility(r,t.target,!1,t.failureMessage||"",s);case"toHaveAttribute":return t.attribute&&t.expectedValue!==void 0?this.validateAttribute(r,t.target,t.attribute,t.expectedValue,t.failureMessage||"",s):{success:!1,failMessage:"Missing attribute or expectedValue for toHaveAttribute assertion"};case"toHaveValue":return t.expectedValue!==void 0?this.validateValue(r,t.target,t.expectedValue,t.failureMessage||"",s):{success:!1,failMessage:"Missing expectedValue for toHaveValue assertion"};case"toHaveFocus":return this.validateFocus(r,t.target,!0,t.failureMessage||"",s);case"notToHaveFocus":return this.validateFocus(r,t.target,!1,t.failureMessage||"",s);case"toHaveRole":return t.expectedValue!==void 0?this.validateRole(r,t.target,t.expectedValue,t.failureMessage||"",s):{success:!1,failMessage:"Missing expectedValue for toHaveRole assertion"};default:return{success:!1,failMessage:`Unknown assertion type: ${t.assertion}`}}}};async function tt(C,t,s,r,i){let a=r?.test?.components?.find(d=>d.name===C),l=!!a?.contractPath,n=new oe(!0,l),R={actionTimeoutMs:400,assertionTimeoutMs:400,navigationTimeoutMs:3e4,componentReadyTimeoutMs:5e3},A=r?.test?.disableTimeouts===!0,N=a?.disableTimeouts===!0||A,_=(d,W,O)=>{if(N)return 0;let L=d??W;return typeof L!="number"||!Number.isFinite(L)||L<0?O:L},J=_(a?.actionTimeoutMs,r?.test?.actionTimeoutMs,R.actionTimeoutMs),z=_(a?.assertionTimeoutMs,r?.test?.assertionTimeoutMs,R.assertionTimeoutMs),se=_(a?.navigationTimeoutMs,r?.test?.navigationTimeoutMs,R.navigationTimeoutMs),ae=_(a?.componentReadyTimeoutMs,r?.test?.componentReadyTimeoutMs,R.componentReadyTimeoutMs),me=Se(s),G=a?.contractPath;if(!G)throw new Error(`Contract path not found for component: ${C}`);let Re=(()=>{if(he.isAbsolute(G))return G;if(i){let W=he.resolve(i,G);try{return de(W,"utf-8"),W}catch{}}let d=he.resolve(process.cwd(),G);try{return de(d,"utf-8"),d}catch{return new URL(G,import.meta.url).pathname}})(),Ae=de(Re,"utf-8"),$=JSON.parse(Ae),Me=($.relationships?.length||0)+($.static[0]?.assertions.length||0)+$.dynamic.length,Ee=$.meta?.source?.apg,U=[],j=[],K=[],x=[],w=null,E=(d,W)=>{let O=X(W),L=ke(O,me);if(L==="error")return U.push(d),{status:"fail",level:O,detail:d};if(L==="warning")return j.push(d),{status:"warn",level:O,detail:d};let H=`${d} (ignored by strictness=${me}, level=${O})`;return x.push(H),{status:"skip",level:O,detail:H}};try{if(w=await Te(),t){try{await w.goto(t,{waitUntil:"domcontentloaded",timeout:se})}catch(e){throw new Error(`Failed to navigate to ${t}. Ensure dev server is running and accessible. Original error: ${e instanceof Error?e.message:String(e)}`)}await w.addStyleTag({content:"* { transition: none !important; animation: none !important; }"})}let d=await ne.detect(C,a,J,z,i);if(!d)throw new Error(`Unsupported component: ${C}`);let W=d.getMainSelector();if(!W)throw new Error(`CRITICAL: No selector found in contract for ${C}`);try{await w.locator(W).first().waitFor({state:"attached",timeout:ae})}catch(e){throw new Error(`
|
|
23
|
+
\u274C CRITICAL: Component not found on page!
|
|
24
|
+
This usually means:
|
|
25
|
+
- The component didn't render
|
|
26
|
+
- The URL is incorrect
|
|
27
|
+
- The component selector '${W}' in the contract is wrong
|
|
28
|
+
- Original error: ${e}`)}n.start(C,Me,Ee),C==="menu"&&$.selectors.main&&await w.locator($.selectors.main).first().waitFor({state:"visible",timeout:ae}).catch(()=>{});let O=C==="menu"&&$.selectors.submenuTrigger?await w.locator($.selectors.submenuTrigger).count()>0:!1,L=e=>e.type==="aria-reference"&&[e.from,e.to].some(u=>["submenu","submenuTrigger","submenuItems"].includes(u||""))||e.type==="contains"&&[e.parent,e.child].some(u=>["submenu","submenuTrigger","submenuItems"].includes(u||"")),H=0,P=0,I=0;for(let e of $.relationships||[]){if(d&&typeof d.resetState=="function")try{await d.resetState(w)}catch(g){j.push(`Warning: resetState failed before relationship test: ${g instanceof Error?g.message:String(g)}`)}let u=X(e.level);if(Array.isArray(e.setup)&&e.setup.length>0){let b=function(o){return v.includes(o)};var ye=b;let g=new Z(w,$.selectors,J),T=e.type==="aria-reference"?`${e.from}.${e.attribute} references ${e.to}`:`${e.parent} contains ${e.child}`,v=["focus","type","click","keypress","hover"],m=o=>({...o,type:b(o.type)?o.type:"click"}),y=e.setup.map(m),p=await ce(y,g,d,w,T,["submenu","submenuTrigger","submenuItems"]);if(p.skip){x.push(p.message||"Setup action skipped"),n.reportStaticTest(T,"skip",p.message,u);continue}if(!p.success){let o=`Relationship setup failed: ${p.error}`,f=E(o,e.level);f.status==="fail"&&(P+=1),f.status==="warn"&&(I+=1),n.reportStaticTest(T,f.status,f.detail,f.level);continue}}if(C==="menu"&&!O&&L(e)){let T=e.type==="aria-reference"?`${e.from}.${e.attribute} references ${e.to}`:`${e.parent} contains ${e.child}`,v="Skipping submenu relationship assertion: no submenu capability detected in rendered component.";x.push(v),n.reportStaticTest(T,"skip",v,u);continue}if(e.type==="aria-reference"){let g=`${e.from}.${e.attribute} references ${e.to}`,T=$.selectors[e.from],v=$.selectors[e.to];if(!T||!v){let h=E(`Relationship selector missing: from="${e.from}" or to="${e.to}" not found in selectors.`,e.level);h.status==="fail"&&(P+=1),h.status==="warn"&&(I+=1),n.reportStaticTest(g,h.status,h.detail,h.level);continue}let b=w.locator(T).first(),m=w.locator(v).first(),y=await b.count()>0,p=await m.count()>0;if(!y||!p){if(C==="menu"&&L(e)){let ee="Skipping submenu relationship assertion in static phase: submenu elements are not present until submenu is opened.";x.push(ee),n.reportStaticTest(g,"skip",ee,u);continue}let h=E(`Relationship target not found: ${y?e.to:e.from}.`,e.level);h.status==="fail"&&(P+=1),h.status==="warn"&&(I+=1),n.reportStaticTest(g,h.status,h.detail,h.level);continue}let o=await b.getAttribute(e.attribute),f=await m.getAttribute("id");if(!f){let h=E(`Relationship target "${e.to}" must have an id for ${e.attribute} validation.`,e.level);h.status==="fail"&&(P+=1),h.status==="warn"&&(I+=1),n.reportStaticTest(g,h.status,h.detail,h.level);continue}if(!(o||"").split(/\s+/).filter(Boolean).includes(f)){let h=E(`Expected ${e.from} ${e.attribute} to reference id "${f}", found "${o||""}".`,e.level);h.status==="fail"&&(P+=1),h.status==="warn"&&(I+=1),n.reportStaticTest(g,h.status,h.detail,h.level);continue}K.push(`Relationship valid: ${e.from}.${e.attribute} -> ${e.to} (id=${f}).`),H+=1,n.reportStaticTest(g,"pass",void 0,u);continue}if(e.type==="contains"){let g=`${e.parent} contains ${e.child}`,T=$.selectors[e.parent],v=$.selectors[e.child];if(!T||!v){let o=E(`Relationship selector missing: parent="${e.parent}" or child="${e.child}" not found in selectors.`,e.level);o.status==="fail"&&(P+=1),o.status==="warn"&&(I+=1),n.reportStaticTest(g,o.status,o.detail,o.level);continue}let b=w.locator(T).first();if(!(await b.count()>0)){if(C==="menu"&&L(e)){let f="Skipping submenu relationship assertion in static phase: submenu container is not present until submenu is opened.";x.push(f),n.reportStaticTest(g,"skip",f,u);continue}let o=E(`Relationship parent target not found: ${e.parent}.`,e.level);o.status==="fail"&&(P+=1),o.status==="warn"&&(I+=1),n.reportStaticTest(g,o.status,o.detail,o.level);continue}if(await b.locator(v).count()<1){if(C==="menu"&&L(e)){let f="Skipping submenu relationship assertion in static phase: submenu descendants are not present until submenu is opened.";x.push(f),n.reportStaticTest(g,"skip",f,u);continue}let o=E(`Expected ${e.parent} to contain descendant matching selector for ${e.child}.`,e.level);o.status==="fail"&&(P+=1),o.status==="warn"&&(I+=1),n.reportStaticTest(g,o.status,o.detail,o.level);continue}K.push(`Relationship valid: ${e.parent} contains ${e.child}.`),H+=1,n.reportStaticTest(g,"pass",void 0,u)}}async function ve(e,u,g,T={}){if(!e||typeof e!="object"||!("ref"in e))return e;let v;if(e.ref==="relative"){if(!e.relativeTarget||!T.relativeBaseSelector)return;let b=g.locator(T.relativeBaseSelector),m=await b.count(),y=0;if(e.relativeTarget==="first"?y=0:e.relativeTarget==="second"?y=1:e.relativeTarget==="last"?y=m-1:isNaN(Number(e.relativeTarget))?y=0:y=Number(e.relativeTarget),y<0||y>=m)return;let p=b.nth(y);return await we(p,e.property||e.attribute)}else{if(v=u[e.ref],!v)throw new Error(`Selector for ref '${e.ref}' not found in contract selectors.`);let b=g.locator(v).first();return await we(b,e.property||e.attribute)}}async function we(e,u){if(e)return!u||u==="id"?await e.getAttribute("id")??void 0:u==="class"?await e.getAttribute("class")??void 0:u==="textContent"?await e.evaluate(g=>g.textContent??void 0):u.startsWith("aria-")?await e.getAttribute(u)??void 0:u.endsWith("*")?await e.evaluate(T=>{let v=[];for(let b of Array.from(T.attributes))b.name.startsWith("aria-")&&v.push(`${b.name}=${b.value}`);return v.join(";")}):await e.getAttribute(u)??void 0}let xe=new te(w,$.selectors,z);async function ce(e,u,g,T,v,b=[]){if(!Array.isArray(e)||e.length===0)return{success:!0};g&&typeof g.resetState=="function"&&await g.resetState(T);for(let m of e){let y={success:!0};try{if(m.type==="focus")m.target==="relative"&&m.relativeTarget?y=await u.focus("relative",m.relativeTarget):y=await u.focus(m.target);else if(m.type==="type"&&m.value)y=await u.type(m.target,m.value);else if(m.type==="click")y=await u.click(m.target,m.relativeTarget);else if(m.type==="keypress"&&m.key)y=await u.keypress(m.target,m.key);else if(m.type==="hover")y=await u.hover(m.target,m.relativeTarget);else continue}catch(p){y={success:!1,error:p instanceof Error?p.message:String(p)}}if(!y.success){let p=y.error||"Setup action failed";return b.some(f=>v.includes(f)||p.includes(f))?{success:!1,skip:!0,message:`Skipping test - capability not present: ${p}`}:{success:!1,error:p}}}return{success:!0}}for(let e of $.static[0]?.assertions||[]){if(d&&typeof d.resetState=="function")try{await d.resetState(w)}catch(p){j.push(`Warning: resetState failed before static test: ${p instanceof Error?p.message:String(p)}`)}if(e.target==="relative")continue;let u=`${e.target}${e.attribute?` (${e.attribute})`:""}`,g=X(e.level);if(C==="menu"&&e.target==="submenuTrigger"&&!O){let p=`Skipping submenu static assertion for ${e.target}: no submenu capability detected in rendered component.`;x.push(p),n.reportStaticTest(u,"skip",p,g);continue}if(Array.isArray(e.setup)&&e.setup.length>0){let f=function(D){return o.includes(D)};var ye=f;let p=new Z(w,$.selectors,J),o=["focus","type","click","keypress","hover"],B=D=>({...D,type:f(D.type)?D.type:"click"}),k=e.setup.map(B),h=await ce(k,p,d,w,u,["submenu","submenuTrigger","submenuItems"]);if(h.skip){x.push(h.message||"Setup action skipped"),n.reportStaticTest(u,"skip",h.message,g);continue}if(!h.success){let D=`Static setup failed: ${h.error}`,c=E(D,e.level);c.status==="fail"&&(P+=1),c.status==="warn"&&(I+=1),n.reportStaticTest(u,c.status,c.detail,c.level);continue}}let T=$.selectors[e.target];if(!T){let p=`Selector for target ${e.target} not found.`,o=E(p,e.level);o.status==="fail"&&(P+=1),o.status==="warn"&&(I+=1),n.reportStaticTest(u,o.status,o.detail,o.level);continue}let v=w.locator(T).first();if(!(await v.count()>0)){let p=`Target ${e.target} not found.`,o=E(p,e.level);o.status==="fail"&&(P+=1),o.status==="warn"&&(I+=1),n.reportStaticTest(u,o.status,o.detail,o.level);continue}let m=(p,o,f)=>{let B=new RegExp(`\\[${o}(?:=["']?([^\\]"']+)["']?)?\\]`),k=p.match(B);if(!k)return!1;if(!f)return!0;let h=k[1];return h?f.split(" | ").includes(h):!1},y=e.expectedValue;if(e.expectedValue&&typeof e.expectedValue=="object"&&"ref"in e.expectedValue){let p={},o=e.relativeTarget;if(e.expectedValue.ref==="relative"&&e.target==="relative"&&o){let f=$.selectors[o];if(!f)throw new Error(`Selector for relativeTarget '${o}' not found in contract selectors.`);p.relativeBaseSelector=f}else if(e.expectedValue.ref==="relative"&&o){let f=$.selectors[o];if(!f)throw new Error(`Selector for relativeTarget '${o}' not found in contract selectors.`);p.relativeBaseSelector=f}y=await ve(e.expectedValue,$.selectors,w,p),console.log("Expected value in static check",y)}if(e.expectedValue)if(m(T,e.attribute,typeof y=="string"?y:void 0))K.push(`${e.attribute}="${y}" on ${e.target} verified by selector (already present in: ${T}).`),H+=1,n.reportStaticTest(u,"pass",void 0,g);else{let p=y??"",o=await xe.validateAttribute(v,e.target,e.attribute,p,e.failureMessage,"Static ARIA Test");if(o.success&&o.passMessage)K.push(o.passMessage),H+=1,n.reportStaticTest(u,"pass",void 0,g);else if(!o.success&&o.failMessage){let f=E(o.failMessage,e.level);f.status==="fail"&&(P+=1),f.status==="warn"&&(I+=1),n.reportStaticTest(u,f.status,f.detail,f.level)}}else{let p=e.attribute.split(" | "),o=!1,f=!0;for(let B of p){let k=B.trim();if(m(T,k)){K.push(`${k} on ${e.target} verified by selector (already present in: ${T}).`),o=!0;continue}if(f=!1,await v.getAttribute(k)!==null){o=!0;break}}if(!o&&!f){let B=e.failureMessage+` None of the attributes "${e.attribute}" are present.`,k=E(B,e.level);k.status==="fail"&&(P+=1),k.status==="warn"&&(I+=1),n.reportStaticTest(u,k.status,k.detail,k.level)}else!f&&o?(K.push(`At least one of the attributes "${e.attribute}" exists on the element.`),H+=1,n.reportStaticTest(u,"pass",void 0,g)):(H+=1,n.reportStaticTest(u,"pass",void 0,g))}}for(let e of $.dynamic||[]){if(!w||w.isClosed()){console.warn(`
|
|
29
|
+
\u26A0\uFE0F Browser closed - skipping remaining ${$.dynamic.length-$.dynamic.indexOf(e)} tests
|
|
30
|
+
`),U.push(`CRITICAL: Browser/page closed before completing all tests. ${$.dynamic.length-$.dynamic.indexOf(e)} tests skipped.`);break}try{await d.resetState(w)}catch(c){let S=c instanceof Error?c.message:String(c);throw n.error(S),c}let{setup:u=[],action:g,assertions:T}=e,v=X(e.level),b=new Z(w,$.selectors,J);if(Array.isArray(u)&&u.length>0){let S=function(V){return c.includes(V)};var ye=S;let c=["focus","type","click","keypress","hover"],Q=V=>({...V,type:S(V.type)?V.type:"click"}),le=u.map(Q),F=await ce(le,b,d,w,e.description,["submenu","submenuTrigger","submenuItems"]);if(F.skip){x.push(F.message||"Setup action skipped"),n.reportTest({description:e.description,level:v},"skip",F.message);continue}if(!F.success){let V=E(`Setup failed: ${F.error}`,e.level);n.reportTest({description:e.description,level:v},V.status,V.detail);continue}}let m=U.length,y=j.length,p=x.length;if(await d.shouldSkipTest(e,w)){let c="Skipping test - component-specific conditions not met";x.push(c),n.reportTest({description:e.description,level:v},"skip",c);continue}let f=new te(w,$.selectors,z),B=!1,k=null;for(let c of g){if(!w||w.isClosed()){U.push("CRITICAL: Browser/page closed during test execution. Remaining actions skipped."),B=!0;break}let S;if(c.type==="focus")c.target==="relative"&&c.relativeTarget?S=await b.focus("relative",c.relativeTarget):S=await b.focus(c.target);else if(c.type==="type"&&c.value)S=await b.type(c.target,c.value);else if(c.type==="click")S=await b.click(c.target,c.relativeTarget);else if(c.type==="keypress"&&c.key)S=await b.keypress(c.target,c.key);else if(c.type==="hover")S=await b.hover(c.target,c.relativeTarget);else continue;if(!S.success){if(S.error){let Q=E(S.error,e.level);k={status:Q.status,detail:Q.detail}}B=!0;break}}if(B){n.reportTest({description:e.description,level:v},k?.status||"fail",k?.detail||U[U.length-1]);continue}for(let c of T){let S;if(c.expectedValue&&typeof c.expectedValue=="object"&&"ref"in c.expectedValue)if(c.expectedValue.ref==="relative"){let{RelativeTargetResolver:ue}=await import("./RelativeTargetResolver-T4P25J2M.js"),V=$.selectors.relative;if(!V)throw new Error("Relative selector not defined in contract selectors.");let $e=c.relativeTarget||"first",pe=await ue.resolve(w,V,$e);if(!pe)throw new Error(`Could not resolve relative target '${$e}' for expectedValue.`);let be=c.expectedValue.property||c.expectedValue.attribute||"id";if(be==="textContent")S=await pe.evaluate(re=>re.textContent??void 0);else{let re=await pe.getAttribute(be);S=re===null?void 0:re}}else S=await ve(c.expectedValue,$.selectors,w,{});else typeof c.expectedValue=="string"||typeof c.expectedValue>"u"?S=c.expectedValue:S="";let Q={...c,expectedValue:S},le=S??"",F=await f.validate({...Q,expectedValue:le},e.description);if(F.success&&F.passMessage)K.push(F.passMessage);else if(!F.success&&F.failMessage){let ue=X(c.level||e.level);if(E(F.failMessage,ue).status==="skip")continue}}let h=U.length,ee=j.length,D=x.length;h>m?n.reportTest({description:e.description,level:v},"fail",U[U.length-1]):ee>y?n.reportTest({description:e.description,level:v},"warn",j[j.length-1]):D>p?n.reportTest({description:e.description,level:v},"skip",x[x.length-1]):n.reportTest({description:e.description,level:v},"pass")}n.reportStatic(H,P,I),n.summary(U)}catch(d){if(d instanceof Error)throw d.message.includes("Executable doesn't exist")||d.message.includes("browserType.launch")?new Error(`
|
|
31
|
+
\u274C CRITICAL: Playwright browsers not found!
|
|
32
|
+
\u{1F4E6} Run: npx playwright install chromium`):d.message.includes("net::ERR_CONNECTION_REFUSED")||d.message.includes("NS_ERROR_CONNECTION_REFUSED")?new Error(`
|
|
33
|
+
\u274C CRITICAL: Cannot connect to dev server!
|
|
34
|
+
Make sure your dev server is running at ${t}`):d.message.includes("Timeout")&&d.message.includes("waitFor")?new Error(`
|
|
35
|
+
\u274C CRITICAL: Component not found on page!
|
|
36
|
+
The component selector could not be found within ${ae}ms.
|
|
37
|
+
This usually means:
|
|
38
|
+
- The component didn't render
|
|
39
|
+
- The URL is incorrect
|
|
40
|
+
- The component selector was not provided to the component utility, or a wrong selector was used
|
|
41
|
+
`):d.message.includes("Target page, context or browser has been closed")?new Error(`
|
|
42
|
+
\u274C CRITICAL: Browser/page was closed unexpectedly!
|
|
43
|
+
This usually means:
|
|
44
|
+
- The test timeout was too short
|
|
45
|
+
- The browser crashed
|
|
46
|
+
- An external process killed the browser`):d}finally{w&&await w.close()}return{passes:K,failures:U,skipped:x,warnings:j}}export{tt as runContractTestsPlaywright};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import"./chunk-CNU4N4AY.js";function w(r,t){switch(t){case"json":return JSON.stringify(r.flatMap(({url:c,result:s})=>s?s.violations.flatMap(i=>i.nodes.map(e=>({URL:c,Rule:i.id,Impact:i.impact,Description:i.description,Target:e.target,FailureSummary:e.failureSummary}))):[]),null,2);case"csv":return h(r);case"html":return f(r);default:return""}}function h(r){let t=["URL,Rule,Impact,Description,Target,FailureSummary"];return r.forEach(({url:c,result:s})=>{s&&s.violations.forEach(i=>{i.nodes.forEach(e=>{t.push(p(c)+","+p(i.id)+","+p(i.impact)+","+p(i.description)+","+p(Array.isArray(e.target)?e.target.join("; "):String(e.target))+","+p(e.failureSummary??""))})})}),t.join(`
|
|
2
|
+
`)}function p(r){return`"${String(r??"").replace(/"/g,'""')}"`}function f(r){let t={pagesAudited:0,pagesWithViolations:0,totalViolations:0,distinctRules:new Set,impactCounts:new Map};r.forEach(({result:a})=>{if(!a)return;t.pagesAudited++,a.violations.reduce((o,n)=>{let d=(n.nodes||[]).length;if(d>0){t.distinctRules.add(n.id),t.totalViolations+=d,o+=d;let u=String(n.impact??"unknown");t.impactCounts.set(u,(t.impactCounts.get(u)||0)+d)}return o},0)>0&&t.pagesWithViolations++});let c=[];r.forEach(({url:a,result:m})=>{m&&m.violations.forEach(o=>{o.nodes.forEach(n=>{let d=Array.isArray(n.target)?n.target.join("; "):String(n.target);c.push(`
|
|
3
|
+
<tr>
|
|
4
|
+
<td class="nowrap">${l(a)}</td>
|
|
5
|
+
<td class="nowrap">${l(o.id)}</td>
|
|
6
|
+
<td class="impact ${g(String(o.impact??"unknown"))}">${l(String(o.impact??""))}</td>
|
|
7
|
+
<td class="desc">${l(o.description??"")}</td>
|
|
8
|
+
<td class="target"><code>${l(d)}</code></td>
|
|
9
|
+
<td class="fail">${l(n.failureSummary??"").split(/\r?\n/).join("<br/>")}</td>
|
|
10
|
+
</tr>
|
|
11
|
+
`)})})});let s=Array.from(t.impactCounts.entries()).map(([a,m])=>`<li><strong class="impact ${g(a)}">${l(a)}</strong>: ${m}</li>`).join(`
|
|
12
|
+
`),i=new Date,e=a=>String(a).padStart(2,"0");return`
|
|
13
|
+
<!DOCTYPE html>
|
|
14
|
+
<html lang="en">
|
|
15
|
+
<head>
|
|
16
|
+
<meta charset="utf-8"/>
|
|
17
|
+
<title>Aria-Ease Accessibility Audit Report</title>
|
|
18
|
+
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
|
19
|
+
<style>
|
|
20
|
+
:root{
|
|
21
|
+
--bg:#ffffff; --muted:#6b7280; --border:#e6e9ee;
|
|
22
|
+
--impact-critical: red; --impact-moderate:#fff4dd; --impact-serious:rgb(255, 123, 0);
|
|
23
|
+
}
|
|
24
|
+
body{font-family:Inter,ui-sans-serif,system-ui,Segoe UI,Roboto,Helvetica,Arial; background:var(--bg); color:#111827; padding:24px; line-height:1.4}
|
|
25
|
+
h1{margin:0 0 8px}
|
|
26
|
+
.summary{background:#f8fafc;border:1px solid var(--border);padding:12px 16px;border-radius:8px;margin-bottom:18px}
|
|
27
|
+
.summary ul{margin:6px 0 0 0;padding:0 18px}
|
|
28
|
+
.impact-summary h3{margin:12px 0 6px}
|
|
29
|
+
table{width:100%; border-collapse:collapse; margin-top:12px}
|
|
30
|
+
th,td{border:1px solid var(--border); padding:10px; text-align:left; vertical-align:top}
|
|
31
|
+
th{background:#f3f4f6; font-weight:600; position:sticky; top:0; z-index:1}
|
|
32
|
+
.nowrap{white-space:nowrap}
|
|
33
|
+
.target code{font-family:ui-monospace, SFMono-Regular, Menlo, Monaco, "Roboto Mono", "Courier New", monospace; white-space:pre-wrap}
|
|
34
|
+
.desc{max-width:380px}
|
|
35
|
+
tr:nth-child(even){background:#fbfbfb}
|
|
36
|
+
td.fail{color:#7b1e1e}
|
|
37
|
+
.impact.critical{background:var(--impact-critical); font-weight:600}
|
|
38
|
+
.impact.moderate{background:var(--impact-moderate); font-weight:600}
|
|
39
|
+
.impact.serious{background:var(--impact-serious); font-weight:600}
|
|
40
|
+
@media (max-width:900px){
|
|
41
|
+
.desc{max-width:200px}
|
|
42
|
+
table, thead, tbody, th, td, tr{display:block}
|
|
43
|
+
thead{display:none}
|
|
44
|
+
tr{margin-bottom:10px; border: 1px solid var(--border);}
|
|
45
|
+
td{border:1px solid var(--border); padding:6px}
|
|
46
|
+
td::before{font-weight:600; display:inline-block; width:120px}
|
|
47
|
+
}
|
|
48
|
+
.summary-list strong,
|
|
49
|
+
.summary-list li {
|
|
50
|
+
padding: 2px 4px;
|
|
51
|
+
}
|
|
52
|
+
</style>
|
|
53
|
+
</head>
|
|
54
|
+
<body>
|
|
55
|
+
<h1>Aria-Ease Accessibility Audit Report</h1>
|
|
56
|
+
${`
|
|
57
|
+
<section class="summary">
|
|
58
|
+
<h2>Report summary</h2>
|
|
59
|
+
<ul>
|
|
60
|
+
<li><strong>Date:</strong> ${`${e(i.getDate())}-${e(i.getMonth()+1)}-${i.getFullYear()} ${e(i.getHours())}:${e(i.getMinutes())}:${e(i.getSeconds())}`}</li>
|
|
61
|
+
<li><strong>Pages audited:</strong> ${t.pagesAudited}</li>
|
|
62
|
+
<li><strong>Pages with violations:</strong> ${t.pagesWithViolations}</li>
|
|
63
|
+
<li><strong>Total violations:</strong> ${t.totalViolations}</li>
|
|
64
|
+
<li><strong>Distinct rules:</strong> ${t.distinctRules.size}</li>
|
|
65
|
+
</ul>
|
|
66
|
+
<div class="impact-summary">
|
|
67
|
+
<h3>By impact</h3>
|
|
68
|
+
<ul class="summary-list">
|
|
69
|
+
${s||"<li>None</li>"}
|
|
70
|
+
</ul>
|
|
71
|
+
</div>
|
|
72
|
+
</section>
|
|
73
|
+
`.trim()}
|
|
74
|
+
<table>
|
|
75
|
+
<thead>
|
|
76
|
+
<tr>
|
|
77
|
+
<th>URL</th><th>Rule</th><th>Impact</th><th>Description</th><th>Target</th><th>FailureSummary</th>
|
|
78
|
+
</tr>
|
|
79
|
+
</thead>
|
|
80
|
+
<tbody>
|
|
81
|
+
${c.join(`
|
|
82
|
+
`)||'<tr><td colspan="6"><em>No violations found.</em></td></tr>'}
|
|
83
|
+
</tbody>
|
|
84
|
+
</table>
|
|
85
|
+
</body>
|
|
86
|
+
</html>
|
|
87
|
+
`.trim()}function l(r){return String(r??"").replaceAll("&","&").replaceAll("<","<").replaceAll(">",">").replaceAll('"',""").replaceAll("'","'")}function g(r){return String(r??"").toLowerCase().replace(/[^a-z0-9]+/g,"-")}export{w as formatResults};
|