aria-ease 6.2.1 → 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/cli.cjs +227 -115
- package/bin/cli.js +1 -1
- 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-CxWrr421.d.ts → Types.d-CRjhbrcw.d.cts} +10 -0
- package/dist/src/{Types.d-CxWrr421.d.cts → Types.d-CRjhbrcw.d.ts} +10 -0
- 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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as AccordionConfig, a as AccessibilityInstance } from '../Types.d-
|
|
1
|
+
import { A as AccordionConfig, a as AccessibilityInstance } from '../Types.d-CRjhbrcw.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Makes an accordion accessible by managing ARIA attributes, keyboard navigation, and state.
|
|
@@ -241,7 +241,7 @@ function makeComboboxAccessible({ comboboxInputId, comboboxButtonId, listBoxId,
|
|
|
241
241
|
activeIndex = -1;
|
|
242
242
|
setActiveDescendant(-1);
|
|
243
243
|
}
|
|
244
|
-
return { cleanup, refresh };
|
|
244
|
+
return { cleanup, refresh, openListbox, closeListbox };
|
|
245
245
|
}
|
|
246
246
|
|
|
247
247
|
exports.makeComboboxAccessible = makeComboboxAccessible;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as ComboboxConfig, a as AccessibilityInstance } from '../Types.d-
|
|
1
|
+
import { C as ComboboxConfig, a as AccessibilityInstance } from '../Types.d-CRjhbrcw.cjs';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Makes a Combobox accessible by adding appropriate ARIA attributes, keyboard interactions and focus management.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as ComboboxConfig, a as AccessibilityInstance } from '../Types.d-
|
|
1
|
+
import { C as ComboboxConfig, a as AccessibilityInstance } from '../Types.d-CRjhbrcw.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Makes a Combobox accessible by adding appropriate ARIA attributes, keyboard interactions and focus management.
|
|
@@ -239,7 +239,7 @@ function makeComboboxAccessible({ comboboxInputId, comboboxButtonId, listBoxId,
|
|
|
239
239
|
activeIndex = -1;
|
|
240
240
|
setActiveDescendant(-1);
|
|
241
241
|
}
|
|
242
|
-
return { cleanup, refresh };
|
|
242
|
+
return { cleanup, refresh, openListbox, closeListbox };
|
|
243
243
|
}
|
|
244
244
|
|
|
245
245
|
export { makeComboboxAccessible };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { M as MenuConfig, a as AccessibilityInstance } from '../Types.d-
|
|
1
|
+
import { M as MenuConfig, a as AccessibilityInstance } from '../Types.d-CRjhbrcw.cjs';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Adds keyboard interaction to toggle menu. The menu traps focus and can be interacted with using the keyboard. The first interactive item of the menu has focus when menu open.
|
package/dist/src/menu/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { M as MenuConfig, a as AccessibilityInstance } from '../Types.d-
|
|
1
|
+
import { M as MenuConfig, a as AccessibilityInstance } from '../Types.d-CRjhbrcw.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Adds keyboard interaction to toggle menu. The menu traps focus and can be interacted with using the keyboard. The first interactive item of the menu has focus when menu open.
|
package/dist/src/radio/index.cjs
CHANGED
package/dist/src/radio/index.js
CHANGED
|
@@ -4,6 +4,11 @@ import { readFileSync } from 'fs';
|
|
|
4
4
|
|
|
5
5
|
async function runContractTestsPlaywright(componentName, url) {
|
|
6
6
|
const reporter = new ContractReporter(true);
|
|
7
|
+
const actionTimeoutMs = 400;
|
|
8
|
+
const assertionTimeoutMs = 400;
|
|
9
|
+
function isBrowserClosedError(error) {
|
|
10
|
+
return error instanceof Error && error.message.includes("Target page, context or browser has been closed");
|
|
11
|
+
}
|
|
7
12
|
const contractTyped = contract_default;
|
|
8
13
|
const contractPath = contractTyped[componentName]?.path;
|
|
9
14
|
if (!contractPath) {
|
|
@@ -22,17 +27,29 @@ async function runContractTestsPlaywright(componentName, url) {
|
|
|
22
27
|
try {
|
|
23
28
|
page = await createTestPage();
|
|
24
29
|
if (url) {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
30
|
+
try {
|
|
31
|
+
await page.goto(url, {
|
|
32
|
+
waitUntil: "domcontentloaded",
|
|
33
|
+
timeout: 3e4
|
|
34
|
+
});
|
|
35
|
+
} catch (error) {
|
|
36
|
+
throw new Error(
|
|
37
|
+
`Failed to navigate to ${url}. Ensure dev server is running and accessible. Original error: ${error instanceof Error ? error.message : String(error)}`
|
|
38
|
+
);
|
|
39
|
+
}
|
|
29
40
|
await page.addStyleTag({ content: `* { transition: none !important; animation: none !important; }` });
|
|
30
41
|
}
|
|
31
42
|
const mainSelector = componentContract.selectors.trigger || componentContract.selectors.input || componentContract.selectors.container;
|
|
32
43
|
if (!mainSelector) {
|
|
33
|
-
throw new Error(`No main selector (trigger, input, or container) found in contract for ${componentName}`);
|
|
44
|
+
throw new Error(`CRITICAL: No main selector (trigger, input, or container) found in contract for ${componentName}`);
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
await page.locator(mainSelector).first().waitFor({ state: "attached", timeout: 3e4 });
|
|
48
|
+
} catch (error) {
|
|
49
|
+
throw new Error(
|
|
50
|
+
`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)}`
|
|
51
|
+
);
|
|
34
52
|
}
|
|
35
|
-
await page.locator(mainSelector).first().waitFor({ state: "attached", timeout: 3e4 });
|
|
36
53
|
if (componentName === "menu" && componentContract.selectors.trigger) {
|
|
37
54
|
await page.locator(componentContract.selectors.trigger).first().waitFor({
|
|
38
55
|
state: "visible",
|
|
@@ -112,6 +129,13 @@ async function runContractTestsPlaywright(componentName, url) {
|
|
|
112
129
|
}
|
|
113
130
|
}
|
|
114
131
|
for (const dynamicTest of componentContract.dynamic || []) {
|
|
132
|
+
if (!page || page.isClosed()) {
|
|
133
|
+
console.warn(`
|
|
134
|
+
\u26A0\uFE0F Browser closed - skipping remaining ${componentContract.dynamic.length - componentContract.dynamic.indexOf(dynamicTest)} tests
|
|
135
|
+
`);
|
|
136
|
+
failures.push(`CRITICAL: Browser/page closed before completing all tests. ${componentContract.dynamic.length - componentContract.dynamic.indexOf(dynamicTest)} tests skipped.`);
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
115
139
|
const { action, assertions } = dynamicTest;
|
|
116
140
|
const failuresBeforeTest = failures.length;
|
|
117
141
|
if (componentContract.selectors.popup) {
|
|
@@ -131,16 +155,16 @@ async function runContractTestsPlaywright(componentName, url) {
|
|
|
131
155
|
const closeElement = page.locator(closeSelector).first();
|
|
132
156
|
await closeElement.focus();
|
|
133
157
|
await page.keyboard.press("Escape");
|
|
134
|
-
menuClosed = await expect(popupElement).toBeHidden({ timeout:
|
|
158
|
+
menuClosed = await expect(popupElement).toBeHidden({ timeout: assertionTimeoutMs }).then(() => true).catch(() => false);
|
|
135
159
|
}
|
|
136
160
|
if (!menuClosed && componentContract.selectors.trigger) {
|
|
137
161
|
const triggerElement = page.locator(componentContract.selectors.trigger).first();
|
|
138
|
-
await triggerElement.click();
|
|
139
|
-
menuClosed = await expect(popupElement).toBeHidden({ timeout:
|
|
162
|
+
await triggerElement.click({ timeout: actionTimeoutMs });
|
|
163
|
+
menuClosed = await expect(popupElement).toBeHidden({ timeout: assertionTimeoutMs }).then(() => true).catch(() => false);
|
|
140
164
|
}
|
|
141
165
|
if (!menuClosed) {
|
|
142
166
|
await page.mouse.click(10, 10);
|
|
143
|
-
menuClosed = await expect(popupElement).toBeHidden({ timeout:
|
|
167
|
+
menuClosed = await expect(popupElement).toBeHidden({ timeout: assertionTimeoutMs }).then(() => true).catch(() => false);
|
|
144
168
|
}
|
|
145
169
|
if (!menuClosed) {
|
|
146
170
|
throw new Error(
|
|
@@ -169,9 +193,9 @@ This indicates a problem with the menu component's close functionality.`
|
|
|
169
193
|
const isExpanded = await trigger.getAttribute("aria-expanded") === "true";
|
|
170
194
|
const triggerPanel = await trigger.getAttribute("aria-controls");
|
|
171
195
|
if (isExpanded && triggerPanel) {
|
|
172
|
-
await trigger.click();
|
|
196
|
+
await trigger.click({ timeout: actionTimeoutMs });
|
|
173
197
|
const panel = page.locator(`#${triggerPanel}`);
|
|
174
|
-
await expect(panel).toBeHidden({ timeout:
|
|
198
|
+
await expect(panel).toBeHidden({ timeout: assertionTimeoutMs }).catch(() => {
|
|
175
199
|
});
|
|
176
200
|
}
|
|
177
201
|
}
|
|
@@ -210,134 +234,192 @@ This indicates a problem with the menu component's close functionality.`
|
|
|
210
234
|
continue;
|
|
211
235
|
}
|
|
212
236
|
for (const act of action) {
|
|
237
|
+
if (!page || page.isClosed()) {
|
|
238
|
+
failures.push(`CRITICAL: Browser/page closed during test execution. Remaining actions skipped.`);
|
|
239
|
+
break;
|
|
240
|
+
}
|
|
213
241
|
if (act.type === "focus") {
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
242
|
+
try {
|
|
243
|
+
const focusSelector = componentContract.selectors[act.target];
|
|
244
|
+
if (!focusSelector) {
|
|
245
|
+
failures.push(`Selector for focus target ${act.target} not found.`);
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
await page.locator(focusSelector).first().focus({ timeout: actionTimeoutMs });
|
|
249
|
+
} catch (error) {
|
|
250
|
+
if (isBrowserClosedError(error)) {
|
|
251
|
+
failures.push(`CRITICAL: Browser/page closed during test execution. Remaining actions skipped.`);
|
|
252
|
+
break;
|
|
253
|
+
}
|
|
254
|
+
failures.push(`Failed to focus ${act.target}: ${error instanceof Error ? error.message : String(error)}`);
|
|
217
255
|
continue;
|
|
218
256
|
}
|
|
219
|
-
await page.locator(focusSelector).first().focus();
|
|
220
257
|
}
|
|
221
258
|
if (act.type === "type" && act.value) {
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
259
|
+
try {
|
|
260
|
+
const typeSelector = componentContract.selectors[act.target];
|
|
261
|
+
if (!typeSelector) {
|
|
262
|
+
failures.push(`Selector for type target ${act.target} not found.`);
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
await page.locator(typeSelector).first().fill(act.value, { 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 type into ${act.target}: ${error instanceof Error ? error.message : String(error)}`);
|
|
225
272
|
continue;
|
|
226
273
|
}
|
|
227
|
-
await page.locator(typeSelector).first().fill(act.value);
|
|
228
274
|
}
|
|
229
275
|
if (act.type === "click") {
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
276
|
+
try {
|
|
277
|
+
if (act.target === "document") {
|
|
278
|
+
await page.mouse.click(10, 10);
|
|
279
|
+
} else if (act.target === "relative" && act.relativeTarget) {
|
|
280
|
+
const relativeSelector = componentContract.selectors.relative;
|
|
281
|
+
if (!relativeSelector) {
|
|
282
|
+
failures.push(`Relative selector not defined for click action.`);
|
|
283
|
+
continue;
|
|
284
|
+
}
|
|
285
|
+
const relativeElement = await resolveRelativeTarget(relativeSelector, act.relativeTarget);
|
|
286
|
+
if (!relativeElement) {
|
|
287
|
+
failures.push(`Could not resolve relative target ${act.relativeTarget} for click.`);
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
await relativeElement.click({ timeout: actionTimeoutMs });
|
|
291
|
+
} else {
|
|
292
|
+
const actionSelector = componentContract.selectors[act.target];
|
|
293
|
+
if (!actionSelector) {
|
|
294
|
+
failures.push(`Selector for action target ${act.target} not found.`);
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
297
|
+
await page.locator(actionSelector).first().click({ timeout: actionTimeoutMs });
|
|
242
298
|
}
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
failures.push(`Selector for action target ${act.target} not found.`);
|
|
248
|
-
continue;
|
|
299
|
+
} catch (error) {
|
|
300
|
+
if (isBrowserClosedError(error)) {
|
|
301
|
+
failures.push(`CRITICAL: Browser/page closed during test execution. Remaining actions skipped.`);
|
|
302
|
+
break;
|
|
249
303
|
}
|
|
250
|
-
|
|
304
|
+
failures.push(`Failed to click ${act.target}: ${error instanceof Error ? error.message : String(error)}`);
|
|
305
|
+
continue;
|
|
251
306
|
}
|
|
252
307
|
}
|
|
253
308
|
if (act.type === "keypress" && act.key) {
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
keyValue
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
if (act.target === "focusable" && ["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight", "Escape"].includes(keyValue)) {
|
|
273
|
-
await page.keyboard.press(keyValue);
|
|
274
|
-
} else {
|
|
275
|
-
const keypressSelector = componentContract.selectors[act.target];
|
|
276
|
-
if (!keypressSelector) {
|
|
277
|
-
failures.push(`Selector for keypress target ${act.target} not found.`);
|
|
278
|
-
continue;
|
|
309
|
+
try {
|
|
310
|
+
const keyMap = {
|
|
311
|
+
"Space": "Space",
|
|
312
|
+
"Enter": "Enter",
|
|
313
|
+
"Escape": "Escape",
|
|
314
|
+
"Arrow Up": "ArrowUp",
|
|
315
|
+
"Arrow Down": "ArrowDown",
|
|
316
|
+
"Arrow Left": "ArrowLeft",
|
|
317
|
+
"Arrow Right": "ArrowRight",
|
|
318
|
+
"Home": "Home",
|
|
319
|
+
"End": "End",
|
|
320
|
+
"Tab": "Tab"
|
|
321
|
+
};
|
|
322
|
+
let keyValue = keyMap[act.key] || act.key;
|
|
323
|
+
if (keyValue === "Space") {
|
|
324
|
+
keyValue = " ";
|
|
325
|
+
} else if (keyValue.includes(" ")) {
|
|
326
|
+
keyValue = keyValue.replace(/ /g, "");
|
|
279
327
|
}
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
328
|
+
if (act.target === "focusable" && ["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight", "Escape"].includes(keyValue)) {
|
|
329
|
+
await page.keyboard.press(keyValue);
|
|
330
|
+
} else {
|
|
331
|
+
const keypressSelector = componentContract.selectors[act.target];
|
|
332
|
+
if (!keypressSelector) {
|
|
333
|
+
failures.push(`Selector for keypress target ${act.target} not found.`);
|
|
334
|
+
continue;
|
|
335
|
+
}
|
|
336
|
+
const target = page.locator(keypressSelector).first();
|
|
337
|
+
const elementCount = await target.count();
|
|
338
|
+
if (elementCount === 0) {
|
|
339
|
+
reporter.reportTest(dynamicTest, "skip", `Skipping test - ${act.target} element not found (optional submenu test)`);
|
|
340
|
+
break;
|
|
341
|
+
}
|
|
342
|
+
await target.press(keyValue, { timeout: actionTimeoutMs });
|
|
343
|
+
}
|
|
344
|
+
} catch (error) {
|
|
345
|
+
if (isBrowserClosedError(error)) {
|
|
346
|
+
failures.push(`CRITICAL: Browser/page closed during test execution. Remaining actions skipped.`);
|
|
284
347
|
break;
|
|
285
348
|
}
|
|
286
|
-
|
|
349
|
+
failures.push(`Failed to press ${act.key} on ${act.target}: ${error instanceof Error ? error.message : String(error)}`);
|
|
350
|
+
continue;
|
|
287
351
|
}
|
|
288
352
|
}
|
|
289
353
|
if (act.type === "hover") {
|
|
290
|
-
|
|
354
|
+
try {
|
|
355
|
+
if (act.target === "relative" && act.relativeTarget) {
|
|
356
|
+
const relativeSelector = componentContract.selectors.relative;
|
|
357
|
+
if (!relativeSelector) {
|
|
358
|
+
failures.push(`Relative selector not defined for hover action.`);
|
|
359
|
+
continue;
|
|
360
|
+
}
|
|
361
|
+
const relativeElement = await resolveRelativeTarget(relativeSelector, act.relativeTarget);
|
|
362
|
+
if (!relativeElement) {
|
|
363
|
+
failures.push(`Could not resolve relative target ${act.relativeTarget} for hover.`);
|
|
364
|
+
continue;
|
|
365
|
+
}
|
|
366
|
+
await relativeElement.hover({ timeout: actionTimeoutMs });
|
|
367
|
+
} else {
|
|
368
|
+
const hoverSelector = componentContract.selectors[act.target];
|
|
369
|
+
if (!hoverSelector) {
|
|
370
|
+
failures.push(`Selector for hover target ${act.target} not found.`);
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
373
|
+
await page.locator(hoverSelector).first().hover({ timeout: actionTimeoutMs });
|
|
374
|
+
}
|
|
375
|
+
} catch (error) {
|
|
376
|
+
if (isBrowserClosedError(error)) {
|
|
377
|
+
failures.push(`CRITICAL: Browser/page closed during test execution. Remaining actions skipped.`);
|
|
378
|
+
break;
|
|
379
|
+
}
|
|
380
|
+
failures.push(`Failed to hover ${act.target}: ${error instanceof Error ? error.message : String(error)}`);
|
|
381
|
+
continue;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
for (const assertion of assertions) {
|
|
386
|
+
if (!page || page.isClosed()) {
|
|
387
|
+
failures.push(`CRITICAL: Browser/page closed before completing all tests. Increase test timeout or reduce test complexity.`);
|
|
388
|
+
break;
|
|
389
|
+
}
|
|
390
|
+
let target;
|
|
391
|
+
try {
|
|
392
|
+
if (assertion.target === "relative") {
|
|
291
393
|
const relativeSelector = componentContract.selectors.relative;
|
|
292
394
|
if (!relativeSelector) {
|
|
293
|
-
failures.push(
|
|
395
|
+
failures.push("Relative selector is not defined in the contract.");
|
|
294
396
|
continue;
|
|
295
397
|
}
|
|
296
|
-
const
|
|
297
|
-
if (!
|
|
298
|
-
failures.push(
|
|
398
|
+
const relativeTargetValue = assertion.relativeTarget || assertion.expectedValue;
|
|
399
|
+
if (!relativeTargetValue) {
|
|
400
|
+
failures.push("Relative target or expected value is not defined.");
|
|
299
401
|
continue;
|
|
300
402
|
}
|
|
301
|
-
await
|
|
403
|
+
target = await resolveRelativeTarget(relativeSelector, relativeTargetValue);
|
|
302
404
|
} else {
|
|
303
|
-
const
|
|
304
|
-
if (!
|
|
305
|
-
failures.push(`Selector for
|
|
405
|
+
const assertionSelector = componentContract.selectors[assertion.target];
|
|
406
|
+
if (!assertionSelector) {
|
|
407
|
+
failures.push(`Selector for assertion target ${assertion.target} not found.`);
|
|
306
408
|
continue;
|
|
307
409
|
}
|
|
308
|
-
|
|
410
|
+
target = page.locator(assertionSelector).first();
|
|
309
411
|
}
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
for (const assertion of assertions) {
|
|
313
|
-
let target;
|
|
314
|
-
if (assertion.target === "relative") {
|
|
315
|
-
const relativeSelector = componentContract.selectors.relative;
|
|
316
|
-
if (!relativeSelector) {
|
|
317
|
-
failures.push("Relative selector is not defined in the contract.");
|
|
318
|
-
continue;
|
|
319
|
-
}
|
|
320
|
-
const relativeTargetValue = assertion.relativeTarget || assertion.expectedValue;
|
|
321
|
-
if (!relativeTargetValue) {
|
|
322
|
-
failures.push("Relative target or expected value is not defined.");
|
|
412
|
+
if (!target) {
|
|
413
|
+
failures.push(`Target ${assertion.target} not found.`);
|
|
323
414
|
continue;
|
|
324
415
|
}
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
const assertionSelector = componentContract.selectors[assertion.target];
|
|
328
|
-
if (!assertionSelector) {
|
|
329
|
-
failures.push(`Selector for assertion target ${assertion.target} not found.`);
|
|
330
|
-
continue;
|
|
331
|
-
}
|
|
332
|
-
target = page.locator(assertionSelector).first();
|
|
333
|
-
}
|
|
334
|
-
if (!target) {
|
|
335
|
-
failures.push(`Target ${assertion.target} not found.`);
|
|
416
|
+
} catch (error) {
|
|
417
|
+
failures.push(`Failed to resolve target ${assertion.target}: ${error instanceof Error ? error.message : String(error)}`);
|
|
336
418
|
continue;
|
|
337
419
|
}
|
|
338
420
|
if (assertion.assertion === "toBeVisible") {
|
|
339
421
|
try {
|
|
340
|
-
await expect(target).toBeVisible({ timeout:
|
|
422
|
+
await expect(target).toBeVisible({ timeout: assertionTimeoutMs });
|
|
341
423
|
passes.push(`${assertion.target} is visible as expected. Test: "${dynamicTest.description}".`);
|
|
342
424
|
} catch {
|
|
343
425
|
const debugState = await page.evaluate((sel) => {
|
|
@@ -351,7 +433,7 @@ This indicates a problem with the menu component's close functionality.`
|
|
|
351
433
|
}
|
|
352
434
|
if (assertion.assertion === "notToBeVisible") {
|
|
353
435
|
try {
|
|
354
|
-
await expect(target).toBeHidden({ timeout:
|
|
436
|
+
await expect(target).toBeHidden({ timeout: assertionTimeoutMs });
|
|
355
437
|
passes.push(`${assertion.target} is not visible as expected. Test: "${dynamicTest.description}".`);
|
|
356
438
|
} catch {
|
|
357
439
|
const debugState = await page.evaluate((sel) => {
|
|
@@ -373,7 +455,7 @@ This indicates a problem with the menu component's close functionality.`
|
|
|
373
455
|
failures.push(assertion.failureMessage + ` ${assertion.target} "${assertion.attribute}" should not be empty, found "${attributeValue}".`);
|
|
374
456
|
}
|
|
375
457
|
} else {
|
|
376
|
-
await expect(target).toHaveAttribute(assertion.attribute, assertion.expectedValue, { timeout:
|
|
458
|
+
await expect(target).toHaveAttribute(assertion.attribute, assertion.expectedValue, { timeout: assertionTimeoutMs });
|
|
377
459
|
passes.push(`${assertion.target} has expected "${assertion.attribute}". Test: "${dynamicTest.description}".`);
|
|
378
460
|
}
|
|
379
461
|
} catch {
|
|
@@ -403,7 +485,7 @@ This indicates a problem with the menu component's close functionality.`
|
|
|
403
485
|
}
|
|
404
486
|
if (assertion.assertion === "toHaveFocus") {
|
|
405
487
|
try {
|
|
406
|
-
await expect(target).toBeFocused({ timeout:
|
|
488
|
+
await expect(target).toBeFocused({ timeout: assertionTimeoutMs });
|
|
407
489
|
passes.push(`${assertion.target} has focus as expected. Test: "${dynamicTest.description}".`);
|
|
408
490
|
} catch {
|
|
409
491
|
const actualFocus = await page.evaluate(() => {
|
|
@@ -438,18 +520,48 @@ This indicates a problem with the menu component's close functionality.`
|
|
|
438
520
|
reporter.summary(failures);
|
|
439
521
|
} catch (error) {
|
|
440
522
|
if (error instanceof Error) {
|
|
441
|
-
if (error.message.includes("Executable doesn't exist")) {
|
|
442
|
-
console.error("\n\u274C Playwright browsers not found!\n");
|
|
523
|
+
if (error.message.includes("Executable doesn't exist") || error.message.includes("browserType.launch")) {
|
|
524
|
+
console.error("\n\u274C CRITICAL: Playwright browsers not found!\n");
|
|
443
525
|
console.log("\u{1F4E6} Run: npx playwright install chromium\n");
|
|
444
|
-
failures.push("Playwright browser not installed. Run: npx playwright install chromium");
|
|
445
|
-
} else if (error.message.includes("net::ERR_CONNECTION_REFUSED")) {
|
|
446
|
-
console.error("\n\u274C Cannot connect to dev server!\n");
|
|
526
|
+
failures.push("CRITICAL: Playwright browser not installed. Run: npx playwright install chromium");
|
|
527
|
+
} else if (error.message.includes("net::ERR_CONNECTION_REFUSED") || error.message.includes("NS_ERROR_CONNECTION_REFUSED")) {
|
|
528
|
+
console.error("\n\u274C CRITICAL: Cannot connect to dev server!\n");
|
|
447
529
|
console.log(` Make sure your dev server is running at ${url}
|
|
448
530
|
`);
|
|
449
|
-
failures.push(`Dev server not running at ${url}`);
|
|
531
|
+
failures.push(`CRITICAL: Dev server not running at ${url}`);
|
|
532
|
+
} else if (error.message.includes("Timeout") && error.message.includes("waitFor")) {
|
|
533
|
+
console.error("\n\u274C CRITICAL: Component not found on page!\n");
|
|
534
|
+
console.log(` The component selector could not be found within 30 seconds.
|
|
535
|
+
`);
|
|
536
|
+
console.log(` This usually means:
|
|
537
|
+
`);
|
|
538
|
+
console.log(` - The component didn't render
|
|
539
|
+
`);
|
|
540
|
+
console.log(` - The URL is incorrect
|
|
541
|
+
`);
|
|
542
|
+
console.log(` - The component selector in the contract is wrong
|
|
543
|
+
`);
|
|
544
|
+
failures.push(`CRITICAL: Component element not found on page - ${error.message}`);
|
|
545
|
+
} else if (error.message.includes("Target page, context or browser has been closed")) {
|
|
546
|
+
console.error("\n\u274C CRITICAL: Browser/page was closed unexpectedly!\n");
|
|
547
|
+
console.log(` This usually means:
|
|
548
|
+
`);
|
|
549
|
+
console.log(` - The test timeout was too short
|
|
550
|
+
`);
|
|
551
|
+
console.log(` - The browser crashed
|
|
552
|
+
`);
|
|
553
|
+
console.log(` - An external process killed the browser
|
|
554
|
+
`);
|
|
555
|
+
failures.push(`CRITICAL: Browser/page closed unexpectedly - ${error.message}`);
|
|
556
|
+
} else if (error.message.includes("FATAL")) {
|
|
557
|
+
console.error(`
|
|
558
|
+
${error.message}
|
|
559
|
+
`);
|
|
560
|
+
failures.push(error.message);
|
|
450
561
|
} else {
|
|
451
|
-
console.error("\u274C
|
|
452
|
-
|
|
562
|
+
console.error("\n\u274C UNEXPECTED ERROR:", error.message);
|
|
563
|
+
console.error("Stack:", error.stack);
|
|
564
|
+
failures.push(`UNEXPECTED ERROR: ${error.message}`);
|
|
453
565
|
}
|
|
454
566
|
}
|
|
455
567
|
} finally {
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
"meta": {
|
|
3
3
|
"id": "aria-ease.accordion",
|
|
4
4
|
"version": "1.0.0",
|
|
5
|
+
"lastUpdated": "09-06-2026",
|
|
5
6
|
"description": "ARIA Accordion interaction contract. Validates the ARIA and interaction contract for a custom accordion component following the ARIA Authoring Practices Guide accordion with show/hide pattern.",
|
|
6
7
|
"source": {
|
|
7
8
|
"apg": "https://www.w3.org/WAI/ARIA/apg/patterns/accordion/",
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
"meta": {
|
|
3
3
|
"id": "aria-ease.combobox",
|
|
4
4
|
"version": "1.0.0",
|
|
5
|
+
"lastUpdated": "11-06-2026",
|
|
5
6
|
"description": "ARIA Combobox interaction contract. Validates the ARIA and interaction contract for a custom combobox component following the ARIA Authoring Practices Guide combobox with listbox popup pattern.",
|
|
6
7
|
"source": {
|
|
7
8
|
"apg": "https://www.w3.org/WAI/ARIA/apg/patterns/combobox/",
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
"meta": {
|
|
3
3
|
"id": "aria-ease.menu",
|
|
4
4
|
"version": "1.0.0",
|
|
5
|
+
"lastUpdated": "11-06-2026",
|
|
5
6
|
"description": "ARIA Menu interaction contract. Validates the ARIA and interaction contract for a custom menu component following the ARIA Authoring Practices Guide menu with popup pattern.",
|
|
6
7
|
"source": {
|
|
7
8
|
"apg": "https://www.w3.org/WAI/ARIA/apg/patterns/menubar/",
|