aria-ease 6.2.0 → 6.2.2
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 +5 -17
- package/bin/{audit-5JJ3XK46.js → audit-RM6TCZ5C.js} +17 -5
- package/bin/cli.cjs +260 -133
- package/bin/cli.js +18 -15
- package/bin/{contractTestRunnerPlaywright-HL2VPEEV.js → contractTestRunnerPlaywright-ACAWN34W.js} +227 -115
- package/bin/{test-HH2EW2NM.js → test-A3ESFXOR.js} +1 -1
- package/dist/{contractTestRunnerPlaywright-EXEBWWPC.js → contractTestRunnerPlaywright-O7FF7GV4.js} +227 -115
- package/dist/index.cjs +229 -116
- package/dist/index.d.cts +10 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +3 -2
- package/dist/src/{Types.d-CBuuHF3d.d.cts → Types.d-CRjhbrcw.d.cts} +11 -1
- package/dist/src/{Types.d-CBuuHF3d.d.ts → Types.d-CRjhbrcw.d.ts} +11 -1
- package/dist/src/accordion/index.d.cts +1 -1
- package/dist/src/accordion/index.d.ts +1 -1
- package/dist/src/block/index.d.cts +1 -1
- package/dist/src/block/index.d.ts +1 -1
- package/dist/src/checkbox/index.d.cts +1 -1
- package/dist/src/checkbox/index.d.ts +1 -1
- package/dist/src/combobox/index.cjs +1 -1
- package/dist/src/combobox/index.d.cts +1 -1
- package/dist/src/combobox/index.d.ts +1 -1
- package/dist/src/combobox/index.js +1 -1
- package/dist/src/menu/index.d.cts +1 -1
- package/dist/src/menu/index.d.ts +1 -1
- package/dist/src/radio/index.cjs +1 -0
- package/dist/src/radio/index.d.cts +1 -1
- package/dist/src/radio/index.d.ts +1 -1
- package/dist/src/radio/index.js +1 -0
- package/dist/src/toggle/index.d.cts +1 -1
- package/dist/src/toggle/index.d.ts +1 -1
- package/dist/src/utils/test/{contractTestRunnerPlaywright-LJHY3AB4.js → contractTestRunnerPlaywright-7BPRTIN4.js} +227 -115
- package/dist/src/utils/test/contracts/AccordionContract.json +1 -0
- package/dist/src/utils/test/contracts/ComboboxContract.json +1 -0
- package/dist/src/utils/test/contracts/MenuContract.json +1 -0
- package/dist/src/utils/test/index.cjs +227 -115
- package/dist/src/utils/test/index.js +1 -1
- package/package.json +1 -1
package/bin/cli.js
CHANGED
|
@@ -128,7 +128,7 @@ var program = new Command();
|
|
|
128
128
|
program.name("aria-ease").description("Run accessibility tests and audits").version("2.2.3");
|
|
129
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) => {
|
|
130
130
|
console.log(chalk.cyanBright("\u{1F680} Starting accessibility audit...\n"));
|
|
131
|
-
const { runAudit } = await import("./audit-
|
|
131
|
+
const { runAudit } = await import("./audit-RM6TCZ5C.js");
|
|
132
132
|
const { formatResults } = await import("./formatters-32KQIIYS.js");
|
|
133
133
|
const { config, configPath, errors } = await loadConfig(process.cwd());
|
|
134
134
|
if (configPath) {
|
|
@@ -155,22 +155,25 @@ program.command("audit").description("Run axe-core powered accessibility audit o
|
|
|
155
155
|
process.exit(1);
|
|
156
156
|
}
|
|
157
157
|
const allResults = [];
|
|
158
|
-
const
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
158
|
+
const { createAuditBrowser } = await import("./audit-RM6TCZ5C.js");
|
|
159
|
+
const browser = await createAuditBrowser();
|
|
160
|
+
try {
|
|
161
|
+
const auditOptions = { browser };
|
|
162
|
+
for (const url of urls) {
|
|
163
|
+
console.log(chalk.yellow(`\u{1F50E} Auditing: ${url}`));
|
|
164
|
+
try {
|
|
165
|
+
const result = await runAudit(url, auditOptions);
|
|
166
|
+
allResults.push({ url, result });
|
|
167
|
+
console.log(chalk.green(`\u2705 Completed audit for ${url}
|
|
168
168
|
`));
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
169
|
+
} catch (error) {
|
|
170
|
+
if (error instanceof Error && error.message) {
|
|
171
|
+
console.log(chalk.red(`\u274C Failed auditing ${url}: ${error.message}`));
|
|
172
|
+
}
|
|
172
173
|
}
|
|
173
174
|
}
|
|
175
|
+
} finally {
|
|
176
|
+
await browser.close();
|
|
174
177
|
}
|
|
175
178
|
const hasResults = allResults.some((r) => r.result && r.result.violations && r.result.violations.length > 0);
|
|
176
179
|
if (!hasResults) {
|
|
@@ -204,7 +207,7 @@ program.command("audit").description("Run axe-core powered accessibility audit o
|
|
|
204
207
|
console.log(chalk.green("\n\u{1F389} All audits completed."));
|
|
205
208
|
});
|
|
206
209
|
program.command("test").description("Run core a11y accessibility standard tests on UI components").action(async () => {
|
|
207
|
-
const { runTest } = await import("./test-
|
|
210
|
+
const { runTest } = await import("./test-A3ESFXOR.js");
|
|
208
211
|
runTest();
|
|
209
212
|
});
|
|
210
213
|
program.command("help").description("Display help information").action(() => {
|
package/bin/{contractTestRunnerPlaywright-HL2VPEEV.js → contractTestRunnerPlaywright-ACAWN34W.js}
RENAMED
|
@@ -21,6 +21,11 @@ import { default as default2 } from "playwright/test";
|
|
|
21
21
|
import { readFileSync } from "fs";
|
|
22
22
|
async function runContractTestsPlaywright(componentName, url) {
|
|
23
23
|
const reporter = new ContractReporter(true);
|
|
24
|
+
const actionTimeoutMs = 400;
|
|
25
|
+
const assertionTimeoutMs = 400;
|
|
26
|
+
function isBrowserClosedError(error) {
|
|
27
|
+
return error instanceof Error && error.message.includes("Target page, context or browser has been closed");
|
|
28
|
+
}
|
|
24
29
|
const contractTyped = contract_default;
|
|
25
30
|
const contractPath = contractTyped[componentName]?.path;
|
|
26
31
|
if (!contractPath) {
|
|
@@ -39,17 +44,29 @@ async function runContractTestsPlaywright(componentName, url) {
|
|
|
39
44
|
try {
|
|
40
45
|
page = await createTestPage();
|
|
41
46
|
if (url) {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
47
|
+
try {
|
|
48
|
+
await page.goto(url, {
|
|
49
|
+
waitUntil: "domcontentloaded",
|
|
50
|
+
timeout: 3e4
|
|
51
|
+
});
|
|
52
|
+
} catch (error) {
|
|
53
|
+
throw new Error(
|
|
54
|
+
`Failed to navigate to ${url}. Ensure dev server is running and accessible. Original error: ${error instanceof Error ? error.message : String(error)}`
|
|
55
|
+
);
|
|
56
|
+
}
|
|
46
57
|
await page.addStyleTag({ content: `* { transition: none !important; animation: none !important; }` });
|
|
47
58
|
}
|
|
48
59
|
const mainSelector = componentContract.selectors.trigger || componentContract.selectors.input || componentContract.selectors.container;
|
|
49
60
|
if (!mainSelector) {
|
|
50
|
-
throw new Error(`No main selector (trigger, input, or container) found in contract for ${componentName}`);
|
|
61
|
+
throw new Error(`CRITICAL: No main selector (trigger, input, or container) found in contract for ${componentName}`);
|
|
62
|
+
}
|
|
63
|
+
try {
|
|
64
|
+
await page.locator(mainSelector).first().waitFor({ state: "attached", timeout: 3e4 });
|
|
65
|
+
} catch (error) {
|
|
66
|
+
throw new Error(
|
|
67
|
+
`CRITICAL: Component element '${mainSelector}' not found on page within 30s. This usually means the component didn't render or the contract selector is incorrect. Original error: ${error instanceof Error ? error.message : String(error)}`
|
|
68
|
+
);
|
|
51
69
|
}
|
|
52
|
-
await page.locator(mainSelector).first().waitFor({ state: "attached", timeout: 3e4 });
|
|
53
70
|
if (componentName === "menu" && componentContract.selectors.trigger) {
|
|
54
71
|
await page.locator(componentContract.selectors.trigger).first().waitFor({
|
|
55
72
|
state: "visible",
|
|
@@ -129,6 +146,13 @@ async function runContractTestsPlaywright(componentName, url) {
|
|
|
129
146
|
}
|
|
130
147
|
}
|
|
131
148
|
for (const dynamicTest of componentContract.dynamic || []) {
|
|
149
|
+
if (!page || page.isClosed()) {
|
|
150
|
+
console.warn(`
|
|
151
|
+
\u26A0\uFE0F Browser closed - skipping remaining ${componentContract.dynamic.length - componentContract.dynamic.indexOf(dynamicTest)} tests
|
|
152
|
+
`);
|
|
153
|
+
failures.push(`CRITICAL: Browser/page closed before completing all tests. ${componentContract.dynamic.length - componentContract.dynamic.indexOf(dynamicTest)} tests skipped.`);
|
|
154
|
+
break;
|
|
155
|
+
}
|
|
132
156
|
const { action, assertions } = dynamicTest;
|
|
133
157
|
const failuresBeforeTest = failures.length;
|
|
134
158
|
if (componentContract.selectors.popup) {
|
|
@@ -148,16 +172,16 @@ async function runContractTestsPlaywright(componentName, url) {
|
|
|
148
172
|
const closeElement = page.locator(closeSelector).first();
|
|
149
173
|
await closeElement.focus();
|
|
150
174
|
await page.keyboard.press("Escape");
|
|
151
|
-
menuClosed = await (0, test_exports.expect)(popupElement).toBeHidden({ timeout:
|
|
175
|
+
menuClosed = await (0, test_exports.expect)(popupElement).toBeHidden({ timeout: assertionTimeoutMs }).then(() => true).catch(() => false);
|
|
152
176
|
}
|
|
153
177
|
if (!menuClosed && componentContract.selectors.trigger) {
|
|
154
178
|
const triggerElement = page.locator(componentContract.selectors.trigger).first();
|
|
155
|
-
await triggerElement.click();
|
|
156
|
-
menuClosed = await (0, test_exports.expect)(popupElement).toBeHidden({ timeout:
|
|
179
|
+
await triggerElement.click({ timeout: actionTimeoutMs });
|
|
180
|
+
menuClosed = await (0, test_exports.expect)(popupElement).toBeHidden({ timeout: assertionTimeoutMs }).then(() => true).catch(() => false);
|
|
157
181
|
}
|
|
158
182
|
if (!menuClosed) {
|
|
159
183
|
await page.mouse.click(10, 10);
|
|
160
|
-
menuClosed = await (0, test_exports.expect)(popupElement).toBeHidden({ timeout:
|
|
184
|
+
menuClosed = await (0, test_exports.expect)(popupElement).toBeHidden({ timeout: assertionTimeoutMs }).then(() => true).catch(() => false);
|
|
161
185
|
}
|
|
162
186
|
if (!menuClosed) {
|
|
163
187
|
throw new Error(
|
|
@@ -186,9 +210,9 @@ This indicates a problem with the menu component's close functionality.`
|
|
|
186
210
|
const isExpanded = await trigger.getAttribute("aria-expanded") === "true";
|
|
187
211
|
const triggerPanel = await trigger.getAttribute("aria-controls");
|
|
188
212
|
if (isExpanded && triggerPanel) {
|
|
189
|
-
await trigger.click();
|
|
213
|
+
await trigger.click({ timeout: actionTimeoutMs });
|
|
190
214
|
const panel = page.locator(`#${triggerPanel}`);
|
|
191
|
-
await (0, test_exports.expect)(panel).toBeHidden({ timeout:
|
|
215
|
+
await (0, test_exports.expect)(panel).toBeHidden({ timeout: assertionTimeoutMs }).catch(() => {
|
|
192
216
|
});
|
|
193
217
|
}
|
|
194
218
|
}
|
|
@@ -227,134 +251,192 @@ This indicates a problem with the menu component's close functionality.`
|
|
|
227
251
|
continue;
|
|
228
252
|
}
|
|
229
253
|
for (const act of action) {
|
|
254
|
+
if (!page || page.isClosed()) {
|
|
255
|
+
failures.push(`CRITICAL: Browser/page closed during test execution. Remaining actions skipped.`);
|
|
256
|
+
break;
|
|
257
|
+
}
|
|
230
258
|
if (act.type === "focus") {
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
259
|
+
try {
|
|
260
|
+
const focusSelector = componentContract.selectors[act.target];
|
|
261
|
+
if (!focusSelector) {
|
|
262
|
+
failures.push(`Selector for focus target ${act.target} not found.`);
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
await page.locator(focusSelector).first().focus({ timeout: actionTimeoutMs });
|
|
266
|
+
} catch (error) {
|
|
267
|
+
if (isBrowserClosedError(error)) {
|
|
268
|
+
failures.push(`CRITICAL: Browser/page closed during test execution. Remaining actions skipped.`);
|
|
269
|
+
break;
|
|
270
|
+
}
|
|
271
|
+
failures.push(`Failed to focus ${act.target}: ${error instanceof Error ? error.message : String(error)}`);
|
|
234
272
|
continue;
|
|
235
273
|
}
|
|
236
|
-
await page.locator(focusSelector).first().focus();
|
|
237
274
|
}
|
|
238
275
|
if (act.type === "type" && act.value) {
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
276
|
+
try {
|
|
277
|
+
const typeSelector = componentContract.selectors[act.target];
|
|
278
|
+
if (!typeSelector) {
|
|
279
|
+
failures.push(`Selector for type target ${act.target} not found.`);
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
await page.locator(typeSelector).first().fill(act.value, { timeout: actionTimeoutMs });
|
|
283
|
+
} catch (error) {
|
|
284
|
+
if (isBrowserClosedError(error)) {
|
|
285
|
+
failures.push(`CRITICAL: Browser/page closed during test execution. Remaining actions skipped.`);
|
|
286
|
+
break;
|
|
287
|
+
}
|
|
288
|
+
failures.push(`Failed to type into ${act.target}: ${error instanceof Error ? error.message : String(error)}`);
|
|
242
289
|
continue;
|
|
243
290
|
}
|
|
244
|
-
await page.locator(typeSelector).first().fill(act.value);
|
|
245
291
|
}
|
|
246
292
|
if (act.type === "click") {
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
293
|
+
try {
|
|
294
|
+
if (act.target === "document") {
|
|
295
|
+
await page.mouse.click(10, 10);
|
|
296
|
+
} else if (act.target === "relative" && act.relativeTarget) {
|
|
297
|
+
const relativeSelector = componentContract.selectors.relative;
|
|
298
|
+
if (!relativeSelector) {
|
|
299
|
+
failures.push(`Relative selector not defined for click action.`);
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
const relativeElement = await resolveRelativeTarget(relativeSelector, act.relativeTarget);
|
|
303
|
+
if (!relativeElement) {
|
|
304
|
+
failures.push(`Could not resolve relative target ${act.relativeTarget} for click.`);
|
|
305
|
+
continue;
|
|
306
|
+
}
|
|
307
|
+
await relativeElement.click({ timeout: actionTimeoutMs });
|
|
308
|
+
} else {
|
|
309
|
+
const actionSelector = componentContract.selectors[act.target];
|
|
310
|
+
if (!actionSelector) {
|
|
311
|
+
failures.push(`Selector for action target ${act.target} not found.`);
|
|
312
|
+
continue;
|
|
313
|
+
}
|
|
314
|
+
await page.locator(actionSelector).first().click({ timeout: actionTimeoutMs });
|
|
259
315
|
}
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
failures.push(`Selector for action target ${act.target} not found.`);
|
|
265
|
-
continue;
|
|
316
|
+
} catch (error) {
|
|
317
|
+
if (isBrowserClosedError(error)) {
|
|
318
|
+
failures.push(`CRITICAL: Browser/page closed during test execution. Remaining actions skipped.`);
|
|
319
|
+
break;
|
|
266
320
|
}
|
|
267
|
-
|
|
321
|
+
failures.push(`Failed to click ${act.target}: ${error instanceof Error ? error.message : String(error)}`);
|
|
322
|
+
continue;
|
|
268
323
|
}
|
|
269
324
|
}
|
|
270
325
|
if (act.type === "keypress" && act.key) {
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
keyValue
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
if (act.target === "focusable" && ["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight", "Escape"].includes(keyValue)) {
|
|
290
|
-
await page.keyboard.press(keyValue);
|
|
291
|
-
} else {
|
|
292
|
-
const keypressSelector = componentContract.selectors[act.target];
|
|
293
|
-
if (!keypressSelector) {
|
|
294
|
-
failures.push(`Selector for keypress target ${act.target} not found.`);
|
|
295
|
-
continue;
|
|
326
|
+
try {
|
|
327
|
+
const keyMap = {
|
|
328
|
+
"Space": "Space",
|
|
329
|
+
"Enter": "Enter",
|
|
330
|
+
"Escape": "Escape",
|
|
331
|
+
"Arrow Up": "ArrowUp",
|
|
332
|
+
"Arrow Down": "ArrowDown",
|
|
333
|
+
"Arrow Left": "ArrowLeft",
|
|
334
|
+
"Arrow Right": "ArrowRight",
|
|
335
|
+
"Home": "Home",
|
|
336
|
+
"End": "End",
|
|
337
|
+
"Tab": "Tab"
|
|
338
|
+
};
|
|
339
|
+
let keyValue = keyMap[act.key] || act.key;
|
|
340
|
+
if (keyValue === "Space") {
|
|
341
|
+
keyValue = " ";
|
|
342
|
+
} else if (keyValue.includes(" ")) {
|
|
343
|
+
keyValue = keyValue.replace(/ /g, "");
|
|
296
344
|
}
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
345
|
+
if (act.target === "focusable" && ["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight", "Escape"].includes(keyValue)) {
|
|
346
|
+
await page.keyboard.press(keyValue);
|
|
347
|
+
} else {
|
|
348
|
+
const keypressSelector = componentContract.selectors[act.target];
|
|
349
|
+
if (!keypressSelector) {
|
|
350
|
+
failures.push(`Selector for keypress target ${act.target} not found.`);
|
|
351
|
+
continue;
|
|
352
|
+
}
|
|
353
|
+
const target = page.locator(keypressSelector).first();
|
|
354
|
+
const elementCount = await target.count();
|
|
355
|
+
if (elementCount === 0) {
|
|
356
|
+
reporter.reportTest(dynamicTest, "skip", `Skipping test - ${act.target} element not found (optional submenu test)`);
|
|
357
|
+
break;
|
|
358
|
+
}
|
|
359
|
+
await target.press(keyValue, { timeout: actionTimeoutMs });
|
|
360
|
+
}
|
|
361
|
+
} catch (error) {
|
|
362
|
+
if (isBrowserClosedError(error)) {
|
|
363
|
+
failures.push(`CRITICAL: Browser/page closed during test execution. Remaining actions skipped.`);
|
|
301
364
|
break;
|
|
302
365
|
}
|
|
303
|
-
|
|
366
|
+
failures.push(`Failed to press ${act.key} on ${act.target}: ${error instanceof Error ? error.message : String(error)}`);
|
|
367
|
+
continue;
|
|
304
368
|
}
|
|
305
369
|
}
|
|
306
370
|
if (act.type === "hover") {
|
|
307
|
-
|
|
371
|
+
try {
|
|
372
|
+
if (act.target === "relative" && act.relativeTarget) {
|
|
373
|
+
const relativeSelector = componentContract.selectors.relative;
|
|
374
|
+
if (!relativeSelector) {
|
|
375
|
+
failures.push(`Relative selector not defined for hover action.`);
|
|
376
|
+
continue;
|
|
377
|
+
}
|
|
378
|
+
const relativeElement = await resolveRelativeTarget(relativeSelector, act.relativeTarget);
|
|
379
|
+
if (!relativeElement) {
|
|
380
|
+
failures.push(`Could not resolve relative target ${act.relativeTarget} for hover.`);
|
|
381
|
+
continue;
|
|
382
|
+
}
|
|
383
|
+
await relativeElement.hover({ timeout: actionTimeoutMs });
|
|
384
|
+
} else {
|
|
385
|
+
const hoverSelector = componentContract.selectors[act.target];
|
|
386
|
+
if (!hoverSelector) {
|
|
387
|
+
failures.push(`Selector for hover target ${act.target} not found.`);
|
|
388
|
+
continue;
|
|
389
|
+
}
|
|
390
|
+
await page.locator(hoverSelector).first().hover({ timeout: actionTimeoutMs });
|
|
391
|
+
}
|
|
392
|
+
} catch (error) {
|
|
393
|
+
if (isBrowserClosedError(error)) {
|
|
394
|
+
failures.push(`CRITICAL: Browser/page closed during test execution. Remaining actions skipped.`);
|
|
395
|
+
break;
|
|
396
|
+
}
|
|
397
|
+
failures.push(`Failed to hover ${act.target}: ${error instanceof Error ? error.message : String(error)}`);
|
|
398
|
+
continue;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
for (const assertion of assertions) {
|
|
403
|
+
if (!page || page.isClosed()) {
|
|
404
|
+
failures.push(`CRITICAL: Browser/page closed before completing all tests. Increase test timeout or reduce test complexity.`);
|
|
405
|
+
break;
|
|
406
|
+
}
|
|
407
|
+
let target;
|
|
408
|
+
try {
|
|
409
|
+
if (assertion.target === "relative") {
|
|
308
410
|
const relativeSelector = componentContract.selectors.relative;
|
|
309
411
|
if (!relativeSelector) {
|
|
310
|
-
failures.push(
|
|
412
|
+
failures.push("Relative selector is not defined in the contract.");
|
|
311
413
|
continue;
|
|
312
414
|
}
|
|
313
|
-
const
|
|
314
|
-
if (!
|
|
315
|
-
failures.push(
|
|
415
|
+
const relativeTargetValue = assertion.relativeTarget || assertion.expectedValue;
|
|
416
|
+
if (!relativeTargetValue) {
|
|
417
|
+
failures.push("Relative target or expected value is not defined.");
|
|
316
418
|
continue;
|
|
317
419
|
}
|
|
318
|
-
await
|
|
420
|
+
target = await resolveRelativeTarget(relativeSelector, relativeTargetValue);
|
|
319
421
|
} else {
|
|
320
|
-
const
|
|
321
|
-
if (!
|
|
322
|
-
failures.push(`Selector for
|
|
422
|
+
const assertionSelector = componentContract.selectors[assertion.target];
|
|
423
|
+
if (!assertionSelector) {
|
|
424
|
+
failures.push(`Selector for assertion target ${assertion.target} not found.`);
|
|
323
425
|
continue;
|
|
324
426
|
}
|
|
325
|
-
|
|
427
|
+
target = page.locator(assertionSelector).first();
|
|
326
428
|
}
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
for (const assertion of assertions) {
|
|
330
|
-
let target;
|
|
331
|
-
if (assertion.target === "relative") {
|
|
332
|
-
const relativeSelector = componentContract.selectors.relative;
|
|
333
|
-
if (!relativeSelector) {
|
|
334
|
-
failures.push("Relative selector is not defined in the contract.");
|
|
335
|
-
continue;
|
|
336
|
-
}
|
|
337
|
-
const relativeTargetValue = assertion.relativeTarget || assertion.expectedValue;
|
|
338
|
-
if (!relativeTargetValue) {
|
|
339
|
-
failures.push("Relative target or expected value is not defined.");
|
|
429
|
+
if (!target) {
|
|
430
|
+
failures.push(`Target ${assertion.target} not found.`);
|
|
340
431
|
continue;
|
|
341
432
|
}
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
const assertionSelector = componentContract.selectors[assertion.target];
|
|
345
|
-
if (!assertionSelector) {
|
|
346
|
-
failures.push(`Selector for assertion target ${assertion.target} not found.`);
|
|
347
|
-
continue;
|
|
348
|
-
}
|
|
349
|
-
target = page.locator(assertionSelector).first();
|
|
350
|
-
}
|
|
351
|
-
if (!target) {
|
|
352
|
-
failures.push(`Target ${assertion.target} not found.`);
|
|
433
|
+
} catch (error) {
|
|
434
|
+
failures.push(`Failed to resolve target ${assertion.target}: ${error instanceof Error ? error.message : String(error)}`);
|
|
353
435
|
continue;
|
|
354
436
|
}
|
|
355
437
|
if (assertion.assertion === "toBeVisible") {
|
|
356
438
|
try {
|
|
357
|
-
await (0, test_exports.expect)(target).toBeVisible({ timeout:
|
|
439
|
+
await (0, test_exports.expect)(target).toBeVisible({ timeout: assertionTimeoutMs });
|
|
358
440
|
passes.push(`${assertion.target} is visible as expected. Test: "${dynamicTest.description}".`);
|
|
359
441
|
} catch {
|
|
360
442
|
const debugState = await page.evaluate((sel) => {
|
|
@@ -368,7 +450,7 @@ This indicates a problem with the menu component's close functionality.`
|
|
|
368
450
|
}
|
|
369
451
|
if (assertion.assertion === "notToBeVisible") {
|
|
370
452
|
try {
|
|
371
|
-
await (0, test_exports.expect)(target).toBeHidden({ timeout:
|
|
453
|
+
await (0, test_exports.expect)(target).toBeHidden({ timeout: assertionTimeoutMs });
|
|
372
454
|
passes.push(`${assertion.target} is not visible as expected. Test: "${dynamicTest.description}".`);
|
|
373
455
|
} catch {
|
|
374
456
|
const debugState = await page.evaluate((sel) => {
|
|
@@ -390,7 +472,7 @@ This indicates a problem with the menu component's close functionality.`
|
|
|
390
472
|
failures.push(assertion.failureMessage + ` ${assertion.target} "${assertion.attribute}" should not be empty, found "${attributeValue}".`);
|
|
391
473
|
}
|
|
392
474
|
} else {
|
|
393
|
-
await (0, test_exports.expect)(target).toHaveAttribute(assertion.attribute, assertion.expectedValue, { timeout:
|
|
475
|
+
await (0, test_exports.expect)(target).toHaveAttribute(assertion.attribute, assertion.expectedValue, { timeout: assertionTimeoutMs });
|
|
394
476
|
passes.push(`${assertion.target} has expected "${assertion.attribute}". Test: "${dynamicTest.description}".`);
|
|
395
477
|
}
|
|
396
478
|
} catch {
|
|
@@ -420,7 +502,7 @@ This indicates a problem with the menu component's close functionality.`
|
|
|
420
502
|
}
|
|
421
503
|
if (assertion.assertion === "toHaveFocus") {
|
|
422
504
|
try {
|
|
423
|
-
await (0, test_exports.expect)(target).toBeFocused({ timeout:
|
|
505
|
+
await (0, test_exports.expect)(target).toBeFocused({ timeout: assertionTimeoutMs });
|
|
424
506
|
passes.push(`${assertion.target} has focus as expected. Test: "${dynamicTest.description}".`);
|
|
425
507
|
} catch {
|
|
426
508
|
const actualFocus = await page.evaluate(() => {
|
|
@@ -455,18 +537,48 @@ This indicates a problem with the menu component's close functionality.`
|
|
|
455
537
|
reporter.summary(failures);
|
|
456
538
|
} catch (error) {
|
|
457
539
|
if (error instanceof Error) {
|
|
458
|
-
if (error.message.includes("Executable doesn't exist")) {
|
|
459
|
-
console.error("\n\u274C Playwright browsers not found!\n");
|
|
540
|
+
if (error.message.includes("Executable doesn't exist") || error.message.includes("browserType.launch")) {
|
|
541
|
+
console.error("\n\u274C CRITICAL: Playwright browsers not found!\n");
|
|
460
542
|
console.log("\u{1F4E6} Run: npx playwright install chromium\n");
|
|
461
|
-
failures.push("Playwright browser not installed. Run: npx playwright install chromium");
|
|
462
|
-
} else if (error.message.includes("net::ERR_CONNECTION_REFUSED")) {
|
|
463
|
-
console.error("\n\u274C Cannot connect to dev server!\n");
|
|
543
|
+
failures.push("CRITICAL: Playwright browser not installed. Run: npx playwright install chromium");
|
|
544
|
+
} else if (error.message.includes("net::ERR_CONNECTION_REFUSED") || error.message.includes("NS_ERROR_CONNECTION_REFUSED")) {
|
|
545
|
+
console.error("\n\u274C CRITICAL: Cannot connect to dev server!\n");
|
|
464
546
|
console.log(` Make sure your dev server is running at ${url}
|
|
465
547
|
`);
|
|
466
|
-
failures.push(`Dev server not running at ${url}`);
|
|
548
|
+
failures.push(`CRITICAL: Dev server not running at ${url}`);
|
|
549
|
+
} else if (error.message.includes("Timeout") && error.message.includes("waitFor")) {
|
|
550
|
+
console.error("\n\u274C CRITICAL: Component not found on page!\n");
|
|
551
|
+
console.log(` The component selector could not be found within 30 seconds.
|
|
552
|
+
`);
|
|
553
|
+
console.log(` This usually means:
|
|
554
|
+
`);
|
|
555
|
+
console.log(` - The component didn't render
|
|
556
|
+
`);
|
|
557
|
+
console.log(` - The URL is incorrect
|
|
558
|
+
`);
|
|
559
|
+
console.log(` - The component selector in the contract is wrong
|
|
560
|
+
`);
|
|
561
|
+
failures.push(`CRITICAL: Component element not found on page - ${error.message}`);
|
|
562
|
+
} else if (error.message.includes("Target page, context or browser has been closed")) {
|
|
563
|
+
console.error("\n\u274C CRITICAL: Browser/page was closed unexpectedly!\n");
|
|
564
|
+
console.log(` This usually means:
|
|
565
|
+
`);
|
|
566
|
+
console.log(` - The test timeout was too short
|
|
567
|
+
`);
|
|
568
|
+
console.log(` - The browser crashed
|
|
569
|
+
`);
|
|
570
|
+
console.log(` - An external process killed the browser
|
|
571
|
+
`);
|
|
572
|
+
failures.push(`CRITICAL: Browser/page closed unexpectedly - ${error.message}`);
|
|
573
|
+
} else if (error.message.includes("FATAL")) {
|
|
574
|
+
console.error(`
|
|
575
|
+
${error.message}
|
|
576
|
+
`);
|
|
577
|
+
failures.push(error.message);
|
|
467
578
|
} else {
|
|
468
|
-
console.error("\u274C
|
|
469
|
-
|
|
579
|
+
console.error("\n\u274C UNEXPECTED ERROR:", error.message);
|
|
580
|
+
console.error("Stack:", error.stack);
|
|
581
|
+
failures.push(`UNEXPECTED ERROR: ${error.message}`);
|
|
470
582
|
}
|
|
471
583
|
}
|
|
472
584
|
} finally {
|
|
@@ -114,7 +114,7 @@ Error: ${error instanceof Error ? error.message : String(error)}`
|
|
|
114
114
|
const devServerUrl = await checkDevServer(url);
|
|
115
115
|
if (devServerUrl) {
|
|
116
116
|
console.log(`\u{1F3AD} Running Playwright tests on ${devServerUrl}`);
|
|
117
|
-
const { runContractTestsPlaywright } = await import("./contractTestRunnerPlaywright-
|
|
117
|
+
const { runContractTestsPlaywright } = await import("./contractTestRunnerPlaywright-ACAWN34W.js");
|
|
118
118
|
contract = await runContractTestsPlaywright(componentName, devServerUrl);
|
|
119
119
|
} else {
|
|
120
120
|
throw new Error(
|