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.
Files changed (37) hide show
  1. package/README.md +5 -17
  2. package/bin/cli.cjs +227 -115
  3. package/bin/cli.js +1 -1
  4. package/bin/{contractTestRunnerPlaywright-HL2VPEEV.js → contractTestRunnerPlaywright-ACAWN34W.js} +227 -115
  5. package/bin/{test-HH2EW2NM.js → test-A3ESFXOR.js} +1 -1
  6. package/dist/{contractTestRunnerPlaywright-EXEBWWPC.js → contractTestRunnerPlaywright-O7FF7GV4.js} +227 -115
  7. package/dist/index.cjs +229 -116
  8. package/dist/index.d.cts +10 -0
  9. package/dist/index.d.ts +10 -0
  10. package/dist/index.js +3 -2
  11. package/dist/src/{Types.d-CxWrr421.d.ts → Types.d-CRjhbrcw.d.cts} +10 -0
  12. package/dist/src/{Types.d-CxWrr421.d.cts → Types.d-CRjhbrcw.d.ts} +10 -0
  13. package/dist/src/accordion/index.d.cts +1 -1
  14. package/dist/src/accordion/index.d.ts +1 -1
  15. package/dist/src/block/index.d.cts +1 -1
  16. package/dist/src/block/index.d.ts +1 -1
  17. package/dist/src/checkbox/index.d.cts +1 -1
  18. package/dist/src/checkbox/index.d.ts +1 -1
  19. package/dist/src/combobox/index.cjs +1 -1
  20. package/dist/src/combobox/index.d.cts +1 -1
  21. package/dist/src/combobox/index.d.ts +1 -1
  22. package/dist/src/combobox/index.js +1 -1
  23. package/dist/src/menu/index.d.cts +1 -1
  24. package/dist/src/menu/index.d.ts +1 -1
  25. package/dist/src/radio/index.cjs +1 -0
  26. package/dist/src/radio/index.d.cts +1 -1
  27. package/dist/src/radio/index.d.ts +1 -1
  28. package/dist/src/radio/index.js +1 -0
  29. package/dist/src/toggle/index.d.cts +1 -1
  30. package/dist/src/toggle/index.d.ts +1 -1
  31. package/dist/src/utils/test/{contractTestRunnerPlaywright-LJHY3AB4.js → contractTestRunnerPlaywright-7BPRTIN4.js} +227 -115
  32. package/dist/src/utils/test/contracts/AccordionContract.json +1 -0
  33. package/dist/src/utils/test/contracts/ComboboxContract.json +1 -0
  34. package/dist/src/utils/test/contracts/MenuContract.json +1 -0
  35. package/dist/src/utils/test/index.cjs +227 -115
  36. package/dist/src/utils/test/index.js +1 -1
  37. package/package.json +1 -1
@@ -1,4 +1,4 @@
1
- import { A as AccordionConfig, a as AccessibilityInstance } from '../Types.d-CxWrr421.js';
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.
@@ -1,4 +1,4 @@
1
- import { a as AccessibilityInstance } from '../Types.d-CxWrr421.cjs';
1
+ import { a as AccessibilityInstance } from '../Types.d-CRjhbrcw.cjs';
2
2
 
