aria-ease 6.12.2 → 6.14.0

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.
@@ -22,7 +22,7 @@ var MenuComponentStrategy = class {
22
22
  if (!closeSelector && this.selectors.focusable) {
23
23
  closeSelector = this.selectors.focusable;
24
24
  } else if (!closeSelector) {
25
- closeSelector = this.selectors.trigger;
25
+ closeSelector = this.selectors.main;
26
26
  }
27
27
  if (closeSelector) {
28
28
  const closeElement = page.locator(closeSelector).first();
@@ -30,8 +30,8 @@ var MenuComponentStrategy = class {
30
30
  await page.keyboard.press("Escape");
31
31
  menuClosed = await (0, test_exports.expect)(popupElement).toBeHidden({ timeout: this.assertionTimeoutMs }).then(() => true).catch(() => false);
32
32
  }
33
- if (!menuClosed && this.selectors.trigger) {
34
- const triggerElement = page.locator(this.selectors.trigger).first();
33
+ if (!menuClosed && this.selectors.main) {
34
+ const triggerElement = page.locator(this.selectors.main).first();
35
35
  await triggerElement.click({ timeout: this.actionTimeoutMs });
36
36
  menuClosed = await (0, test_exports.expect)(popupElement).toBeHidden({ timeout: this.assertionTimeoutMs }).then(() => true).catch(() => false);
37
37
  }
@@ -51,8 +51,8 @@ This indicates a problem with the menu component's close functionality.`
51
51
  if (this.selectors.input) {
52
52
  await page.locator(this.selectors.input).first().clear();
53
53
  }
54
- if (this.selectors.trigger) {
55
- const triggerElement = page.locator(this.selectors.trigger).first();
54
+ if (this.selectors.main) {
55
+ const triggerElement = page.locator(this.selectors.main).first();
56
56
  await triggerElement.focus();
57
57
  }
58
58
  }
@@ -214,25 +214,6 @@ function validateContractSchema(contract) {
214
214
  });
215
215
  }
216
216
  }
217
- if (c.states !== void 0) {
218
- if (!Array.isArray(c.states)) {
219
- errors.push({ path: "$.states", message: "states must be an array" });
220
- } else {
221
- c.states.forEach((state, idx) => {
222
- if (typeof state !== "object" || state === null) {
223
- errors.push({ path: `$.states[${idx}]`, message: "state must be an object" });
224
- return;
225
- }
226
- const s = state;
227
- if (typeof s.name !== "string") {
228
- errors.push({ path: `$.states[${idx}].name`, message: "name is required and must be a string" });
229
- }
230
- if (!Array.isArray(s.requires)) {
231
- errors.push({ path: `$.states[${idx}].requires`, message: "requires is required and must be an array" });
232
- }
233
- });
234
- }
235
- }
236
217
  return {
237
218
  valid: errors.length === 0,
238
219
  errors
@@ -308,17 +289,8 @@ function validateTargetReferences(contract, selectorKeys) {
308
289
  });
309
290
  }
310
291
  const dynamicItems = c.dynamic;
