aria-ease 4.0.1 → 5.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/README.md +22 -24
  2. package/bin/cli.cjs +85 -13
  3. package/bin/cli.js +1 -1
  4. package/bin/{contractTestRunnerPlaywright-EZLNNJV5.js → contractTestRunnerPlaywright-YPBTKJP7.js} +85 -13
  5. package/bin/{test-45KMD4F4.js → test-TAH4VGZV.js} +1 -1
  6. package/dist/{contractTestRunnerPlaywright-UQQI5MYS.js → contractTestRunnerPlaywright-45CFWUOD.js} +85 -13
  7. package/dist/index.cjs +97 -157
  8. package/dist/index.d.cts +1 -57
  9. package/dist/index.d.ts +1 -57
  10. package/dist/index.js +12 -140
  11. package/dist/src/{Types.d-BrHSyS03.d.ts → Types.d-COr5IFp5.d.cts} +1 -17
  12. package/dist/src/{Types.d-BrHSyS03.d.cts → Types.d-COr5IFp5.d.ts} +1 -17
  13. package/dist/src/accordion/index.cjs +0 -27
  14. package/dist/src/accordion/index.d.cts +2 -12
  15. package/dist/src/accordion/index.d.ts +2 -12
  16. package/dist/src/accordion/index.js +1 -27
  17. package/dist/src/block/index.d.cts +1 -1
  18. package/dist/src/block/index.d.ts +1 -1
  19. package/dist/src/checkbox/index.cjs +0 -32
  20. package/dist/src/checkbox/index.d.cts +2 -12
  21. package/dist/src/checkbox/index.d.ts +2 -12
  22. package/dist/src/checkbox/index.js +1 -32
  23. package/dist/src/combobox/index.cjs +1 -1
  24. package/dist/src/combobox/index.d.cts +1 -1
  25. package/dist/src/combobox/index.d.ts +1 -1
  26. package/dist/src/combobox/index.js +1 -1
  27. package/dist/src/menu/index.cjs +9 -18
  28. package/dist/src/menu/index.d.cts +1 -1
  29. package/dist/src/menu/index.d.ts +1 -1
  30. package/dist/src/menu/index.js +9 -18
  31. package/dist/src/radio/index.cjs +0 -31
  32. package/dist/src/radio/index.d.cts +2 -12
  33. package/dist/src/radio/index.d.ts +2 -12
  34. package/dist/src/radio/index.js +1 -31
  35. package/dist/src/toggle/index.cjs +0 -28
  36. package/dist/src/toggle/index.d.cts +2 -12
  37. package/dist/src/toggle/index.d.ts +2 -12
  38. package/dist/src/toggle/index.js +1 -28
  39. package/dist/src/utils/test/{contractTestRunnerPlaywright-UQQI5MYS.js → contractTestRunnerPlaywright-45CFWUOD.js} +85 -13
  40. package/dist/src/utils/test/contracts/AccordionContract.json +55 -0
  41. package/dist/src/utils/test/contracts/MenuContract.json +1 -1
  42. package/dist/src/utils/test/index.cjs +85 -13
  43. package/dist/src/utils/test/index.js +1 -1
  44. package/package.json +1 -1
package/README.md CHANGED
@@ -203,19 +203,19 @@ accordion.toggleItem(2); // Toggle third panel
203
203
  ```
204
204
 
205
205
  <details>
206
- <summary>📌 Legacy API (Still Supported)</summary>
206
+ <summary>📌 Legacy API deprecated</summary>
207
207
 
208
208
  ```javascript
209
- import { updateAccordionTriggerAriaAttributes } from "aria-ease/accordion";
209
+ import { makeAccordionAccessible } from "aria-ease/accordion";
210
210
 
211
211
  const accordionStates = [{ display: true }, { display: false }];
212
212
 