3
3
  /**
4
4
  * Adds keyboard interaction to block. The block traps focus and can be interacted with using the keyboard.
@@ -1,4 +1,4 @@
1
- import { a as AccessibilityInstance } from '../Types.d-CxWrr421.js';
1
+ import { a as AccessibilityInstance } from '../Types.d-CRjhbrcw.js';
2
2
 
3
3
  /**
4
4
  * Adds keyboard interaction to block. The block traps focus and can be interacted with using the keyboard.
@@ -1,4 +1,4 @@
1
- import { a as AccessibilityInstance } from '../Types.d-CxWrr421.cjs';
1
+ import { a as AccessibilityInstance } from '../Types.d-CRjhbrcw.cjs';
2
2
 
3
3
  /**
4
4
  * Makes a checkbox group accessible by managing ARIA attributes and keyboard navigation.
@@ -1,4 +1,4 @@
1
- import { a as AccessibilityInstance } from '../Types.d-CxWrr421.js';
1
+ import { a as AccessibilityInstance } from '../Types.d-CRjhbrcw.js';
2
2
 
3
3
  /**
4
4
  * Makes a checkbox group accessible by managing ARIA attributes and keyboard navigation.
@@ -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-CxWrr421.cjs';
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-CxWrr421.js';
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-CxWrr421.cjs';
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.
@@ -1,4 +1,4 @@
1
- import { M as MenuConfig, a as AccessibilityInstance } from '../Types.d-CxWrr421.js';
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.
@@ -68,6 +68,7 @@ function makeRadioAccessible({ radioGroupId, radiosClass, defaultSelectedIndex =
68
68
  selectRadio(nextIndex);
69
69
  break;
70
70
  case " ":
71
+ case "Enter":
71
72
  event.preventDefault();
72
73
  selectRadio(index);
73
74
  break;
@@ -1,4 +1,4 @@
1
- import { a as AccessibilityInstance } from '../Types.d-CxWrr421.cjs';
1
+ import { a as AccessibilityInstance } from '../Types.d-CRjhbrcw.cjs';
2
2
 
3
3
  /**
4
4
  * Makes a radio group accessible by managing ARIA attributes, keyboard navigation, and state.
@@ -1,4 +1,4 @@
1
- import { a as AccessibilityInstance } from '../Types.d-CxWrr421.js';
1
+ import { a as AccessibilityInstance } from '../Types.d-CRjhbrcw.js';
2
2
 
3
3
  /**
4
4
  * Makes a radio group accessible by managing ARIA attributes, keyboard navigation, and state.
@@ -66,6 +66,7 @@ function makeRadioAccessible({ radioGroupId, radiosClass, defaultSelectedIndex =
66
66
  selectRadio(nextIndex);
67
67
  break;
68
68
  case " ":
69
+ case "Enter":
69
70
  event.preventDefault();
70
71
  selectRadio(index);
71
72
  break;
@@ -1,4 +1,4 @@
1
- import { a as AccessibilityInstance } from '../Types.d-CxWrr421.cjs';
1
+ import { a as AccessibilityInstance } from '../Types.d-CRjhbrcw.cjs';
2
2
 
3
3
  /**
4
4
  * Makes a toggle button accessible by managing ARIA attributes and keyboard interactions.
@@ -1,4 +1,4 @@
1
- import { a as AccessibilityInstance } from '../Types.d-CxWrr421.js';
1
+ import { a as AccessibilityInstance } from '../Types.d-CRjhbrcw.js';
2
2
 
3
3
  /**
4
4
  * Makes a toggle button accessible by managing ARIA attributes and keyboard interactions.
@@ -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
- await page.goto(url, {
26
- waitUntil: "domcontentloaded",
27
- timeout: 3e4
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: 2e3 }).then(() => true).catch(() => false);
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: 2e3 }).then(() => true).catch(() => false);
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: 2e3 }).then(() => true).catch(() => false);
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: 1e3 }).catch(() => {
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
- const focusSelector = componentContract.selectors[act.target];
215
- if (!focusSelector) {
216
- failures.push(`Selector for focus target ${act.target} not found.`);
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
- const typeSelector = componentContract.selectors[act.target];
223
- if (!typeSelector) {
224
- failures.push(`Selector for type target ${act.target} not found.`);
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
- if (act.target === "document") {
231
- await page.mouse.click(10, 10);
232
- } else if (act.target === "relative" && act.relativeTarget) {
233
- const relativeSelector = componentContract.selectors.relative;
234
- if (!relativeSelector) {
235
- failures.push(`Relative selector not defined for click action.`);
236
- continue;
237
- }
238
- const relativeElement = await resolveRelativeTarget(relativeSelector, act.relativeTarget);
239
- if (!relativeElement) {
240
- failures.push(`Could not resolve relative target ${act.relativeTarget} for click.`);
241
- continue;
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
- await relativeElement.click();
244
- } else {
245
- const actionSelector = componentContract.selectors[act.target];
246
- if (!actionSelector) {
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
- await page.locator(actionSelector).first().click();
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
- const keyMap = {
255
- "Space": "Space",
256
- "Enter": "Enter",
257
- "Escape": "Escape",
258
- "Arrow Up": "ArrowUp",
259
- "Arrow Down": "ArrowDown",
260
- "Arrow Left": "ArrowLeft",
261
- "Arrow Right": "ArrowRight",
262
- "Home": "Home",
263
- "End": "End",
264
- "Tab": "Tab"
265
- };
266
- let keyValue = keyMap[act.key] || act.key;
267
- if (keyValue === "Space") {
268
- keyValue = " ";
269
- } else if (keyValue.includes(" ")) {
270
- keyValue = keyValue.replace(/ /g, "");
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
- const target = page.locator(keypressSelector).first();
281
- const elementCount = await target.count();
282
- if (elementCount === 0) {
283
- reporter.reportTest(dynamicTest, "skip", `Skipping test - ${act.target} element not found (optional submenu test)`);
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
- await target.press(keyValue);
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
- if (act.target === "relative" && act.relativeTarget) {
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(`Relative selector not defined for hover action.`);
395
+ failures.push("Relative selector is not defined in the contract.");
294
396
  continue;
295
397
  }
296
- const relativeElement = await resolveRelativeTarget(relativeSelector, act.relativeTarget);
297
- if (!relativeElement) {
298
- failures.push(`Could not resolve relative target ${act.relativeTarget} for hover.`);
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 relativeElement.hover();
403
+ target = await resolveRelativeTarget(relativeSelector, relativeTargetValue);
302
404
  } else {
303
- const hoverSelector = componentContract.selectors[act.target];
304
- if (!hoverSelector) {
305
- failures.push(`Selector for hover target ${act.target} not found.`);
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
- await page.locator(hoverSelector).first().hover();
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
- target = await resolveRelativeTarget(relativeSelector, relativeTargetValue);
326
- } else {
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: 2e3 });
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: 2e3 });
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: 3e3 });
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: 5e3 });
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 Playwright test error:", error.message);
452
- failures.push(`Test error: ${error.message}`);
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/",