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.
- package/dist/{MenuComponentStrategy-JAMTCSNF.js → MenuComponentStrategy-VYCC2XOM.js} +5 -5
- package/dist/{buildContracts-FT6KWUJN.js → buildContracts-VIV6GM56.js} +0 -28
- package/dist/cli.cjs +137 -71
- package/dist/cli.js +2 -2
- package/dist/{contractTestRunnerPlaywright-47DCBO4A.js → contractTestRunnerPlaywright-B2HLZKKK.js} +133 -39
- package/dist/{contractTestRunnerPlaywright-UJKXRXBS.js → contractTestRunnerPlaywright-RWK52C7S.js} +133 -39
- package/dist/index.cjs +524 -78
- package/dist/index.d.cts +23 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.js +388 -36
- package/dist/src/utils/test/{MenuComponentStrategy-VKZQYLBE.js → MenuComponentStrategy-6XWU5KLW.js} +5 -5
- package/dist/src/utils/test/{contractTestRunnerPlaywright-AZ4QKLYT.js → contractTestRunnerPlaywright-5FIGA5G4.js} +133 -39
- package/dist/src/utils/test/dsl/index.cjs +387 -35
- package/dist/src/utils/test/dsl/index.d.cts +23 -0
- package/dist/src/utils/test/dsl/index.d.ts +23 -0
- package/dist/src/utils/test/dsl/index.js +387 -35
- package/dist/src/utils/test/index.cjs +137 -43
- package/dist/src/utils/test/index.js +1 -1
- package/dist/{test-6Y4CIQOM.js → test-WDBS5JWO.js} +1 -1
- package/package.json +1 -1
|
@@ -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.
|
|
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.
|
|
34
|
-
const triggerElement = page.locator(this.selectors.
|
|
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.
|
|
55
|
-
const triggerElement = page.locator(this.selectors.
|
|
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.
|
|
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.
|
|
1167
|
-
const triggerElement = page.locator(this.selectors.
|
|
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.
|
|
1188
|
-
const triggerElement = page.locator(this.selectors.
|
|
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.
|
|
2227
|
-
await page.locator(componentContract.selectors.
|
|
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
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
}
|
|
2564
|
-
|
|
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-
|
|
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-
|
|
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);
|
package/dist/{contractTestRunnerPlaywright-47DCBO4A.js → contractTestRunnerPlaywright-B2HLZKKK.js}
RENAMED
|
@@ -35,7 +35,7 @@ var StrategyRegistry = class {
|
|
|
35
35
|
registerBuiltInStrategies() {
|
|
36
36
|
this.builtInStrategies.set(
|
|
37
37
|
"menu",
|
|
38
|
-
() => import("./MenuComponentStrategy-
|
|
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.
|
|
802
|
-
await page.locator(componentContract.selectors.
|
|
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
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
}
|
|
1139
|
-
|
|
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;
|