aria-ease 2.8.2 → 2.8.3
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/bin/cli.js +5 -243
- package/dist/{contractTestRunnerPlaywright-YNHMLHQ2.js → contractTestRunnerPlaywright-7O2T4JES.js} +5 -2
- package/dist/{contractTestRunnerPlaywright-ZY2T4UTV.js → contractTestRunnerPlaywright-AJ2DOOJT.js} +10 -2
- package/dist/index.cjs +10 -2
- package/dist/index.js +1 -1
- package/dist/src/utils/test/{contractTestRunnerPlaywright-I36Y2NHA.js → contractTestRunnerPlaywright-7O2T4JES.js} +5 -2
- package/dist/src/utils/test/{contractTestRunnerPlaywright-YNHMLHQ2.js → contractTestRunnerPlaywright-AJ2DOOJT.js} +10 -2
- package/dist/src/utils/test/index.cjs +10 -2
- package/dist/src/utils/test/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-PCORWVIQ.js +0 -213
- package/dist/contractTestRunnerPlaywright-SE6TPWZZ.js +0 -254
- package/dist/src/utils/test/contractTestRunnerPlaywright-ZY2T4UTV.js +0 -249
package/bin/cli.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import "./chunk-JSBRDJBE.js";
|
|
2
3
|
|
|
3
4
|
// src/utils/cli/cli.ts
|
|
4
5
|
import { Command } from "commander";
|
|
@@ -6,248 +7,6 @@ import chalk from "chalk";
|
|
|
6
7
|
import path2 from "path";
|
|
7
8
|
import fs2 from "fs-extra";
|
|
8
9
|
|
|
9
|
-
// src/utils/audit/src/audit/audit.js
|
|
10
|
-
import AxeBuilder from "@axe-core/playwright";
|
|
11
|
-
import { chromium } from "playwright";
|
|
12
|
-
async function runAudit(url, options) {
|
|
13
|
-
let browser;
|
|
14
|
-
const timeout = options?.timeout || 6e4;
|
|
15
|
-
const waitUntil = options?.waitUntil || "domcontentloaded";
|
|
16
|
-
try {
|
|
17
|
-
browser = await chromium.launch({ headless: true });
|
|
18
|
-
const context = await browser.newContext();
|
|
19
|
-
const page = await context.newPage();
|
|
20
|
-
await page.goto(url, { waitUntil, timeout });
|
|
21
|
-
const axe2 = new AxeBuilder({ page });
|
|
22
|
-
const axeResults = await axe2.analyze();
|
|
23
|
-
return axeResults;
|
|
24
|
-
} catch (error) {
|
|
25
|
-
if (error instanceof Error) {
|
|
26
|
-
if (error.message.includes("Executable doesn't exist")) {
|
|
27
|
-
console.error("\n\u274C Playwright browsers not found!\n");
|
|
28
|
-
console.log("\u{1F4E6} First-time setup required:");
|
|
29
|
-
console.log(" Run: npx playwright install chromium\n");
|
|
30
|
-
console.log("\u{1F4A1} This downloads the browser needed for auditing (~200MB)");
|
|
31
|
-
console.log(" You only need to do this once.\n");
|
|
32
|
-
} else if (error.message.includes("page.goto: net::ERR_CONNECTION_REFUSED")) {
|
|
33
|
-
console.error("\n\u274C Server Not Running!\n");
|
|
34
|
-
console.log(" Make sure your server is running before auditing URL");
|
|
35
|
-
console.log(" Run: npm run dev # or your start command");
|
|
36
|
-
} else if (error.message.includes("page.goto: Protocol error (Page.navigate): Cannot navigate to invalid URL")) {
|
|
37
|
-
console.error("\n\u274C Cannot audit invalid URL\n");
|
|
38
|
-
} else {
|
|
39
|
-
console.error("\u274C Audit error:", error.message);
|
|
40
|
-
console.log(" Make sure you provide a valid URL");
|
|
41
|
-
}
|
|
42
|
-
} else {
|
|
43
|
-
console.error("\u274C Audit error (non-Error):", String(error));
|
|
44
|
-
}
|
|
45
|
-
throw error;
|
|
46
|
-
} finally {
|
|
47
|
-
if (browser)
|
|
48
|
-
await browser.close();
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// src/utils/audit/src/formatters/formatters.js
|
|
53
|
-
function formatResults(allResults, format) {
|
|
54
|
-
switch (format) {
|
|
55
|
-
case "json":
|
|
56
|
-
return JSON.stringify(allResults.flatMap(({ url, result }) => result ? result.violations.flatMap((v) => v.nodes.map((n) => ({
|
|
57
|
-
URL: url,
|
|
58
|
-
Rule: v.id,
|
|
59
|
-
Impact: v.impact,
|
|
60
|
-
Description: v.description,
|
|
61
|
-
Target: n.target,
|
|
62
|
-
FailureSummary: n.failureSummary
|
|
63
|
-
}))) : []), null, 2);
|
|
64
|
-
case "csv":
|
|
65
|
-
return toCSV(allResults);
|
|
66
|
-
case "html":
|
|
67
|
-
return toHTML(allResults);
|
|
68
|
-
default:
|
|
69
|
-
return "";
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
function toCSV(allResults) {
|
|
73
|
-
const rows = ["URL,Rule,Impact,Description,Target,FailureSummary"];
|
|
74
|
-
allResults.forEach(({ url, result }) => {
|
|
75
|
-
if (result) {
|
|
76
|
-
result.violations.forEach((v) => {
|
|
77
|
-
v.nodes.forEach((n) => {
|
|
78
|
-
rows.push(escapeCSV(url) + "," + escapeCSV(v.id) + "," + escapeCSV(v.impact) + "," + escapeCSV(v.description) + "," + escapeCSV(Array.isArray(n.target) ? n.target.join("; ") : String(n.target)) + "," + escapeCSV(n.failureSummary ?? ""));
|
|
79
|
-
});
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
});
|
|
83
|
-
return rows.join("\n");
|
|
84
|
-
}
|
|
85
|
-
function escapeCSV(value) {
|
|
86
|
-
const s = String(value ?? "");
|
|
87
|
-
return `"${s.replace(/"/g, '""')}"`;
|
|
88
|
-
}
|
|
89
|
-
function toHTML(allResults) {
|
|
90
|
-
const summary = {
|
|
91
|
-
pagesAudited: 0,
|
|
92
|
-
pagesWithViolations: 0,
|
|
93
|
-
totalViolations: 0,
|
|
94
|
-
distinctRules: /* @__PURE__ */ new Set(),
|
|
95
|
-
impactCounts: /* @__PURE__ */ new Map()
|
|
96
|
-
};
|
|
97
|
-
allResults.forEach(({ result }) => {
|
|
98
|
-
if (!result)
|
|
99
|
-
return;
|
|
100
|
-
summary.pagesAudited++;
|
|
101
|
-
const pageViolations = result.violations.reduce((acc, v) => {
|
|
102
|
-
const nodesCount = (v.nodes || []).length;
|
|
103
|
-
if (nodesCount > 0) {
|
|
104
|
-
summary.distinctRules.add(v.id);
|
|
105
|
-
summary.totalViolations += nodesCount;
|
|
106
|
-
acc += nodesCount;
|
|
107
|
-
const impact = String(v.impact ?? "unknown");
|
|
108
|
-
summary.impactCounts.set(impact, (summary.impactCounts.get(impact) || 0) + nodesCount);
|
|
109
|
-
}
|
|
110
|
-
return acc;
|
|
111
|
-
}, 0);
|
|
112
|
-
if (pageViolations > 0)
|
|
113
|
-
summary.pagesWithViolations++;
|
|
114
|
-
});
|
|
115
|
-
const rows = [];
|
|
116
|
-
allResults.forEach(({ url, result }) => {
|
|
117
|
-
if (!result)
|
|
118
|
-
return;
|
|
119
|
-
result.violations.forEach((v) => {
|
|
120
|
-
v.nodes.forEach((n) => {
|
|
121
|
-
const target = Array.isArray(n.target) ? n.target.join("; ") : String(n.target);
|
|
122
|
-
rows.push(`
|
|
123
|
-
<tr>
|
|
124
|
-
<td class="nowrap">${escapeHTML(url)}</td>
|
|
125
|
-
<td class="nowrap">${escapeHTML(v.id)}</td>
|
|
126
|
-
<td class="impact ${escapeClass(String(v.impact ?? "unknown"))}">${escapeHTML(String(v.impact ?? ""))}</td>
|
|
127
|
-
<td class="desc">${escapeHTML(v.description ?? "")}</td>
|
|
128
|
-
<td class="target"><code>${escapeHTML(target)}</code></td>
|
|
129
|
-
<td class="fail">${escapeHTML(n.failureSummary ?? "").split(/\r?\n/).join("<br/>")}</td>
|
|
130
|
-
</tr>
|
|
131
|
-
`);
|
|
132
|
-
});
|
|
133
|
-
});
|
|
134
|
-
});
|
|
135
|
-
const impactSummary = Array.from(summary.impactCounts.entries()).map(([impact, count]) => `<li><strong class="impact ${escapeClass(impact)}">${escapeHTML(impact)}</strong>: ${count}</li>`).join("\n");
|
|
136
|
-
const d = /* @__PURE__ */ new Date();
|
|
137
|
-
const pad = (n) => String(n).padStart(2, "0");
|
|
138
|
-
const reportDateTime = `${pad(d.getDate())}-${pad(d.getMonth() + 1)}-${d.getFullYear()} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
|
|
139
|
-
const headerSummary = `
|
|
140
|
-
<section class="summary">
|
|
141
|
-
<h2>Report summary</h2>
|
|
142
|
-
<ul>
|
|
143
|
-
<li><strong>Date:</strong> ${reportDateTime}</li>
|
|
144
|
-
<li><strong>Pages audited:</strong> ${summary.pagesAudited}</li>
|
|
145
|
-
<li><strong>Pages with violations:</strong> ${summary.pagesWithViolations}</li>
|
|
146
|
-
<li><strong>Total violations:</strong> ${summary.totalViolations}</li>
|
|
147
|
-
<li><strong>Distinct rules:</strong> ${summary.distinctRules.size}</li>
|
|
148
|
-
</ul>
|
|
149
|
-
<div class="impact-summary">
|
|
150
|
-
<h3>By impact</h3>
|
|
151
|
-
<ul class="summary-list">
|
|
152
|
-
${impactSummary || "<li>None</li>"}
|
|
153
|
-
</ul>
|
|
154
|
-
</div>
|
|
155
|
-
</section>
|
|
156
|
-
`.trim();
|
|
157
|
-
const html = `
|
|
158
|
-
<!DOCTYPE html>
|
|
159
|
-
<html lang="en">
|
|
160
|
-
<head>
|
|
161
|
-
<meta charset="utf-8"/>
|
|
162
|
-
<title>Aria-Ease Accessibility Audit Report</title>
|
|
163
|
-
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
|
164
|
-
<style>
|
|
165
|
-
:root{
|
|
166
|
-
--bg:#ffffff; --muted:#6b7280; --border:#e6e9ee;
|
|
167
|
-
--impact-critical: red; --impact-moderate:#fff4dd; --impact-serious:rgb(255, 123, 0);
|
|
168
|
-
}
|
|
169
|
-
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}
|
|
170
|
-
h1{margin:0 0 8px}
|
|
171
|
-
.summary{background:#f8fafc;border:1px solid var(--border);padding:12px 16px;border-radius:8px;margin-bottom:18px}
|
|
172
|
-
.summary ul{margin:6px 0 0 0;padding:0 18px}
|
|
173
|
-
.impact-summary h3{margin:12px 0 6px}
|
|
174
|
-
table{width:100%; border-collapse:collapse; margin-top:12px}
|
|
175
|
-
th,td{border:1px solid var(--border); padding:10px; text-align:left; vertical-align:top}
|
|
176
|
-
th{background:#f3f4f6; font-weight:600; position:sticky; top:0; z-index:1}
|
|
177
|
-
.nowrap{white-space:nowrap}
|
|
178
|
-
.target code{font-family:ui-monospace, SFMono-Regular, Menlo, Monaco, "Roboto Mono", "Courier New", monospace; white-space:pre-wrap}
|
|
179
|
-
.desc{max-width:380px}
|
|
180
|
-
tr:nth-child(even){background:#fbfbfb}
|
|
181
|
-
td.fail{color:#7b1e1e}
|
|
182
|
-
.impact.critical{background:var(--impact-critical); font-weight:600}
|
|
183
|
-
.impact.moderate{background:var(--impact-moderate); font-weight:600}
|
|
184
|
-
.impact.serious{background:var(--impact-serious); font-weight:600}
|
|
185
|
-
@media (max-width:900px){
|
|
186
|
-
.desc{max-width:200px}
|
|
187
|
-
table, thead, tbody, th, td, tr{display:block}
|
|
188
|
-
thead{display:none}
|
|
189
|
-
tr{margin-bottom:10px; border: 1px solid var(--border);}
|
|
190
|
-
td{border:1px solid var(--border); padding:6px}
|
|
191
|
-
td::before{font-weight:600; display:inline-block; width:120px}
|
|
192
|
-
}
|
|
193
|
-
.summary-list strong,
|
|
194
|
-
.summary-list li {
|
|
195
|
-
padding: 2px 4px;
|
|
196
|
-
}
|
|
197
|
-
</style>
|
|
198
|
-
</head>
|
|
199
|
-
<body>
|
|
200
|
-
<h1>Aria-Ease Accessibility Audit Report</h1>
|
|
201
|
-
${headerSummary}
|
|
202
|
-
<table>
|
|
203
|
-
<thead>
|
|
204
|
-
<tr>
|
|
205
|
-
<th>URL</th><th>Rule</th><th>Impact</th><th>Description</th><th>Target</th><th>FailureSummary</th>
|
|
206
|
-
</tr>
|
|
207
|
-
</thead>
|
|
208
|
-
<tbody>
|
|
209
|
-
${rows.join("\n") || '<tr><td colspan="6"><em>No violations found.</em></td></tr>'}
|
|
210
|
-
</tbody>
|
|
211
|
-
</table>
|
|
212
|
-
</body>
|
|
213
|
-
</html>
|
|
214
|
-
`.trim();
|
|
215
|
-
return html;
|
|
216
|
-
}
|
|
217
|
-
function escapeHTML(str) {
|
|
218
|
-
return String(str ?? "").replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
219
|
-
}
|
|
220
|
-
function escapeClass(s) {
|
|
221
|
-
return String(s ?? "").toLowerCase().replace(/[^a-z0-9]+/g, "-");
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
// src/utils/test/src/test.ts
|
|
225
|
-
import { axe } from "jest-axe";
|
|
226
|
-
var runTest = async () => {
|
|
227
|
-
};
|
|
228
|
-
if (typeof window === "undefined") {
|
|
229
|
-
runTest = async () => {
|
|
230
|
-
console.log(`\u{1F680} Running component accessibility tests...
|
|
231
|
-
`);
|
|
232
|
-
const { exec } = await import("child_process");
|
|
233
|
-
exec(
|
|
234
|
-
`npx vitest --run --reporter verbose`,
|
|
235
|
-
{ cwd: process.cwd() },
|
|
236
|
-
(error, stdout, stderr) => {
|
|
237
|
-
if (stdout) {
|
|
238
|
-
console.log(stdout);
|
|
239
|
-
}
|
|
240
|
-
if (stderr) {
|
|
241
|
-
console.error(stderr);
|
|
242
|
-
}
|
|
243
|
-
if (error && error.code) {
|
|
244
|
-
process.exit(error.code);
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
);
|
|
248
|
-
};
|
|
249
|
-
}
|
|
250
|
-
|
|
251
10
|
// src/utils/cli/configLoader.js
|
|
252
11
|
import path from "path";
|
|
253
12
|
import fs from "fs-extra";
|
|
@@ -369,6 +128,8 @@ var program = new Command();
|
|
|
369
128
|
program.name("aria-ease").description("Run accessibility tests and audits").version("2.2.3");
|
|
370
129
|
program.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 (opts) => {
|
|
371
130
|
console.log(chalk.cyanBright("\u{1F680} Starting accessibility audit...\n"));
|
|
131
|
+
const { runAudit } = await import("./audit-XABLAI36.js");
|
|
132
|
+
const { formatResults } = await import("./formatters-2RPHPXW2.js");
|
|
372
133
|
const { config, configPath, errors } = await loadConfig(process.cwd());
|
|
373
134
|
if (configPath) {
|
|
374
135
|
console.log(chalk.green(`\u2705 Loaded config from ${path2.basename(configPath)}
|
|
@@ -442,7 +203,8 @@ program.command("audit").description("Run axe-core powered accessibility audit o
|
|
|
442
203
|
}
|
|
443
204
|
console.log(chalk.green("\n\u{1F389} All audits completed."));
|
|
444
205
|
});
|
|
445
|
-
program.command("test").description("Run core a11y accessibility standard tests on UI components").action(() => {
|
|
206
|
+
program.command("test").description("Run core a11y accessibility standard tests on UI components").action(async () => {
|
|
207
|
+
const { runTest } = await import("./test-S2U633GD.js");
|
|
446
208
|
runTest();
|
|
447
209
|
});
|
|
448
210
|
program.command("help").description("Display help information").action(() => {
|
package/dist/{contractTestRunnerPlaywright-YNHMLHQ2.js → contractTestRunnerPlaywright-7O2T4JES.js}
RENAMED
|
@@ -25,8 +25,11 @@ async function runContractTestsPlaywright(componentName, url) {
|
|
|
25
25
|
browser = await chromium.launch({ headless: true });
|
|
26
26
|
const context = await browser.newContext();
|
|
27
27
|
const page = await context.newPage();
|
|
28
|
-
await page.goto(url, {
|
|
29
|
-
|
|
28
|
+
await page.goto(url, {
|
|
29
|
+
waitUntil: "domcontentloaded",
|
|
30
|
+
timeout: 6e4
|
|
31
|
+
});
|
|
32
|
+
await page.waitForSelector(componentContract.selectors.trigger, { timeout: 6e4 });
|
|
30
33
|
async function resolveRelativeTarget(selector, relative) {
|
|
31
34
|
const items = await page.locator(selector).all();
|
|
32
35
|
switch (relative) {
|
package/dist/{contractTestRunnerPlaywright-ZY2T4UTV.js → contractTestRunnerPlaywright-AJ2DOOJT.js}
RENAMED
|
@@ -25,8 +25,11 @@ async function runContractTestsPlaywright(componentName, url) {
|
|
|
25
25
|
browser = await chromium.launch({ headless: true });
|
|
26
26
|
const context = await browser.newContext();
|
|
27
27
|
const page = await context.newPage();
|
|
28
|
-
await page.goto(url, {
|
|
29
|
-
|
|
28
|
+
await page.goto(url, {
|
|
29
|
+
waitUntil: "domcontentloaded",
|
|
30
|
+
timeout: 6e4
|
|
31
|
+
});
|
|
32
|
+
await page.waitForSelector(componentContract.selectors.trigger, { timeout: 6e4 });
|
|
30
33
|
async function resolveRelativeTarget(selector, relative) {
|
|
31
34
|
const items = await page.locator(selector).all();
|
|
32
35
|
switch (relative) {
|
|
@@ -148,6 +151,11 @@ async function runContractTestsPlaywright(componentName, url) {
|
|
|
148
151
|
continue;
|
|
149
152
|
}
|
|
150
153
|
const target = page.locator(keypressSelector).first();
|
|
154
|
+
const elementCount = await target.count();
|
|
155
|
+
if (elementCount === 0) {
|
|
156
|
+
reporter.reportTest(dynamicTest, "skip", `Skipping test - ${act.target} element not found (optional submenu test)`);
|
|
157
|
+
break;
|
|
158
|
+
}
|
|
151
159
|
await target.press(keyValue);
|
|
152
160
|
}
|
|
153
161
|
}
|
package/dist/index.cjs
CHANGED
|
@@ -9419,8 +9419,11 @@ async function runContractTestsPlaywright(componentName, url) {
|
|
|
9419
9419
|
browser = await import_playwright.chromium.launch({ headless: true });
|
|
9420
9420
|
const context = await browser.newContext();
|
|
9421
9421
|
const page = await context.newPage();
|
|
9422
|
-
await page.goto(url, {
|
|
9423
|
-
|
|
9422
|
+
await page.goto(url, {
|
|
9423
|
+
waitUntil: "domcontentloaded",
|
|
9424
|
+
timeout: 6e4
|
|
9425
|
+
});
|
|
9426
|
+
await page.waitForSelector(componentContract.selectors.trigger, { timeout: 6e4 });
|
|
9424
9427
|
async function resolveRelativeTarget(selector, relative) {
|
|
9425
9428
|
const items = await page.locator(selector).all();
|
|
9426
9429
|
switch (relative) {
|
|
@@ -9542,6 +9545,11 @@ async function runContractTestsPlaywright(componentName, url) {
|
|
|
9542
9545
|
continue;
|
|
9543
9546
|
}
|
|
9544
9547
|
const target = page.locator(keypressSelector).first();
|
|
9548
|
+
const elementCount = await target.count();
|
|
9549
|
+
if (elementCount === 0) {
|
|
9550
|
+
reporter.reportTest(dynamicTest, "skip", `Skipping test - ${act.target} element not found (optional submenu test)`);
|
|
9551
|
+
break;
|
|
9552
|
+
}
|
|
9545
9553
|
await target.press(keyValue);
|
|
9546
9554
|
}
|
|
9547
9555
|
}
|
package/dist/index.js
CHANGED
|
@@ -13171,7 +13171,7 @@ async function testUiComponent(componentName, component, url) {
|
|
|
13171
13171
|
let contract;
|
|
13172
13172
|
if (url) {
|
|
13173
13173
|
console.log(`\u{1F3AD} Running Playwright E2E tests on ${url}`);
|
|
13174
|
-
const { runContractTestsPlaywright } = await import("./contractTestRunnerPlaywright-
|
|
13174
|
+
const { runContractTestsPlaywright } = await import("./contractTestRunnerPlaywright-AJ2DOOJT.js");
|
|
13175
13175
|
contract = await runContractTestsPlaywright(componentName, url);
|
|
13176
13176
|
} else {
|
|
13177
13177
|
console.log(`\u{1F9EA} Running jsdom tests (limited event handling)`);
|
|
@@ -21,8 +21,11 @@ async function runContractTestsPlaywright(componentName, url) {
|
|
|
21
21
|
browser = await chromium.launch({ headless: true });
|
|
22
22
|
const context = await browser.newContext();
|
|
23
23
|
const page = await context.newPage();
|
|
24
|
-
await page.goto(url, {
|
|
25
|
-
|
|
24
|
+
await page.goto(url, {
|
|
25
|
+
waitUntil: "domcontentloaded",
|
|
26
|
+
timeout: 6e4
|
|
27
|
+
});
|
|
28
|
+
await page.waitForSelector(componentContract.selectors.trigger, { timeout: 6e4 });
|
|
26
29
|
async function resolveRelativeTarget(selector, relative) {
|
|
27
30
|
const items = await page.locator(selector).all();
|
|
28
31
|
switch (relative) {
|
|
@@ -21,8 +21,11 @@ async function runContractTestsPlaywright(componentName, url) {
|
|
|
21
21
|
browser = await chromium.launch({ headless: true });
|
|
22
22
|
const context = await browser.newContext();
|
|
23
23
|
const page = await context.newPage();
|
|
24
|
-
await page.goto(url, {
|
|
25
|
-
|
|
24
|
+
await page.goto(url, {
|
|
25
|
+
waitUntil: "domcontentloaded",
|
|
26
|
+
timeout: 6e4
|
|
27
|
+
});
|
|
28
|
+
await page.waitForSelector(componentContract.selectors.trigger, { timeout: 6e4 });
|
|
26
29
|
async function resolveRelativeTarget(selector, relative) {
|
|
27
30
|
const items = await page.locator(selector).all();
|
|
28
31
|
switch (relative) {
|
|
@@ -144,6 +147,11 @@ async function runContractTestsPlaywright(componentName, url) {
|
|
|
144
147
|
continue;
|
|
145
148
|
}
|
|
146
149
|
const target = page.locator(keypressSelector).first();
|
|
150
|
+
const elementCount = await target.count();
|
|
151
|
+
if (elementCount === 0) {
|
|
152
|
+
reporter.reportTest(dynamicTest, "skip", `Skipping test - ${act.target} element not found (optional submenu test)`);
|
|
153
|
+
break;
|
|
154
|
+
}
|
|
147
155
|
await target.press(keyValue);
|
|
148
156
|
}
|
|
149
157
|
}
|
|
@@ -9257,8 +9257,11 @@ async function runContractTestsPlaywright(componentName, url) {
|
|
|
9257
9257
|
browser = await playwright.chromium.launch({ headless: true });
|
|
9258
9258
|
const context = await browser.newContext();
|
|
9259
9259
|
const page = await context.newPage();
|
|
9260
|
-
await page.goto(url, {
|
|
9261
|
-
|
|
9260
|
+
await page.goto(url, {
|
|
9261
|
+
waitUntil: "domcontentloaded",
|
|
9262
|
+
timeout: 6e4
|
|
9263
|
+
});
|
|
9264
|
+
await page.waitForSelector(componentContract.selectors.trigger, { timeout: 6e4 });
|
|
9262
9265
|
async function resolveRelativeTarget(selector, relative) {
|
|
9263
9266
|
const items = await page.locator(selector).all();
|
|
9264
9267
|
switch (relative) {
|
|
@@ -9380,6 +9383,11 @@ async function runContractTestsPlaywright(componentName, url) {
|
|
|
9380
9383
|
continue;
|
|
9381
9384
|
}
|
|
9382
9385
|
const target = page.locator(keypressSelector).first();
|
|
9386
|
+
const elementCount = await target.count();
|
|
9387
|
+
if (elementCount === 0) {
|
|
9388
|
+
reporter.reportTest(dynamicTest, "skip", `Skipping test - ${act.target} element not found (optional submenu test)`);
|
|
9389
|
+
break;
|
|
9390
|
+
}
|
|
9383
9391
|
await target.press(keyValue);
|
|
9384
9392
|
}
|
|
9385
9393
|
}
|
|
@@ -12524,7 +12524,7 @@ async function testUiComponent(componentName, component, url) {
|
|
|
12524
12524
|
let contract;
|
|
12525
12525
|
if (url) {
|
|
12526
12526
|
console.log(`\u{1F3AD} Running Playwright E2E tests on ${url}`);
|
|
12527
|
-
const { runContractTestsPlaywright } = await import('./contractTestRunnerPlaywright-
|
|
12527
|
+
const { runContractTestsPlaywright } = await import('./contractTestRunnerPlaywright-AJ2DOOJT.js');
|
|
12528
12528
|
contract = await runContractTestsPlaywright(componentName, url);
|
|
12529
12529
|
} else {
|
|
12530
12530
|
console.log(`\u{1F9EA} Running jsdom tests (limited event handling)`);
|
package/package.json
CHANGED
package/dist/chunk-PCORWVIQ.js
DELETED
|
@@ -1,213 +0,0 @@
|
|
|
1
|
-
var __create = Object.create;
|
|
2
|
-
var __defProp = Object.defineProperty;
|
|
3
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
6
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
-
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
8
|
-
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
9
|
-
}) : x)(function(x) {
|
|
10
|
-
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
11
|
-
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
12
|
-
});
|
|
13
|
-
var __commonJS = (cb, mod) => function __require2() {
|
|
14
|
-
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
15
|
-
};
|
|
16
|
-
var __copyProps = (to, from, except, desc) => {
|
|
17
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
18
|
-
for (let key of __getOwnPropNames(from))
|
|
19
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
20
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
21
|
-
}
|
|
22
|
-
return to;
|
|
23
|
-
};
|
|
24
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
25
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
26
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
27
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
28
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
29
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
30
|
-
mod
|
|
31
|
-
));
|
|
32
|
-
|
|
33
|
-
// src/utils/test/contract/contract.json
|
|
34
|
-
var contract_default = {
|
|
35
|
-
menu: {
|
|
36
|
-
path: "./contracts/MenuContract.json",
|
|
37
|
-
component: "menu"
|
|
38
|
-
}
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
// src/utils/test/contract/ContractReporter.ts
|
|
42
|
-
var ContractReporter = class {
|
|
43
|
-
startTime = 0;
|
|
44
|
-
componentName = "";
|
|
45
|
-
staticPasses = 0;
|
|
46
|
-
staticFailures = 0;
|
|
47
|
-
dynamicResults = [];
|
|
48
|
-
totalTests = 0;
|
|
49
|
-
skipped = 0;
|
|
50
|
-
isPlaywright = false;
|
|
51
|
-
constructor(isPlaywright = false) {
|
|
52
|
-
this.isPlaywright = isPlaywright;
|
|
53
|
-
}
|
|
54
|
-
log(message) {
|
|
55
|
-
process.stderr.write(message + "\n");
|
|
56
|
-
}
|
|
57
|
-
start(componentName, totalTests) {
|
|
58
|
-
this.startTime = Date.now();
|
|
59
|
-
this.componentName = componentName;
|
|
60
|
-
this.totalTests = totalTests;
|
|
61
|
-
const mode = this.isPlaywright ? "Playwright (Real Browser)" : "jsdom (Fast)";
|
|
62
|
-
this.log(`
|
|
63
|
-
${"\u2550".repeat(60)}`);
|
|
64
|
-
this.log(`\u{1F50D} Testing ${componentName} Component - ${mode}`);
|
|
65
|
-
this.log(`${"\u2550".repeat(60)}
|
|
66
|
-
`);
|
|
67
|
-
}
|
|
68
|
-
reportStatic(passes, failures) {
|
|
69
|
-
this.staticPasses = passes;
|
|
70
|
-
this.staticFailures = failures;
|
|
71
|
-
const icon = failures === 0 ? "\u2705" : "\u274C";
|
|
72
|
-
const status = failures === 0 ? "PASS" : "FAIL";
|
|
73
|
-
this.log(`${icon} Static ARIA Tests: ${status}`);
|
|
74
|
-
this.log(` ${passes}/${passes + failures} required attributes present
|
|
75
|
-
`);
|
|
76
|
-
}
|
|
77
|
-
/**
|
|
78
|
-
* Report individual dynamic test result
|
|
79
|
-
*/
|
|
80
|
-
reportTest(test, status, failureMessage) {
|
|
81
|
-
const result = {
|
|
82
|
-
description: test.description,
|
|
83
|
-
status,
|
|
84
|
-
failureMessage
|
|
85
|
-
};
|
|
86
|
-
if (status === "skip" && test.requiresBrowser) {
|
|
87
|
-
result.skipReason = "Requires real browser (addEventListener events)";
|
|
88
|
-
}
|
|
89
|
-
this.dynamicResults.push(result);
|
|
90
|
-
const icons = { pass: "\u2713", fail: "\u2717", skip: "\u25CB" };
|
|
91
|
-
this.log(` ${icons[status]} ${test.description}`);
|
|
92
|
-
if (status === "skip" && !this.isPlaywright) {
|
|
93
|
-
this.log(` \u21B3 Skipped in jsdom (runs in Playwright)`);
|
|
94
|
-
}
|
|
95
|
-
if (status === "fail" && failureMessage) {
|
|
96
|
-
this.log(` \u21B3 ${failureMessage}`);
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
/**
|
|
100
|
-
* Report all failures with actionable context
|
|
101
|
-
*/
|
|
102
|
-
reportFailures(failures) {
|
|
103
|
-
if (failures.length === 0) return;
|
|
104
|
-
this.log(`
|
|
105
|
-
${"\u2500".repeat(60)}`);
|
|
106
|
-
this.log(`\u274C Failures (${failures.length}):
|
|
107
|
-
`);
|
|
108
|
-
failures.forEach((failure, index) => {
|
|
109
|
-
this.log(`${index + 1}. ${failure}`);
|
|
110
|
-
if (failure.includes("aria-")) {
|
|
111
|
-
this.log(` \u{1F4A1} Add the missing ARIA attribute to improve screen reader support`);
|
|
112
|
-
} else if (failure.includes("focus")) {
|
|
113
|
-
this.log(` \u{1F4A1} Check keyboard event handlers and focus management`);
|
|
114
|
-
} else if (failure.includes("visible")) {
|
|
115
|
-
this.log(` \u{1F4A1} Verify display/visibility styles and state management`);
|
|
116
|
-
}
|
|
117
|
-
this.log("");
|
|
118
|
-
});
|
|
119
|
-
}
|
|
120
|
-
/**
|
|
121
|
-
* Report skipped tests with helpful context
|
|
122
|
-
*/
|
|
123
|
-
reportSkipped() {
|
|
124
|
-
if (this.skipped === 0 || this.isPlaywright) return;
|
|
125
|
-
const skippedTests = this.dynamicResults.filter((r) => r.status === "skip");
|
|
126
|
-
this.log(`
|
|
127
|
-
${"\u2500".repeat(60)}`);
|
|
128
|
-
this.log(`\u2139\uFE0F Skipped Tests (${this.skipped}):
|
|
129
|
-
`);
|
|
130
|
-
this.log(`These tests use native keyboard events via addEventListener,`);
|
|
131
|
-
this.log(`which jsdom cannot simulate. They run successfully in Playwright.
|
|
132
|
-
`);
|
|
133
|
-
skippedTests.forEach((test, index) => {
|
|
134
|
-
this.log(`${index + 1}. ${test.description}`);
|
|
135
|
-
});
|
|
136
|
-
this.log(`
|
|
137
|
-
\u{1F4A1} Run with Playwright for full validation:`);
|
|
138
|
-
this.log(` testUiComponent('${this.componentName}', component, 'http://localhost:5173/')
|
|
139
|
-
`);
|
|
140
|
-
}
|
|
141
|
-
/**
|
|
142
|
-
* Generate final summary with statistics
|
|
143
|
-
*/
|
|
144
|
-
summary(failures) {
|
|
145
|
-
const duration = Date.now() - this.startTime;
|
|
146
|
-
const dynamicPasses = this.dynamicResults.filter((r) => r.status === "pass").length;
|
|
147
|
-
const dynamicFailures = this.dynamicResults.filter((r) => r.status === "fail").length;
|
|
148
|
-
this.skipped = this.dynamicResults.filter((r) => r.status === "skip").length;
|
|
149
|
-
const totalPasses = this.staticPasses + dynamicPasses;
|
|
150
|
-
const totalFailures = this.staticFailures + dynamicFailures;
|
|
151
|
-
const totalRun = totalPasses + totalFailures;
|
|
152
|
-
if (failures.length > 0) {
|
|
153
|
-
this.reportFailures(failures);
|
|
154
|
-
}
|
|
155
|
-
this.reportSkipped();
|
|
156
|
-
this.log(`
|
|
157
|
-
${"\u2550".repeat(60)}`);
|
|
158
|
-
this.log(`\u{1F4CA} Summary
|
|
159
|
-
`);
|
|
160
|
-
if (totalFailures === 0 && this.skipped === 0) {
|
|
161
|
-
this.log(`\u2705 All ${totalRun} tests passed!`);
|
|
162
|
-
this.log(` ${this.componentName} component meets APG and WCAG guidelines \u2713`);
|
|
163
|
-
} else if (totalFailures === 0) {
|
|
164
|
-
this.log(`\u2705 ${totalPasses}/${totalRun} tests passed`);
|
|
165
|
-
this.log(`\u25CB ${this.skipped} tests skipped (jsdom limitation)`);
|
|
166
|
-
this.log(` ${this.componentName} component works correctly`);
|
|
167
|
-
} else {
|
|
168
|
-
this.log(`\u274C ${totalFailures} test${totalFailures > 1 ? "s" : ""} failed`);
|
|
169
|
-
this.log(`\u2705 ${totalPasses} test${totalPasses > 1 ? "s" : ""} passed`);
|
|
170
|
-
if (this.skipped > 0) {
|
|
171
|
-
this.log(`\u25CB ${this.skipped} test${this.skipped > 1 ? "s" : ""} skipped`);
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
this.log(`\u23F1\uFE0F Duration: ${duration}ms`);
|
|
175
|
-
this.log(`${"\u2550".repeat(60)}
|
|
176
|
-
`);
|
|
177
|
-
if (totalFailures > 0) {
|
|
178
|
-
this.log(`\u{1F527} Next Steps:`);
|
|
179
|
-
this.log(` 1. Review the failures above`);
|
|
180
|
-
this.log(` 2. Fix ARIA attributes and keyboard handlers`);
|
|
181
|
-
this.log(` 3. Re-run tests to verify fixes
|
|
182
|
-
`);
|
|
183
|
-
} else if (!this.isPlaywright && this.skipped > 0) {
|
|
184
|
-
this.log(`\u2728 Optional: Run Playwright tests for complete validation
|
|
185
|
-
`);
|
|
186
|
-
}
|
|
187
|
-
return {
|
|
188
|
-
passes: totalPasses,
|
|
189
|
-
failures: totalFailures,
|
|
190
|
-
skipped: this.skipped,
|
|
191
|
-
duration
|
|
192
|
-
};
|
|
193
|
-
}
|
|
194
|
-
/**
|
|
195
|
-
* Report an error during test execution
|
|
196
|
-
*/
|
|
197
|
-
error(message, context) {
|
|
198
|
-
this.log(`
|
|
199
|
-
\u274C Error: ${message}`);
|
|
200
|
-
if (context) {
|
|
201
|
-
this.log(` Context: ${context}`);
|
|
202
|
-
}
|
|
203
|
-
this.log("");
|
|
204
|
-
}
|
|
205
|
-
};
|
|
206
|
-
|
|
207
|
-
export {
|
|
208
|
-
__require,
|
|
209
|
-
__commonJS,
|
|
210
|
-
__toESM,
|
|
211
|
-
contract_default,
|
|
212
|
-
ContractReporter
|
|
213
|
-
};
|
|
@@ -1,254 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
ContractReporter,
|
|
3
|
-
contract_default
|
|
4
|
-
} from "./chunk-PCORWVIQ.js";
|
|
5
|
-
|
|
6
|
-
// src/utils/test/contract/contractTestRunnerPlaywright.ts
|
|
7
|
-
import { chromium } from "playwright";
|
|
8
|
-
import { readFileSync } from "fs";
|
|
9
|
-
async function runContractTestsPlaywright(componentName, url) {
|
|
10
|
-
const reporter = new ContractReporter(true);
|
|
11
|
-
const contractTyped = contract_default;
|
|
12
|
-
const contractPath = contractTyped[componentName]?.path;
|
|
13
|
-
if (!contractPath) {
|
|
14
|
-
throw new Error(`Contract path not found for component: ${componentName}`);
|
|
15
|
-
}
|
|
16
|
-
const resolvedPath = new URL(contractPath, import.meta.url).pathname;
|
|
17
|
-
const contractData = readFileSync(resolvedPath, "utf-8");
|
|
18
|
-
const componentContract = JSON.parse(contractData);
|
|
19
|
-
const totalTests = componentContract.static[0].assertions.length + componentContract.dynamic.length;
|
|
20
|
-
reporter.start(componentName, totalTests);
|
|
21
|
-
const failures = [];
|
|
22
|
-
const passes = [];
|
|
23
|
-
let browser = null;
|
|
24
|
-
try {
|
|
25
|
-
browser = await chromium.launch({ headless: true });
|
|
26
|
-
const context = await browser.newContext();
|
|
27
|
-
const page = await context.newPage();
|
|
28
|
-
await page.goto(url, { waitUntil: "networkidle" });
|
|
29
|
-
await page.waitForSelector(componentContract.selectors.trigger, { timeout: 5e3 });
|
|
30
|
-
async function resolveRelativeTarget(selector, relative) {
|
|
31
|
-
const items = await page.locator(selector).all();
|
|
32
|
-
switch (relative) {
|
|
33
|
-
case "first":
|
|
34
|
-
return items[0];
|
|
35
|
-
case "second":
|
|
36
|
-
return items[1];
|
|
37
|
-
case "last":
|
|
38
|
-
return items[items.length - 1];
|
|
39
|
-
case "next": {
|
|
40
|
-
const currentIndex = await page.evaluate(([sel]) => {
|
|
41
|
-
const items2 = Array.from(document.querySelectorAll(sel));
|
|
42
|
-
return items2.indexOf(document.activeElement);
|
|
43
|
-
}, [selector]);
|
|
44
|
-
const nextIndex = (currentIndex + 1) % items.length;
|
|
45
|
-
return items[nextIndex];
|
|
46
|
-
}
|
|
47
|
-
case "previous": {
|
|
48
|
-
const currentIndex = await page.evaluate(([sel]) => {
|
|
49
|
-
const items2 = Array.from(document.querySelectorAll(sel));
|
|
50
|
-
return items2.indexOf(document.activeElement);
|
|
51
|
-
}, [selector]);
|
|
52
|
-
const prevIndex = (currentIndex - 1 + items.length) % items.length;
|
|
53
|
-
return items[prevIndex];
|
|
54
|
-
}
|
|
55
|
-
default:
|
|
56
|
-
return null;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
for (const test of componentContract.static[0]?.assertions || []) {
|
|
60
|
-
if (test.target === "relative") continue;
|
|
61
|
-
const targetSelector = componentContract.selectors[test.target];
|
|
62
|
-
if (!targetSelector) {
|
|
63
|
-
failures.push(`Selector for target ${test.target} not found.`);
|
|
64
|
-
continue;
|
|
65
|
-
}
|
|
66
|
-
const target = page.locator(targetSelector).first();
|
|
67
|
-
const exists = await target.count() > 0;
|
|
68
|
-
if (!exists) {
|
|
69
|
-
failures.push(`Target ${test.target} not found.`);
|
|
70
|
-
continue;
|
|
71
|
-
}
|
|
72
|
-
if (!test.expectedValue) {
|
|
73
|
-
const attributes = test.attribute.split(" | ");
|
|
74
|
-
let hasAny = false;
|
|
75
|
-
for (const attr of attributes) {
|
|
76
|
-
const value = await target.getAttribute(attr.trim());
|
|
77
|
-
if (value !== null) {
|
|
78
|
-
hasAny = true;
|
|
79
|
-
break;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
if (!hasAny) {
|
|
83
|
-
failures.push(test.failureMessage + ` None of the attributes "${test.attribute}" are present.`);
|
|
84
|
-
} else {
|
|
85
|
-
passes.push(`At least one of the attributes "${test.attribute}" exists on the element.`);
|
|
86
|
-
}
|
|
87
|
-
} else {
|
|
88
|
-
const attributeValue = await target.getAttribute(test.attribute);
|
|
89
|
-
const expectedValues = test.expectedValue.split(" | ");
|
|
90
|
-
if (!attributeValue || !expectedValues.includes(attributeValue)) {
|
|
91
|
-
failures.push(test.failureMessage + ` Attribute value does not match expected value. Expected: ${test.expectedValue}, Found: ${attributeValue}`);
|
|
92
|
-
} else {
|
|
93
|
-
passes.push(`Attribute value matches expected value. Expected: ${test.expectedValue}, Found: ${attributeValue}`);
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
for (const dynamicTest of componentContract.dynamic || []) {
|
|
98
|
-
const { action, assertions } = dynamicTest;
|
|
99
|
-
const failuresBeforeTest = failures.length;
|
|
100
|
-
const containerElement = page.locator(componentContract.selectors.container).first();
|
|
101
|
-
const triggerElement = page.locator(componentContract.selectors.trigger).first();
|
|
102
|
-
const isContainerVisible = await containerElement.isVisible();
|
|
103
|
-
if (isContainerVisible) {
|
|
104
|
-
await triggerElement.click();
|
|
105
|
-
await page.waitForTimeout(50);
|
|
106
|
-
}
|
|
107
|
-
for (const act of action) {
|
|
108
|
-
if (act.type === "click") {
|
|
109
|
-
if (act.target === "document") {
|
|
110
|
-
await page.mouse.click(10, 10);
|
|
111
|
-
} else {
|
|
112
|
-
const actionSelector = componentContract.selectors[act.target];
|
|
113
|
-
if (!actionSelector) {
|
|
114
|
-
failures.push(`Selector for action target ${act.target} not found.`);
|
|
115
|
-
continue;
|
|
116
|
-
}
|
|
117
|
-
await page.locator(actionSelector).first().click();
|
|
118
|
-
await page.waitForTimeout(200);
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
if (act.type === "keypress" && act.key) {
|
|
122
|
-
const keyMap = {
|
|
123
|
-
"Space": "Space",
|
|
124
|
-
"Enter": "Enter",
|
|
125
|
-
"Escape": "Escape",
|
|
126
|
-
"Arrow Up": "ArrowUp",
|
|
127
|
-
"Arrow Down": "ArrowDown",
|
|
128
|
-
"Arrow Left": "ArrowLeft",
|
|
129
|
-
"Arrow Right": "ArrowRight",
|
|
130
|
-
"Home": "Home",
|
|
131
|
-
"End": "End",
|
|
132
|
-
"Tab": "Tab"
|
|
133
|
-
};
|
|
134
|
-
let keyValue = keyMap[act.key] || act.key;
|
|
135
|
-
if (keyValue === "Space") {
|
|
136
|
-
keyValue = " ";
|
|
137
|
-
} else if (keyValue.includes(" ")) {
|
|
138
|
-
keyValue = keyValue.replace(/ /g, "");
|
|
139
|
-
}
|
|
140
|
-
if (act.target === "focusable" && ["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight", "Escape"].includes(keyValue)) {
|
|
141
|
-
await page.waitForTimeout(100);
|
|
142
|
-
await page.keyboard.press(keyValue);
|
|
143
|
-
await page.waitForTimeout(50);
|
|
144
|
-
} else {
|
|
145
|
-
const keypressSelector = componentContract.selectors[act.target];
|
|
146
|
-
if (!keypressSelector) {
|
|
147
|
-
failures.push(`Selector for keypress target ${act.target} not found.`);
|
|
148
|
-
continue;
|
|
149
|
-
}
|
|
150
|
-
const target = page.locator(keypressSelector).first();
|
|
151
|
-
await target.press(keyValue);
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
await page.waitForTimeout(100);
|
|
155
|
-
}
|
|
156
|
-
for (const assertion of assertions) {
|
|
157
|
-
let target;
|
|
158
|
-
if (assertion.target === "relative") {
|
|
159
|
-
const relativeSelector = componentContract.selectors.relative;
|
|
160
|
-
if (!relativeSelector) {
|
|
161
|
-
failures.push("Relative selector is not defined in the contract.");
|
|
162
|
-
continue;
|
|
163
|
-
}
|
|
164
|
-
if (!assertion.expectedValue) {
|
|
165
|
-
failures.push("Expected value for relative target is not defined.");
|
|
166
|
-
continue;
|
|
167
|
-
}
|
|
168
|
-
target = await resolveRelativeTarget(relativeSelector, assertion.expectedValue);
|
|
169
|
-
} else {
|
|
170
|
-
const assertionSelector = componentContract.selectors[assertion.target];
|
|
171
|
-
if (!assertionSelector) {
|
|
172
|
-
failures.push(`Selector for assertion target ${assertion.target} not found.`);
|
|
173
|
-
continue;
|
|
174
|
-
}
|
|
175
|
-
target = page.locator(assertionSelector).first();
|
|
176
|
-
}
|
|
177
|
-
if (!target) {
|
|
178
|
-
failures.push(`Target ${assertion.target} not found.`);
|
|
179
|
-
continue;
|
|
180
|
-
}
|
|
181
|
-
if (assertion.assertion === "toBeVisible") {
|
|
182
|
-
const isVisible = await target.isVisible();
|
|
183
|
-
if (isVisible) {
|
|
184
|
-
passes.push(`${assertion.target} is visible as expected. Test: "${dynamicTest.description}".`);
|
|
185
|
-
} else {
|
|
186
|
-
failures.push(`${assertion.failureMessage}`);
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
if (assertion.assertion === "notToBeVisible") {
|
|
190
|
-
const isVisible = await target.isVisible();
|
|
191
|
-
if (!isVisible) {
|
|
192
|
-
passes.push(`${assertion.target} is not visible as expected. Test: "${dynamicTest.description}".`);
|
|
193
|
-
} else {
|
|
194
|
-
failures.push(assertion.failureMessage + ` ${assertion.target} is still visible.`);
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
if (assertion.assertion === "toHaveAttribute" && assertion.attribute && assertion.expectedValue) {
|
|
198
|
-
const attributeValue = await target.getAttribute(assertion.attribute);
|
|
199
|
-
if (attributeValue === assertion.expectedValue) {
|
|
200
|
-
passes.push(`${assertion.target} has expected "${assertion.attribute}". Test: "${dynamicTest.description}".`);
|
|
201
|
-
} else {
|
|
202
|
-
failures.push(assertion.failureMessage + ` ${assertion.target} "${assertion.attribute}" should be "${assertion.expectedValue}", found "${attributeValue}".`);
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
if (assertion.assertion === "toHaveFocus") {
|
|
206
|
-
const hasFocus = await target.evaluate((el) => el === document.activeElement);
|
|
207
|
-
if (hasFocus) {
|
|
208
|
-
passes.push(`${assertion.target} has focus as expected. Test: "${dynamicTest.description}".`);
|
|
209
|
-
} else {
|
|
210
|
-
failures.push(`${assertion.failureMessage}`);
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
if (assertion.assertion === "toHaveRole" && assertion.expectedValue) {
|
|
214
|
-
const roleValue = await target.getAttribute("role");
|
|
215
|
-
if (roleValue === assertion.expectedValue) {
|
|
216
|
-
passes.push(`${assertion.target} has role "${assertion.expectedValue}". Test: "${dynamicTest.description}".`);
|
|
217
|
-
} else {
|
|
218
|
-
failures.push(assertion.failureMessage + ` Expected role "${assertion.expectedValue}", found "${roleValue}".`);
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
const failuresAfterTest = failures.length;
|
|
223
|
-
const testPassed = failuresAfterTest === failuresBeforeTest;
|
|
224
|
-
const failureMessage = testPassed ? void 0 : failures[failures.length - 1];
|
|
225
|
-
reporter.reportTest(dynamicTest, testPassed ? "pass" : "fail", failureMessage);
|
|
226
|
-
}
|
|
227
|
-
const staticPassed = componentContract.static[0].assertions.length;
|
|
228
|
-
const staticFailed = 0;
|
|
229
|
-
reporter.reportStatic(staticPassed, staticFailed);
|
|
230
|
-
reporter.summary(failures);
|
|
231
|
-
} catch (error) {
|
|
232
|
-
if (error instanceof Error) {
|
|
233
|
-
if (error.message.includes("Executable doesn't exist")) {
|
|
234
|
-
console.error("\n\u274C Playwright browsers not found!\n");
|
|
235
|
-
console.log("\u{1F4E6} Run: npx playwright install chromium\n");
|
|
236
|
-
failures.push("Playwright browser not installed. Run: npx playwright install chromium");
|
|
237
|
-
} else if (error.message.includes("net::ERR_CONNECTION_REFUSED")) {
|
|
238
|
-
console.error("\n\u274C Cannot connect to dev server!\n");
|
|
239
|
-
console.log(` Make sure your dev server is running at ${url}
|
|
240
|
-
`);
|
|
241
|
-
failures.push(`Dev server not running at ${url}`);
|
|
242
|
-
} else {
|
|
243
|
-
console.error("\u274C Playwright test error:", error.message);
|
|
244
|
-
failures.push(`Test error: ${error.message}`);
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
} finally {
|
|
248
|
-
if (browser) await browser.close();
|
|
249
|
-
}
|
|
250
|
-
return { passes, failures };
|
|
251
|
-
}
|
|
252
|
-
export {
|
|
253
|
-
runContractTestsPlaywright
|
|
254
|
-
};
|
|
@@ -1,249 +0,0 @@
|
|
|
1
|
-
import { ContractReporter, contract_default } from './chunk-SSBW5VAA.js';
|
|
2
|
-
import { chromium } from 'playwright';
|
|
3
|
-
import { readFileSync } from 'fs';
|
|
4
|
-
|
|
5
|
-
async function runContractTestsPlaywright(componentName, url) {
|
|
6
|
-
const reporter = new ContractReporter(true);
|
|
7
|
-
const contractTyped = contract_default;
|
|
8
|
-
const contractPath = contractTyped[componentName]?.path;
|
|
9
|
-
if (!contractPath) {
|
|
10
|
-
throw new Error(`Contract path not found for component: ${componentName}`);
|
|
11
|
-
}
|
|
12
|
-
const resolvedPath = new URL(contractPath, import.meta.url).pathname;
|
|
13
|
-
const contractData = readFileSync(resolvedPath, "utf-8");
|
|
14
|
-
const componentContract = JSON.parse(contractData);
|
|
15
|
-
const totalTests = componentContract.static[0].assertions.length + componentContract.dynamic.length;
|
|
16
|
-
reporter.start(componentName, totalTests);
|
|
17
|
-
const failures = [];
|
|
18
|
-
const passes = [];
|
|
19
|
-
let browser = null;
|
|
20
|
-
try {
|
|
21
|
-
browser = await chromium.launch({ headless: true });
|
|
22
|
-
const context = await browser.newContext();
|
|
23
|
-
const page = await context.newPage();
|
|
24
|
-
await page.goto(url, { waitUntil: "networkidle" });
|
|
25
|
-
await page.waitForSelector(componentContract.selectors.trigger, { timeout: 9e4 });
|
|
26
|
-
async function resolveRelativeTarget(selector, relative) {
|
|
27
|
-
const items = await page.locator(selector).all();
|
|
28
|
-
switch (relative) {
|
|
29
|
-
case "first":
|
|
30
|
-
return items[0];
|
|
31
|
-
case "second":
|
|
32
|
-
return items[1];
|
|
33
|
-
case "last":
|
|
34
|
-
return items[items.length - 1];
|
|
35
|
-
case "next": {
|
|
36
|
-
const currentIndex = await page.evaluate(([sel]) => {
|
|
37
|
-
const items2 = Array.from(document.querySelectorAll(sel));
|
|
38
|
-
return items2.indexOf(document.activeElement);
|
|
39
|
-
}, [selector]);
|
|
40
|
-
const nextIndex = (currentIndex + 1) % items.length;
|
|
41
|
-
return items[nextIndex];
|
|
42
|
-
}
|
|
43
|
-
case "previous": {
|
|
44
|
-
const currentIndex = await page.evaluate(([sel]) => {
|
|
45
|
-
const items2 = Array.from(document.querySelectorAll(sel));
|
|
46
|
-
return items2.indexOf(document.activeElement);
|
|
47
|
-
}, [selector]);
|
|
48
|
-
const prevIndex = (currentIndex - 1 + items.length) % items.length;
|
|
49
|
-
return items[prevIndex];
|
|
50
|
-
}
|
|
51
|
-
default:
|
|
52
|
-
return null;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
for (const test of componentContract.static[0]?.assertions || []) {
|
|
56
|
-
if (test.target === "relative") continue;
|
|
57
|
-
const targetSelector = componentContract.selectors[test.target];
|
|
58
|
-
if (!targetSelector) {
|
|
59
|
-
failures.push(`Selector for target ${test.target} not found.`);
|
|
60
|
-
continue;
|
|
61
|
-
}
|
|
62
|
-
const target = page.locator(targetSelector).first();
|
|
63
|
-
const exists = await target.count() > 0;
|
|
64
|
-
if (!exists) {
|
|
65
|
-
failures.push(`Target ${test.target} not found.`);
|
|
66
|
-
continue;
|
|
67
|
-
}
|
|
68
|
-
if (!test.expectedValue) {
|
|
69
|
-
const attributes = test.attribute.split(" | ");
|
|
70
|
-
let hasAny = false;
|
|
71
|
-
for (const attr of attributes) {
|
|
72
|
-
const value = await target.getAttribute(attr.trim());
|
|
73
|
-
if (value !== null) {
|
|
74
|
-
hasAny = true;
|
|
75
|
-
break;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
if (!hasAny) {
|
|
79
|
-
failures.push(test.failureMessage + ` None of the attributes "${test.attribute}" are present.`);
|
|
80
|
-
} else {
|
|
81
|
-
passes.push(`At least one of the attributes "${test.attribute}" exists on the element.`);
|
|
82
|
-
}
|
|
83
|
-
} else {
|
|
84
|
-
const attributeValue = await target.getAttribute(test.attribute);
|
|
85
|
-
const expectedValues = test.expectedValue.split(" | ");
|
|
86
|
-
if (!attributeValue || !expectedValues.includes(attributeValue)) {
|
|
87
|
-
failures.push(test.failureMessage + ` Attribute value does not match expected value. Expected: ${test.expectedValue}, Found: ${attributeValue}`);
|
|
88
|
-
} else {
|
|
89
|
-
passes.push(`Attribute value matches expected value. Expected: ${test.expectedValue}, Found: ${attributeValue}`);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
for (const dynamicTest of componentContract.dynamic || []) {
|
|
94
|
-
const { action, assertions } = dynamicTest;
|
|
95
|
-
const failuresBeforeTest = failures.length;
|
|
96
|
-
const containerElement = page.locator(componentContract.selectors.container).first();
|
|
97
|
-
const triggerElement = page.locator(componentContract.selectors.trigger).first();
|
|
98
|
-
const isContainerVisible = await containerElement.isVisible();
|
|
99
|
-
if (isContainerVisible) {
|
|
100
|
-
await triggerElement.click();
|
|
101
|
-
await page.waitForTimeout(50);
|
|
102
|
-
}
|
|
103
|
-
for (const act of action) {
|
|
104
|
-
if (act.type === "click") {
|
|
105
|
-
if (act.target === "document") {
|
|
106
|
-
await page.mouse.click(10, 10);
|
|
107
|
-
} else {
|
|
108
|
-
const actionSelector = componentContract.selectors[act.target];
|
|
109
|
-
if (!actionSelector) {
|
|
110
|
-
failures.push(`Selector for action target ${act.target} not found.`);
|
|
111
|
-
continue;
|
|
112
|
-
}
|
|
113
|
-
await page.locator(actionSelector).first().click();
|
|
114
|
-
await page.waitForTimeout(200);
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
if (act.type === "keypress" && act.key) {
|
|
118
|
-
const keyMap = {
|
|
119
|
-
"Space": "Space",
|
|
120
|
-
"Enter": "Enter",
|
|
121
|
-
"Escape": "Escape",
|
|
122
|
-
"Arrow Up": "ArrowUp",
|
|
123
|
-
"Arrow Down": "ArrowDown",
|
|
124
|
-
"Arrow Left": "ArrowLeft",
|
|
125
|
-
"Arrow Right": "ArrowRight",
|
|
126
|
-
"Home": "Home",
|
|
127
|
-
"End": "End",
|
|
128
|
-
"Tab": "Tab"
|
|
129
|
-
};
|
|
130
|
-
let keyValue = keyMap[act.key] || act.key;
|
|
131
|
-
if (keyValue === "Space") {
|
|
132
|
-
keyValue = " ";
|
|
133
|
-
} else if (keyValue.includes(" ")) {
|
|
134
|
-
keyValue = keyValue.replace(/ /g, "");
|
|
135
|
-
}
|
|
136
|
-
if (act.target === "focusable" && ["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight", "Escape"].includes(keyValue)) {
|
|
137
|
-
await page.waitForTimeout(100);
|
|
138
|
-
await page.keyboard.press(keyValue);
|
|
139
|
-
await page.waitForTimeout(50);
|
|
140
|
-
} else {
|
|
141
|
-
const keypressSelector = componentContract.selectors[act.target];
|
|
142
|
-
if (!keypressSelector) {
|
|
143
|
-
failures.push(`Selector for keypress target ${act.target} not found.`);
|
|
144
|
-
continue;
|
|
145
|
-
}
|
|
146
|
-
const target = page.locator(keypressSelector).first();
|
|
147
|
-
await target.press(keyValue);
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
await page.waitForTimeout(100);
|
|
151
|
-
}
|
|
152
|
-
for (const assertion of assertions) {
|
|
153
|
-
let target;
|
|
154
|
-
if (assertion.target === "relative") {
|
|
155
|
-
const relativeSelector = componentContract.selectors.relative;
|
|
156
|
-
if (!relativeSelector) {
|
|
157
|
-
failures.push("Relative selector is not defined in the contract.");
|
|
158
|
-
continue;
|
|
159
|
-
}
|
|
160
|
-
if (!assertion.expectedValue) {
|
|
161
|
-
failures.push("Expected value for relative target is not defined.");
|
|
162
|
-
continue;
|
|
163
|
-
}
|
|
164
|
-
target = await resolveRelativeTarget(relativeSelector, assertion.expectedValue);
|
|
165
|
-
} else {
|
|
166
|
-
const assertionSelector = componentContract.selectors[assertion.target];
|
|
167
|
-
if (!assertionSelector) {
|
|
168
|
-
failures.push(`Selector for assertion target ${assertion.target} not found.`);
|
|
169
|
-
continue;
|
|
170
|
-
}
|
|
171
|
-
target = page.locator(assertionSelector).first();
|
|
172
|
-
}
|
|
173
|
-
if (!target) {
|
|
174
|
-
failures.push(`Target ${assertion.target} not found.`);
|
|
175
|
-
continue;
|
|
176
|
-
}
|
|
177
|
-
if (assertion.assertion === "toBeVisible") {
|
|
178
|
-
const isVisible = await target.isVisible();
|
|
179
|
-
if (isVisible) {
|
|
180
|
-
passes.push(`${assertion.target} is visible as expected. Test: "${dynamicTest.description}".`);
|
|
181
|
-
} else {
|
|
182
|
-
failures.push(`${assertion.failureMessage}`);
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
if (assertion.assertion === "notToBeVisible") {
|
|
186
|
-
const isVisible = await target.isVisible();
|
|
187
|
-
if (!isVisible) {
|
|
188
|
-
passes.push(`${assertion.target} is not visible as expected. Test: "${dynamicTest.description}".`);
|
|
189
|
-
} else {
|
|
190
|
-
failures.push(assertion.failureMessage + ` ${assertion.target} is still visible.`);
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
if (assertion.assertion === "toHaveAttribute" && assertion.attribute && assertion.expectedValue) {
|
|
194
|
-
const attributeValue = await target.getAttribute(assertion.attribute);
|
|
195
|
-
if (attributeValue === assertion.expectedValue) {
|
|
196
|
-
passes.push(`${assertion.target} has expected "${assertion.attribute}". Test: "${dynamicTest.description}".`);
|
|
197
|
-
} else {
|
|
198
|
-
failures.push(assertion.failureMessage + ` ${assertion.target} "${assertion.attribute}" should be "${assertion.expectedValue}", found "${attributeValue}".`);
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
if (assertion.assertion === "toHaveFocus") {
|
|
202
|
-
const hasFocus = await target.evaluate((el) => el === document.activeElement);
|
|
203
|
-
if (hasFocus) {
|
|
204
|
-
passes.push(`${assertion.target} has focus as expected. Test: "${dynamicTest.description}".`);
|
|
205
|
-
} else {
|
|
206
|
-
failures.push(`${assertion.failureMessage}`);
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
if (assertion.assertion === "toHaveRole" && assertion.expectedValue) {
|
|
210
|
-
const roleValue = await target.getAttribute("role");
|
|
211
|
-
if (roleValue === assertion.expectedValue) {
|
|
212
|
-
passes.push(`${assertion.target} has role "${assertion.expectedValue}". Test: "${dynamicTest.description}".`);
|
|
213
|
-
} else {
|
|
214
|
-
failures.push(assertion.failureMessage + ` Expected role "${assertion.expectedValue}", found "${roleValue}".`);
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
const failuresAfterTest = failures.length;
|
|
219
|
-
const testPassed = failuresAfterTest === failuresBeforeTest;
|
|
220
|
-
const failureMessage = testPassed ? void 0 : failures[failures.length - 1];
|
|
221
|
-
reporter.reportTest(dynamicTest, testPassed ? "pass" : "fail", failureMessage);
|
|
222
|
-
}
|
|
223
|
-
const staticPassed = componentContract.static[0].assertions.length;
|
|
224
|
-
const staticFailed = 0;
|
|
225
|
-
reporter.reportStatic(staticPassed, staticFailed);
|
|
226
|
-
reporter.summary(failures);
|
|
227
|
-
} catch (error) {
|
|
228
|
-
if (error instanceof Error) {
|
|
229
|
-
if (error.message.includes("Executable doesn't exist")) {
|
|
230
|
-
console.error("\n\u274C Playwright browsers not found!\n");
|
|
231
|
-
console.log("\u{1F4E6} Run: npx playwright install chromium\n");
|
|
232
|
-
failures.push("Playwright browser not installed. Run: npx playwright install chromium");
|
|
233
|
-
} else if (error.message.includes("net::ERR_CONNECTION_REFUSED")) {
|
|
234
|
-
console.error("\n\u274C Cannot connect to dev server!\n");
|
|
235
|
-
console.log(` Make sure your dev server is running at ${url}
|
|
236
|
-
`);
|
|
237
|
-
failures.push(`Dev server not running at ${url}`);
|
|
238
|
-
} else {
|
|
239
|
-
console.error("\u274C Playwright test error:", error.message);
|
|
240
|
-
failures.push(`Test error: ${error.message}`);
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
} finally {
|
|
244
|
-
if (browser) await browser.close();
|
|
245
|
-
}
|
|
246
|
-
return { passes, failures };
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
export { runContractTestsPlaywright };
|