aria-ease 6.2.1 → 6.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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 -174
- package/dist/index.d.cts +10 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +3 -60
- 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.cjs +0 -22
- package/dist/src/checkbox/index.d.cts +1 -1
- package/dist/src/checkbox/index.d.ts +1 -1
- package/dist/src/checkbox/index.js +0 -22
- 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 -8
- 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 -8
- package/dist/src/toggle/index.cjs +0 -28
- package/dist/src/toggle/index.d.cts +1 -1
- package/dist/src/toggle/index.d.ts +1 -1
- package/dist/src/toggle/index.js +0 -28
- 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/dist/index.cjs
CHANGED
|
@@ -350,6 +350,11 @@ __export(contractTestRunnerPlaywright_exports, {
|
|
|
350
350
|
});
|
|
351
351
|
async function runContractTestsPlaywright(componentName, url) {
|
|
352
352
|
const reporter = new ContractReporter(true);
|
|
353
|
+
const actionTimeoutMs = 400;
|
|
354
|
+
const assertionTimeoutMs = 400;
|
|
355
|
+
function isBrowserClosedError(error) {
|
|
356
|
+
return error instanceof Error && error.message.includes("Target page, context or browser has been closed");
|
|
357
|
+
}
|
|
353
358
|
const contractTyped = contract_default;
|
|
354
359
|
const contractPath = contractTyped[componentName]?.path;
|
|
355
360
|
if (!contractPath) {
|
|
@@ -368,17 +373,29 @@ async function runContractTestsPlaywright(componentName, url) {
|
|
|
368
373
|
try {
|
|
369
374
|
page = await createTestPage();
|
|
370
375
|
if (url) {
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
376
|
+
try {
|
|
377
|
+
await page.goto(url, {
|
|
378
|
+
waitUntil: "domcontentloaded",
|
|
379
|
+
timeout: 3e4
|
|
380
|
+
});
|
|
381
|
+
} catch (error) {
|
|
382
|
+
throw new Error(
|
|
383
|
+
`Failed to navigate to ${url}. Ensure dev server is running and accessible. Original error: ${error instanceof Error ? error.message : String(error)}`
|
|
384
|
+
);
|
|
385
|
+
}
|
|
375
386
|
await page.addStyleTag({ content: `* { transition: none !important; animation: none !important; }` });
|
|
376
387
|
}
|
|
377
388
|
const mainSelector = componentContract.selectors.trigger || componentContract.selectors.input || componentContract.selectors.container;
|
|
378
389
|
if (!mainSelector) {
|
|
379
|
-
throw new Error(`No main selector (trigger, input, or container) found in contract for ${componentName}`);
|
|
390
|
+
throw new Error(`CRITICAL: No main selector (trigger, input, or container) found in contract for ${componentName}`);
|
|
391
|
+
}
|
|
392
|
+
try {
|
|
393
|
+
await page.locator(mainSelector).first().waitFor({ state: "attached", timeout: 3e4 });
|
|
394
|
+
} catch (error) {
|
|
395
|
+
throw new Error(
|
|
396
|
+
`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)}`
|
|
397
|
+
);
|
|
380
398
|
}
|
|
381
|
-
await page.locator(mainSelector).first().waitFor({ state: "attached", timeout: 3e4 });
|
|
382
399
|
if (componentName === "menu" && componentContract.selectors.trigger) {
|
|
383
400
|
await page.locator(componentContract.selectors.trigger).first().waitFor({
|
|
384
401
|
state: "visible",
|
|
@@ -458,6 +475,13 @@ async function runContractTestsPlaywright(componentName, url) {
|
|
|
458
475
|
}
|
|
459
476
|
}
|
|
460
477
|
for (const dynamicTest of componentContract.dynamic || []) {
|
|
478
|
+
if (!page || page.isClosed()) {
|
|
479
|
+
console.warn(`
|
|
480
|
+
\u26A0\uFE0F Browser closed - skipping remaining ${componentContract.dynamic.length - componentContract.dynamic.indexOf(dynamicTest)} tests
|
|
481
|
+
`);
|
|
482
|
+
failures.push(`CRITICAL: Browser/page closed before completing all tests. ${componentContract.dynamic.length - componentContract.dynamic.indexOf(dynamicTest)} tests skipped.`);
|
|
483
|
+
break;
|
|
484
|
+
}
|
|
461
485
|
const { action, assertions } = dynamicTest;
|
|
462
486
|
const failuresBeforeTest = failures.length;
|
|
463
487
|
if (componentContract.selectors.popup) {
|
|
@@ -477,16 +501,16 @@ async function runContractTestsPlaywright(componentName, url) {
|
|
|
477
501
|
const closeElement = page.locator(closeSelector).first();
|
|
478
502
|
await closeElement.focus();
|
|
479
503
|
await page.keyboard.press("Escape");
|
|
480
|
-
menuClosed = await (0, test_exports.expect)(popupElement).toBeHidden({ timeout:
|
|
504
|
+
menuClosed = await (0, test_exports.expect)(popupElement).toBeHidden({ timeout: assertionTimeoutMs }).then(() => true).catch(() => false);
|
|
481
505
|
}
|
|
482
506
|
if (!menuClosed && componentContract.selectors.trigger) {
|
|
483
507
|
const triggerElement = page.locator(componentContract.selectors.trigger).first();
|
|
484
|
-
await triggerElement.click();
|
|
485
|
-
menuClosed = await (0, test_exports.expect)(popupElement).toBeHidden({ timeout:
|
|
508
|
+
await triggerElement.click({ timeout: actionTimeoutMs });
|
|
509
|
+
menuClosed = await (0, test_exports.expect)(popupElement).toBeHidden({ timeout: assertionTimeoutMs }).then(() => true).catch(() => false);
|
|
486
510
|
}
|
|
487
511
|
if (!menuClosed) {
|
|
488
512
|
await page.mouse.click(10, 10);
|
|
489
|
-
menuClosed = await (0, test_exports.expect)(popupElement).toBeHidden({ timeout:
|
|
513
|
+
menuClosed = await (0, test_exports.expect)(popupElement).toBeHidden({ timeout: assertionTimeoutMs }).then(() => true).catch(() => false);
|
|
490
514
|
}
|
|
491
515
|
if (!menuClosed) {
|
|
492
516
|
throw new Error(
|
|
@@ -515,9 +539,9 @@ This indicates a problem with the menu component's close functionality.`
|
|
|
515
539
|
const isExpanded = await trigger.getAttribute("aria-expanded") === "true";
|
|
516
540
|
const triggerPanel = await trigger.getAttribute("aria-controls");
|
|
517
541
|
if (isExpanded && triggerPanel) {
|
|
518
|
-
await trigger.click();
|
|
542
|
+
await trigger.click({ timeout: actionTimeoutMs });
|
|
519
543
|
const panel = page.locator(`#${triggerPanel}`);
|
|
520
|
-
await (0, test_exports.expect)(panel).toBeHidden({ timeout:
|
|
544
|
+
await (0, test_exports.expect)(panel).toBeHidden({ timeout: assertionTimeoutMs }).catch(() => {
|
|
521
545
|
});
|
|
522
546
|
}
|
|
523
547
|
}
|
|
@@ -556,134 +580,192 @@ This indicates a problem with the menu component's close functionality.`
|
|
|
556
580
|
continue;
|
|
557
581
|
}
|
|
558
582
|
for (const act of action) {
|
|
583
|
+
if (!page || page.isClosed()) {
|
|
584
|
+
failures.push(`CRITICAL: Browser/page closed during test execution. Remaining actions skipped.`);
|
|
585
|
+
break;
|
|
586
|
+
}
|
|
559
587
|
if (act.type === "focus") {
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
588
|
+
try {
|
|
589
|
+
const focusSelector = componentContract.selectors[act.target];
|
|
590
|
+
if (!focusSelector) {
|
|
591
|
+
failures.push(`Selector for focus target ${act.target} not found.`);
|
|
592
|
+
continue;
|
|
593
|
+
}
|
|
594
|
+
await page.locator(focusSelector).first().focus({ timeout: actionTimeoutMs });
|
|
595
|
+
} catch (error) {
|
|
596
|
+
if (isBrowserClosedError(error)) {
|
|
597
|
+
failures.push(`CRITICAL: Browser/page closed during test execution. Remaining actions skipped.`);
|
|
598
|
+
break;
|
|
599
|
+
}
|
|
600
|
+
failures.push(`Failed to focus ${act.target}: ${error instanceof Error ? error.message : String(error)}`);
|
|
563
601
|
continue;
|
|
564
602
|
}
|
|
565
|
-
await page.locator(focusSelector).first().focus();
|
|
566
603
|
}
|
|
567
604
|
if (act.type === "type" && act.value) {
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
605
|
+
try {
|
|
606
|
+
const typeSelector = componentContract.selectors[act.target];
|
|
607
|
+
if (!typeSelector) {
|
|
608
|
+
failures.push(`Selector for type target ${act.target} not found.`);
|
|
609
|
+
continue;
|
|
610
|
+
}
|
|
611
|
+
await page.locator(typeSelector).first().fill(act.value, { timeout: actionTimeoutMs });
|
|
612
|
+
} catch (error) {
|
|
613
|
+
if (isBrowserClosedError(error)) {
|
|
614
|
+
failures.push(`CRITICAL: Browser/page closed during test execution. Remaining actions skipped.`);
|
|
615
|
+
break;
|
|
616
|
+
}
|
|
617
|
+
failures.push(`Failed to type into ${act.target}: ${error instanceof Error ? error.message : String(error)}`);
|
|
571
618
|
continue;
|
|
572
619
|
}
|
|
573
|
-
await page.locator(typeSelector).first().fill(act.value);
|
|
574
620
|
}
|
|
575
621
|
if (act.type === "click") {
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
622
|
+
try {
|
|
623
|
+
if (act.target === "document") {
|
|
624
|
+
await page.mouse.click(10, 10);
|
|
625
|
+
} else if (act.target === "relative" && act.relativeTarget) {
|
|
626
|
+
const relativeSelector = componentContract.selectors.relative;
|
|
627
|
+
if (!relativeSelector) {
|
|
628
|
+
failures.push(`Relative selector not defined for click action.`);
|
|
629
|
+
continue;
|
|
630
|
+
}
|
|
631
|
+
const relativeElement = await resolveRelativeTarget(relativeSelector, act.relativeTarget);
|
|
632
|
+
if (!relativeElement) {
|
|
633
|
+
failures.push(`Could not resolve relative target ${act.relativeTarget} for click.`);
|
|
634
|
+
continue;
|
|
635
|
+
}
|
|
636
|
+
await relativeElement.click({ timeout: actionTimeoutMs });
|
|
637
|
+
} else {
|
|
638
|
+
const actionSelector = componentContract.selectors[act.target];
|
|
639
|
+
if (!actionSelector) {
|
|
640
|
+
failures.push(`Selector for action target ${act.target} not found.`);
|
|
641
|
+
continue;
|
|
642
|
+
}
|
|
643
|
+
await page.locator(actionSelector).first().click({ timeout: actionTimeoutMs });
|
|
588
644
|
}
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
failures.push(`Selector for action target ${act.target} not found.`);
|
|
594
|
-
continue;
|
|
645
|
+
} catch (error) {
|
|
646
|
+
if (isBrowserClosedError(error)) {
|
|
647
|
+
failures.push(`CRITICAL: Browser/page closed during test execution. Remaining actions skipped.`);
|
|
648
|
+
break;
|
|
595
649
|
}
|
|
596
|
-
|
|
650
|
+
failures.push(`Failed to click ${act.target}: ${error instanceof Error ? error.message : String(error)}`);
|
|
651
|
+
continue;
|
|
597
652
|
}
|
|
598
653
|
}
|
|
599
654
|
if (act.type === "keypress" && act.key) {
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
keyValue
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
if (act.target === "focusable" && ["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight", "Escape"].includes(keyValue)) {
|
|
619
|
-
await page.keyboard.press(keyValue);
|
|
620
|
-
} else {
|
|
621
|
-
const keypressSelector = componentContract.selectors[act.target];
|
|
622
|
-
if (!keypressSelector) {
|
|
623
|
-
failures.push(`Selector for keypress target ${act.target} not found.`);
|
|
624
|
-
continue;
|
|
655
|
+
try {
|
|
656
|
+
const keyMap = {
|
|
657
|
+
"Space": "Space",
|
|
658
|
+
"Enter": "Enter",
|
|
659
|
+
"Escape": "Escape",
|
|
660
|
+
"Arrow Up": "ArrowUp",
|
|
661
|
+
"Arrow Down": "ArrowDown",
|
|
662
|
+
"Arrow Left": "ArrowLeft",
|
|
663
|
+
"Arrow Right": "ArrowRight",
|
|
664
|
+
"Home": "Home",
|
|
665
|
+
"End": "End",
|
|
666
|
+
"Tab": "Tab"
|
|
667
|
+
};
|
|
668
|
+
let keyValue = keyMap[act.key] || act.key;
|
|
669
|
+
if (keyValue === "Space") {
|
|
670
|
+
keyValue = " ";
|
|
671
|
+
} else if (keyValue.includes(" ")) {
|
|
672
|
+
keyValue = keyValue.replace(/ /g, "");
|
|
625
673
|
}
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
674
|
+
if (act.target === "focusable" && ["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight", "Escape"].includes(keyValue)) {
|
|
675
|
+
await page.keyboard.press(keyValue);
|
|
676
|
+
} else {
|
|
677
|
+
const keypressSelector = componentContract.selectors[act.target];
|
|
678
|
+
if (!keypressSelector) {
|
|
679
|
+
failures.push(`Selector for keypress target ${act.target} not found.`);
|
|
680
|
+
continue;
|
|
681
|
+
}
|
|
682
|
+
const target = page.locator(keypressSelector).first();
|
|
683
|
+
const elementCount = await target.count();
|
|
684
|
+
if (elementCount === 0) {
|
|
685
|
+
reporter.reportTest(dynamicTest, "skip", `Skipping test - ${act.target} element not found (optional submenu test)`);
|
|
686
|
+
break;
|
|
687
|
+
}
|
|
688
|
+
await target.press(keyValue, { timeout: actionTimeoutMs });
|
|
689
|
+
}
|
|
690
|
+
} catch (error) {
|
|
691
|
+
if (isBrowserClosedError(error)) {
|
|
692
|
+
failures.push(`CRITICAL: Browser/page closed during test execution. Remaining actions skipped.`);
|
|
630
693
|
break;
|
|
631
694
|
}
|
|
632
|
-
|
|
695
|
+
failures.push(`Failed to press ${act.key} on ${act.target}: ${error instanceof Error ? error.message : String(error)}`);
|
|
696
|
+
continue;
|
|
633
697
|
}
|
|
634
698
|
}
|
|
635
699
|
if (act.type === "hover") {
|
|
636
|
-
|
|
700
|
+
try {
|
|
701
|
+
if (act.target === "relative" && act.relativeTarget) {
|
|
702
|
+
const relativeSelector = componentContract.selectors.relative;
|
|
703
|
+
if (!relativeSelector) {
|
|
704
|
+
failures.push(`Relative selector not defined for hover action.`);
|
|
705
|
+
continue;
|
|
706
|
+
}
|
|
707
|
+
const relativeElement = await resolveRelativeTarget(relativeSelector, act.relativeTarget);
|
|
708
|
+
if (!relativeElement) {
|
|
709
|
+
failures.push(`Could not resolve relative target ${act.relativeTarget} for hover.`);
|
|
710
|
+
continue;
|
|
711
|
+
}
|
|
712
|
+
await relativeElement.hover({ timeout: actionTimeoutMs });
|
|
713
|
+
} else {
|
|
714
|
+
const hoverSelector = componentContract.selectors[act.target];
|
|
715
|
+
if (!hoverSelector) {
|
|
716
|
+
failures.push(`Selector for hover target ${act.target} not found.`);
|
|
717
|
+
continue;
|
|
718
|
+
}
|
|
719
|
+
await page.locator(hoverSelector).first().hover({ timeout: actionTimeoutMs });
|
|
720
|
+
}
|
|
721
|
+
} catch (error) {
|
|
722
|
+
if (isBrowserClosedError(error)) {
|
|
723
|
+
failures.push(`CRITICAL: Browser/page closed during test execution. Remaining actions skipped.`);
|
|
724
|
+
break;
|
|
725
|
+
}
|
|
726
|
+
failures.push(`Failed to hover ${act.target}: ${error instanceof Error ? error.message : String(error)}`);
|
|
727
|
+
continue;
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
for (const assertion of assertions) {
|
|
732
|
+
if (!page || page.isClosed()) {
|
|
733
|
+
failures.push(`CRITICAL: Browser/page closed before completing all tests. Increase test timeout or reduce test complexity.`);
|
|
734
|
+
break;
|
|
735
|
+
}
|
|
736
|
+
let target;
|
|
737
|
+
try {
|
|
738
|
+
if (assertion.target === "relative") {
|
|
637
739
|
const relativeSelector = componentContract.selectors.relative;
|
|
638
740
|
if (!relativeSelector) {
|
|
639
|
-
failures.push(
|
|
741
|
+
failures.push("Relative selector is not defined in the contract.");
|
|
640
742
|
continue;
|
|
641
743
|
}
|
|
642
|
-
const
|
|
643
|
-
if (!
|
|
644
|
-
failures.push(
|
|
744
|
+
const relativeTargetValue = assertion.relativeTarget || assertion.expectedValue;
|
|
745
|
+
if (!relativeTargetValue) {
|
|
746
|
+
failures.push("Relative target or expected value is not defined.");
|
|
645
747
|
continue;
|
|
646
748
|
}
|
|
647
|
-
await
|
|
749
|
+
target = await resolveRelativeTarget(relativeSelector, relativeTargetValue);
|
|
648
750
|
} else {
|
|
649
|
-
const
|
|
650
|
-
if (!
|
|
651
|
-
failures.push(`Selector for
|
|
751
|
+
const assertionSelector = componentContract.selectors[assertion.target];
|
|
752
|
+
if (!assertionSelector) {
|
|
753
|
+
failures.push(`Selector for assertion target ${assertion.target} not found.`);
|
|
652
754
|
continue;
|
|
653
755
|
}
|
|
654
|
-
|
|
756
|
+
target = page.locator(assertionSelector).first();
|
|
655
757
|
}
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
for (const assertion of assertions) {
|
|
659
|
-
let target;
|
|
660
|
-
if (assertion.target === "relative") {
|
|
661
|
-
const relativeSelector = componentContract.selectors.relative;
|
|
662
|
-
if (!relativeSelector) {
|
|
663
|
-
failures.push("Relative selector is not defined in the contract.");
|
|
758
|
+
if (!target) {
|
|
759
|
+
failures.push(`Target ${assertion.target} not found.`);
|
|
664
760
|
continue;
|
|
665
761
|
}
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
failures.push("Relative target or expected value is not defined.");
|
|
669
|
-
continue;
|
|
670
|
-
}
|
|
671
|
-
target = await resolveRelativeTarget(relativeSelector, relativeTargetValue);
|
|
672
|
-
} else {
|
|
673
|
-
const assertionSelector = componentContract.selectors[assertion.target];
|
|
674
|
-
if (!assertionSelector) {
|
|
675
|
-
failures.push(`Selector for assertion target ${assertion.target} not found.`);
|
|
676
|
-
continue;
|
|
677
|
-
}
|
|
678
|
-
target = page.locator(assertionSelector).first();
|
|
679
|
-
}
|
|
680
|
-
if (!target) {
|
|
681
|
-
failures.push(`Target ${assertion.target} not found.`);
|
|
762
|
+
} catch (error) {
|
|
763
|
+
failures.push(`Failed to resolve target ${assertion.target}: ${error instanceof Error ? error.message : String(error)}`);
|
|
682
764
|
continue;
|
|
683
765
|
}
|
|
684
766
|
if (assertion.assertion === "toBeVisible") {
|
|
685
767
|
try {
|
|
686
|
-
await (0, test_exports.expect)(target).toBeVisible({ timeout:
|
|
768
|
+
await (0, test_exports.expect)(target).toBeVisible({ timeout: assertionTimeoutMs });
|
|
687
769
|
passes.push(`${assertion.target} is visible as expected. Test: "${dynamicTest.description}".`);
|
|
688
770
|
} catch {
|
|
689
771
|
const debugState = await page.evaluate((sel) => {
|
|
@@ -697,7 +779,7 @@ This indicates a problem with the menu component's close functionality.`
|
|
|
697
779
|
}
|
|
698
780
|
if (assertion.assertion === "notToBeVisible") {
|
|
699
781
|
try {
|
|
700
|
-
await (0, test_exports.expect)(target).toBeHidden({ timeout:
|
|
782
|
+
await (0, test_exports.expect)(target).toBeHidden({ timeout: assertionTimeoutMs });
|
|
701
783
|
passes.push(`${assertion.target} is not visible as expected. Test: "${dynamicTest.description}".`);
|
|
702
784
|
} catch {
|
|
703
785
|
const debugState = await page.evaluate((sel) => {
|
|
@@ -719,7 +801,7 @@ This indicates a problem with the menu component's close functionality.`
|
|
|
719
801
|
failures.push(assertion.failureMessage + ` ${assertion.target} "${assertion.attribute}" should not be empty, found "${attributeValue}".`);
|
|
720
802
|
}
|
|
721
803
|
} else {
|
|
722
|
-
await (0, test_exports.expect)(target).toHaveAttribute(assertion.attribute, assertion.expectedValue, { timeout:
|
|
804
|
+
await (0, test_exports.expect)(target).toHaveAttribute(assertion.attribute, assertion.expectedValue, { timeout: assertionTimeoutMs });
|
|
723
805
|
passes.push(`${assertion.target} has expected "${assertion.attribute}". Test: "${dynamicTest.description}".`);
|
|
724
806
|
}
|
|
725
807
|
} catch {
|
|
@@ -749,7 +831,7 @@ This indicates a problem with the menu component's close functionality.`
|
|
|
749
831
|
}
|
|
750
832
|
if (assertion.assertion === "toHaveFocus") {
|
|
751
833
|
try {
|
|
752
|
-
await (0, test_exports.expect)(target).toBeFocused({ timeout:
|
|
834
|
+
await (0, test_exports.expect)(target).toBeFocused({ timeout: assertionTimeoutMs });
|
|
753
835
|
passes.push(`${assertion.target} has focus as expected. Test: "${dynamicTest.description}".`);
|
|
754
836
|
} catch {
|
|
755
837
|
const actualFocus = await page.evaluate(() => {
|
|
@@ -784,18 +866,48 @@ This indicates a problem with the menu component's close functionality.`
|
|
|
784
866
|
reporter.summary(failures);
|
|
785
867
|
} catch (error) {
|
|
786
868
|
if (error instanceof Error) {
|
|
787
|
-
if (error.message.includes("Executable doesn't exist")) {
|
|
788
|
-
console.error("\n\u274C Playwright browsers not found!\n");
|
|
869
|
+
if (error.message.includes("Executable doesn't exist") || error.message.includes("browserType.launch")) {
|
|
870
|
+
console.error("\n\u274C CRITICAL: Playwright browsers not found!\n");
|
|
789
871
|
console.log("\u{1F4E6} Run: npx playwright install chromium\n");
|
|
790
|
-
failures.push("Playwright browser not installed. Run: npx playwright install chromium");
|
|
791
|
-
} else if (error.message.includes("net::ERR_CONNECTION_REFUSED")) {
|
|
792
|
-
console.error("\n\u274C Cannot connect to dev server!\n");
|
|
872
|
+
failures.push("CRITICAL: Playwright browser not installed. Run: npx playwright install chromium");
|
|
873
|
+
} else if (error.message.includes("net::ERR_CONNECTION_REFUSED") || error.message.includes("NS_ERROR_CONNECTION_REFUSED")) {
|
|
874
|
+
console.error("\n\u274C CRITICAL: Cannot connect to dev server!\n");
|
|
793
875
|
console.log(` Make sure your dev server is running at ${url}
|
|
794
876
|
`);
|
|
795
|
-
failures.push(`Dev server not running at ${url}`);
|
|
877
|
+
failures.push(`CRITICAL: Dev server not running at ${url}`);
|
|
878
|
+
} else if (error.message.includes("Timeout") && error.message.includes("waitFor")) {
|
|
879
|
+
console.error("\n\u274C CRITICAL: Component not found on page!\n");
|
|
880
|
+
console.log(` The component selector could not be found within 30 seconds.
|
|
881
|
+
`);
|
|
882
|
+
console.log(` This usually means:
|
|
883
|
+
`);
|
|
884
|
+
console.log(` - The component didn't render
|
|
885
|
+
`);
|
|
886
|
+
console.log(` - The URL is incorrect
|
|
887
|
+
`);
|
|
888
|
+
console.log(` - The component selector in the contract is wrong
|
|
889
|
+
`);
|
|
890
|
+
failures.push(`CRITICAL: Component element not found on page - ${error.message}`);
|
|
891
|
+
} else if (error.message.includes("Target page, context or browser has been closed")) {
|
|
892
|
+
console.error("\n\u274C CRITICAL: Browser/page was closed unexpectedly!\n");
|
|
893
|
+
console.log(` This usually means:
|
|
894
|
+
`);
|
|
895
|
+
console.log(` - The test timeout was too short
|
|
896
|
+
`);
|
|
897
|
+
console.log(` - The browser crashed
|
|
898
|
+
`);
|
|
899
|
+
console.log(` - An external process killed the browser
|
|
900
|
+
`);
|
|
901
|
+
failures.push(`CRITICAL: Browser/page closed unexpectedly - ${error.message}`);
|
|
902
|
+
} else if (error.message.includes("FATAL")) {
|
|
903
|
+
console.error(`
|
|
904
|
+
${error.message}
|
|
905
|
+
`);
|
|
906
|
+
failures.push(error.message);
|
|
796
907
|
} else {
|
|
797
|
-
console.error("\u274C
|
|
798
|
-
|
|
908
|
+
console.error("\n\u274C UNEXPECTED ERROR:", error.message);
|
|
909
|
+
console.error("Stack:", error.stack);
|
|
910
|
+
failures.push(`UNEXPECTED ERROR: ${error.message}`);
|
|
799
911
|
}
|
|
800
912
|
}
|
|
801
913
|
} finally {
|
|
@@ -1250,28 +1362,6 @@ function makeCheckboxAccessible({ checkboxGroupId, checkboxesClass }) {
|
|
|
1250
1362
|
event.preventDefault();
|
|
1251
1363
|
toggleCheckbox(index);
|
|
1252
1364
|
break;
|
|
1253
|
-
case "ArrowDown":
|
|
1254
|
-
event.preventDefault();
|
|
1255
|
-
{
|
|
1256
|
-
const nextIndex = (index + 1) % checkboxes.length;
|
|
1257
|
-
checkboxes[nextIndex].focus();
|
|
1258
|
-
}
|
|
1259
|
-
break;
|
|
1260
|
-
case "ArrowUp":
|
|
1261
|
-
event.preventDefault();
|
|
1262
|
-
{
|
|
1263
|
-
const prevIndex = (index - 1 + checkboxes.length) % checkboxes.length;
|
|
1264
|
-
checkboxes[prevIndex].focus();
|
|
1265
|
-
}
|
|
1266
|
-
break;
|
|
1267
|
-
case "Home":
|
|
1268
|
-
event.preventDefault();
|
|
1269
|
-
checkboxes[0].focus();
|
|
1270
|
-
break;
|
|
1271
|
-
case "End":
|
|
1272
|
-
event.preventDefault();
|
|
1273
|
-
checkboxes[checkboxes.length - 1].focus();
|
|
1274
|
-
break;
|
|
1275
1365
|
}
|
|
1276
1366
|
};
|
|
1277
1367
|
}
|
|
@@ -1614,17 +1704,10 @@ function makeRadioAccessible({ radioGroupId, radiosClass, defaultSelectedIndex =
|
|
|
1614
1704
|
selectRadio(nextIndex);
|
|
1615
1705
|
break;
|
|
1616
1706
|
case " ":
|
|
1707
|
+
case "Enter":
|
|
1617
1708
|
event.preventDefault();
|
|
1618
1709
|
selectRadio(index);
|
|
1619
1710
|
break;
|
|
1620
|
-
case "Home":
|
|
1621
|
-
event.preventDefault();
|
|
1622
|
-
selectRadio(0);
|
|
1623
|
-
break;
|
|
1624
|
-
case "End":
|
|
1625
|
-
event.preventDefault();
|
|
1626
|
-
selectRadio(radios.length - 1);
|
|
1627
|
-
break;
|
|
1628
1711
|
}
|
|
1629
1712
|
};
|
|
1630
1713
|
}
|
|
@@ -1736,34 +1819,6 @@ function makeToggleAccessible({ toggleId, togglesClass, isSingleToggle = true })
|
|
|
1736
1819
|
event.preventDefault();
|
|
1737
1820
|
toggleButton(index);
|
|
1738
1821
|
break;
|
|
1739
|
-
case "ArrowDown":
|
|
1740
|
-
case "ArrowRight":
|
|
1741
|
-
if (!isSingleToggle && toggles.length > 1) {
|
|
1742
|
-
event.preventDefault();
|
|
1743
|
-
const nextIndex = (index + 1) % toggles.length;
|
|
1744
|
-
toggles[nextIndex].focus();
|
|
1745
|
-
}
|
|
1746
|
-
break;
|
|
1747
|
-
case "ArrowUp":
|
|
1748
|
-
case "ArrowLeft":
|
|
1749
|
-
if (!isSingleToggle && toggles.length > 1) {
|
|
1750
|
-
event.preventDefault();
|
|
1751
|
-
const prevIndex = (index - 1 + toggles.length) % toggles.length;
|
|
1752
|
-
toggles[prevIndex].focus();
|
|
1753
|
-
}
|
|
1754
|
-
break;
|
|
1755
|
-
case "Home":
|
|
1756
|
-
if (!isSingleToggle && toggles.length > 1) {
|
|
1757
|
-
event.preventDefault();
|
|
1758
|
-
toggles[0].focus();
|
|
1759
|
-
}
|
|
1760
|
-
break;
|
|
1761
|
-
case "End":
|
|
1762
|
-
if (!isSingleToggle && toggles.length > 1) {
|
|
1763
|
-
event.preventDefault();
|
|
1764
|
-
toggles[toggles.length - 1].focus();
|
|
1765
|
-
}
|
|
1766
|
-
break;
|
|
1767
1822
|
}
|
|
1768
1823
|
};
|
|
1769
1824
|
}
|
|
@@ -2052,7 +2107,7 @@ function makeComboboxAccessible({ comboboxInputId, comboboxButtonId, listBoxId,
|
|
|
2052
2107
|
activeIndex = -1;
|
|
2053
2108
|
setActiveDescendant(-1);
|
|
2054
2109
|
}
|
|
2055
|
-
return { cleanup, refresh };
|
|
2110
|
+
return { cleanup, refresh, openListbox, closeListbox };
|
|
2056
2111
|
}
|
|
2057
2112
|
|
|
2058
2113
|
// src/utils/test/src/test.ts
|
package/dist/index.d.cts
CHANGED
|
@@ -7,25 +7,35 @@ interface JestAxeResult {
|
|
|
7
7
|
interface AccessibilityInstance {
|
|
8
8
|
cleanup: () => void;
|
|
9
9
|
refresh?: () => void;
|
|
10
|
+
|
|
11
|
+
//Menu methods
|
|
10
12
|
openMenu?: () => void;
|
|
11
13
|
closeMenu?: () => void;
|
|
14
|
+
|
|
12
15
|
// Accordion methods
|
|
13
16
|
expandItem?: (index: number) => void;
|
|
14
17
|
collapseItem?: (index: number) => void;
|
|
15
18
|
toggleItem?: (index: number) => void;
|
|
19
|
+
|
|
16
20
|
// Radio methods
|
|
17
21
|
selectRadio?: (index: number) => void;
|
|
18
22
|
getSelectedIndex?: () => number;
|
|
23
|
+
|
|
19
24
|
// Checkbox methods
|
|
20
25
|
toggleCheckbox?: (index: number) => void;
|
|
21
26
|
setCheckboxState?: (index: number, checked: boolean) => void;
|
|
22
27
|
getCheckedStates?: () => boolean[];
|
|
23
28
|
getCheckedIndices?: () => number[];
|
|
29
|
+
|
|
24
30
|
// Toggle methods
|
|
25
31
|
toggleButton?: (index: number) => void;
|
|
26
32
|
setPressed?: (index: number, pressed: boolean) => void;
|
|
27
33
|
getPressedStates?: () => boolean[];
|
|
28
34
|
getPressedIndices?: () => number[];
|
|
35
|
+
|
|
36
|
+
//Combobox methods
|
|
37
|
+
openListbox?: () => void;
|
|
38
|
+
closeListbox?: () => void;
|
|
29
39
|
}
|
|
30
40
|
|
|
31
41
|
interface AccordionConfig {
|
package/dist/index.d.ts
CHANGED
|
@@ -7,25 +7,35 @@ interface JestAxeResult {
|
|
|
7
7
|
interface AccessibilityInstance {
|
|
8
8
|
cleanup: () => void;
|
|
9
9
|
refresh?: () => void;
|
|
10
|
+
|
|
11
|
+
//Menu methods
|
|
10
12
|
openMenu?: () => void;
|
|
11
13
|
closeMenu?: () => void;
|
|
14
|
+
|
|
12
15
|
// Accordion methods
|
|
13
16
|
expandItem?: (index: number) => void;
|
|
14
17
|
collapseItem?: (index: number) => void;
|
|
15
18
|
toggleItem?: (index: number) => void;
|
|
19
|
+
|
|
16
20
|
// Radio methods
|
|
17
21
|
selectRadio?: (index: number) => void;
|
|
18
22
|
getSelectedIndex?: () => number;
|
|
23
|
+
|
|
19
24
|
// Checkbox methods
|
|
20
25
|
toggleCheckbox?: (index: number) => void;
|
|
21
26
|
setCheckboxState?: (index: number, checked: boolean) => void;
|
|
22
27
|
getCheckedStates?: () => boolean[];
|
|
23
28
|
getCheckedIndices?: () => number[];
|
|
29
|
+
|
|
24
30
|
// Toggle methods
|
|
25
31
|
toggleButton?: (index: number) => void;
|
|
26
32
|
setPressed?: (index: number, pressed: boolean) => void;
|
|
27
33
|
getPressedStates?: () => boolean[];
|
|
28
34
|
getPressedIndices?: () => number[];
|
|
35
|
+
|
|
36
|
+
//Combobox methods
|
|
37
|
+
openListbox?: () => void;
|
|
38
|
+
closeListbox?: () => void;
|
|
29
39
|
}
|
|
30
40
|
|
|
31
41
|
interface AccordionConfig {
|