aria-ease 6.2.0 → 6.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/README.md +5 -17
  2. package/bin/{audit-5JJ3XK46.js → audit-RM6TCZ5C.js} +17 -5
  3. package/bin/cli.cjs +260 -133
  4. package/bin/cli.js +18 -15
  5. package/bin/{contractTestRunnerPlaywright-HL2VPEEV.js → contractTestRunnerPlaywright-ACAWN34W.js} +227 -115
  6. package/bin/{test-HH2EW2NM.js → test-A3ESFXOR.js} +1 -1
  7. package/dist/{contractTestRunnerPlaywright-EXEBWWPC.js → contractTestRunnerPlaywright-O7FF7GV4.js} +227 -115
  8. package/dist/index.cjs +229 -116
  9. package/dist/index.d.cts +10 -0
  10. package/dist/index.d.ts +10 -0
  11. package/dist/index.js +3 -2
  12. package/dist/src/{Types.d-CBuuHF3d.d.cts → Types.d-CRjhbrcw.d.cts} +11 -1
  13. package/dist/src/{Types.d-CBuuHF3d.d.ts → Types.d-CRjhbrcw.d.ts} +11 -1
  14. package/dist/src/accordion/index.d.cts +1 -1
  15. package/dist/src/accordion/index.d.ts +1 -1
  16. package/dist/src/block/index.d.cts +1 -1
  17. package/dist/src/block/index.d.ts +1 -1
  18. package/dist/src/checkbox/index.d.cts +1 -1
  19. package/dist/src/checkbox/index.d.ts +1 -1
  20. package/dist/src/combobox/index.cjs +1 -1
  21. package/dist/src/combobox/index.d.cts +1 -1
  22. package/dist/src/combobox/index.d.ts +1 -1
  23. package/dist/src/combobox/index.js +1 -1
  24. package/dist/src/menu/index.d.cts +1 -1
  25. package/dist/src/menu/index.d.ts +1 -1
  26. package/dist/src/radio/index.cjs +1 -0
  27. package/dist/src/radio/index.d.cts +1 -1
  28. package/dist/src/radio/index.d.ts +1 -1
  29. package/dist/src/radio/index.js +1 -0
  30. package/dist/src/toggle/index.d.cts +1 -1
  31. package/dist/src/toggle/index.d.ts +1 -1
  32. package/dist/src/utils/test/{contractTestRunnerPlaywright-LJHY3AB4.js → contractTestRunnerPlaywright-7BPRTIN4.js} +227 -115
  33. package/dist/src/utils/test/contracts/AccordionContract.json +1 -0
  34. package/dist/src/utils/test/contracts/ComboboxContract.json +1 -0
  35. package/dist/src/utils/test/contracts/MenuContract.json +1 -0
  36. package/dist/src/utils/test/index.cjs +227 -115
  37. package/dist/src/utils/test/index.js +1 -1
  38. package/package.json +1 -1
package/README.md CHANGED
@@ -128,23 +128,11 @@ menu.refresh();
128
128
  **Required HTML structure:**
129
129
 
130
130
  ```html
131
- <button
132
- id="menu-button"
133
- aria-expanded="false"
134
- aria-controls="dropdown-menu"
135
- aria-haspopup="true"
136
- >
137
- Menu
138
- </button>
139
- <div
140
- id="dropdown-menu"
141
- style="display: none;"
142
- aria-labelledby="menu-button"
143
- role="menu"
144
- >
145
- <a role="menuitem" href="#" class="menu-item">Item 1</a>
146
- <a role="menuitem" href="#" class="menu-item">Item 2</a>
147
- <button role="menuitem" class="menu-item">Item 3</button>
131
+ <button id="menu-button" aria-label="Home example menu">Menu</button>
132
+ <div id="dropdown-menu" style="display: none;">
133
+ <a href="#" class="menu-item">Item 1</a>
134
+ <a href="#" class="menu-item">Item 2</a>
135
+ <button class="menu-item">Item 3</button>
148
136
  </div>
149
137
  ```
150
138
 
@@ -3,17 +3,26 @@ import "./chunk-I2KLQ2HA.js";
3
3
  // src/utils/audit/src/audit/audit.ts
4
4
  import AxeBuilder from "@axe-core/playwright";