213
- updateAccordionTriggerAriaAttributes(
214
- "accordion-container",
215
- "accordion-trigger",
216
- accordionStates,
217
- 0,
218
- );
213
+ makeAccordionAccessible({
214
+ accordionId: "faq-div",
215
+ triggersClass: "dropdown-button",
216
+ panelsClass: "accordion-panel",
217
+ allowMultiple: false, // Only one panel open at a time
218
+ });
219
219
  ```
220
220
 
221
221
  </details>
@@ -265,19 +265,15 @@ const indices = checkboxGroup.getCheckedIndices(); // [0, 2]
265
265
  ```
266
266
 
267
267
  <details>
268
- <summary>📌 Legacy API (Still Supported)</summary>
268
+ <summary>📌 Legacy API deprecated</summary>
269
269
 
270
270
  ```javascript
271
- import { updateCheckboxAriaAttributes } from "aria-ease/checkbox";
272
-
273
- const checkboxStates = [{ checked: true }, { checked: false }];
271
+ import { makeCheckboxAccessible } from "aria-ease/checkbox";
274
272
 
275
- updateCheckboxAriaAttributes(
276
- "checkbox-group",
277
- "custom-checkbox",
278
- checkboxStates,
279
- 0,
280
- );
273
+ makeCheckboxAccessible({
274
+ checkboxGroupId: "checkbox-div",
275
+ checkboxesClass: "course-checkbox",
276
+ });
281
277
  ```
282
278
 
283
279
  </details>
@@ -326,14 +322,16 @@ const selected = radioGroup.getSelectedIndex(); // Get current selection
326
322
  ```
327
323
 
328
324
  <details>
329
- <summary>📌 Legacy API (Still Supported)</summary>
325
+ <summary>📌 Legacy API deprecated</summary>
330
326
 
331
327
  ```javascript
332
- import { updateRadioAriaAttributes } from "aria-ease/radio";
333
-
334
- const radioStates = [{ checked: true }, { checked: false }];
328
+ import { makeRadioAccessible } from "aria-ease/radio";
335
329
 
336
- updateRadioAriaAttributes("radio-group", "custom-radio", radioStates, 0);
330
+ makeRadioAccessible({
331
+ radioGroupId: "radio-div",
332
+ radiosClass: "radio",
333
+ defaultSelectedIndex: 0, // Optional: which radio is selected initially
334
+ });
337
335
  ```
338
336
 
339
337
  </details>
@@ -393,7 +391,7 @@ toggle.cleanup();
393
391
  ```
394
392
 
395
393
  <details>
396
- <summary>📌 Legacy API (Still Supported)</summary>
394
+ <summary>📌 Legacy API deprecated</summary>
397
395
 
