aria-ease 6.12.0 → 6.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/{ComboboxComponentStrategy-OGRVZXAF.js → ComboboxComponentStrategy-DU342VMB.js} +7 -7
- package/dist/RelativeTargetResolver-DJAITO6D.js +7 -0
- package/dist/{audit-RM6TCZ5C.js → audit-JYEPKLHR.js} +5 -0
- package/dist/{chunk-FZ7GMIJB.js → chunk-4DU5Z5BR.js} +0 -2
- package/dist/chunk-GLT43UVH.js +43 -0
- package/dist/cli.cjs +121 -21
- package/dist/cli.js +3 -3
- package/dist/{contractTestRunnerPlaywright-EWAWQVHT.js → contractTestRunnerPlaywright-H24LQ45R.js} +109 -53
- package/dist/{contractTestRunnerPlaywright-DIXP5DQ3.js → contractTestRunnerPlaywright-NL3JNJYH.js} +109 -53
- package/dist/index.cjs +216 -46
- package/dist/index.js +103 -28
- package/dist/src/combobox/index.cjs +1 -0
- package/dist/src/combobox/index.js +1 -0
- package/dist/src/utils/test/{ComboboxComponentStrategy-5AECQSRN.js → ComboboxComponentStrategy-XKQ72RFD.js} +7 -7
- package/dist/src/utils/test/RelativeTargetResolver-G2XDN2VV.js +1 -0
- package/dist/src/utils/test/{chunk-FZ7GMIJB.js → chunk-4DU5Z5BR.js} +0 -2
- package/dist/src/utils/test/chunk-GLT43UVH.js +41 -0
- package/dist/src/utils/test/{contractTestRunnerPlaywright-CIZOXYRW.js → contractTestRunnerPlaywright-5FT6K2WN.js} +107 -53
- package/dist/src/utils/test/dsl/index.cjs +99 -25
- package/dist/src/utils/test/dsl/index.js +99 -25
- package/dist/src/utils/test/index.cjs +116 -21
- package/dist/src/utils/test/index.js +3 -3
- package/dist/{test-HBPCSYH5.js → test-FYSJXQWO.js} +3 -3
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1000,7 +1000,7 @@ export default {
|
|
|
1000
1000
|
audit: {
|
|
1001
1001
|
urls: [
|
|
1002
1002
|
"http://localhost:5173", // Homepage
|
|
1003
|
-
"http://localhost:5173/
|
|
1003
|
+
"http://localhost:5173/getting-started", // Docs
|
|
1004
1004
|
"http://localhost:5173/examples", // Examples
|
|
1005
1005
|
],
|
|
1006
1006
|
output: {
|
|
@@ -1130,7 +1130,7 @@ You've shifted accessibility left (into development), automated the verification
|
|
|
1130
1130
|
|
|
1131
1131
|
## 📖 More Resources
|
|
1132
1132
|
|
|
1133
|
-
- [Full Documentation](https://ariaease.site/
|
|
1133
|
+
- [Full Documentation](https://ariaease.site/getting-started)
|
|
1134
1134
|
- [GitHub Repository](https://github.com/aria-ease/aria-ease)
|
|
1135
1135
|
- [Report Issues](https://github.com/aria-ease/aria-ease/issues)
|
|
1136
1136
|
- [Contributing Guide](https://github.com/aria-ease/aria-ease/blob/main/CONTRIBUTION-GUIDELINES.md)
|
package/dist/{ComboboxComponentStrategy-OGRVZXAF.js → ComboboxComponentStrategy-DU342VMB.js}
RENAMED
|
@@ -17,7 +17,7 @@ var ComboboxComponentStrategy = class {
|
|
|
17
17
|
const popupElement = page.locator(popupSelector).first();
|
|
18
18
|
const isPopupVisible = await popupElement.isVisible().catch(() => false);
|
|
19
19
|
if (!isPopupVisible) return;
|
|
20
|
-
let
|
|
20
|
+
let popupClosed = false;
|
|
21
21
|
let closeSelector = this.selectors.input;
|
|
22
22
|
if (!closeSelector && this.selectors.focusable) {
|
|
23
23
|
closeSelector = this.selectors.focusable;
|
|
@@ -28,18 +28,18 @@ var ComboboxComponentStrategy = class {
|
|
|
28
28
|
const closeElement = page.locator(closeSelector).first();
|
|
29
29
|
await closeElement.focus();
|
|
30
30
|
await page.keyboard.press("Escape");
|
|
31
|
-
|
|
31
|
+
popupClosed = await (0, test_exports.expect)(popupElement).toBeHidden({ timeout: this.assertionTimeoutMs }).then(() => true).catch(() => false);
|
|
32
32
|
}
|
|
33
|
-
if (!
|
|
33
|
+
if (!popupClosed && this.selectors.button) {
|
|
34
34
|
const buttonElement = page.locator(this.selectors.button).first();
|
|
35
35
|
await buttonElement.click({ timeout: this.actionTimeoutMs });
|
|
36
|
-
|
|
36
|
+
popupClosed = await (0, test_exports.expect)(popupElement).toBeHidden({ timeout: this.assertionTimeoutMs }).then(() => true).catch(() => false);
|
|
37
37
|
}
|
|
38
|
-
if (!
|
|
38
|
+
if (!popupClosed) {
|
|
39
39
|
await page.mouse.click(10, 10);
|
|
40
|
-
|
|
40
|
+
popupClosed = await (0, test_exports.expect)(popupElement).toBeHidden({ timeout: this.assertionTimeoutMs }).then(() => true).catch(() => false);
|
|
41
41
|
}
|
|
42
|
-
if (!
|
|
42
|
+
if (!popupClosed) {
|
|
43
43
|
throw new Error(
|
|
44
44
|
`\u274C FATAL: Cannot close combobox popup between tests. Popup remains visible after trying:
|
|
45
45
|
1. Escape key
|
|
@@ -19,6 +19,11 @@ async function runAudit(url, options) {
|
|
|
19
19
|
const context = await browser.newContext();
|
|
20
20
|
const page = await context.newPage();
|
|
21
21
|
await page.goto(url, { waitUntil, timeout });
|
|
22
|
+
try {
|
|
23
|
+
await page.waitForSelector("main", { state: "visible", timeout });
|
|
24
|
+
} catch (waitError) {
|
|
25
|
+
console.warn(`\u26A0\uFE0F Warning: <main> landmark not found or not visible on ${url} after ${timeout}ms. Audit will continue, but results may be inaccurate. Consider adding a <main> element to improve audit accuracy. ${waitError instanceof Error ? waitError.message : String(waitError)}`);
|
|
26
|
+
}
|
|
22
27
|
const axe = new AxeBuilder({ page });
|
|
23
28
|
const axeResults = await axe.analyze();
|
|
24
29
|
await page.close();
|
|
@@ -309,9 +309,7 @@ async function getOrCreateContext() {
|
|
|
309
309
|
if (!sharedContext) {
|
|
310
310
|
const browser = await getOrCreateBrowser();
|
|
311
311
|
sharedContext = await browser.newContext({
|
|
312
|
-
// Isolated context - no permissions, no geolocation, etc.
|
|
313
312
|
permissions: [],
|
|
314
|
-
// Ignore HTTPS errors for local dev servers
|
|
315
313
|
ignoreHTTPSErrors: true
|
|
316
314
|
});
|
|
317
315
|
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// src/utils/test/src/RelativeTargetResolver.ts
|
|
2
|
+
var RelativeTargetResolver = class {
|
|
3
|
+
/**
|
|
4
|
+
* Resolve a relative target like "first", "second", "last", "next", "previous"
|
|
5
|
+
* @param page Playwright page instance
|
|
6
|
+
* @param selector Base selector to find elements
|
|
7
|
+
* @param relative Relative position (first, second, last, next, previous)
|
|
8
|
+
* @returns The resolved Locator or null if not found
|
|
9
|
+
*/
|
|
10
|
+
static async resolve(page, selector, relative) {
|
|
11
|
+
const items = await page.locator(selector).all();
|
|
12
|
+
switch (relative) {
|
|
13
|
+
case "first":
|
|
14
|
+
return items[0];
|
|
15
|
+
case "second":
|
|
16
|
+
return items[1];
|
|
17
|
+
case "last":
|
|
18
|
+
return items[items.length - 1];
|
|
19
|
+
case "next": {
|
|
20
|
+
const currentIndex = await page.evaluate(([sel]) => {
|
|
21
|
+
const items2 = Array.from(document.querySelectorAll(sel));
|
|
22
|
+
return items2.indexOf(document.activeElement);
|
|
23
|
+
}, [selector]);
|
|
24
|
+
const nextIndex = (currentIndex + 1) % items.length;
|
|
25
|
+
return items[nextIndex];
|
|
26
|
+
}
|
|
27
|
+
case "previous": {
|
|
28
|
+
const currentIndex = await page.evaluate(([sel]) => {
|
|
29
|
+
const items2 = Array.from(document.querySelectorAll(sel));
|
|
30
|
+
return items2.indexOf(document.activeElement);
|
|
31
|
+
}, [selector]);
|
|
32
|
+
const prevIndex = (currentIndex - 1 + items.length) % items.length;
|
|
33
|
+
return items[prevIndex];
|
|
34
|
+
}
|
|
35
|
+
default:
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export {
|
|
42
|
+
RelativeTargetResolver
|
|
43
|
+
};
|
package/dist/cli.cjs
CHANGED
|
@@ -356,6 +356,11 @@ async function runAudit(url, options) {
|
|
|
356
356
|
const context = await browser.newContext();
|
|
357
357
|
const page = await context.newPage();
|
|
358
358
|
await page.goto(url, { waitUntil, timeout });
|
|
359
|
+
try {
|
|
360
|
+
await page.waitForSelector("main", { state: "visible", timeout });
|
|
361
|
+
} catch (waitError) {
|
|
362
|
+
console.warn(`\u26A0\uFE0F Warning: <main> landmark not found or not visible on ${url} after ${timeout}ms. Audit will continue, but results may be inaccurate. Consider adding a <main> element to improve audit accuracy. ${waitError instanceof Error ? waitError.message : String(waitError)}`);
|
|
363
|
+
}
|
|
359
364
|
const axe2 = new import_playwright.default({ page });
|
|
360
365
|
const axeResults = await axe2.analyze();
|
|
361
366
|
await page.close();
|
|
@@ -1030,7 +1035,7 @@ async function runContractTests(contractPath, componentName, component, strictne
|
|
|
1030
1035
|
staticPassed += 1;
|
|
1031
1036
|
reporter.reportStaticTest(`${test.target} has ${test.attribute}`, "pass", void 0, staticLevel);
|
|
1032
1037
|
}
|
|
1033
|
-
} else if (!attributeValue || !test.expectedValue.split(" | ").includes(attributeValue)) {
|
|
1038
|
+
} else if (!attributeValue || typeof test.expectedValue === "string" && !test.expectedValue.split(" | ").includes(attributeValue)) {
|
|
1034
1039
|
const outcome = classifyFailure(test.failureMessage + ` Attribute value does not match expected value. Expected: ${test.expectedValue}, Found: ${attributeValue}`, test.level);
|
|
1035
1040
|
if (outcome.status === "fail") staticFailed += 1;
|
|
1036
1041
|
if (outcome.status === "warn") staticWarnings += 1;
|
|
@@ -1078,9 +1083,7 @@ async function getOrCreateContext() {
|
|
|
1078
1083
|
if (!sharedContext) {
|
|
1079
1084
|
const browser = await getOrCreateBrowser();
|
|
1080
1085
|
sharedContext = await browser.newContext({
|
|
1081
|
-
// Isolated context - no permissions, no geolocation, etc.
|
|
1082
1086
|
permissions: [],
|
|
1083
|
-
// Ignore HTTPS errors for local dev servers
|
|
1084
1087
|
ignoreHTTPSErrors: true
|
|
1085
1088
|
});
|
|
1086
1089
|
}
|
|
@@ -1278,7 +1281,7 @@ var init_ComboboxComponentStrategy = __esm({
|
|
|
1278
1281
|
const popupElement = page.locator(popupSelector).first();
|
|
1279
1282
|
const isPopupVisible = await popupElement.isVisible().catch(() => false);
|
|
1280
1283
|
if (!isPopupVisible) return;
|
|
1281
|
-
let
|
|
1284
|
+
let popupClosed = false;
|
|
1282
1285
|
let closeSelector = this.selectors.input;
|
|
1283
1286
|
if (!closeSelector && this.selectors.focusable) {
|
|
1284
1287
|
closeSelector = this.selectors.focusable;
|
|
@@ -1289,18 +1292,18 @@ var init_ComboboxComponentStrategy = __esm({
|
|
|
1289
1292
|
const closeElement = page.locator(closeSelector).first();
|
|
1290
1293
|
await closeElement.focus();
|
|
1291
1294
|
await page.keyboard.press("Escape");
|
|
1292
|
-
|
|
1295
|
+
popupClosed = await (0, test_exports.expect)(popupElement).toBeHidden({ timeout: this.assertionTimeoutMs }).then(() => true).catch(() => false);
|
|
1293
1296
|
}
|
|
1294
|
-
if (!
|
|
1297
|
+
if (!popupClosed && this.selectors.button) {
|
|
1295
1298
|
const buttonElement = page.locator(this.selectors.button).first();
|
|
1296
1299
|
await buttonElement.click({ timeout: this.actionTimeoutMs });
|
|
1297
|
-
|
|
1300
|
+
popupClosed = await (0, test_exports.expect)(popupElement).toBeHidden({ timeout: this.assertionTimeoutMs }).then(() => true).catch(() => false);
|
|
1298
1301
|
}
|
|
1299
|
-
if (!
|
|
1302
|
+
if (!popupClosed) {
|
|
1300
1303
|
await page.mouse.click(10, 10);
|
|
1301
|
-
|
|
1304
|
+
popupClosed = await (0, test_exports.expect)(popupElement).toBeHidden({ timeout: this.assertionTimeoutMs }).then(() => true).catch(() => false);
|
|
1302
1305
|
}
|
|
1303
|
-
if (!
|
|
1306
|
+
if (!popupClosed) {
|
|
1304
1307
|
throw new Error(
|
|
1305
1308
|
`\u274C FATAL: Cannot close combobox popup between tests. Popup remains visible after trying:
|
|
1306
1309
|
1. Escape key
|
|
@@ -1520,6 +1523,10 @@ var init_ComponentDetector = __esm({
|
|
|
1520
1523
|
});
|
|
1521
1524
|
|
|
1522
1525
|
// src/utils/test/src/RelativeTargetResolver.ts
|
|
1526
|
+
var RelativeTargetResolver_exports = {};
|
|
1527
|
+
__export(RelativeTargetResolver_exports, {
|
|
1528
|
+
RelativeTargetResolver: () => RelativeTargetResolver
|
|
1529
|
+
});
|
|
1523
1530
|
var RelativeTargetResolver;
|
|
1524
1531
|
var init_RelativeTargetResolver = __esm({
|
|
1525
1532
|
"src/utils/test/src/RelativeTargetResolver.ts"() {
|
|
@@ -1595,16 +1602,16 @@ var init_ActionExecutor = __esm({
|
|
|
1595
1602
|
async focus(target, relativeTarget, virtualId) {
|
|
1596
1603
|
try {
|
|
1597
1604
|
if (target === "virtual" && virtualId) {
|
|
1598
|
-
const
|
|
1599
|
-
if (!
|
|
1600
|
-
return { success: false, error: `
|
|
1605
|
+
const mainSelector = this.selectors.main;
|
|
1606
|
+
if (!mainSelector) {
|
|
1607
|
+
return { success: false, error: `Main selector not defined for virtual focus.` };
|
|
1601
1608
|
}
|
|
1602
|
-
const
|
|
1603
|
-
const exists = await
|
|
1609
|
+
const main = this.page.locator(mainSelector).first();
|
|
1610
|
+
const exists = await main.count();
|
|
1604
1611
|
if (!exists) {
|
|
1605
|
-
return { success: false, error: `
|
|
1612
|
+
return { success: false, error: `Main element not found for virtual focus.` };
|
|
1606
1613
|
}
|
|
1607
|
-
await
|
|
1614
|
+
await main.evaluate((el, id) => {
|
|
1608
1615
|
el.setAttribute("aria-activedescendant", id);
|
|
1609
1616
|
}, virtualId);
|
|
1610
1617
|
return { success: true };
|
|
@@ -1906,6 +1913,10 @@ var init_AssertionRunner = __esm({
|
|
|
1906
1913
|
};
|
|
1907
1914
|
}
|
|
1908
1915
|
}
|
|
1916
|
+
if (typeof expectedValue !== "string") {
|
|
1917
|
+
console.error("[AssertionRunner] expectedValue is not a string:", expectedValue);
|
|
1918
|
+
throw new Error(`AssertionRunner: expectedValue for attribute assertion must be a string, but got: ${JSON.stringify(expectedValue)}`);
|
|
1919
|
+
}
|
|
1909
1920
|
const expectedValues = expectedValue.split(" | ").map((v) => v.trim());
|
|
1910
1921
|
const attributeValue = await target.getAttribute(attribute);
|
|
1911
1922
|
if (attributeValue !== null && expectedValues.includes(attributeValue)) {
|
|
@@ -2338,6 +2349,52 @@ This usually means:
|
|
|
2338
2349
|
reporter.reportStaticTest(relDescription, "pass", void 0, relationshipLevel);
|
|
2339
2350
|
}
|
|
2340
2351
|
}
|
|
2352
|
+
async function resolveExpectedValue(expectedValue, selectors, page2, context = {}) {
|
|
2353
|
+
if (!expectedValue || typeof expectedValue !== "object" || !("ref" in expectedValue)) return expectedValue;
|
|
2354
|
+
let refSelector;
|
|
2355
|
+
if (expectedValue.ref === "relative") {
|
|
2356
|
+
if (!expectedValue.relativeTarget || !context.relativeBaseSelector) return void 0;
|
|
2357
|
+
const baseLocator = page2.locator(context.relativeBaseSelector);
|
|
2358
|
+
const count = await baseLocator.count();
|
|
2359
|
+
let idx = 0;
|
|
2360
|
+
if (expectedValue.relativeTarget === "first") idx = 0;
|
|
2361
|
+
else if (expectedValue.relativeTarget === "second") idx = 1;
|
|
2362
|
+
else if (expectedValue.relativeTarget === "last") idx = count - 1;
|
|
2363
|
+
else if (!isNaN(Number(expectedValue.relativeTarget))) idx = Number(expectedValue.relativeTarget);
|
|
2364
|
+
else idx = 0;
|
|
2365
|
+
if (idx < 0 || idx >= count) return void 0;
|
|
2366
|
+
const relElem = baseLocator.nth(idx);
|
|
2367
|
+
return await getPropertyFromLocator(relElem, expectedValue.property || expectedValue.attribute);
|
|
2368
|
+
} else {
|
|
2369
|
+
refSelector = selectors[expectedValue.ref];
|
|
2370
|
+
if (!refSelector) throw new Error(`Selector for ref '${expectedValue.ref}' not found in contract selectors.`);
|
|
2371
|
+
const refLocator = page2.locator(refSelector).first();
|
|
2372
|
+
return await getPropertyFromLocator(refLocator, expectedValue.property || expectedValue.attribute);
|
|
2373
|
+
}
|
|
2374
|
+
}
|
|
2375
|
+
async function getPropertyFromLocator(locator, property) {
|
|
2376
|
+
if (!locator) return void 0;
|
|
2377
|
+
if (!property || property === "id") {
|
|
2378
|
+
return await locator.getAttribute("id") ?? void 0;
|
|
2379
|
+
} else if (property === "class") {
|
|
2380
|
+
return await locator.getAttribute("class") ?? void 0;
|
|
2381
|
+
} else if (property === "textContent") {
|
|
2382
|
+
return await locator.evaluate((el) => el.textContent ?? void 0);
|
|
2383
|
+
} else if (property.startsWith("aria-")) {
|
|
2384
|
+
return await locator.getAttribute(property) ?? void 0;
|
|
2385
|
+
} else if (property.endsWith("*")) {
|
|
2386
|
+
const attrs = await locator.evaluate((el) => {
|
|
2387
|
+
const out = [];
|
|
2388
|
+
for (const attr of Array.from(el.attributes)) {
|
|
2389
|
+
if (attr.name.startsWith("aria-")) out.push(`${attr.name}=${attr.value}`);
|
|
2390
|
+
}
|
|
2391
|
+
return out.join(";");
|
|
2392
|
+
});
|
|
2393
|
+
return attrs;
|
|
2394
|
+
} else {
|
|
2395
|
+
return await locator.getAttribute(property) ?? void 0;
|
|
2396
|
+
}
|
|
2397
|
+
}
|
|
2341
2398
|
const staticAssertionRunner = new AssertionRunner(page, componentContract.selectors, assertionTimeoutMs);
|
|
2342
2399
|
for (const test of componentContract.static[0]?.assertions || []) {
|
|
2343
2400
|
if (test.target === "relative") continue;
|
|
@@ -2380,6 +2437,22 @@ This usually means:
|
|
|
2380
2437
|
}
|
|
2381
2438
|
return false;
|
|
2382
2439
|
};
|
|
2440
|
+
let expectedValue = test.expectedValue;
|
|
2441
|
+
if (test.expectedValue && typeof test.expectedValue === "object" && "ref" in test.expectedValue) {
|
|
2442
|
+
const context = {};
|
|
2443
|
+
const relTarget = test.relativeTarget;
|
|
2444
|
+
if (test.expectedValue.ref === "relative" && test.target === "relative" && relTarget) {
|
|
2445
|
+
const baseSel = componentContract.selectors[relTarget];
|
|
2446
|
+
if (!baseSel) throw new Error(`Selector for relativeTarget '${relTarget}' not found in contract selectors.`);
|
|
2447
|
+
context.relativeBaseSelector = baseSel;
|
|
2448
|
+
} else if (test.expectedValue.ref === "relative" && relTarget) {
|
|
2449
|
+
const baseSel = componentContract.selectors[relTarget];
|
|
2450
|
+
if (!baseSel) throw new Error(`Selector for relativeTarget '${relTarget}' not found in contract selectors.`);
|
|
2451
|
+
context.relativeBaseSelector = baseSel;
|
|
2452
|
+
}
|
|
2453
|
+
expectedValue = await resolveExpectedValue(test.expectedValue, componentContract.selectors, page, context);
|
|
2454
|
+
console.log("Expected value in static check", expectedValue);
|
|
2455
|
+
}
|
|
2383
2456
|
if (!test.expectedValue) {
|
|
2384
2457
|
const attributes = test.attribute.split(" | ");
|
|
2385
2458
|
let hasAny = false;
|
|
@@ -2413,16 +2486,17 @@ This usually means:
|
|
|
2413
2486
|
reporter.reportStaticTest(staticDescription, "pass", void 0, staticLevel);
|
|
2414
2487
|
}
|
|
2415
2488
|
} else {
|
|
2416
|
-
if (isRedundantCheck(targetSelector, test.attribute,
|
|
2417
|
-
passes.push(`${test.attribute}="${
|
|
2489
|
+
if (isRedundantCheck(targetSelector, test.attribute, typeof expectedValue === "string" ? expectedValue : void 0)) {
|
|
2490
|
+
passes.push(`${test.attribute}="${expectedValue}" on ${test.target} verified by selector (already present in: ${targetSelector}).`);
|
|
2418
2491
|
staticPassed += 1;
|
|
2419
2492
|
reporter.reportStaticTest(staticDescription, "pass", void 0, staticLevel);
|
|
2420
2493
|
} else {
|
|
2494
|
+
const valueToCheck = expectedValue ?? "";
|
|
2421
2495
|
const result = await staticAssertionRunner.validateAttribute(
|
|
2422
2496
|
target,
|
|
2423
2497
|
test.target,
|
|
2424
2498
|
test.attribute,
|
|
2425
|
-
|
|
2499
|
+
valueToCheck,
|
|
2426
2500
|
test.failureMessage,
|
|
2427
2501
|
"Static ARIA Test"
|
|
2428
2502
|
);
|
|
@@ -2540,7 +2614,33 @@ This usually means:
|
|
|
2540
2614
|
continue;
|
|
2541
2615
|
}
|
|
2542
2616
|
for (const assertion of assertions) {
|
|
2543
|
-
|
|
2617
|
+
let expectedValue;
|
|
2618
|
+
if (assertion.expectedValue && typeof assertion.expectedValue === "object" && "ref" in assertion.expectedValue) {
|
|
2619
|
+
if (assertion.expectedValue.ref === "relative") {
|
|
2620
|
+
const { RelativeTargetResolver: RelativeTargetResolver2 } = await Promise.resolve().then(() => (init_RelativeTargetResolver(), RelativeTargetResolver_exports));
|
|
2621
|
+
const relativeSelector = componentContract.selectors.relative;
|
|
2622
|
+
if (!relativeSelector) throw new Error("Relative selector not defined in contract selectors.");
|
|
2623
|
+
const relTarget = assertion.relativeTarget || "first";
|
|
2624
|
+
const relElem = await RelativeTargetResolver2.resolve(page, relativeSelector, relTarget);
|
|
2625
|
+
if (!relElem) throw new Error(`Could not resolve relative target '${relTarget}' for expectedValue.`);
|
|
2626
|
+
const prop = assertion.expectedValue.property || assertion.expectedValue.attribute || "id";
|
|
2627
|
+
if (prop === "textContent") {
|
|
2628
|
+
expectedValue = await relElem.evaluate((el) => el.textContent ?? void 0);
|
|
2629
|
+
} else {
|
|
2630
|
+
const attr = await relElem.getAttribute(prop);
|
|
2631
|
+
expectedValue = attr === null ? void 0 : attr;
|
|
2632
|
+
}
|
|
2633
|
+
} else {
|
|
2634
|
+
expectedValue = await resolveExpectedValue(assertion.expectedValue, componentContract.selectors, page, {});
|
|
2635
|
+
}
|
|
2636
|
+
} else if (typeof assertion.expectedValue === "string" || typeof assertion.expectedValue === "undefined") {
|
|
2637
|
+
expectedValue = assertion.expectedValue;
|
|
2638
|
+
} else {
|
|
2639
|
+
expectedValue = "";
|
|
2640
|
+
}
|
|
2641
|
+
const assertionToRun = { ...assertion, expectedValue };
|
|
2642
|
+
const valueToCheck = expectedValue ?? "";
|
|
2643
|
+
const result = await assertionRunner.validate({ ...assertionToRun, expectedValue: valueToCheck }, dynamicTest.description);
|
|
2544
2644
|
if (result.success && result.passMessage) {
|
|
2545
2645
|
passes.push(result.passMessage);
|
|
2546
2646
|
} else if (!result.success && result.failMessage) {
|
package/dist/cli.js
CHANGED
|
@@ -17,7 +17,7 @@ var program = new Command();
|
|
|
17
17
|
program.name("aria-ease").description("Run accessibility tests and audits").version("2.2.3");
|
|
18
18
|
program.command("audit").description("Run axe-core powered accessibility audit on webpages").option("-u, --url <url>", "Single URL to audit").option("-f, --format <format>", "Output format for the audit report: json | csv | html | all", "all").option("-o, --out <path>", "Directory to save the audit report", "./accessibility-reports/audit").action(async (opts) => {
|
|
19
19
|
console.log(chalk.cyanBright("\u{1F680} Starting accessibility audit...\n"));
|
|
20
|
-
const { runAudit } = await import("./audit-
|
|
20
|
+
const { runAudit } = await import("./audit-JYEPKLHR.js");
|
|
21
21
|
const { formatResults } = await import("./formatters-32KQIIYS.js");
|
|
22
22
|
const needsConfig = !opts.url;
|
|
23
23
|
const { config, configPath, errors } = await loadConfig(process.cwd());
|
|
@@ -50,7 +50,7 @@ program.command("audit").description("Run axe-core powered accessibility audit o
|
|
|
50
50
|
process.exit(1);
|
|
51
51
|
}
|
|
52
52
|
const allResults = [];
|
|
53
|
-
const { createAuditBrowser } = await import("./audit-
|
|
53
|
+
const { createAuditBrowser } = await import("./audit-JYEPKLHR.js");
|
|
54
54
|
const browser = await createAuditBrowser();
|
|
55
55
|
try {
|
|
56
56
|
const auditOptions = { browser };
|
|
@@ -122,7 +122,7 @@ 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-FYSJXQWO.js");
|
|
126
126
|
runTest();
|
|
127
127
|
});
|
|
128
128
|
program.command("build").description("Build accessibility artifacts").addCommand(
|
package/dist/{contractTestRunnerPlaywright-EWAWQVHT.js → contractTestRunnerPlaywright-H24LQ45R.js}
RENAMED
|
@@ -1,10 +1,13 @@
|
|
|
1
|
+
import {
|
|
2
|
+
RelativeTargetResolver
|
|
3
|
+
} from "./chunk-GLT43UVH.js";
|
|
1
4
|
import {
|
|
2
5
|
ContractReporter,
|
|
3
6
|
createTestPage,
|
|
4
7
|
normalizeLevel,
|
|
5
8
|
normalizeStrictness,
|
|
6
9
|
resolveEnforcement
|
|
7
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-4DU5Z5BR.js";
|
|
8
11
|
import {
|
|
9
12
|
test_exports
|
|
10
13
|
} from "./chunk-PK5L2SAF.js";
|
|
@@ -44,7 +47,7 @@ var StrategyRegistry = class {
|
|
|
44
47
|
);
|
|
45
48
|
this.builtInStrategies.set(
|
|
46
49
|
"combobox",
|
|
47
|
-
() => import("./ComboboxComponentStrategy-
|
|
50
|
+
() => import("./ComboboxComponentStrategy-DU342VMB.js").then(
|
|
48
51
|
(m) => m.ComboboxComponentStrategy
|
|
49
52
|
)
|
|
50
53
|
);
|
|
@@ -163,46 +166,6 @@ var ComponentDetector = class {
|
|
|
163
166
|
}
|
|
164
167
|
};
|
|
165
168
|
|
|
166
|
-
// src/utils/test/src/RelativeTargetResolver.ts
|
|
167
|
-
var RelativeTargetResolver = class {
|
|
168
|
-
/**
|
|
169
|
-
* Resolve a relative target like "first", "second", "last", "next", "previous"
|
|
170
|
-
* @param page Playwright page instance
|
|
171
|
-
* @param selector Base selector to find elements
|
|
172
|
-
* @param relative Relative position (first, second, last, next, previous)
|
|
173
|
-
* @returns The resolved Locator or null if not found
|
|
174
|
-
*/
|
|
175
|
-
static async resolve(page, selector, relative) {
|
|
176
|
-
const items = await page.locator(selector).all();
|
|
177
|
-
switch (relative) {
|
|
178
|
-
case "first":
|
|
179
|
-
return items[0];
|
|
180
|
-
case "second":
|
|
181
|
-
return items[1];
|
|
182
|
-
case "last":
|
|
183
|
-
return items[items.length - 1];
|
|
184
|
-
case "next": {
|
|
185
|
-
const currentIndex = await page.evaluate(([sel]) => {
|
|
186
|
-
const items2 = Array.from(document.querySelectorAll(sel));
|
|
187
|
-
return items2.indexOf(document.activeElement);
|
|
188
|
-
}, [selector]);
|
|
189
|
-
const nextIndex = (currentIndex + 1) % items.length;
|
|
190
|
-
return items[nextIndex];
|
|
191
|
-
}
|
|
192
|
-
case "previous": {
|
|
193
|
-
const currentIndex = await page.evaluate(([sel]) => {
|
|
194
|
-
const items2 = Array.from(document.querySelectorAll(sel));
|
|
195
|
-
return items2.indexOf(document.activeElement);
|
|
196
|
-
}, [selector]);
|
|
197
|
-
const prevIndex = (currentIndex - 1 + items.length) % items.length;
|
|
198
|
-
return items[prevIndex];
|
|
199
|
-
}
|
|
200
|
-
default:
|
|
201
|
-
return null;
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
};
|
|
205
|
-
|
|
206
169
|
// src/utils/test/src/ActionExecutor.ts
|
|
207
170
|
var ActionExecutor = class {
|
|
208
171
|
constructor(page, selectors, timeoutMs = 400) {
|
|
@@ -228,16 +191,16 @@ var ActionExecutor = class {
|
|
|
228
191
|
async focus(target, relativeTarget, virtualId) {
|
|
229
192
|
try {
|
|
230
193
|
if (target === "virtual" && virtualId) {
|
|
231
|
-
const
|
|
232
|
-
if (!
|
|
233
|
-
return { success: false, error: `
|
|
194
|
+
const mainSelector = this.selectors.main;
|
|
195
|
+
if (!mainSelector) {
|
|
196
|
+
return { success: false, error: `Main selector not defined for virtual focus.` };
|
|
234
197
|
}
|
|
235
|
-
const
|
|
236
|
-
const exists = await
|
|
198
|
+
const main = this.page.locator(mainSelector).first();
|
|
199
|
+
const exists = await main.count();
|
|
237
200
|
if (!exists) {
|
|
238
|
-
return { success: false, error: `
|
|
201
|
+
return { success: false, error: `Main element not found for virtual focus.` };
|
|
239
202
|
}
|
|
240
|
-
await
|
|
203
|
+
await main.evaluate((el, id) => {
|
|
241
204
|
el.setAttribute("aria-activedescendant", id);
|
|
242
205
|
}, virtualId);
|
|
243
206
|
return { success: true };
|
|
@@ -531,6 +494,10 @@ var AssertionRunner = class {
|
|
|
531
494
|
};
|
|
532
495
|
}
|
|
533
496
|
}
|
|
497
|
+
if (typeof expectedValue !== "string") {
|
|
498
|
+
console.error("[AssertionRunner] expectedValue is not a string:", expectedValue);
|
|
499
|
+
throw new Error(`AssertionRunner: expectedValue for attribute assertion must be a string, but got: ${JSON.stringify(expectedValue)}`);
|
|
500
|
+
}
|
|
534
501
|
const expectedValues = expectedValue.split(" | ").map((v) => v.trim());
|
|
535
502
|
const attributeValue = await target.getAttribute(attribute);
|
|
536
503
|
if (attributeValue !== null && expectedValues.includes(attributeValue)) {
|
|
@@ -957,6 +924,52 @@ This usually means:
|
|
|
957
924
|
reporter.reportStaticTest(relDescription, "pass", void 0, relationshipLevel);
|
|
958
925
|
}
|
|
959
926
|
}
|
|
927
|
+
async function resolveExpectedValue(expectedValue, selectors, page2, context = {}) {
|
|
928
|
+
if (!expectedValue || typeof expectedValue !== "object" || !("ref" in expectedValue)) return expectedValue;
|
|
929
|
+
let refSelector;
|
|
930
|
+
if (expectedValue.ref === "relative") {
|
|
931
|
+
if (!expectedValue.relativeTarget || !context.relativeBaseSelector) return void 0;
|
|
932
|
+
const baseLocator = page2.locator(context.relativeBaseSelector);
|
|
933
|
+
const count = await baseLocator.count();
|
|
934
|
+
let idx = 0;
|
|
935
|
+
if (expectedValue.relativeTarget === "first") idx = 0;
|
|
936
|
+
else if (expectedValue.relativeTarget === "second") idx = 1;
|
|
937
|
+
else if (expectedValue.relativeTarget === "last") idx = count - 1;
|
|
938
|
+
else if (!isNaN(Number(expectedValue.relativeTarget))) idx = Number(expectedValue.relativeTarget);
|
|
939
|
+
else idx = 0;
|
|
940
|
+
if (idx < 0 || idx >= count) return void 0;
|
|
941
|
+
const relElem = baseLocator.nth(idx);
|
|
942
|
+
return await getPropertyFromLocator(relElem, expectedValue.property || expectedValue.attribute);
|
|
943
|
+
} else {
|
|
944
|
+
refSelector = selectors[expectedValue.ref];
|
|
945
|
+
if (!refSelector) throw new Error(`Selector for ref '${expectedValue.ref}' not found in contract selectors.`);
|
|
946
|
+
const refLocator = page2.locator(refSelector).first();
|
|
947
|
+
return await getPropertyFromLocator(refLocator, expectedValue.property || expectedValue.attribute);
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
async function getPropertyFromLocator(locator, property) {
|
|
951
|
+
if (!locator) return void 0;
|
|
952
|
+
if (!property || property === "id") {
|
|
953
|
+
return await locator.getAttribute("id") ?? void 0;
|
|
954
|
+
} else if (property === "class") {
|
|
955
|
+
return await locator.getAttribute("class") ?? void 0;
|
|
956
|
+
} else if (property === "textContent") {
|
|
957
|
+
return await locator.evaluate((el) => el.textContent ?? void 0);
|
|
958
|
+
} else if (property.startsWith("aria-")) {
|
|
959
|
+
return await locator.getAttribute(property) ?? void 0;
|
|
960
|
+
} else if (property.endsWith("*")) {
|
|
961
|
+
const attrs = await locator.evaluate((el) => {
|
|
962
|
+
const out = [];
|
|
963
|
+
for (const attr of Array.from(el.attributes)) {
|
|
964
|
+
if (attr.name.startsWith("aria-")) out.push(`${attr.name}=${attr.value}`);
|
|
965
|
+
}
|
|
966
|
+
return out.join(";");
|
|
967
|
+
});
|
|
968
|
+
return attrs;
|
|
969
|
+
} else {
|
|
970
|
+
return await locator.getAttribute(property) ?? void 0;
|
|
971
|
+
}
|
|
972
|
+
}
|
|
960
973
|
const staticAssertionRunner = new AssertionRunner(page, componentContract.selectors, assertionTimeoutMs);
|
|
961
974
|
for (const test of componentContract.static[0]?.assertions || []) {
|
|
962
975
|
if (test.target === "relative") continue;
|
|
@@ -999,6 +1012,22 @@ This usually means:
|
|
|
999
1012
|
}
|
|
1000
1013
|
return false;
|
|
1001
1014
|
};
|
|
1015
|
+
let expectedValue = test.expectedValue;
|
|
1016
|
+
if (test.expectedValue && typeof test.expectedValue === "object" && "ref" in test.expectedValue) {
|
|
1017
|
+
const context = {};
|
|
1018
|
+
const relTarget = test.relativeTarget;
|
|
1019
|
+
if (test.expectedValue.ref === "relative" && test.target === "relative" && relTarget) {
|
|
1020
|
+
const baseSel = componentContract.selectors[relTarget];
|
|
1021
|
+
if (!baseSel) throw new Error(`Selector for relativeTarget '${relTarget}' not found in contract selectors.`);
|
|
1022
|
+
context.relativeBaseSelector = baseSel;
|
|
1023
|
+
} else if (test.expectedValue.ref === "relative" && relTarget) {
|
|
1024
|
+
const baseSel = componentContract.selectors[relTarget];
|
|
1025
|
+
if (!baseSel) throw new Error(`Selector for relativeTarget '${relTarget}' not found in contract selectors.`);
|
|
1026
|
+
context.relativeBaseSelector = baseSel;
|
|
1027
|
+
}
|
|
1028
|
+
expectedValue = await resolveExpectedValue(test.expectedValue, componentContract.selectors, page, context);
|
|
1029
|
+
console.log("Expected value in static check", expectedValue);
|
|
1030
|
+
}
|
|
1002
1031
|
if (!test.expectedValue) {
|
|
1003
1032
|
const attributes = test.attribute.split(" | ");
|
|
1004
1033
|
let hasAny = false;
|
|
@@ -1032,16 +1061,17 @@ This usually means:
|
|
|
1032
1061
|
reporter.reportStaticTest(staticDescription, "pass", void 0, staticLevel);
|
|
1033
1062
|
}
|
|
1034
1063
|
} else {
|
|
1035
|
-
if (isRedundantCheck(targetSelector, test.attribute,
|
|
1036
|
-
passes.push(`${test.attribute}="${
|
|
1064
|
+
if (isRedundantCheck(targetSelector, test.attribute, typeof expectedValue === "string" ? expectedValue : void 0)) {
|
|
1065
|
+
passes.push(`${test.attribute}="${expectedValue}" on ${test.target} verified by selector (already present in: ${targetSelector}).`);
|
|
1037
1066
|
staticPassed += 1;
|
|
1038
1067
|
reporter.reportStaticTest(staticDescription, "pass", void 0, staticLevel);
|
|
1039
1068
|
} else {
|
|
1069
|
+
const valueToCheck = expectedValue ?? "";
|
|
1040
1070
|
const result = await staticAssertionRunner.validateAttribute(
|
|
1041
1071
|
target,
|
|
1042
1072
|
test.target,
|
|
1043
1073
|
test.attribute,
|
|
1044
|
-
|
|
1074
|
+
valueToCheck,
|
|
1045
1075
|
test.failureMessage,
|
|
1046
1076
|
"Static ARIA Test"
|
|
1047
1077
|
);
|
|
@@ -1159,7 +1189,33 @@ This usually means:
|
|
|
1159
1189
|
continue;
|
|
1160
1190
|
}
|
|
1161
1191
|
for (const assertion of assertions) {
|
|
1162
|
-
|
|
1192
|
+
let expectedValue;
|
|
1193
|
+
if (assertion.expectedValue && typeof assertion.expectedValue === "object" && "ref" in assertion.expectedValue) {
|
|
1194
|
+
if (assertion.expectedValue.ref === "relative") {
|
|
1195
|
+
const { RelativeTargetResolver: RelativeTargetResolver2 } = await import("./RelativeTargetResolver-DJAITO6D.js");
|
|
1196
|
+
const relativeSelector = componentContract.selectors.relative;
|
|
1197
|
+
if (!relativeSelector) throw new Error("Relative selector not defined in contract selectors.");
|
|
1198
|
+
const relTarget = assertion.relativeTarget || "first";
|
|
1199
|
+
const relElem = await RelativeTargetResolver2.resolve(page, relativeSelector, relTarget);
|
|
1200
|
+
if (!relElem) throw new Error(`Could not resolve relative target '${relTarget}' for expectedValue.`);
|
|
1201
|
+
const prop = assertion.expectedValue.property || assertion.expectedValue.attribute || "id";
|
|
1202
|
+
if (prop === "textContent") {
|
|
1203
|
+
expectedValue = await relElem.evaluate((el) => el.textContent ?? void 0);
|
|
1204
|
+
} else {
|
|
1205
|
+
const attr = await relElem.getAttribute(prop);
|
|
1206
|
+
expectedValue = attr === null ? void 0 : attr;
|
|
1207
|
+
}
|
|
1208
|
+
} else {
|
|
1209
|
+
expectedValue = await resolveExpectedValue(assertion.expectedValue, componentContract.selectors, page, {});
|
|
1210
|
+
}
|
|
1211
|
+
} else if (typeof assertion.expectedValue === "string" || typeof assertion.expectedValue === "undefined") {
|
|
1212
|
+
expectedValue = assertion.expectedValue;
|
|
1213
|
+
} else {
|
|
1214
|
+
expectedValue = "";
|
|
1215
|
+
}
|
|
1216
|
+
const assertionToRun = { ...assertion, expectedValue };
|
|
1217
|
+
const valueToCheck = expectedValue ?? "";
|
|
1218
|
+
const result = await assertionRunner.validate({ ...assertionToRun, expectedValue: valueToCheck }, dynamicTest.description);
|
|
1163
1219
|
if (result.success && result.passMessage) {
|
|
1164
1220
|
passes.push(result.passMessage);
|
|
1165
1221
|
} else if (!result.success && result.failMessage) {
|