5
5
  import { chromium } from "playwright";
6
+ async function createAuditBrowser() {
7
+ return await chromium.launch({ headless: true });
8
+ }
6
9
  async function runAudit(url, options) {
7
- let browser;
8
- const timeout = options?.timeout || 6e4;
9
- const waitUntil = options?.waitUntil || "domcontentloaded";
10
+ let browser = options.browser;
11
+ let browserCreated = false;
12
+ const timeout = 6e4;
13
+ const waitUntil = "domcontentloaded";
10
14
  try {
11
- browser = await chromium.launch({ headless: true });
15
+ if (!browser) {
16
+ browser = await chromium.launch({ headless: true });
17
+ browserCreated = true;
18
+ }
12
19
  const context = await browser.newContext();
13
20
  const page = await context.newPage();
14
21
  await page.goto(url, { waitUntil, timeout });
15
22
  const axe = new AxeBuilder({ page });
16
23
  const axeResults = await axe.analyze();
24
+ await page.close();
25
+ await context.close();
17
26
  return axeResults;
18
27
  } catch (error) {
19
28
  if (error instanceof Error) {
@@ -38,9 +47,12 @@ async function runAudit(url, options) {
38
47
  }
39
48
  throw error;
40
49
  } finally {
41
- if (browser) await browser.close();
50
+ if (browser && browserCreated) {
51
+ await browser.close();
52
+ }
42
53
  }
43
54
  }
44
55
  export {
56
+ createAuditBrowser,
45
57
  runAudit
46
58
  };
package/bin/cli.cjs CHANGED
@@ -34,19 +34,29 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
34
34
  // src/utils/audit/src/audit/audit.ts
35
35
  var audit_exports = {};
36
36
  __export(audit_exports, {
37
+ createAuditBrowser: () => createAuditBrowser,
37
38
  runAudit: () => runAudit
38
39
  });
40
+ async function createAuditBrowser() {
41
+ return await import_playwright2.chromium.launch({ headless: true });
42
+ }
39
43
  async function runAudit(url, options) {
40
- let browser;
41
- const timeout = options?.timeout || 6e4;
42
- const waitUntil = options?.waitUntil || "domcontentloaded";
44
+ let browser = options.browser;
45
+ let browserCreated = false;
46
+ const timeout = 6e4;
47
+ const waitUntil = "domcontentloaded";
43
48
  try {
44
- browser = await import_playwright2.chromium.launch({ headless: true });
49
+ if (!browser) {
50
+ browser = await import_playwright2.chromium.launch({ headless: true });
51
+ browserCreated = true;
52
+ }
45
53
  const context = await browser.newContext();
46
54
  const page = await context.newPage();
47
55
  await page.goto(url, { waitUntil, timeout });
48
56
  const axe2 = new import_playwright.default({ page });
49
57
  const axeResults = await axe2.analyze();
58
+ await page.close();
59
+ await context.close();
50
60
  return axeResults;
51
61
  } catch (error) {
52
62
  if (error instanceof Error) {
@@ -71,7 +81,9 @@ async function runAudit(url, options) {
71
81
  }
72
82
  throw error;
73
83
  } finally {
74
- if (browser) await browser.close();
84
+ if (browser && browserCreated) {
85
+ await browser.close();
86
+ }
75
87
  }
76
88
  }
77
89
  var import_playwright, import_playwright2;
@@ -661,6 +673,11 @@ __export(contractTestRunnerPlaywright_exports, {
661
673
  });
662
674
  async function runContractTestsPlaywright(componentName, url) {
663
675
  const reporter = new ContractReporter(true);
676
+ const actionTimeoutMs = 400;
677
+ const assertionTimeoutMs = 400;
678
+ function isBrowserClosedError(error) {
679
+ return error instanceof Error && error.message.includes("Target page, context or browser has been closed");
680
+ }
664
681
  const contractTyped = contract_default;
665
682
  const contractPath = contractTyped[componentName]?.path;
666
683
  if (!contractPath) {
@@ -679,17 +696,29 @@ async function runContractTestsPlaywright(componentName, url) {
679
696
  try {
680
697
  page = await createTestPage();
681
698
  if (url) {
682
- await page.goto(url, {
683
- waitUntil: "domcontentloaded",
684
- timeout: 3e4
685
- });
699
+ try {
700
+ await page.goto(url, {
701
+ waitUntil: "domcontentloaded",
702
+ timeout: 3e4
703
+ });
704
+ } catch (error) {
705
+ throw new Error(
706
+ `Failed to navigate to ${url}. Ensure dev server is running and accessible. Original error: ${error instanceof Error ? error.message : String(error)}`
707
+ );
708
+ }
686
709
  await page.addStyleTag({ content: `* { transition: none !important; animation: none !important; }` });
687
710
  }
688
711
  const mainSelector = componentContract.selectors.trigger || componentContract.selectors.input || componentContract.selectors.container;
689
712
  if (!mainSelector) {
690
- throw new Error(`No main selector (trigger, input, or container) found in contract for ${componentName}`);
713
+ throw new Error(`CRITICAL: No main selector (trigger, input, or container) found in contract for ${componentName}`);
714
+ }
715
+ try {
716
+ await page.locator(mainSelector).first().waitFor({ state: "attached", timeout: 3e4 });
717
+ } catch (error) {
718
+ throw new Error(
719
+ `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)}`
720
+ );
691
721
  }
692
- await page.locator(mainSelector).first().waitFor({ state: "attached", timeout: 3e4 });
693
722
  if (componentName === "menu" && componentContract.selectors.trigger) {
694
723
  await page.locator(componentContract.selectors.trigger).first().waitFor({
695
724
  state: "visible",
@@ -769,6 +798,13 @@ async function runContractTestsPlaywright(componentName, url) {
769
798
  }
770
799
  }
771
800
  for (const dynamicTest of componentContract.dynamic || []) {
801
+ if (!page || page.isClosed()) {
802
+ console.warn(`
803
+ \u26A0\uFE0F Browser closed - skipping remaining ${componentContract.dynamic.length - componentContract.dynamic.indexOf(dynamicTest)} tests
804
+ `);
805
+ failures.push(`CRITICAL: Browser/page closed before completing all tests. ${componentContract.dynamic.length - componentContract.dynamic.indexOf(dynamicTest)} tests skipped.`);
806
+ break;
807
+ }
772
808
  const { action, assertions } = dynamicTest;
773
809
  const failuresBeforeTest = failures.length;
774
810
  if (componentContract.selectors.popup) {
@@ -788,16 +824,16 @@ async function runContractTestsPlaywright(componentName, url) {
788
824
  const closeElement = page.locator(closeSelector).first();
789
825
  await closeElement.focus();
790
826
  await page.keyboard.press("Escape");
791
- menuClosed = await (0, test_exports.expect)(popupElement).toBeHidden({ timeout: 2e3 }).then(() => true).catch(() => false);
827
+ menuClosed = await (0, test_exports.expect)(popupElement).toBeHidden({ timeout: assertionTimeoutMs }).then(() => true).catch(() => false);
792
828
  }
793
829
  if (!menuClosed && componentContract.selectors.trigger) {
794
830
  const triggerElement = page.locator(componentContract.selectors.trigger).first();
795
- await triggerElement.click();
796
- menuClosed = await (0, test_exports.expect)(popupElement).toBeHidden({ timeout: 2e3 }).then(() => true).catch(() => false);
831
+ await triggerElement.click({ timeout: actionTimeoutMs });
832
+ menuClosed = await (0, test_exports.expect)(popupElement).toBeHidden({ timeout: assertionTimeoutMs }).then(() => true).catch(() => false);
797
833
  }
798
834
  if (!menuClosed) {
799
835
  await page.mouse.click(10, 10);
800
- menuClosed = await (0, test_exports.expect)(popupElement).toBeHidden({ timeout: 2e3 }).then(() => true).catch(() => false);
836
+ menuClosed = await (0, test_exports.expect)(popupElement).toBeHidden({ timeout: assertionTimeoutMs }).then(() => true).catch(() => false);
801
837
  }
802
838
  if (!menuClosed) {
803
839
  throw new Error(
@@ -826,9 +862,9 @@ This indicates a problem with the menu component's close functionality.`
826
862
  const isExpanded = await trigger.getAttribute("aria-expanded") === "true";
827
863
  const triggerPanel = await trigger.getAttribute("aria-controls");
828
864
  if (isExpanded && triggerPanel) {
829
- await trigger.click();
865
+ await trigger.click({ timeout: actionTimeoutMs });
830
866
  const panel = page.locator(`#${triggerPanel}`);
831
- await (0, test_exports.expect)(panel).toBeHidden({ timeout: 1e3 }).catch(() => {
867
+ await (0, test_exports.expect)(panel).toBeHidden({ timeout: assertionTimeoutMs }).catch(() => {
832
868
  });
833
869
  }
834
870
  }
@@ -867,134 +903,192 @@ This indicates a problem with the menu component's close functionality.`
867
903
  continue;
868
904
  }
869
905
  for (const act of action) {
906
+ if (!page || page.isClosed()) {
907
+ failures.push(`CRITICAL: Browser/page closed during test execution. Remaining actions skipped.`);
908
+ break;
909
+ }
870
910
  if (act.type === "focus") {
871
- const focusSelector = componentContract.selectors[act.target];
872
- if (!focusSelector) {
873
- failures.push(`Selector for focus target ${act.target} not found.`);
911
+ try {
912
+ const focusSelector = componentContract.selectors[act.target];
913
+ if (!focusSelector) {
914
+ failures.push(`Selector for focus target ${act.target} not found.`);
915
+ continue;
916
+ }
917
+ await page.locator(focusSelector).first().focus({ timeout: actionTimeoutMs });
918
+ } catch (error) {
919
+ if (isBrowserClosedError(error)) {
920
+ failures.push(`CRITICAL: Browser/page closed during test execution. Remaining actions skipped.`);
921
+ break;
922
+ }
923
+ failures.push(`Failed to focus ${act.target}: ${error instanceof Error ? error.message : String(error)}`);
874
924
  continue;
875
925
  }
876
- await page.locator(focusSelector).first().focus();
877
926
  }
878
927
  if (act.type === "type" && act.value) {
879
- const typeSelector = componentContract.selectors[act.target];
880
- if (!typeSelector) {
881
- failures.push(`Selector for type target ${act.target} not found.`);
928
+ try {
929
+ const typeSelector = componentContract.selectors[act.target];
930
+ if (!typeSelector) {
931
+ failures.push(`Selector for type target ${act.target} not found.`);
932
+ continue;
933
+ }
934
+ await page.locator(typeSelector).first().fill(act.value, { timeout: actionTimeoutMs });
935
+ } catch (error) {
936
+ if (isBrowserClosedError(error)) {
937
+ failures.push(`CRITICAL: Browser/page closed during test execution. Remaining actions skipped.`);
938
+ break;
939
+ }
940
+ failures.push(`Failed to type into ${act.target}: ${error instanceof Error ? error.message : String(error)}`);
882
941
  continue;
883
942
  }
884
- await page.locator(typeSelector).first().fill(act.value);
885
943
  }
886
944
  if (act.type === "click") {
887
- if (act.target === "document") {
888
- await page.mouse.click(10, 10);
889
- } else if (act.target === "relative" && act.relativeTarget) {
890
- const relativeSelector = componentContract.selectors.relative;
891
- if (!relativeSelector) {
892
- failures.push(`Relative selector not defined for click action.`);
893
- continue;
894
- }
895
- const relativeElement = await resolveRelativeTarget(relativeSelector, act.relativeTarget);
896
- if (!relativeElement) {
897
- failures.push(`Could not resolve relative target ${act.relativeTarget} for click.`);
898
- continue;
945
+ try {
946
+ if (act.target === "document") {
947
+ await page.mouse.click(10, 10);
948
+ } else if (act.target === "relative" && act.relativeTarget) {
949
+ const relativeSelector = componentContract.selectors.relative;
950
+ if (!relativeSelector) {
951
+ failures.push(`Relative selector not defined for click action.`);
952
+ continue;
953
+ }
954
+ const relativeElement = await resolveRelativeTarget(relativeSelector, act.relativeTarget);
955
+ if (!relativeElement) {
956
+ failures.push(`Could not resolve relative target ${act.relativeTarget} for click.`);
957
+ continue;
958
+ }
959
+ await relativeElement.click({ timeout: actionTimeoutMs });
960
+ } else {
961
+ const actionSelector = componentContract.selectors[act.target];
962
+ if (!actionSelector) {
963
+ failures.push(`Selector for action target ${act.target} not found.`);
964
+ continue;
965
+ }
966
+ await page.locator(actionSelector).first().click({ timeout: actionTimeoutMs });
899
967
  }
900
- await relativeElement.click();
901
- } else {
902
- const actionSelector = componentContract.selectors[act.target];
903
- if (!actionSelector) {
904
- failures.push(`Selector for action target ${act.target} not found.`);
905
- continue;
968
+ } catch (error) {
969
+ if (isBrowserClosedError(error)) {
970
+ failures.push(`CRITICAL: Browser/page closed during test execution. Remaining actions skipped.`);
971
+ break;
906
972
  }
907
- await page.locator(actionSelector).first().click();
973
+ failures.push(`Failed to click ${act.target}: ${error instanceof Error ? error.message : String(error)}`);
974
+ continue;
908
975
  }
909
976
  }
910
977
  if (act.type === "keypress" && act.key) {
911
- const keyMap = {
912
- "Space": "Space",
913
- "Enter": "Enter",
914
- "Escape": "Escape",
915
- "Arrow Up": "ArrowUp",
916
- "Arrow Down": "ArrowDown",
917
- "Arrow Left": "ArrowLeft",
918
- "Arrow Right": "ArrowRight",
919
- "Home": "Home",
920
- "End": "End",
921
- "Tab": "Tab"
922
- };
923
- let keyValue = keyMap[act.key] || act.key;
924
- if (keyValue === "Space") {
925
- keyValue = " ";
926
- } else if (keyValue.includes(" ")) {
927
- keyValue = keyValue.replace(/ /g, "");
928
- }
929
- if (act.target === "focusable" && ["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight", "Escape"].includes(keyValue)) {
930
- await page.keyboard.press(keyValue);
931
- } else {
932
- const keypressSelector = componentContract.selectors[act.target];
933
- if (!keypressSelector) {
934
- failures.push(`Selector for keypress target ${act.target} not found.`);
935
- continue;
978
+ try {
979
+ const keyMap = {
980
+ "Space": "Space",
981
+ "Enter": "Enter",
982
+ "Escape": "Escape",
983
+ "Arrow Up": "ArrowUp",
984
+ "Arrow Down": "ArrowDown",
985
+ "Arrow Left": "ArrowLeft",
986
+ "Arrow Right": "ArrowRight",
987
+ "Home": "Home",
988
+ "End": "End",
989
+ "Tab": "Tab"
990
+ };
991
+ let keyValue = keyMap[act.key] || act.key;
992
+ if (keyValue === "Space") {
993
+ keyValue = " ";
994
+ } else if (keyValue.includes(" ")) {
995
+ keyValue = keyValue.replace(/ /g, "");
936
996
  }
937
- const target = page.locator(keypressSelector).first();
938
- const elementCount = await target.count();
939
- if (elementCount === 0) {
940
- reporter.reportTest(dynamicTest, "skip", `Skipping test - ${act.target} element not found (optional submenu test)`);
997
+ if (act.target === "focusable" && ["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight", "Escape"].includes(keyValue)) {
998
+ await page.keyboard.press(keyValue);
999
+ } else {
1000
+ const keypressSelector = componentContract.selectors[act.target];
1001
+ if (!keypressSelector) {
1002
+ failures.push(`Selector for keypress target ${act.target} not found.`);
1003
+ continue;
1004
+ }
1005
+ const target = page.locator(keypressSelector).first();
1006
+ const elementCount = await target.count();
1007
+ if (elementCount === 0) {
1008
+ reporter.reportTest(dynamicTest, "skip", `Skipping test - ${act.target} element not found (optional submenu test)`);
1009
+ break;
1010
+ }
1011
+ await target.press(keyValue, { timeout: actionTimeoutMs });
1012
+ }
1013
+ } catch (error) {
1014
+ if (isBrowserClosedError(error)) {
1015
+ failures.push(`CRITICAL: Browser/page closed during test execution. Remaining actions skipped.`);
941
1016
  break;
942
1017
  }
943
- await target.press(keyValue);
1018
+ failures.push(`Failed to press ${act.key} on ${act.target}: ${error instanceof Error ? error.message : String(error)}`);
1019
+ continue;
944
1020
  }
945
1021
  }
946
1022
  if (act.type === "hover") {
947
- if (act.target === "relative" && act.relativeTarget) {
1023
+ try {
1024
+ if (act.target === "relative" && act.relativeTarget) {
1025
+ const relativeSelector = componentContract.selectors.relative;
1026
+ if (!relativeSelector) {
1027
+ failures.push(`Relative selector not defined for hover action.`);
1028
+ continue;
1029
+ }
1030
+ const relativeElement = await resolveRelativeTarget(relativeSelector, act.relativeTarget);
1031
+ if (!relativeElement) {
1032
+ failures.push(`Could not resolve relative target ${act.relativeTarget} for hover.`);
1033
+ continue;
1034
+ }
1035
+ await relativeElement.hover({ timeout: actionTimeoutMs });
1036
+ } else {
1037
+ const hoverSelector = componentContract.selectors[act.target];
1038
+ if (!hoverSelector) {
1039
+ failures.push(`Selector for hover target ${act.target} not found.`);
1040
+ continue;
1041
+ }
1042
+ await page.locator(hoverSelector).first().hover({ timeout: actionTimeoutMs });
1043
+ }
1044
+ } catch (error) {
1045
+ if (isBrowserClosedError(error)) {
1046
+ failures.push(`CRITICAL: Browser/page closed during test execution. Remaining actions skipped.`);
1047
+ break;
1048
+ }
1049
+ failures.push(`Failed to hover ${act.target}: ${error instanceof Error ? error.message : String(error)}`);
1050
+ continue;
1051
+ }
1052
+ }
1053
+ }
1054
+ for (const assertion of assertions) {
1055
+ if (!page || page.isClosed()) {
1056
+ failures.push(`CRITICAL: Browser/page closed before completing all tests. Increase test timeout or reduce test complexity.`);
1057
+ break;
1058
+ }
1059
+ let target;
1060
+ try {
1061
+ if (assertion.target === "relative") {
948
1062
  const relativeSelector = componentContract.selectors.relative;
949
1063
  if (!relativeSelector) {
950
- failures.push(`Relative selector not defined for hover action.`);
1064
+ failures.push("Relative selector is not defined in the contract.");
951
1065
  continue;
952
1066
  }
953
- const relativeElement = await resolveRelativeTarget(relativeSelector, act.relativeTarget);
954
- if (!relativeElement) {
955
- failures.push(`Could not resolve relative target ${act.relativeTarget} for hover.`);
1067
+ const relativeTargetValue = assertion.relativeTarget || assertion.expectedValue;
1068
+ if (!relativeTargetValue) {
1069
+ failures.push("Relative target or expected value is not defined.");
956
1070
  continue;
957
1071
  }
958
- await relativeElement.hover();
1072
+ target = await resolveRelativeTarget(relativeSelector, relativeTargetValue);
959
1073
  } else {
960
- const hoverSelector = componentContract.selectors[act.target];
961
- if (!hoverSelector) {
962
- failures.push(`Selector for hover target ${act.target} not found.`);
1074
+ const assertionSelector = componentContract.selectors[assertion.target];
1075
+ if (!assertionSelector) {
1076
+ failures.push(`Selector for assertion target ${assertion.target} not found.`);
963
1077
  continue;
964
1078
  }
965
- await page.locator(hoverSelector).first().hover();
966
- }
967
- }
968
- }
969
- for (const assertion of assertions) {
970
- let target;
971
- if (assertion.target === "relative") {
972
- const relativeSelector = componentContract.selectors.relative;
973
- if (!relativeSelector) {
974
- failures.push("Relative selector is not defined in the contract.");
975
- continue;
976
- }
977
- const relativeTargetValue = assertion.relativeTarget || assertion.expectedValue;
978
- if (!relativeTargetValue) {
979
- failures.push("Relative target or expected value is not defined.");
980
- continue;
1079
+ target = page.locator(assertionSelector).first();
981
1080
  }
982
- target = await resolveRelativeTarget(relativeSelector, relativeTargetValue);
983
- } else {
984
- const assertionSelector = componentContract.selectors[assertion.target];
985
- if (!assertionSelector) {
986
- failures.push(`Selector for assertion target ${assertion.target} not found.`);
1081
+ if (!target) {
1082
+ failures.push(`Target ${assertion.target} not found.`);
987
1083
  continue;
988
1084
  }
989
- target = page.locator(assertionSelector).first();
990
- }
991
- if (!target) {
992
- failures.push(`Target ${assertion.target} not found.`);
1085
+ } catch (error) {
1086
+ failures.push(`Failed to resolve target ${assertion.target}: ${error instanceof Error ? error.message : String(error)}`);
993
1087
  continue;
994
1088
  }
995
1089
  if (assertion.assertion === "toBeVisible") {
996
1090
  try {
997
- await (0, test_exports.expect)(target).toBeVisible({ timeout: 2e3 });
1091
+ await (0, test_exports.expect)(target).toBeVisible({ timeout: assertionTimeoutMs });
998
1092
  passes.push(`${assertion.target} is visible as expected. Test: "${dynamicTest.description}".`);
999
1093
  } catch {
1000
1094
  const debugState = await page.evaluate((sel) => {
@@ -1008,7 +1102,7 @@ This indicates a problem with the menu component's close functionality.`
1008
1102
  }
1009
1103
  if (assertion.assertion === "notToBeVisible") {
1010
1104
  try {
1011
- await (0, test_exports.expect)(target).toBeHidden({ timeout: 2e3 });
1105
+ await (0, test_exports.expect)(target).toBeHidden({ timeout: assertionTimeoutMs });
1012
1106
  passes.push(`${assertion.target} is not visible as expected. Test: "${dynamicTest.description}".`);
1013
1107
  } catch {
1014
1108
  const debugState = await page.evaluate((sel) => {
@@ -1030,7 +1124,7 @@ This indicates a problem with the menu component's close functionality.`
1030
1124
  failures.push(assertion.failureMessage + ` ${assertion.target} "${assertion.attribute}" should not be empty, found "${attributeValue}".`);
1031
1125
  }
1032
1126
  } else {
1033
- await (0, test_exports.expect)(target).toHaveAttribute(assertion.attribute, assertion.expectedValue, { timeout: 3e3 });
1127
+ await (0, test_exports.expect)(target).toHaveAttribute(assertion.attribute, assertion.expectedValue, { timeout: assertionTimeoutMs });
1034
1128
  passes.push(`${assertion.target} has expected "${assertion.attribute}". Test: "${dynamicTest.description}".`);
1035
1129
  }
1036
1130
  } catch {
@@ -1060,7 +1154,7 @@ This indicates a problem with the menu component's close functionality.`
1060
1154
  }
1061
1155
  if (assertion.assertion === "toHaveFocus") {
1062
1156
  try {
1063
- await (0, test_exports.expect)(target).toBeFocused({ timeout: 5e3 });
1157
+ await (0, test_exports.expect)(target).toBeFocused({ timeout: assertionTimeoutMs });
1064
1158
  passes.push(`${assertion.target} has focus as expected. Test: "${dynamicTest.description}".`);
1065
1159
  } catch {
1066
1160
  const actualFocus = await page.evaluate(() => {
@@ -1095,18 +1189,48 @@ This indicates a problem with the menu component's close functionality.`
1095
1189
  reporter.summary(failures);
1096
1190
  } catch (error) {
1097
1191
  if (error instanceof Error) {
1098
- if (error.message.includes("Executable doesn't exist")) {
1099
- console.error("\n\u274C Playwright browsers not found!\n");
1192
+ if (error.message.includes("Executable doesn't exist") || error.message.includes("browserType.launch")) {
1193
+ console.error("\n\u274C CRITICAL: Playwright browsers not found!\n");
1100
1194
  console.log("\u{1F4E6} Run: npx playwright install chromium\n");
1101
- failures.push("Playwright browser not installed. Run: npx playwright install chromium");
1102
- } else if (error.message.includes("net::ERR_CONNECTION_REFUSED")) {
1103
- console.error("\n\u274C Cannot connect to dev server!\n");
1195
+ failures.push("CRITICAL: Playwright browser not installed. Run: npx playwright install chromium");
1196
+ } else if (error.message.includes("net::ERR_CONNECTION_REFUSED") || error.message.includes("NS_ERROR_CONNECTION_REFUSED")) {
1197
+ console.error("\n\u274C CRITICAL: Cannot connect to dev server!\n");
1104
1198
  console.log(` Make sure your dev server is running at ${url}
1105
1199
  `);
1106
- failures.push(`Dev server not running at ${url}`);
1200
+ failures.push(`CRITICAL: Dev server not running at ${url}`);
1201
+ } else if (error.message.includes("Timeout") && error.message.includes("waitFor")) {
1202
+ console.error("\n\u274C CRITICAL: Component not found on page!\n");
1203
+ console.log(` The component selector could not be found within 30 seconds.
1204
+ `);
1205
+ console.log(` This usually means:
1206
+ `);
1207
+ console.log(` - The component didn't render
1208
+ `);
1209
+ console.log(` - The URL is incorrect
1210
+ `);
1211
+ console.log(` - The component selector in the contract is wrong
1212
+ `);
1213
+ failures.push(`CRITICAL: Component element not found on page - ${error.message}`);
1214
+ } else if (error.message.includes("Target page, context or browser has been closed")) {
1215
+ console.error("\n\u274C CRITICAL: Browser/page was closed unexpectedly!\n");
1216
+ console.log(` This usually means:
1217
+ `);
1218
+ console.log(` - The test timeout was too short
1219
+ `);
1220
+ console.log(` - The browser crashed
1221
+ `);
1222
+ console.log(` - An external process killed the browser
1223
+ `);
1224
+ failures.push(`CRITICAL: Browser/page closed unexpectedly - ${error.message}`);
1225
+ } else if (error.message.includes("FATAL")) {
1226
+ console.error(`
1227
+ ${error.message}
1228
+ `);
1229
+ failures.push(error.message);
1107
1230
  } else {
1108
- console.error("\u274C Playwright test error:", error.message);
1109
- failures.push(`Test error: ${error.message}`);
1231
+ console.error("\n\u274C UNEXPECTED ERROR:", error.message);
1232
+ console.error("Stack:", error.stack);
1233
+ failures.push(`UNEXPECTED ERROR: ${error.message}`);
1110
1234
  }
1111
1235
  }
1112
1236
  } finally {
@@ -1430,22 +1554,25 @@ program.command("audit").description("Run axe-core powered accessibility audit o
1430
1554
  process.exit(1);
1431
1555
  }
1432
1556
  const allResults = [];
1433
- const auditOptions = {
1434
- timeout: config.audit?.timeout,
1435
- waitUntil: config.audit?.waitUntil
1436
- };
1437
- for (const url of urls) {
1438
- console.log(import_chalk.default.yellow(`\u{1F50E} Auditing: ${url}`));
1439
- try {
1440
- const result = await runAudit2(url, auditOptions);
1441
- allResults.push({ url, result });
1442
- console.log(import_chalk.default.green(`\u2705 Completed audit for ${url}
1557
+ const { createAuditBrowser: createAuditBrowser2 } = await Promise.resolve().then(() => (init_audit(), audit_exports));
1558
+ const browser = await createAuditBrowser2();
1559
+ try {
1560
+ const auditOptions = { browser };
1561
+ for (const url of urls) {
1562
+ console.log(import_chalk.default.yellow(`\u{1F50E} Auditing: ${url}`));
1563
+ try {
1564
+ const result = await runAudit2(url, auditOptions);
1565
+ allResults.push({ url, result });
1566
+ console.log(import_chalk.default.green(`\u2705 Completed audit for ${url}
1443
1567
  `));
1444
- } catch (error) {
1445
- if (error instanceof Error && error.message) {
1446
- console.log(import_chalk.default.red(`\u274C Failed auditing ${url}: ${error.message}`));
1568
+ } catch (error) {
1569
+ if (error instanceof Error && error.message) {
1570
+ console.log(import_chalk.default.red(`\u274C Failed auditing ${url}: ${error.message}`));
1571
+ }
1447
1572
  }
1448
1573
  }
1574
+ } finally {
1575
+ await browser.close();
1449
1576
  }
1450
1577
  const hasResults = allResults.some((r) => r.result && r.result.violations && r.result.violations.length > 0);
1451
1578
  if (!hasResults) {