398
396
  ```javascript
399
397
  import { updateToggleAriaAttribute } from "aria-ease/toggle";
package/bin/cli.cjs CHANGED
@@ -13330,10 +13330,11 @@ async function runContractTestsPlaywright(componentName, url) {
13330
13330
  return trigger && trigger.getAttribute("data-menu-initialized") === "true";
13331
13331
  },
13332
13332
  componentContract.selectors.trigger,
13333
- { timeout: 6e3 }
13333
+ { timeout: 1e4 }
13334
13334
  ).catch(() => {
13335
13335
  console.warn("Menu initialization signal not detected, continuing with tests...");
13336
13336
  });
13337
+ await page.waitForTimeout(300);
13337
13338
  }
13338
13339
  async function resolveRelativeTarget(selector, relative) {
13339
13340
  const items = await page.locator(selector).all();
@@ -13411,18 +13412,84 @@ async function runContractTestsPlaywright(componentName, url) {
13411
13412
  const popupElement = page.locator(popupSelector).first();
13412
13413
  const isPopupVisible = await popupElement.isVisible();
13413
13414
  if (isPopupVisible) {
13414
- const closeSelector = componentContract.selectors.input || componentContract.selectors.trigger;
13415
+ let menuClosed = false;
13416
+ let closeSelector = componentContract.selectors.input;
13417
+ if (!closeSelector && componentContract.selectors.focusable) {
13418
+ closeSelector = componentContract.selectors.focusable;
13419
+ } else if (!closeSelector) {
13420
+ closeSelector = componentContract.selectors.trigger;
13421
+ }
13415
13422
  if (closeSelector) {
13416
13423
  const closeElement = page.locator(closeSelector).first();
13417
13424
  await closeElement.focus();
13418
- await page.keyboard.press("Escape");
13419
13425
  await page.waitForTimeout(200);
13420
- if (componentContract.selectors.input) {
13421
- const inputElement = page.locator(componentContract.selectors.input).first();
13422
- await inputElement.clear();
13423
- await page.waitForTimeout(100);
13426
+ await page.keyboard.press("Escape");
13427
+ menuClosed = await page.waitForFunction(
13428
+ (selector) => {
13429
+ const popup = document.querySelector(selector);
13430
+ return popup && getComputedStyle(popup).display === "none";
13431
+ },
13432
+ popupSelector,
13433
+ { timeout: 3e3 }
13434
+ ).then(() => true).catch(() => false);
13435
+ }
13436
+ if (!menuClosed && componentContract.selectors.trigger) {
13437
+ const triggerElement = page.locator(componentContract.selectors.trigger).first();
13438
+ await triggerElement.click();
13439
+ await page.waitForTimeout(500);
13440
+ menuClosed = await page.waitForFunction(
13441
+ (selector) => {
13442
+ const popup = document.querySelector(selector);
13443
+ return popup && getComputedStyle(popup).display === "none";
13444
+ },
13445
+ popupSelector,
13446
+ { timeout: 3e3 }
13447
+ ).then(() => true).catch(() => false);
13448
+ }
13449
+ if (!menuClosed) {
13450
+ await page.mouse.click(10, 10);
13451
+ await page.waitForTimeout(500);
13452
+ menuClosed = await page.waitForFunction(
13453
+ (selector) => {
13454
+ const popup = document.querySelector(selector);
13455
+ return popup && getComputedStyle(popup).display === "none";
13456
+ },
13457
+ popupSelector,
13458
+ { timeout: 3e3 }
13459
+ ).then(() => true).catch(() => false);
13460
+ if (menuClosed) {
13461
+ console.log("\u{1F3AF} Strategy 3 (Click outside) worked");
13424
13462
  }
13425
13463
  }
13464
+ if (!menuClosed) {
13465
+ throw new Error(
13466
+ `\u274C FATAL: Cannot close menu between tests. Menu remains visible after trying:
13467
+ 1. Escape key
13468
+ 2. Clicking trigger
13469
+ 3. Clicking outside
13470
+ This indicates a problem with the menu component's close functionality.`
13471
+ );
13472
+ }
13473
+ if (componentName === "menu" && componentContract.selectors.trigger) {
13474
+ await page.waitForFunction(
13475
+ (selector) => {
13476
+ const trigger = document.querySelector(selector);
13477
+ return document.activeElement === trigger;
13478
+ },
13479
+ componentContract.selectors.trigger,
13480
+ { timeout: 2e3 }
13481
+ ).catch(async () => {
13482
+ const triggerElement = page.locator(componentContract.selectors.trigger).first();
13483
+ await triggerElement.focus();
13484
+ await page.waitForTimeout(200);
13485
+ });
13486
+ }
13487
+ await page.waitForTimeout(500);
13488
+ if (componentContract.selectors.input) {
13489
+ const inputElement = page.locator(componentContract.selectors.input).first();
13490
+ await inputElement.clear();
13491
+ await page.waitForTimeout(100);
13492
+ }
13426
13493
  }
13427
13494
  }
13428
13495
  let shouldSkipTest = false;
@@ -13491,7 +13558,7 @@ async function runContractTestsPlaywright(componentName, url) {
13491
13558
  continue;
13492
13559
  }