311
- const states = c.states;
312
- const stateNames = new Set((states || []).map((s) => String(s.name)));
313
292
  if (Array.isArray(dynamicItems)) {
314
293
  dynamicItems.forEach((item, itemIdx) => {
315
- const given = item.given;
316
- if (given && !stateNames.has(given)) {
317
- errors.push({
318
- path: `$.dynamic[${itemIdx}].given`,
319
- message: `State '${given}' not found in states`
320
- });
321
- }
322
294
  const actions = item.action;
323
295
  if (Array.isArray(actions)) {
324
296
  actions.forEach((action, actIdx) => {
package/dist/cli.cjs CHANGED
@@ -1155,7 +1155,7 @@ var init_MenuComponentStrategy = __esm({
1155
1155
  if (!closeSelector && this.selectors.focusable) {
1156
1156
  closeSelector = this.selectors.focusable;
1157
1157
  } else if (!closeSelector) {
1158
- closeSelector = this.selectors.trigger;
1158
+ closeSelector = this.selectors.main;
1159
1159
  }
1160
1160
  if (closeSelector) {
1161
1161
  const closeElement = page.locator(closeSelector).first();
@@ -1163,8 +1163,8 @@ var init_MenuComponentStrategy = __esm({
1163
1163
  await page.keyboard.press("Escape");
1164
1164
  menuClosed = await (0, test_exports.expect)(popupElement).toBeHidden({ timeout: this.assertionTimeoutMs }).then(() => true).catch(() => false);
1165
1165
  }
1166
- if (!menuClosed && this.selectors.trigger) {
1167
- const triggerElement = page.locator(this.selectors.trigger).first();
1166
+ if (!menuClosed && this.selectors.main) {
1167
+ const triggerElement = page.locator(this.selectors.main).first();
1168
1168
  await triggerElement.click({ timeout: this.actionTimeoutMs });
1169
1169
  menuClosed = await (0, test_exports.expect)(popupElement).toBeHidden({ timeout: this.assertionTimeoutMs }).then(() => true).catch(() => false);
1170
1170
  }
@@ -1184,8 +1184,8 @@ This indicates a problem with the menu component's close functionality.`
1184
1184
  if (this.selectors.input) {
1185
1185
  await page.locator(this.selectors.input).first().clear();
1186
1186
  }
1187
- if (this.selectors.trigger) {
1188
- const triggerElement = page.locator(this.selectors.trigger).first();
1187
+ if (this.selectors.main) {
1188
+ const triggerElement = page.locator(this.selectors.main).first();
1189
1189
  await triggerElement.focus();
1190
1190
  }
1191
1191
  }
@@ -2102,12 +2102,7 @@ async function runContractTestsPlaywright(componentName, url, strictness, config
2102
2102
  const componentConfig = config?.test?.components?.find((c) => c.name === componentName);
2103
2103
  const isCustomContract = !!componentConfig?.contractPath;
2104
2104
  const reporter = new ContractReporter(true, isCustomContract);
2105
- const defaultTimeouts = {
2106
- actionTimeoutMs: 400,
2107
- assertionTimeoutMs: 400,
2108
- navigationTimeoutMs: 3e4,
2109
- componentReadyTimeoutMs: 5e3
2110
- };
2105
+ const defaultTimeouts = { actionTimeoutMs: 400, assertionTimeoutMs: 400, navigationTimeoutMs: 3e4, componentReadyTimeoutMs: 5e3 };
2111
2106
  const globalDisableTimeouts = config?.test?.disableTimeouts === true;
2112
2107
  const componentDisableTimeouts = componentConfig?.disableTimeouts === true;
2113
2108
  const disableTimeouts = componentDisableTimeouts || globalDisableTimeouts;
@@ -2119,11 +2114,7 @@ async function runContractTestsPlaywright(componentName, url, strictness, config
2119
2114
  }
2120
2115
  return value;
2121
2116
  };
2122
- const actionTimeoutMs = resolveTimeout(
2123
- componentConfig?.actionTimeoutMs,
2124
- config?.test?.actionTimeoutMs,
2125
- defaultTimeouts.actionTimeoutMs
2126
- );
2117
+ const actionTimeoutMs = resolveTimeout(componentConfig?.actionTimeoutMs, config?.test?.actionTimeoutMs, defaultTimeouts.actionTimeoutMs);
2127
2118
  const assertionTimeoutMs = resolveTimeout(
2128
2119
  componentConfig?.assertionTimeoutMs,
2129
2120
  config?.test?.assertionTimeoutMs,
@@ -2223,8 +2214,8 @@ This usually means:
2223
2214
  );
2224
2215
  }
2225
2216
  reporter.start(componentName, totalTests, apgUrl);
2226
- if (componentName === "menu" && componentContract.selectors.trigger) {
2227
- await page.locator(componentContract.selectors.trigger).first().waitFor({ state: "attached", timeout: componentReadyTimeoutMs }).catch(() => {
2217
+ if (componentName === "menu" && componentContract.selectors.main) {
2218
+ await page.locator(componentContract.selectors.main).first().waitFor({ state: "visible", timeout: componentReadyTimeoutMs }).catch(() => {
2228
2219
  });
2229
2220
  }
2230
2221
  const hasSubmenuCapability = componentName === "menu" && !!componentContract.selectors.submenuTrigger ? await page.locator(componentContract.selectors.submenuTrigger).count() > 0 : false;
@@ -2233,7 +2224,42 @@ This usually means:
2233
2224
  let staticFailed = 0;
2234
2225
  let staticWarnings = 0;
2235
2226
  for (const rel of componentContract.relationships || []) {
2227
+ if (strategy && typeof strategy.resetState === "function") {
2228
+ try {
2229
+ await strategy.resetState(page);
2230
+ } catch (err) {
2231
+ warnings.push(`Warning: resetState failed before relationship test: ${err instanceof Error ? err.message : String(err)}`);
2232
+ }
2233
+ }
2236
2234
  const relationshipLevel = normalizeLevel(rel.level);
2235
+ if (Array.isArray(rel.setup) && rel.setup.length > 0) {
2236
+ let isAllowedType2 = function(t) {
2237
+ return allowedTypes.includes(t);
2238
+ };
2239
+ var isAllowedType = isAllowedType2;
2240
+ const actionExecutor = new ActionExecutor(page, componentContract.selectors, actionTimeoutMs);
2241
+ const relDescription = rel.type === "aria-reference" ? `${rel.from}.${rel.attribute} references ${rel.to}` : `${rel.parent} contains ${rel.child}`;
2242
+ const allowedTypes = ["focus", "type", "click", "keypress", "hover"];
2243
+ const toSetupAction = (a) => ({
2244
+ ...a,
2245
+ type: isAllowedType2(a.type) ? a.type : "click"
2246
+ });
2247
+ const setupActions = rel.setup.map(toSetupAction);
2248
+ const setupResult = await runSetupActions(setupActions, actionExecutor, strategy, page, relDescription, ["submenu", "submenuTrigger", "submenuItems"]);
2249
+ if (setupResult.skip) {
2250
+ skipped.push(setupResult.message || "Setup action skipped");
2251
+ reporter.reportStaticTest(relDescription, "skip", setupResult.message, relationshipLevel);
2252
+ continue;
2253
+ }
2254
+ if (!setupResult.success) {
2255
+ const failure = `Relationship setup failed: ${setupResult.error}`;
2256
+ const outcome = classifyFailure(failure, rel.level);
2257
+ if (outcome.status === "fail") staticFailed += 1;
2258
+ if (outcome.status === "warn") staticWarnings += 1;
2259
+ reporter.reportStaticTest(relDescription, outcome.status, outcome.detail, outcome.level);
2260
+ continue;
2261
+ }
2262
+ }
2237
2263
  if (componentName === "menu" && !hasSubmenuCapability) {
2238
2264
  const involvesSubmenu = isSubmenuRelation(rel);
2239
2265
  if (involvesSubmenu) {
@@ -2406,7 +2432,53 @@ This usually means:
2406
2432
  }
2407
2433
  }
2408
2434
  const staticAssertionRunner = new AssertionRunner(page, componentContract.selectors, assertionTimeoutMs);
2435
+ async function runSetupActions(setup, actionExecutor, strategy2, page2, description, skipKeywords = []) {
2436
+ if (!Array.isArray(setup) || setup.length === 0) return { success: true };
2437
+ if (strategy2 && typeof strategy2.resetState === "function") {
2438
+ await strategy2.resetState(page2);
2439
+ }
2440
+ for (const setupAct of setup) {
2441
+ let setupResult = { success: true };
2442
+ try {
2443
+ if (setupAct.type === "focus") {
2444
+ if (setupAct.target === "relative" && setupAct.relativeTarget) {
2445
+ setupResult = await actionExecutor.focus("relative", setupAct.relativeTarget);
2446
+ } else {
2447
+ setupResult = await actionExecutor.focus(setupAct.target);
2448
+ }
2449
+ } else if (setupAct.type === "type" && setupAct.value) {
2450
+ setupResult = await actionExecutor.type(setupAct.target, setupAct.value);
2451
+ } else if (setupAct.type === "click") {
2452
+ setupResult = await actionExecutor.click(setupAct.target, setupAct.relativeTarget);
2453
+ } else if (setupAct.type === "keypress" && setupAct.key) {
2454
+ setupResult = await actionExecutor.keypress(setupAct.target, setupAct.key);
2455
+ } else if (setupAct.type === "hover") {
2456
+ setupResult = await actionExecutor.hover(setupAct.target, setupAct.relativeTarget);
2457
+ } else {
2458
+ continue;
2459
+ }
2460
+ } catch (err) {
2461
+ setupResult = { success: false, error: err instanceof Error ? err.message : String(err) };
2462
+ }
2463
+ if (!setupResult.success) {
2464
+ const setupMsg = setupResult.error || "Setup action failed";
2465
+ const isSkip = skipKeywords.some((kw) => description.includes(kw) || setupMsg.includes(kw));
2466
+ if (isSkip) {
2467
+ return { success: false, skip: true, message: `Skipping test - capability not present: ${setupMsg}` };
2468
+ }
2469
+ return { success: false, error: setupMsg };
2470
+ }
2471
+ }
2472
+ return { success: true };
2473
+ }
2409
2474
  for (const test of componentContract.static[0]?.assertions || []) {
2475
+ if (strategy && typeof strategy.resetState === "function") {
2476
+ try {
2477
+ await strategy.resetState(page);
2478
+ } catch (err) {
2479
+ warnings.push(`Warning: resetState failed before static test: ${err instanceof Error ? err.message : String(err)}`);
2480
+ }
2481
+ }
2410
2482
  if (test.target === "relative") continue;
2411
2483
  const staticDescription = `${test.target}${test.attribute ? ` (${test.attribute})` : ""}`;
2412
2484
  const staticLevel = normalizeLevel(test.level);
@@ -2416,6 +2488,33 @@ This usually means:
2416
2488
  reporter.reportStaticTest(staticDescription, "skip", skipMessage, staticLevel);
2417
2489
  continue;
2418
2490
  }
2491
+ if (Array.isArray(test.setup) && test.setup.length > 0) {
2492
+ let isAllowedType2 = function(t) {
2493
+ return allowedTypes.includes(t);
2494
+ };
2495
+ var isAllowedType = isAllowedType2;
2496
+ const actionExecutor = new ActionExecutor(page, componentContract.selectors, actionTimeoutMs);
2497
+ const allowedTypes = ["focus", "type", "click", "keypress", "hover"];
2498
+ const toSetupAction = (a) => ({
2499
+ ...a,
2500
+ type: isAllowedType2(a.type) ? a.type : "click"
2501
+ });
2502
+ const setupActions = test.setup.map(toSetupAction);
2503
+ const setupResult = await runSetupActions(setupActions, actionExecutor, strategy, page, staticDescription, ["submenu", "submenuTrigger", "submenuItems"]);
2504
+ if (setupResult.skip) {
2505
+ skipped.push(setupResult.message || "Setup action skipped");
2506
+ reporter.reportStaticTest(staticDescription, "skip", setupResult.message, staticLevel);
2507
+ continue;
2508
+ }
2509
+ if (!setupResult.success) {
2510
+ const failure = `Static setup failed: ${setupResult.error}`;
2511
+ const outcome = classifyFailure(failure, test.level);
2512
+ if (outcome.status === "fail") staticFailed += 1;
2513
+ if (outcome.status === "warn") staticWarnings += 1;
2514
+ reporter.reportStaticTest(staticDescription, outcome.status, outcome.detail, outcome.level);
2515
+ continue;
2516
+ }
2517
+ }
2419
2518
  const targetSelector = componentContract.selectors[test.target];
2420
2519
  if (!targetSelector) {
2421
2520
  const failure = `Selector for target ${test.target} not found.`;
@@ -2542,31 +2641,26 @@ This usually means:
2542
2641
  const dynamicLevel = normalizeLevel(dynamicTest.level);
2543
2642
  const actionExecutor = new ActionExecutor(page, componentContract.selectors, actionTimeoutMs);
2544
2643
  if (Array.isArray(setup) && setup.length > 0) {
2545
- for (const setupAct of setup) {
2546
- let setupResult;
2547
- if (setupAct.type === "focus") {
2548
- if (setupAct.target === "relative" && setupAct.relativeTarget) {
2549
- setupResult = await actionExecutor.focus("relative", setupAct.relativeTarget);
2550
- } else {
2551
- setupResult = await actionExecutor.focus(setupAct.target);
2552
- }
2553
- } else if (setupAct.type === "type" && setupAct.value) {
2554
- setupResult = await actionExecutor.type(setupAct.target, setupAct.value);
2555
- } else if (setupAct.type === "click") {
2556
- setupResult = await actionExecutor.click(setupAct.target, setupAct.relativeTarget);
2557
- } else if (setupAct.type === "keypress" && setupAct.key) {
2558
- setupResult = await actionExecutor.keypress(setupAct.target, setupAct.key);
2559
- } else if (setupAct.type === "hover") {
2560
- setupResult = await actionExecutor.hover(setupAct.target, setupAct.relativeTarget);
2561
- } else {
2562
- continue;
2563
- }
2564
- if (!setupResult.success) {
2565
- const setupMsg = setupResult.error || "Setup action failed";
2566
- const outcome = classifyFailure(`Setup failed: ${setupMsg}`, dynamicTest.level);
2567
- reporter.reportTest({ description: dynamicTest.description, level: dynamicLevel }, outcome.status, outcome.detail);
2568
- continue;
2569
- }
2644
+ let isAllowedType2 = function(t) {
2645
+ return allowedTypes.includes(t);
2646
+ };
2647
+ var isAllowedType = isAllowedType2;
2648
+ const allowedTypes = ["focus", "type", "click", "keypress", "hover"];
2649
+ const toSetupAction = (a) => ({
2650
+ ...a,
2651
+ type: isAllowedType2(a.type) ? a.type : "click"
2652
+ });
2653
+ const setupActions = setup.map(toSetupAction);
2654
+ const setupResult = await runSetupActions(setupActions, actionExecutor, strategy, page, dynamicTest.description, ["submenu", "submenuTrigger", "submenuItems"]);
2655
+ if (setupResult.skip) {
2656
+ skipped.push(setupResult.message || "Setup action skipped");
2657
+ reporter.reportTest({ description: dynamicTest.description, level: dynamicLevel }, "skip", setupResult.message);
2658
+ continue;
2659
+ }
2660
+ if (!setupResult.success) {
2661
+ const outcome = classifyFailure(`Setup failed: ${setupResult.error}`, dynamicTest.level);
2662
+ reporter.reportTest({ description: dynamicTest.description, level: dynamicLevel }, outcome.status, outcome.detail);
2663
+ continue;
2570
2664
  }
2571
2665
  }
2572
2666
  const failuresBeforeTest = failures.length;
@@ -3146,25 +3240,6 @@ function validateContractSchema(contract) {
3146
3240
  });
3147
3241
  }
3148
3242
  }
3149
- if (c.states !== void 0) {
3150
- if (!Array.isArray(c.states)) {
3151
- errors.push({ path: "$.states", message: "states must be an array" });
3152
- } else {
3153
- c.states.forEach((state, idx) => {
3154
- if (typeof state !== "object" || state === null) {
3155
- errors.push({ path: `$.states[${idx}]`, message: "state must be an object" });
3156
- return;
3157
- }
3158
- const s = state;
3159
- if (typeof s.name !== "string") {
3160
- errors.push({ path: `$.states[${idx}].name`, message: "name is required and must be a string" });
3161
- }
3162
- if (!Array.isArray(s.requires)) {
3163
- errors.push({ path: `$.states[${idx}].requires`, message: "requires is required and must be an array" });
3164
- }
3165
- });
3166
- }
3167
- }
3168
3243
  return {
3169
3244
  valid: errors.length === 0,
3170
3245
  errors
@@ -3240,17 +3315,8 @@ function validateTargetReferences(contract, selectorKeys) {
3240
3315
  });
3241
3316
  }
3242
3317
  const dynamicItems = c.dynamic;
3243
- const states = c.states;
3244
- const stateNames = new Set((states || []).map((s) => String(s.name)));
3245
3318
  if (Array.isArray(dynamicItems)) {
3246
3319
  dynamicItems.forEach((item, itemIdx) => {
3247
- const given = item.given;
3248
- if (given && !stateNames.has(given)) {
3249
- errors.push({
3250
- path: `$.dynamic[${itemIdx}].given`,
3251
- message: `State '${given}' not found in states`
3252
- });
3253
- }
3254
3320
  const actions = item.action;
3255
3321
  if (Array.isArray(actions)) {
3256
3322
  actions.forEach((action, actIdx) => {
package/dist/cli.js CHANGED
@@ -122,12 +122,12 @@ program.command("audit").description("Run axe-core powered accessibility audit o
122
122
  process.exit(1);
123
123
  });
124
124
  program.command("test").description("Run core a11y accessibility standard tests on UI components").action(async () => {
125
- const { runTest } = await import("./test-6Y4CIQOM.js");
125
+ const { runTest } = await import("./test-WDBS5JWO.js");
126
126
  runTest();
127
127
  });
128
128
  program.command("build").description("Build accessibility artifacts").addCommand(
129
129
  new Command("contracts").description("Build DSL contracts to JSON").action(async () => {
130
- const { buildContracts } = await import("./buildContracts-FT6KWUJN.js");
130
+ const { buildContracts } = await import("./buildContracts-VIV6GM56.js");
131
131
  const { loadConfig: loadConfig2 } = await import("./configLoader-REHK3S3Q.js");
132
132
  const cwd = process.cwd();
133
133
  const { config, configPath, errors } = await loadConfig2(cwd);
@@ -35,7 +35,7 @@ var StrategyRegistry = class {
35
35
  registerBuiltInStrategies() {
36
36
  this.builtInStrategies.set(
37
37
  "menu",
38
- () => import("./MenuComponentStrategy-JAMTCSNF.js").then(
38
+ () => import("./MenuComponentStrategy-VYCC2XOM.js").then(
39
39
  (m) => m.MenuComponentStrategy
40
40
  )
41
41
  );
@@ -677,12 +677,7 @@ async function runContractTestsPlaywright(componentName, url, strictness, config
677
677
  const componentConfig = config?.test?.components?.find((c) => c.name === componentName);
678
678
  const isCustomContract = !!componentConfig?.contractPath;
679
679
  const reporter = new ContractReporter(true, isCustomContract);
680
- const defaultTimeouts = {
681
- actionTimeoutMs: 400,
682
- assertionTimeoutMs: 400,
683
- navigationTimeoutMs: 3e4,
684
- componentReadyTimeoutMs: 5e3
685
- };
680
+ const defaultTimeouts = { actionTimeoutMs: 400, assertionTimeoutMs: 400, navigationTimeoutMs: 3e4, componentReadyTimeoutMs: 5e3 };
686
681
  const globalDisableTimeouts = config?.test?.disableTimeouts === true;
687
682
  const componentDisableTimeouts = componentConfig?.disableTimeouts === true;
688
683
  const disableTimeouts = componentDisableTimeouts || globalDisableTimeouts;
@@ -694,11 +689,7 @@ async function runContractTestsPlaywright(componentName, url, strictness, config
694
689
  }
695
690
  return value;
696
691
  };
697
- const actionTimeoutMs = resolveTimeout(
698
- componentConfig?.actionTimeoutMs,
699
- config?.test?.actionTimeoutMs,
700
- defaultTimeouts.actionTimeoutMs
701
- );
692
+ const actionTimeoutMs = resolveTimeout(componentConfig?.actionTimeoutMs, config?.test?.actionTimeoutMs, defaultTimeouts.actionTimeoutMs);
702
693
  const assertionTimeoutMs = resolveTimeout(
703
694
  componentConfig?.assertionTimeoutMs,
704
695
  config?.test?.assertionTimeoutMs,
@@ -798,8 +789,8 @@ This usually means:
798
789
  );
799
790
  }
800
791
  reporter.start(componentName, totalTests, apgUrl);
801
- if (componentName === "menu" && componentContract.selectors.trigger) {
802
- await page.locator(componentContract.selectors.trigger).first().waitFor({ state: "attached", timeout: componentReadyTimeoutMs }).catch(() => {
792
+ if (componentName === "menu" && componentContract.selectors.main) {
793
+ await page.locator(componentContract.selectors.main).first().waitFor({ state: "visible", timeout: componentReadyTimeoutMs }).catch(() => {
803
794
  });
804
795
  }
805
796
  const hasSubmenuCapability = componentName === "menu" && !!componentContract.selectors.submenuTrigger ? await page.locator(componentContract.selectors.submenuTrigger).count() > 0 : false;
@@ -808,7 +799,42 @@ This usually means:
808
799
  let staticFailed = 0;
809
800
  let staticWarnings = 0;
810
801
  for (const rel of componentContract.relationships || []) {
802
+ if (strategy && typeof strategy.resetState === "function") {
803
+ try {
804
+ await strategy.resetState(page);
805
+ } catch (err) {
806
+ warnings.push(`Warning: resetState failed before relationship test: ${err instanceof Error ? err.message : String(err)}`);
807
+ }
808
+ }
811
809
  const relationshipLevel = normalizeLevel(rel.level);
810
+ if (Array.isArray(rel.setup) && rel.setup.length > 0) {
811
+ let isAllowedType2 = function(t) {
812
+ return allowedTypes.includes(t);
813
+ };
814
+ var isAllowedType = isAllowedType2;
815
+ const actionExecutor = new ActionExecutor(page, componentContract.selectors, actionTimeoutMs);
816
+ const relDescription = rel.type === "aria-reference" ? `${rel.from}.${rel.attribute} references ${rel.to}` : `${rel.parent} contains ${rel.child}`;
817
+ const allowedTypes = ["focus", "type", "click", "keypress", "hover"];
818
+ const toSetupAction = (a) => ({
819
+ ...a,
820
+ type: isAllowedType2(a.type) ? a.type : "click"
821
+ });
822
+ const setupActions = rel.setup.map(toSetupAction);
823
+ const setupResult = await runSetupActions(setupActions, actionExecutor, strategy, page, relDescription, ["submenu", "submenuTrigger", "submenuItems"]);
824
+ if (setupResult.skip) {
825
+ skipped.push(setupResult.message || "Setup action skipped");
826
+ reporter.reportStaticTest(relDescription, "skip", setupResult.message, relationshipLevel);
827
+ continue;
828
+ }
829
+ if (!setupResult.success) {
830
+ const failure = `Relationship setup failed: ${setupResult.error}`;
831
+ const outcome = classifyFailure(failure, rel.level);
832
+ if (outcome.status === "fail") staticFailed += 1;
833
+ if (outcome.status === "warn") staticWarnings += 1;
834
+ reporter.reportStaticTest(relDescription, outcome.status, outcome.detail, outcome.level);
835
+ continue;
836
+ }
837
+ }
812
838
  if (componentName === "menu" && !hasSubmenuCapability) {
813
839
  const involvesSubmenu = isSubmenuRelation(rel);
814
840
  if (involvesSubmenu) {
@@ -981,7 +1007,53 @@ This usually means:
981
1007
  }
982
1008
  }
983
1009
  const staticAssertionRunner = new AssertionRunner(page, componentContract.selectors, assertionTimeoutMs);
1010
+ async function runSetupActions(setup, actionExecutor, strategy2, page2, description, skipKeywords = []) {
1011
+ if (!Array.isArray(setup) || setup.length === 0) return { success: true };
1012
+ if (strategy2 && typeof strategy2.resetState === "function") {
1013
+ await strategy2.resetState(page2);
1014
+ }
1015
+ for (const setupAct of setup) {
1016
+ let setupResult = { success: true };
1017
+ try {
1018
+ if (setupAct.type === "focus") {
1019
+ if (setupAct.target === "relative" && setupAct.relativeTarget) {
1020
+ setupResult = await actionExecutor.focus("relative", setupAct.relativeTarget);
1021
+ } else {
1022
+ setupResult = await actionExecutor.focus(setupAct.target);
1023
+ }
1024
+ } else if (setupAct.type === "type" && setupAct.value) {
1025
+ setupResult = await actionExecutor.type(setupAct.target, setupAct.value);
1026
+ } else if (setupAct.type === "click") {
1027
+ setupResult = await actionExecutor.click(setupAct.target, setupAct.relativeTarget);
1028
+ } else if (setupAct.type === "keypress" && setupAct.key) {
1029
+ setupResult = await actionExecutor.keypress(setupAct.target, setupAct.key);
1030
+ } else if (setupAct.type === "hover") {
1031
+ setupResult = await actionExecutor.hover(setupAct.target, setupAct.relativeTarget);
1032
+ } else {
1033
+ continue;
1034
+ }
1035
+ } catch (err) {
1036
+ setupResult = { success: false, error: err instanceof Error ? err.message : String(err) };
1037
+ }
1038
+ if (!setupResult.success) {
1039
+ const setupMsg = setupResult.error || "Setup action failed";
1040
+ const isSkip = skipKeywords.some((kw) => description.includes(kw) || setupMsg.includes(kw));
1041
+ if (isSkip) {
1042
+ return { success: false, skip: true, message: `Skipping test - capability not present: ${setupMsg}` };
1043
+ }
1044
+ return { success: false, error: setupMsg };
1045
+ }
1046
+ }
1047
+ return { success: true };
1048
+ }
984
1049
  for (const test of componentContract.static[0]?.assertions || []) {
1050
+ if (strategy && typeof strategy.resetState === "function") {
1051
+ try {
1052
+ await strategy.resetState(page);
1053
+ } catch (err) {
1054
+ warnings.push(`Warning: resetState failed before static test: ${err instanceof Error ? err.message : String(err)}`);
1055
+ }
1056
+ }
985
1057
  if (test.target === "relative") continue;
986
1058
  const staticDescription = `${test.target}${test.attribute ? ` (${test.attribute})` : ""}`;
987
1059
  const staticLevel = normalizeLevel(test.level);
@@ -991,6 +1063,33 @@ This usually means:
991
1063
  reporter.reportStaticTest(staticDescription, "skip", skipMessage, staticLevel);
992
1064
  continue;
993
1065
  }
1066
+ if (Array.isArray(test.setup) && test.setup.length > 0) {
1067
+ let isAllowedType2 = function(t) {
1068
+ return allowedTypes.includes(t);
1069
+ };
1070
+ var isAllowedType = isAllowedType2;
1071
+ const actionExecutor = new ActionExecutor(page, componentContract.selectors, actionTimeoutMs);
1072
+ const allowedTypes = ["focus", "type", "click", "keypress", "hover"];
1073
+ const toSetupAction = (a) => ({
1074
+ ...a,
1075
+ type: isAllowedType2(a.type) ? a.type : "click"
1076
+ });
1077
+ const setupActions = test.setup.map(toSetupAction);
1078
+ const setupResult = await runSetupActions(setupActions, actionExecutor, strategy, page, staticDescription, ["submenu", "submenuTrigger", "submenuItems"]);
1079
+ if (setupResult.skip) {
1080
+ skipped.push(setupResult.message || "Setup action skipped");
1081
+ reporter.reportStaticTest(staticDescription, "skip", setupResult.message, staticLevel);
1082
+ continue;
1083
+ }
1084
+ if (!setupResult.success) {
1085
+ const failure = `Static setup failed: ${setupResult.error}`;
1086
+ const outcome = classifyFailure(failure, test.level);
1087
+ if (outcome.status === "fail") staticFailed += 1;
1088
+ if (outcome.status === "warn") staticWarnings += 1;
1089
+ reporter.reportStaticTest(staticDescription, outcome.status, outcome.detail, outcome.level);
1090
+ continue;
1091
+ }
1092
+ }
994
1093
  const targetSelector = componentContract.selectors[test.target];
995
1094
  if (!targetSelector) {
996
1095
  const failure = `Selector for target ${test.target} not found.`;
@@ -1117,31 +1216,26 @@ This usually means:
1117
1216
  const dynamicLevel = normalizeLevel(dynamicTest.level);
1118
1217
  const actionExecutor = new ActionExecutor(page, componentContract.selectors, actionTimeoutMs);
1119
1218
  if (Array.isArray(setup) && setup.length > 0) {
1120
- for (const setupAct of setup) {
1121
- let setupResult;
1122
- if (setupAct.type === "focus") {
1123
- if (setupAct.target === "relative" && setupAct.relativeTarget) {
1124
- setupResult = await actionExecutor.focus("relative", setupAct.relativeTarget);
1125
- } else {
1126
- setupResult = await actionExecutor.focus(setupAct.target);
1127
- }
1128
- } else if (setupAct.type === "type" && setupAct.value) {
1129
- setupResult = await actionExecutor.type(setupAct.target, setupAct.value);
1130
- } else if (setupAct.type === "click") {
1131
- setupResult = await actionExecutor.click(setupAct.target, setupAct.relativeTarget);
1132
- } else if (setupAct.type === "keypress" && setupAct.key) {
1133
- setupResult = await actionExecutor.keypress(setupAct.target, setupAct.key);
1134
- } else if (setupAct.type === "hover") {
1135
- setupResult = await actionExecutor.hover(setupAct.target, setupAct.relativeTarget);
1136
- } else {
1137
- continue;
1138
- }
1139
- if (!setupResult.success) {
1140
- const setupMsg = setupResult.error || "Setup action failed";
1141
- const outcome = classifyFailure(`Setup failed: ${setupMsg}`, dynamicTest.level);
1142
- reporter.reportTest({ description: dynamicTest.description, level: dynamicLevel }, outcome.status, outcome.detail);
1143
- continue;
1144
- }
1219
+ let isAllowedType2 = function(t) {
1220
+ return allowedTypes.includes(t);
1221
+ };
1222
+ var isAllowedType = isAllowedType2;
1223
+ const allowedTypes = ["focus", "type", "click", "keypress", "hover"];
1224
+ const toSetupAction = (a) => ({
1225
+ ...a,
1226
+ type: isAllowedType2(a.type) ? a.type : "click"
1227
+ });
1228
+ const setupActions = setup.map(toSetupAction);
1229
+ const setupResult = await runSetupActions(setupActions, actionExecutor, strategy, page, dynamicTest.description, ["submenu", "submenuTrigger", "submenuItems"]);
1230
+ if (setupResult.skip) {
1231
+ skipped.push(setupResult.message || "Setup action skipped");
1232
+ reporter.reportTest({ description: dynamicTest.description, level: dynamicLevel }, "skip", setupResult.message);
1233
+ continue;
1234
+ }
1235
+ if (!setupResult.success) {
1236
+ const outcome = classifyFailure(`Setup failed: ${setupResult.error}`, dynamicTest.level);
1237
+ reporter.reportTest({ description: dynamicTest.description, level: dynamicLevel }, outcome.status, outcome.detail);
1238
+ continue;
1145
1239
  }
1146
1240
  }
1147
1241
  const failuresBeforeTest = failures.length;