aria-ease 6.11.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 +3 -3
- 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-XERMSYEH.js → chunk-4DU5Z5BR.js} +0 -23
- package/dist/{chunk-NI3MQCAS.js → chunk-GJGUY643.js} +2 -2
- package/dist/chunk-GLT43UVH.js +43 -0
- package/dist/cli.cjs +147 -84
- package/dist/cli.js +5 -5
- package/dist/{configLoader-DWHOHXHL.js → configLoader-Q7N5XV4P.js} +2 -2
- package/dist/{configLoader-UJZHQBYS.js → configLoader-REHK3S3Q.js} +1 -1
- package/dist/{contractTestRunnerPlaywright-QDXSK3FE.js → contractTestRunnerPlaywright-H24LQ45R.js} +113 -72
- package/dist/{contractTestRunnerPlaywright-WNWQYSXZ.js → contractTestRunnerPlaywright-NL3JNJYH.js} +113 -72
- package/dist/index.cjs +248 -112
- package/dist/index.d.cts +3 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +124 -41
- 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-XERMSYEH.js → chunk-4DU5Z5BR.js} +1 -23
- package/dist/src/utils/test/chunk-GLT43UVH.js +41 -0
- package/dist/src/utils/test/{configLoader-SHJSRG2A.js → configLoader-NA7IBCS3.js} +2 -2
- package/dist/src/utils/test/{contractTestRunnerPlaywright-Z2AHXSNM.js → contractTestRunnerPlaywright-5FT6K2WN.js} +111 -71
- package/dist/src/utils/test/dsl/index.cjs +106 -29
- package/dist/src/utils/test/dsl/index.d.cts +3 -0
- package/dist/src/utils/test/dsl/index.d.ts +3 -0
- package/dist/src/utils/test/dsl/index.js +106 -29
- package/dist/src/utils/test/index.cjs +135 -76
- package/dist/src/utils/test/index.js +17 -11
- package/dist/{test-O3J4ZPQR.js → test-FYSJXQWO.js} +17 -12
- package/package.json +3 -4
- package/dist/src/utils/test/aria-contracts/accordion/accordion.contract.json +0 -290
- package/dist/src/utils/test/aria-contracts/combobox/combobox.listbox.contract.json +0 -463
- package/dist/src/utils/test/aria-contracts/menu/menu.contract.json +0 -562
- package/dist/src/utils/test/aria-contracts/tabs/tabs.contract.json +0 -361
package/README.md
CHANGED
|
@@ -195,7 +195,7 @@ export default {
|
|
|
195
195
|
strictness: "balanced",
|
|
196
196
|
components: [
|
|
197
197
|
{
|
|
198
|
-
name: "combobox
|
|
198
|
+
name: "combobox",
|
|
199
199
|
path: "./tests/external-contracts/combobox.listbox.contract.json",
|
|
200
200
|
strategyPath: "./tests/external-strategies/CustomComboboxStrategy.js",
|
|
201
201
|
},
|
|
@@ -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();
|
|
@@ -1,23 +1,3 @@
|
|
|
1
|
-
// src/utils/test/contract/contract.json
|
|
2
|
-
var contract_default = {
|
|
3
|
-
menu: {
|
|
4
|
-
path: "./aria-contracts/menu/menu.contract.json",
|
|
5
|
-
component: "menu"
|
|
6
|
-
},
|
|
7
|
-
"combobox.listbox": {
|
|
8
|
-
path: "./aria-contracts/combobox/combobox.listbox.contract.json",
|
|
9
|
-
component: "combobox.listbox"
|
|
10
|
-
},
|
|
11
|
-
accordion: {
|
|
12
|
-
path: "./aria-contracts/accordion/accordion.contract.json",
|
|
13
|
-
component: "accordion"
|
|
14
|
-
},
|
|
15
|
-
tabs: {
|
|
16
|
-
path: "./aria-contracts/tabs/tabs.contract.json",
|
|
17
|
-
component: "tabs"
|
|
18
|
-
}
|
|
19
|
-
};
|
|
20
|
-
|
|
21
1
|
// src/utils/test/src/ContractReporter.ts
|
|
22
2
|
var ContractReporter = class {
|
|
23
3
|
startTime = 0;
|
|
@@ -329,9 +309,7 @@ async function getOrCreateContext() {
|
|
|
329
309
|
if (!sharedContext) {
|
|
330
310
|
const browser = await getOrCreateBrowser();
|
|
331
311
|
sharedContext = await browser.newContext({
|
|
332
|
-
// Isolated context - no permissions, no geolocation, etc.
|
|
333
312
|
permissions: [],
|
|
334
|
-
// Ignore HTTPS errors for local dev servers
|
|
335
313
|
ignoreHTTPSErrors: true
|
|
336
314
|
});
|
|
337
315
|
}
|
|
@@ -353,7 +331,6 @@ async function closeSharedBrowser() {
|
|
|
353
331
|
}
|
|
354
332
|
|
|
355
333
|
export {
|
|
356
|
-
contract_default,
|
|
357
334
|
ContractReporter,
|
|
358
335
|
normalizeLevel,
|
|
359
336
|
normalizeStrictness,
|
|
@@ -68,8 +68,8 @@ function validateConfig(config) {
|
|
|
68
68
|
if (typeof comp.name !== "string") {
|
|
69
69
|
errors.push(`test.components[${idx}].name must be a string`);
|
|
70
70
|
}
|
|
71
|
-
if (comp.
|
|
72
|
-
errors.push(`test.components[${idx}].
|
|
71
|
+
if (comp.contractPath !== void 0 && typeof comp.contractPath !== "string") {
|
|
72
|
+
errors.push(`test.components[${idx}].contractPath must be a string when provided`);
|
|
73
73
|
}
|
|
74
74
|
if (comp.strategyPath !== void 0 && typeof comp.strategyPath !== "string") {
|
|
75
75
|
errors.push(`test.components[${idx}].strategyPath must be a string when provided`);
|
|
@@ -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
|
@@ -103,8 +103,8 @@ function validateConfig(config) {
|
|
|
103
103
|
if (typeof comp.name !== "string") {
|
|
104
104
|
errors.push(`test.components[${idx}].name must be a string`);
|
|
105
105
|
}
|
|
106
|
-
if (comp.
|
|
107
|
-
errors.push(`test.components[${idx}].
|
|
106
|
+
if (comp.contractPath !== void 0 && typeof comp.contractPath !== "string") {
|
|
107
|
+
errors.push(`test.components[${idx}].contractPath must be a string when provided`);
|
|
108
108
|
}
|
|
109
109
|
if (comp.strategyPath !== void 0 && typeof comp.strategyPath !== "string") {
|
|
110
110
|
errors.push(`test.components[${idx}].strategyPath must be a string when provided`);
|
|
@@ -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();
|
|
@@ -586,31 +591,6 @@ var init_formatters = __esm({
|
|
|
586
591
|
}
|
|
587
592
|
});
|
|
588
593
|
|
|
589
|
-
// src/utils/test/contract/contract.json
|
|
590
|
-
var contract_default;
|
|
591
|
-
var init_contract = __esm({
|
|
592
|
-
"src/utils/test/contract/contract.json"() {
|
|
593
|
-
contract_default = {
|
|
594
|
-
menu: {
|
|
595
|
-
path: "./aria-contracts/menu/menu.contract.json",
|
|
596
|
-
component: "menu"
|
|
597
|
-
},
|
|
598
|
-
"combobox.listbox": {
|
|
599
|
-
path: "./aria-contracts/combobox/combobox.listbox.contract.json",
|
|
600
|
-
component: "combobox.listbox"
|
|
601
|
-
},
|
|
602
|
-
accordion: {
|
|
603
|
-
path: "./aria-contracts/accordion/accordion.contract.json",
|
|
604
|
-
component: "accordion"
|
|
605
|
-
},
|
|
606
|
-
tabs: {
|
|
607
|
-
path: "./aria-contracts/tabs/tabs.contract.json",
|
|
608
|
-
component: "tabs"
|
|
609
|
-
}
|
|
610
|
-
};
|
|
611
|
-
}
|
|
612
|
-
});
|
|
613
|
-
|
|
614
594
|
// src/utils/test/src/ContractReporter.ts
|
|
615
595
|
var ContractReporter;
|
|
616
596
|
var init_ContractReporter = __esm({
|
|
@@ -914,16 +894,13 @@ var init_strictness = __esm({
|
|
|
914
894
|
});
|
|
915
895
|
|
|
916
896
|
// src/utils/test/src/contractTestRunner.ts
|
|
917
|
-
async function runContractTests(componentName, component, strictness) {
|
|
897
|
+
async function runContractTests(contractPath, componentName, component, strictness) {
|
|
918
898
|
const reporter = new ContractReporter(false);
|
|
919
899
|
const strictnessMode = normalizeStrictness(strictness);
|
|
920
|
-
const contractTyped = contract_default;
|
|
921
|
-
const contractPath = contractTyped[componentName]?.path;
|
|
922
900
|
if (!contractPath) {
|
|
923
|
-
throw new Error(`No contract
|
|
901
|
+
throw new Error(`No contract path provided for component: ${componentName}`);
|
|
924
902
|
}
|
|
925
|
-
const
|
|
926
|
-
const contractData = await import_promises.default.readFile(resolvedPath, "utf-8");
|
|
903
|
+
const contractData = await import_promises.default.readFile(contractPath, "utf-8");
|
|
927
904
|
const componentContract = JSON.parse(contractData);
|
|
928
905
|
const totalTests = (componentContract.relationships?.length || 0) + (componentContract.static[0]?.assertions.length || 0) + componentContract.dynamic.length;
|
|
929
906
|
reporter.start(componentName, totalTests);
|
|
@@ -1058,7 +1035,7 @@ async function runContractTests(componentName, component, strictness) {
|
|
|
1058
1035
|
staticPassed += 1;
|
|
1059
1036
|
reporter.reportStaticTest(`${test.target} has ${test.attribute}`, "pass", void 0, staticLevel);
|
|
1060
1037
|
}
|
|
1061
|
-
} else if (!attributeValue || !test.expectedValue.split(" | ").includes(attributeValue)) {
|
|
1038
|
+
} else if (!attributeValue || typeof test.expectedValue === "string" && !test.expectedValue.split(" | ").includes(attributeValue)) {
|
|
1062
1039
|
const outcome = classifyFailure(test.failureMessage + ` Attribute value does not match expected value. Expected: ${test.expectedValue}, Found: ${attributeValue}`, test.level);
|
|
1063
1040
|
if (outcome.status === "fail") staticFailed += 1;
|
|
1064
1041
|
if (outcome.status === "warn") staticWarnings += 1;
|
|
@@ -1078,15 +1055,13 @@ async function runContractTests(componentName, component, strictness) {
|
|
|
1078
1055
|
reporter.summary(failures);
|
|
1079
1056
|
return { passes, failures, skipped, warnings };
|
|
1080
1057
|
}
|
|
1081
|
-
var import_promises
|
|
1058
|
+
var import_promises;
|
|
1082
1059
|
var init_contractTestRunner = __esm({
|
|
1083
1060
|
"src/utils/test/src/contractTestRunner.ts"() {
|
|
1084
1061
|
"use strict";
|
|
1085
|
-
init_contract();
|
|
1086
1062
|
import_promises = __toESM(require("fs/promises"), 1);
|
|
1087
1063
|
init_ContractReporter();
|
|
1088
1064
|
init_strictness();
|
|
1089
|
-
import_meta = {};
|
|
1090
1065
|
}
|
|
1091
1066
|
});
|
|
1092
1067
|
|
|
@@ -1108,9 +1083,7 @@ async function getOrCreateContext() {
|
|
|
1108
1083
|
if (!sharedContext) {
|
|
1109
1084
|
const browser = await getOrCreateBrowser();
|
|
1110
1085
|
sharedContext = await browser.newContext({
|
|
1111
|
-
// Isolated context - no permissions, no geolocation, etc.
|
|
1112
1086
|
permissions: [],
|
|
1113
|
-
// Ignore HTTPS errors for local dev servers
|
|
1114
1087
|
ignoreHTTPSErrors: true
|
|
1115
1088
|
});
|
|
1116
1089
|
}
|
|
@@ -1308,7 +1281,7 @@ var init_ComboboxComponentStrategy = __esm({
|
|
|
1308
1281
|
const popupElement = page.locator(popupSelector).first();
|
|
1309
1282
|
const isPopupVisible = await popupElement.isVisible().catch(() => false);
|
|
1310
1283
|
if (!isPopupVisible) return;
|
|
1311
|
-
let
|
|
1284
|
+
let popupClosed = false;
|
|
1312
1285
|
let closeSelector = this.selectors.input;
|
|
1313
1286
|
if (!closeSelector && this.selectors.focusable) {
|
|
1314
1287
|
closeSelector = this.selectors.focusable;
|
|
@@ -1319,18 +1292,18 @@ var init_ComboboxComponentStrategy = __esm({
|
|
|
1319
1292
|
const closeElement = page.locator(closeSelector).first();
|
|
1320
1293
|
await closeElement.focus();
|
|
1321
1294
|
await page.keyboard.press("Escape");
|
|
1322
|
-
|
|
1295
|
+
popupClosed = await (0, test_exports.expect)(popupElement).toBeHidden({ timeout: this.assertionTimeoutMs }).then(() => true).catch(() => false);
|
|
1323
1296
|
}
|
|
1324
|
-
if (!
|
|
1297
|
+
if (!popupClosed && this.selectors.button) {
|
|
1325
1298
|
const buttonElement = page.locator(this.selectors.button).first();
|
|
1326
1299
|
await buttonElement.click({ timeout: this.actionTimeoutMs });
|
|
1327
|
-
|
|
1300
|
+
popupClosed = await (0, test_exports.expect)(popupElement).toBeHidden({ timeout: this.assertionTimeoutMs }).then(() => true).catch(() => false);
|
|
1328
1301
|
}
|
|
1329
|
-
if (!
|
|
1302
|
+
if (!popupClosed) {
|
|
1330
1303
|
await page.mouse.click(10, 10);
|
|
1331
|
-
|
|
1304
|
+
popupClosed = await (0, test_exports.expect)(popupElement).toBeHidden({ timeout: this.assertionTimeoutMs }).then(() => true).catch(() => false);
|
|
1332
1305
|
}
|
|
1333
|
-
if (!
|
|
1306
|
+
if (!popupClosed) {
|
|
1334
1307
|
throw new Error(
|
|
1335
1308
|
`\u274C FATAL: Cannot close combobox popup between tests. Popup remains visible after trying:
|
|
1336
1309
|
1. Escape key
|
|
@@ -1428,12 +1401,6 @@ var init_StrategyRegistry = __esm({
|
|
|
1428
1401
|
(m) => m.TabsComponentStrategy
|
|
1429
1402
|
)
|
|
1430
1403
|
);
|
|
1431
|
-
this.builtInStrategies.set(
|
|
1432
|
-
"combobox.listbox",
|
|
1433
|
-
() => Promise.resolve().then(() => (init_ComboboxComponentStrategy(), ComboboxComponentStrategy_exports)).then(
|
|
1434
|
-
(m) => m.ComboboxComponentStrategy
|
|
1435
|
-
)
|
|
1436
|
-
);
|
|
1437
1404
|
}
|
|
1438
1405
|
/**
|
|
1439
1406
|
* Load a strategy - either from custom path or built-in registry
|
|
@@ -1480,15 +1447,14 @@ var init_StrategyRegistry = __esm({
|
|
|
1480
1447
|
});
|
|
1481
1448
|
|
|
1482
1449
|
// src/utils/test/src/ComponentDetector.ts
|
|
1483
|
-
var import_fs, import_path4,
|
|
1450
|
+
var import_fs, import_path4, import_meta, ComponentDetector;
|
|
1484
1451
|
var init_ComponentDetector = __esm({
|
|
1485
1452
|
"src/utils/test/src/ComponentDetector.ts"() {
|
|
1486
1453
|
"use strict";
|
|
1487
1454
|
import_fs = require("fs");
|
|
1488
1455
|
import_path4 = __toESM(require("path"), 1);
|
|
1489
|
-
init_contract();
|
|
1490
1456
|
init_StrategyRegistry();
|
|
1491
|
-
|
|
1457
|
+
import_meta = {};
|
|
1492
1458
|
ComponentDetector = class {
|
|
1493
1459
|
static strategyRegistry = new StrategyRegistry();
|
|
1494
1460
|
static isComponentConfig(value) {
|
|
@@ -1508,11 +1474,7 @@ var init_ComponentDetector = __esm({
|
|
|
1508
1474
|
*/
|
|
1509
1475
|
static async detect(componentName, componentConfig, actionTimeoutMs = 400, assertionTimeoutMs = 400, configBaseDir) {
|
|
1510
1476
|
const typedComponentConfig = this.isComponentConfig(componentConfig) ? componentConfig : void 0;
|
|
1511
|
-
|
|
1512
|
-
if (!contractPath) {
|
|
1513
|
-
const contractTyped = contract_default;
|
|
1514
|
-
contractPath = contractTyped[componentName]?.path;
|
|
1515
|
-
}
|
|
1477
|
+
const contractPath = typedComponentConfig?.contractPath;
|
|
1516
1478
|
if (!contractPath) {
|
|
1517
1479
|
throw new Error(`Contract path not found for component: ${componentName}`);
|
|
1518
1480
|
}
|
|
@@ -1531,7 +1493,7 @@ var init_ComponentDetector = __esm({
|
|
|
1531
1493
|
(0, import_fs.readFileSync)(cwdResolved, "utf-8");
|
|
1532
1494
|
return cwdResolved;
|
|
1533
1495
|
} catch {
|
|
1534
|
-
return new URL(contractPath,
|
|
1496
|
+
return new URL(contractPath, import_meta.url).pathname;
|
|
1535
1497
|
}
|
|
1536
1498
|
})();
|
|
1537
1499
|
const contractData = (0, import_fs.readFileSync)(resolvedPath, "utf-8");
|
|
@@ -1545,7 +1507,7 @@ var init_ComponentDetector = __esm({
|
|
|
1545
1507
|
if (!strategyClass) {
|
|
1546
1508
|
return null;
|
|
1547
1509
|
}
|
|
1548
|
-
const mainSelector = selectors.
|
|
1510
|
+
const mainSelector = selectors.main;
|
|
1549
1511
|
if (componentName === "tabs") {
|
|
1550
1512
|
return new strategyClass(mainSelector, selectors);
|
|
1551
1513
|
}
|
|
@@ -1561,6 +1523,10 @@ var init_ComponentDetector = __esm({
|
|
|
1561
1523
|
});
|
|
1562
1524
|
|
|
1563
1525
|
// src/utils/test/src/RelativeTargetResolver.ts
|
|
1526
|
+
var RelativeTargetResolver_exports = {};
|
|
1527
|
+
__export(RelativeTargetResolver_exports, {
|
|
1528
|
+
RelativeTargetResolver: () => RelativeTargetResolver
|
|
1529
|
+
});
|
|
1564
1530
|
var RelativeTargetResolver;
|
|
1565
1531
|
var init_RelativeTargetResolver = __esm({
|
|
1566
1532
|
"src/utils/test/src/RelativeTargetResolver.ts"() {
|
|
@@ -1636,16 +1602,16 @@ var init_ActionExecutor = __esm({
|
|
|
1636
1602
|
async focus(target, relativeTarget, virtualId) {
|
|
1637
1603
|
try {
|
|
1638
1604
|
if (target === "virtual" && virtualId) {
|
|
1639
|
-
const
|
|
1640
|
-
if (!
|
|
1641
|
-
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.` };
|
|
1642
1608
|
}
|
|
1643
|
-
const
|
|
1644
|
-
const exists = await
|
|
1609
|
+
const main = this.page.locator(mainSelector).first();
|
|
1610
|
+
const exists = await main.count();
|
|
1645
1611
|
if (!exists) {
|
|
1646
|
-
return { success: false, error: `
|
|
1612
|
+
return { success: false, error: `Main element not found for virtual focus.` };
|
|
1647
1613
|
}
|
|
1648
|
-
await
|
|
1614
|
+
await main.evaluate((el, id) => {
|
|
1649
1615
|
el.setAttribute("aria-activedescendant", id);
|
|
1650
1616
|
}, virtualId);
|
|
1651
1617
|
return { success: true };
|
|
@@ -1947,6 +1913,10 @@ var init_AssertionRunner = __esm({
|
|
|
1947
1913
|
};
|
|
1948
1914
|
}
|
|
1949
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
|
+
}
|
|
1950
1920
|
const expectedValues = expectedValue.split(" | ").map((v) => v.trim());
|
|
1951
1921
|
const attributeValue = await target.getAttribute(attribute);
|
|
1952
1922
|
if (attributeValue !== null && expectedValues.includes(attributeValue)) {
|
|
@@ -2120,7 +2090,7 @@ __export(contractTestRunnerPlaywright_exports, {
|
|
|
2120
2090
|
});
|
|
2121
2091
|
async function runContractTestsPlaywright(componentName, url, strictness, config, configBaseDir) {
|
|
2122
2092
|
const componentConfig = config?.test?.components?.find((c) => c.name === componentName);
|
|
2123
|
-
const isCustomContract = !!componentConfig?.
|
|
2093
|
+
const isCustomContract = !!componentConfig?.contractPath;
|
|
2124
2094
|
const reporter = new ContractReporter(true, isCustomContract);
|
|
2125
2095
|
const defaultTimeouts = {
|
|
2126
2096
|
actionTimeoutMs: 400,
|
|
@@ -2160,11 +2130,7 @@ async function runContractTestsPlaywright(componentName, url, strictness, config
|
|
|
2160
2130
|
defaultTimeouts.componentReadyTimeoutMs
|
|
2161
2131
|
);
|
|
2162
2132
|
const strictnessMode = normalizeStrictness(strictness);
|
|
2163
|
-
|
|
2164
|
-
if (!contractPath) {
|
|
2165
|
-
const contractTyped = contract_default;
|
|
2166
|
-
contractPath = contractTyped[componentName]?.path;
|
|
2167
|
-
}
|
|
2133
|
+
const contractPath = componentConfig?.contractPath;
|
|
2168
2134
|
if (!contractPath) {
|
|
2169
2135
|
throw new Error(`Contract path not found for component: ${componentName}`);
|
|
2170
2136
|
}
|
|
@@ -2183,7 +2149,7 @@ async function runContractTestsPlaywright(componentName, url, strictness, config
|
|
|
2183
2149
|
(0, import_fs2.readFileSync)(cwdResolved, "utf-8");
|
|
2184
2150
|
return cwdResolved;
|
|
2185
2151
|
} catch {
|
|
2186
|
-
return new URL(contractPath,
|
|
2152
|
+
return new URL(contractPath, import_meta2.url).pathname;
|
|
2187
2153
|
}
|
|
2188
2154
|
})();
|
|
2189
2155
|
const contractData = (0, import_fs2.readFileSync)(resolvedPath, "utf-8");
|
|
@@ -2383,6 +2349,52 @@ This usually means:
|
|
|
2383
2349
|
reporter.reportStaticTest(relDescription, "pass", void 0, relationshipLevel);
|
|
2384
2350
|
}
|
|
2385
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
|
+
}
|
|
2386
2398
|
const staticAssertionRunner = new AssertionRunner(page, componentContract.selectors, assertionTimeoutMs);
|
|
2387
2399
|
for (const test of componentContract.static[0]?.assertions || []) {
|
|
2388
2400
|
if (test.target === "relative") continue;
|
|
@@ -2425,6 +2437,22 @@ This usually means:
|
|
|
2425
2437
|
}
|
|
2426
2438
|
return false;
|
|
2427
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
|
+
}
|
|
2428
2456
|
if (!test.expectedValue) {
|
|
2429
2457
|
const attributes = test.attribute.split(" | ");
|
|
2430
2458
|
let hasAny = false;
|
|
@@ -2458,16 +2486,17 @@ This usually means:
|
|
|
2458
2486
|
reporter.reportStaticTest(staticDescription, "pass", void 0, staticLevel);
|
|
2459
2487
|
}
|
|
2460
2488
|
} else {
|
|
2461
|
-
if (isRedundantCheck(targetSelector, test.attribute,
|
|
2462
|
-
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}).`);
|
|
2463
2491
|
staticPassed += 1;
|
|
2464
2492
|
reporter.reportStaticTest(staticDescription, "pass", void 0, staticLevel);
|
|
2465
2493
|
} else {
|
|
2494
|
+
const valueToCheck = expectedValue ?? "";
|
|
2466
2495
|
const result = await staticAssertionRunner.validateAttribute(
|
|
2467
2496
|
target,
|
|
2468
2497
|
test.target,
|
|
2469
2498
|
test.attribute,
|
|
2470
|
-
|
|
2499
|
+
valueToCheck,
|
|
2471
2500
|
test.failureMessage,
|
|
2472
2501
|
"Static ARIA Test"
|
|
2473
2502
|
);
|
|
@@ -2585,7 +2614,33 @@ This usually means:
|
|
|
2585
2614
|
continue;
|
|
2586
2615
|
}
|
|
2587
2616
|
for (const assertion of assertions) {
|
|
2588
|
-
|
|
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);
|
|
2589
2644
|
if (result.success && result.passMessage) {
|
|
2590
2645
|
passes.push(result.passMessage);
|
|
2591
2646
|
} else if (!result.success && result.failMessage) {
|
|
@@ -2655,20 +2710,19 @@ This usually means:
|
|
|
2655
2710
|
}
|
|
2656
2711
|
return { passes, failures, skipped, warnings };
|
|
2657
2712
|
}
|
|
2658
|
-
var import_fs2, import_path5,
|
|
2713
|
+
var import_fs2, import_path5, import_meta2;
|
|
2659
2714
|
var init_contractTestRunnerPlaywright = __esm({
|
|
2660
2715
|
"src/utils/test/src/contractTestRunnerPlaywright.ts"() {
|
|
2661
2716
|
"use strict";
|
|
2662
2717
|
import_fs2 = require("fs");
|
|
2663
2718
|
import_path5 = __toESM(require("path"), 1);
|
|
2664
|
-
init_contract();
|
|
2665
2719
|
init_playwrightTestHarness();
|
|
2666
2720
|
init_ComponentDetector();
|
|
2667
2721
|
init_ContractReporter();
|
|
2668
2722
|
init_ActionExecutor();
|
|
2669
2723
|
init_AssertionRunner();
|
|
2670
2724
|
init_strictness();
|
|
2671
|
-
|
|
2725
|
+
import_meta2 = {};
|
|
2672
2726
|
}
|
|
2673
2727
|
});
|
|
2674
2728
|
|
|
@@ -2747,7 +2801,16 @@ Please start your dev server and try again.`
|
|
|
2747
2801
|
}
|
|
2748
2802
|
} else if (component) {
|
|
2749
2803
|
console.log(`\u{1F3AD} Running component contract tests in JSDOM mode`);
|
|
2750
|
-
|
|
2804
|
+
const contractPath = config.test?.components?.find((comp) => comp?.name === componentName)?.contractPath;
|
|
2805
|
+
if (!contractPath) {
|
|
2806
|
+
throw new Error(`\u274C No contract path found for component: ${componentName}`);
|
|
2807
|
+
}
|
|
2808
|
+
contract = await runContractTests(
|
|
2809
|
+
import_path6.default.resolve(configBaseDir, contractPath),
|
|
2810
|
+
componentName,
|
|
2811
|
+
component,
|
|
2812
|
+
strictness
|
|
2813
|
+
);
|
|
2751
2814
|
} else {
|
|
2752
2815
|
throw new Error("\u274C Either component or URL must be provided");
|
|
2753
2816
|
}
|