13493
13560
  await relativeElement.click();
13494
- await page.waitForTimeout(componentName === "menu" ? 500 : 200);
13561
+ await page.waitForTimeout(componentName === "menu" ? 800 : 200);
13495
13562
  } else {
13496
13563
  const actionSelector = componentContract.selectors[act.target];
13497
13564
  if (!actionSelector) {
@@ -13499,7 +13566,7 @@ async function runContractTestsPlaywright(componentName, url) {
13499
13566
  continue;
13500
13567
  }
13501
13568
  await page.locator(actionSelector).first().click();
13502
- await page.waitForTimeout(componentName === "menu" ? 500 : 200);
13569
+ await page.waitForTimeout(componentName === "menu" ? 800 : 200);
13503
13570
  }
13504
13571
  }
13505
13572
  if (act.type === "keypress" && act.key) {
@@ -13522,9 +13589,9 @@ async function runContractTestsPlaywright(componentName, url) {
13522
13589
  keyValue = keyValue.replace(/ /g, "");
13523
13590
  }
13524
13591
  if (act.target === "focusable" && ["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight", "Escape"].includes(keyValue)) {
13525
- await page.waitForTimeout(100);
13592
+ await page.waitForTimeout(componentName === "menu" ? 200 : 100);
13526
13593
  await page.keyboard.press(keyValue);
13527
- await page.waitForTimeout(100);
13594
+ await page.waitForTimeout(componentName === "menu" ? 300 : 100);
13528
13595
  } else {
13529
13596
  const keypressSelector = componentContract.selectors[act.target];
13530
13597
  if (!keypressSelector) {
@@ -13564,8 +13631,9 @@ async function runContractTestsPlaywright(componentName, url) {
13564
13631
  await page.waitForTimeout(100);
13565
13632
  }
13566
13633
  }
13567
- await page.waitForTimeout(100);
13634
+ await page.waitForTimeout(componentName === "menu" ? 200 : 100);
13568
13635
  }
13636
+ await page.waitForTimeout(componentName === "menu" ? 300 : 100);
13569
13637
  for (const assertion of assertions) {
13570
13638
  let target;
13571
13639
  if (assertion.target === "relative") {
@@ -13663,7 +13731,11 @@ async function runContractTestsPlaywright(componentName, url) {
13663
13731
  await (0, test_exports.expect)(target).toBeFocused({ timeout: 5e3 });
13664
13732
  passes.push(`${assertion.target} has focus as expected. Test: "${dynamicTest.description}".`);
13665
13733
  } catch {
13666
- failures.push(`${assertion.failureMessage}`);
13734
+ const actualFocus = await page.evaluate(() => {
13735
+ const focused = document.activeElement;
13736
+ return focused ? `${focused.tagName}#${focused.id || "no-id"}.${focused.className || "no-class"}` : "no element focused";
13737
+ });
13738
+ failures.push(`${assertion.failureMessage} (actual focus: ${actualFocus})`);
13667
13739
  }
13668
13740
  }
13669
13741
  if (assertion.assertion === "toHaveRole" && assertion.expectedValue) {
package/bin/cli.js CHANGED
@@ -204,7 +204,7 @@ program.command("audit").description("Run axe-core powered accessibility audit o
204
204
  console.log(chalk.green("\n\u{1F389} All audits completed."));
205
205
  });
206
206
  program.command("test").description("Run core a11y accessibility standard tests on UI components").action(async () => {
207
- const { runTest } = await import("./test-45KMD4F4.js");
207
+ const { runTest } = await import("./test-TAH4VGZV.js");
208
208
  runTest();
209
209
  });
210
210
  program.command("help").description("Display help information").action(() => {
@@ -57,10 +57,11 @@ async function runContractTestsPlaywright(componentName, url) {
57
57
  return trigger && trigger.getAttribute("data-menu-initialized") === "true";
58
58
  },
59
59
  componentContract.selectors.trigger,
60
- { timeout: 6e3 }
60
+ { timeout: 1e4 }
61
61
  ).catch(() => {
62
62
  console.warn("Menu initialization signal not detected, continuing with tests...");
63
63
  });
64
+ await page.waitForTimeout(300);
64
65
  }
65
66
  async function resolveRelativeTarget(selector, relative) {
66
67
  const items = await page.locator(selector).all();
@@ -138,18 +139,84 @@ async function runContractTestsPlaywright(componentName, url) {
138
139
  const popupElement = page.locator(popupSelector).first();
139
140
  const isPopupVisible = await popupElement.isVisible();
140
141
  if (isPopupVisible) {
141
- const closeSelector = componentContract.selectors.input || componentContract.selectors.trigger;
142
+ let menuClosed = false;
143
+ let closeSelector = componentContract.selectors.input;
144
+ if (!closeSelector && componentContract.selectors.focusable) {
145
+ closeSelector = componentContract.selectors.focusable;
146
+ } else if (!closeSelector) {
147
+ closeSelector = componentContract.selectors.trigger;
148
+ }
142
149
  if (closeSelector) {
143
150
  const closeElement = page.locator(closeSelector).first();
144
151
  await closeElement.focus();
145
- await page.keyboard.press("Escape");
146
152
  await page.waitForTimeout(200);
147
- if (componentContract.selectors.input) {
148
- const inputElement = page.locator(componentContract.selectors.input).first();
149
- await inputElement.clear();
150
- await page.waitForTimeout(100);
153
+ await page.keyboard.press("Escape");
154
+ menuClosed = await page.waitForFunction(
155
+ (selector) => {
156
+ const popup = document.querySelector(selector);
157
+ return popup && getComputedStyle(popup).display === "none";
158
+ },
159
+ popupSelector,
160
+ { timeout: 3e3 }
161
+ ).then(() => true).catch(() => false);
162
+ }
163
+ if (!menuClosed && componentContract.selectors.trigger) {
164
+ const triggerElement = page.locator(componentContract.selectors.trigger).first();
165
+ await triggerElement.click();
166
+ await page.waitForTimeout(500);
167
+ menuClosed = await page.waitForFunction(
168
+ (selector) => {
169
+ const popup = document.querySelector(selector);
170
+ return popup && getComputedStyle(popup).display === "none";
171
+ },
172
+ popupSelector,
173
+ { timeout: 3e3 }
174
+ ).then(() => true).catch(() => false);
175
+ }
176
+ if (!menuClosed) {
177
+ await page.mouse.click(10, 10);
178
+ await page.waitForTimeout(500);
179
+ menuClosed = await page.waitForFunction(
180
+ (selector) => {
181
+ const popup = document.querySelector(selector);
182
+ return popup && getComputedStyle(popup).display === "none";
183
+ },
184
+ popupSelector,
185
+ { timeout: 3e3 }
186
+ ).then(() => true).catch(() => false);
187
+ if (menuClosed) {
188
+ console.log("\u{1F3AF} Strategy 3 (Click outside) worked");
151
189
  }
152
190
  }
191
+ if (!menuClosed) {
192
+ throw new Error(
193
+ `\u274C FATAL: Cannot close menu between tests. Menu remains visible after trying:
194
+ 1. Escape key
195
+ 2. Clicking trigger
196
+ 3. Clicking outside
197
+ This indicates a problem with the menu component's close functionality.`
198
+ );
199
+ }
200
+ if (componentName === "menu" && componentContract.selectors.trigger) {
201
+ await page.waitForFunction(
202
+ (selector) => {
203
+ const trigger = document.querySelector(selector);
204
+ return document.activeElement === trigger;
205
+ },
206
+ componentContract.selectors.trigger,
207
+ { timeout: 2e3 }
208
+ ).catch(async () => {
209
+ const triggerElement = page.locator(componentContract.selectors.trigger).first();
210
+ await triggerElement.focus();
211
+ await page.waitForTimeout(200);
212
+ });
213
+ }
214
+ await page.waitForTimeout(500);
215
+ if (componentContract.selectors.input) {
216
+ const inputElement = page.locator(componentContract.selectors.input).first();
217
+ await inputElement.clear();
218
+ await page.waitForTimeout(100);
219
+ }
153
220
  }
154
221
  }
155
222
  let shouldSkipTest = false;
@@ -218,7 +285,7 @@ async function runContractTestsPlaywright(componentName, url) {
218
285
  continue;
219
286
  }
220
287
  await relativeElement.click();
221
- await page.waitForTimeout(componentName === "menu" ? 500 : 200);
288
+ await page.waitForTimeout(componentName === "menu" ? 800 : 200);
222
289
  } else {
223
290
  const actionSelector = componentContract.selectors[act.target];
224
291
  if (!actionSelector) {
@@ -226,7 +293,7 @@ async function runContractTestsPlaywright(componentName, url) {
226
293
  continue;
227
294
  }
228
295
  await page.locator(actionSelector).first().click();
229
- await page.waitForTimeout(componentName === "menu" ? 500 : 200);
296
+ await page.waitForTimeout(componentName === "menu" ? 800 : 200);
230
297
  }
231
298
  }
232
299
  if (act.type === "keypress" && act.key) {
@@ -249,9 +316,9 @@ async function runContractTestsPlaywright(componentName, url) {
249
316
  keyValue = keyValue.replace(/ /g, "");
250
317
  }
251
318
  if (act.target === "focusable" && ["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight", "Escape"].includes(keyValue)) {
252
- await page.waitForTimeout(100);
319
+ await page.waitForTimeout(componentName === "menu" ? 200 : 100);
253
320
  await page.keyboard.press(keyValue);
254
- await page.waitForTimeout(100);
321
+ await page.waitForTimeout(componentName === "menu" ? 300 : 100);
255
322
  } else {
256
323
  const keypressSelector = componentContract.selectors[act.target];
257
324
  if (!keypressSelector) {
@@ -291,8 +358,9 @@ async function runContractTestsPlaywright(componentName, url) {
291
358
  await page.waitForTimeout(100);
292
359
  }
293
360
  }
294
- await page.waitForTimeout(100);
361
+ await page.waitForTimeout(componentName === "menu" ? 200 : 100);
295
362
  }
363
+ await page.waitForTimeout(componentName === "menu" ? 300 : 100);
296
364
  for (const assertion of assertions) {
297
365
  let target;
298
366
  if (assertion.target === "relative") {
@@ -390,7 +458,11 @@ async function runContractTestsPlaywright(componentName, url) {
390
458
  await (0, test_exports.expect)(target).toBeFocused({ timeout: 5e3 });
391
459
  passes.push(`${assertion.target} has focus as expected. Test: "${dynamicTest.description}".`);
392
460
  } catch {
393
- failures.push(`${assertion.failureMessage}`);
461
+ const actualFocus = await page.evaluate(() => {
462
+ const focused = document.activeElement;
463
+ return focused ? `${focused.tagName}#${focused.id || "no-id"}.${focused.className || "no-class"}` : "no element focused";
464
+ });
465
+ failures.push(`${assertion.failureMessage} (actual focus: ${actualFocus})`);
394
466
  }
395
467
  }
396
468
  if (assertion.assertion === "toHaveRole" && assertion.expectedValue) {
@@ -12776,7 +12776,7 @@ Error: ${error instanceof Error ? error.message : String(error)}`
12776
12776
  URL must include protocol (e.g., "http://localhost:5173/test")`
12777
12777
  );
12778
12778
  }
12779
- const { runContractTestsPlaywright } = await import("./contractTestRunnerPlaywright-EZLNNJV5.js");
12779
+ const { runContractTestsPlaywright } = await import("./contractTestRunnerPlaywright-YPBTKJP7.js");
12780
12780
  contract = await runContractTestsPlaywright(componentName, url);
12781
12781
  } else {
12782
12782
  console.log(`\u{1F9EA} Running jsdom tests (limited event handling)`);
@@ -55,10 +55,11 @@ async function runContractTestsPlaywright(componentName, url) {
55
55
  return trigger && trigger.getAttribute("data-menu-initialized") === "true";
56
56
  },
57
57
  componentContract.selectors.trigger,
58
- { timeout: 6e3 }
58
+ { timeout: 1e4 }
59
59
  ).catch(() => {
60
60
  console.warn("Menu initialization signal not detected, continuing with tests...");
61
61
  });
62
+ await page.waitForTimeout(300);
62
63
  }
63
64
  async function resolveRelativeTarget(selector, relative) {
64
65
  const items = await page.locator(selector).all();
@@ -136,18 +137,84 @@ async function runContractTestsPlaywright(componentName, url) {
136
137
  const popupElement = page.locator(popupSelector).first();
137
138
  const isPopupVisible = await popupElement.isVisible();
138
139
  if (isPopupVisible) {
139
- const closeSelector = componentContract.selectors.input || componentContract.selectors.trigger;
140
+ let menuClosed = false;
141
+ let closeSelector = componentContract.selectors.input;
142
+ if (!closeSelector && componentContract.selectors.focusable) {
143
+ closeSelector = componentContract.selectors.focusable;
144
+ } else if (!closeSelector) {
145
+ closeSelector = componentContract.selectors.trigger;
146
+ }
140
147
  if (closeSelector) {
141
148
  const closeElement = page.locator(closeSelector).first();
142
149
  await closeElement.focus();
143
- await page.keyboard.press("Escape");
144
150
  await page.waitForTimeout(200);
145
- if (componentContract.selectors.input) {
146
- const inputElement = page.locator(componentContract.selectors.input).first();
147
- await inputElement.clear();
148
- await page.waitForTimeout(100);
151
+ await page.keyboard.press("Escape");
152
+ menuClosed = await page.waitForFunction(
153
+ (selector) => {
154
+ const popup = document.querySelector(selector);
155
+ return popup && getComputedStyle(popup).display === "none";
156
+ },
157
+ popupSelector,
158
+ { timeout: 3e3 }
159
+ ).then(() => true).catch(() => false);
160
+ }
161
+ if (!menuClosed && componentContract.selectors.trigger) {
162
+ const triggerElement = page.locator(componentContract.selectors.trigger).first();
163
+ await triggerElement.click();
164
+ await page.waitForTimeout(500);
165
+ menuClosed = await page.waitForFunction(
166
+ (selector) => {
167
+ const popup = document.querySelector(selector);
168
+ return popup && getComputedStyle(popup).display === "none";
169
+ },
170
+ popupSelector,
171
+ { timeout: 3e3 }
172
+ ).then(() => true).catch(() => false);
173
+ }
174
+ if (!menuClosed) {
175
+ await page.mouse.click(10, 10);
176
+ await page.waitForTimeout(500);
177
+ menuClosed = await page.waitForFunction(
178
+ (selector) => {
179
+ const popup = document.querySelector(selector);
180
+ return popup && getComputedStyle(popup).display === "none";
181
+ },
182
+ popupSelector,
183
+ { timeout: 3e3 }
184
+ ).then(() => true).catch(() => false);
185
+ if (menuClosed) {
186
+ console.log("\u{1F3AF} Strategy 3 (Click outside) worked");
149
187
  }
150
188
  }
189
+ if (!menuClosed) {
190
+ throw new Error(
191
+ `\u274C FATAL: Cannot close menu between tests. Menu remains visible after trying:
192
+ 1. Escape key
193
+ 2. Clicking trigger
194
+ 3. Clicking outside
195
+ This indicates a problem with the menu component's close functionality.`
196
+ );
197
+ }
198
+ if (componentName === "menu" && componentContract.selectors.trigger) {
199
+ await page.waitForFunction(
200
+ (selector) => {
201
+ const trigger = document.querySelector(selector);
202
+ return document.activeElement === trigger;
203
+ },
204
+ componentContract.selectors.trigger,
205
+ { timeout: 2e3 }
206
+ ).catch(async () => {
207
+ const triggerElement = page.locator(componentContract.selectors.trigger).first();
208
+ await triggerElement.focus();
209
+ await page.waitForTimeout(200);
210
+ });
211
+ }
212
+ await page.waitForTimeout(500);
213
+ if (componentContract.selectors.input) {
214
+ const inputElement = page.locator(componentContract.selectors.input).first();
215
+ await inputElement.clear();
216
+ await page.waitForTimeout(100);
217
+ }
151
218
  }
152
219
  }
153
220
  let shouldSkipTest = false;
@@ -216,7 +283,7 @@ async function runContractTestsPlaywright(componentName, url) {
216
283
  continue;
217
284
  }
218
285
  await relativeElement.click();
219
- await page.waitForTimeout(componentName === "menu" ? 500 : 200);
286
+ await page.waitForTimeout(componentName === "menu" ? 800 : 200);
220
287
  } else {
221
288
  const actionSelector = componentContract.selectors[act.target];
222
289
  if (!actionSelector) {
@@ -224,7 +291,7 @@ async function runContractTestsPlaywright(componentName, url) {
224
291
  continue;
225
292
  }
226
293
  await page.locator(actionSelector).first().click();
227
- await page.waitForTimeout(componentName === "menu" ? 500 : 200);
294
+ await page.waitForTimeout(componentName === "menu" ? 800 : 200);
228
295
  }
229
296
  }
230
297
  if (act.type === "keypress" && act.key) {
@@ -247,9 +314,9 @@ async function runContractTestsPlaywright(componentName, url) {
247
314
  keyValue = keyValue.replace(/ /g, "");
248
315
  }
249
316
  if (act.target === "focusable" && ["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight", "Escape"].includes(keyValue)) {
250
- await page.waitForTimeout(100);
317
+ await page.waitForTimeout(componentName === "menu" ? 200 : 100);
251
318
  await page.keyboard.press(keyValue);
252
- await page.waitForTimeout(100);
319
+ await page.waitForTimeout(componentName === "menu" ? 300 : 100);
253
320
  } else {
254
321
  const keypressSelector = componentContract.selectors[act.target];
255
322
  if (!keypressSelector) {
@@ -289,8 +356,9 @@ async function runContractTestsPlaywright(componentName, url) {
289
356
  await page.waitForTimeout(100);
290
357
  }
291
358
  }
292
- await page.waitForTimeout(100);
359
+ await page.waitForTimeout(componentName === "menu" ? 200 : 100);
293
360
  }
361
+ await page.waitForTimeout(componentName === "menu" ? 300 : 100);
294
362
  for (const assertion of assertions) {
295
363
  let target;
296
364
  if (assertion.target === "relative") {
@@ -388,7 +456,11 @@ async function runContractTestsPlaywright(componentName, url) {
388
456
  await (0, test_exports.expect)(target).toBeFocused({ timeout: 5e3 });
389
457
  passes.push(`${assertion.target} has focus as expected. Test: "${dynamicTest.description}".`);
390
458
  } catch {
391
- failures.push(`${assertion.failureMessage}`);
459
+ const actualFocus = await page.evaluate(() => {
460
+ const focused = document.activeElement;
461
+ return focused ? `${focused.tagName}#${focused.id || "no-id"}.${focused.className || "no-class"}` : "no element focused";
462
+ });
463
+ failures.push(`${assertion.failureMessage} (actual focus: ${actualFocus})`);
392
464
  }
393
465
  }
394
466
  if (assertion.assertion === "toHaveRole" && assertion.expectedValue) {