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/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
loadConfig
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-GJGUY643.js";
|
|
5
5
|
import {
|
|
6
6
|
displayBadgeInfo,
|
|
7
7
|
promptAddBadge
|
|
@@ -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,13 +122,13 @@ 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(
|
|
129
129
|
new Command("contracts").description("Build DSL contracts to JSON").action(async () => {
|
|
130
130
|
const { buildContracts } = await import("./buildContracts-FT6KWUJN.js");
|
|
131
|
-
const { loadConfig: loadConfig2 } = await import("./configLoader-
|
|
131
|
+
const { loadConfig: loadConfig2 } = await import("./configLoader-REHK3S3Q.js");
|
|
132
132
|
const cwd = process.cwd();
|
|
133
133
|
const { config, configPath, errors } = await loadConfig2(cwd);
|
|
134
134
|
if (configPath) {
|
|
@@ -70,8 +70,8 @@ function validateConfig(config) {
|
|
|
70
70
|
if (typeof comp.name !== "string") {
|
|
71
71
|
errors.push(`test.components[${idx}].name must be a string`);
|
|
72
72
|
}
|
|
73
|
-
if (comp.
|
|
74
|
-
errors.push(`test.components[${idx}].
|
|
73
|
+
if (comp.contractPath !== void 0 && typeof comp.contractPath !== "string") {
|
|
74
|
+
errors.push(`test.components[${idx}].contractPath must be a string when provided`);
|
|
75
75
|
}
|
|
76
76
|
if (comp.strategyPath !== void 0 && typeof comp.strategyPath !== "string") {
|
|
77
77
|
errors.push(`test.components[${idx}].strategyPath must be a string when provided`);
|
package/dist/{contractTestRunnerPlaywright-QDXSK3FE.js → contractTestRunnerPlaywright-H24LQ45R.js}
RENAMED
|
@@ -1,11 +1,13 @@
|
|
|
1
|
+
import {
|
|
2
|
+
RelativeTargetResolver
|
|
3
|
+
} from "./chunk-GLT43UVH.js";
|
|
1
4
|
import {
|
|
2
5
|
ContractReporter,
|
|
3
|
-
contract_default,
|
|
4
6
|
createTestPage,
|
|
5
7
|
normalizeLevel,
|
|
6
8
|
normalizeStrictness,
|
|
7
9
|
resolveEnforcement
|
|
8
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-4DU5Z5BR.js";
|
|
9
11
|
import {
|
|
10
12
|
test_exports
|
|
11
13
|
} from "./chunk-PK5L2SAF.js";
|
|
@@ -45,7 +47,7 @@ var StrategyRegistry = class {
|
|
|
45
47
|
);
|
|
46
48
|
this.builtInStrategies.set(
|
|
47
49
|
"combobox",
|
|
48
|
-
() => import("./ComboboxComponentStrategy-
|
|
50
|
+
() => import("./ComboboxComponentStrategy-DU342VMB.js").then(
|
|
49
51
|
(m) => m.ComboboxComponentStrategy
|
|
50
52
|
)
|
|
51
53
|
);
|
|
@@ -55,12 +57,6 @@ var StrategyRegistry = class {
|
|
|
55
57
|
(m) => m.TabsComponentStrategy
|
|
56
58
|
)
|
|
57
59
|
);
|
|
58
|
-
this.builtInStrategies.set(
|
|
59
|
-
"combobox.listbox",
|
|
60
|
-
() => import("./ComboboxComponentStrategy-OGRVZXAF.js").then(
|
|
61
|
-
(m) => m.ComboboxComponentStrategy
|
|
62
|
-
)
|
|
63
|
-
);
|
|
64
60
|
}
|
|
65
61
|
/**
|
|
66
62
|
* Load a strategy - either from custom path or built-in registry
|
|
@@ -124,11 +120,7 @@ var ComponentDetector = class {
|
|
|
124
120
|
*/
|
|
125
121
|
static async detect(componentName, componentConfig, actionTimeoutMs = 400, assertionTimeoutMs = 400, configBaseDir) {
|
|
126
122
|
const typedComponentConfig = this.isComponentConfig(componentConfig) ? componentConfig : void 0;
|
|
127
|
-
|
|
128
|
-
if (!contractPath) {
|
|
129
|
-
const contractTyped = contract_default;
|
|
130
|
-
contractPath = contractTyped[componentName]?.path;
|
|
131
|
-
}
|
|
123
|
+
const contractPath = typedComponentConfig?.contractPath;
|
|
132
124
|
if (!contractPath) {
|
|
133
125
|
throw new Error(`Contract path not found for component: ${componentName}`);
|
|
134
126
|
}
|
|
@@ -161,7 +153,7 @@ var ComponentDetector = class {
|
|
|
161
153
|
if (!strategyClass) {
|
|
162
154
|
return null;
|
|
163
155
|
}
|
|
164
|
-
const mainSelector = selectors.
|
|
156
|
+
const mainSelector = selectors.main;
|
|
165
157
|
if (componentName === "tabs") {
|
|
166
158
|
return new strategyClass(mainSelector, selectors);
|
|
167
159
|
}
|
|
@@ -174,46 +166,6 @@ var ComponentDetector = class {
|
|
|
174
166
|
}
|
|
175
167
|
};
|
|
176
168
|
|
|
177
|
-
// src/utils/test/src/RelativeTargetResolver.ts
|
|
178
|
-
var RelativeTargetResolver = class {
|
|
179
|
-
/**
|
|
180
|
-
* Resolve a relative target like "first", "second", "last", "next", "previous"
|
|
181
|
-
* @param page Playwright page instance
|
|
182
|
-
* @param selector Base selector to find elements
|
|
183
|
-
* @param relative Relative position (first, second, last, next, previous)
|
|
184
|
-
* @returns The resolved Locator or null if not found
|
|
185
|
-
*/
|
|
186
|
-
static async resolve(page, selector, relative) {
|
|
187
|
-
const items = await page.locator(selector).all();
|
|
188
|
-
switch (relative) {
|
|
189
|
-
case "first":
|
|
190
|
-
return items[0];
|
|
191
|
-
case "second":
|
|
192
|
-
return items[1];
|
|
193
|
-
case "last":
|
|
194
|
-
return items[items.length - 1];
|
|
195
|
-
case "next": {
|
|
196
|
-
const currentIndex = await page.evaluate(([sel]) => {
|
|
197
|
-
const items2 = Array.from(document.querySelectorAll(sel));
|
|
198
|
-
return items2.indexOf(document.activeElement);
|
|
199
|
-
}, [selector]);
|
|
200
|
-
const nextIndex = (currentIndex + 1) % items.length;
|
|
201
|
-
return items[nextIndex];
|
|
202
|
-
}
|
|
203
|
-
case "previous": {
|
|
204
|
-
const currentIndex = await page.evaluate(([sel]) => {
|
|
205
|
-
const items2 = Array.from(document.querySelectorAll(sel));
|
|
206
|
-
return items2.indexOf(document.activeElement);
|
|
207
|
-
}, [selector]);
|
|
208
|
-
const prevIndex = (currentIndex - 1 + items.length) % items.length;
|
|
209
|
-
return items[prevIndex];
|
|
210
|
-
}
|
|
211
|
-
default:
|
|
212
|
-
return null;
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
};
|
|
216
|
-
|
|
217
169
|
// src/utils/test/src/ActionExecutor.ts
|
|
218
170
|
var ActionExecutor = class {
|
|
219
171
|
constructor(page, selectors, timeoutMs = 400) {
|
|
@@ -239,16 +191,16 @@ var ActionExecutor = class {
|
|
|
239
191
|
async focus(target, relativeTarget, virtualId) {
|
|
240
192
|
try {
|
|
241
193
|
if (target === "virtual" && virtualId) {
|
|
242
|
-
const
|
|
243
|
-
if (!
|
|
244
|
-
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.` };
|
|
245
197
|
}
|
|
246
|
-
const
|
|
247
|
-
const exists = await
|
|
198
|
+
const main = this.page.locator(mainSelector).first();
|
|
199
|
+
const exists = await main.count();
|
|
248
200
|
if (!exists) {
|
|
249
|
-
return { success: false, error: `
|
|
201
|
+
return { success: false, error: `Main element not found for virtual focus.` };
|
|
250
202
|
}
|
|
251
|
-
await
|
|
203
|
+
await main.evaluate((el, id) => {
|
|
252
204
|
el.setAttribute("aria-activedescendant", id);
|
|
253
205
|
}, virtualId);
|
|
254
206
|
return { success: true };
|
|
@@ -542,6 +494,10 @@ var AssertionRunner = class {
|
|
|
542
494
|
};
|
|
543
495
|
}
|
|
544
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
|
+
}
|
|
545
501
|
const expectedValues = expectedValue.split(" | ").map((v) => v.trim());
|
|
546
502
|
const attributeValue = await target.getAttribute(attribute);
|
|
547
503
|
if (attributeValue !== null && expectedValues.includes(attributeValue)) {
|
|
@@ -709,7 +665,7 @@ var AssertionRunner = class {
|
|
|
709
665
|
// src/utils/test/src/contractTestRunnerPlaywright.ts
|
|
710
666
|
async function runContractTestsPlaywright(componentName, url, strictness, config, configBaseDir) {
|
|
711
667
|
const componentConfig = config?.test?.components?.find((c) => c.name === componentName);
|
|
712
|
-
const isCustomContract = !!componentConfig?.
|
|
668
|
+
const isCustomContract = !!componentConfig?.contractPath;
|
|
713
669
|
const reporter = new ContractReporter(true, isCustomContract);
|
|
714
670
|
const defaultTimeouts = {
|
|
715
671
|
actionTimeoutMs: 400,
|
|
@@ -749,11 +705,7 @@ async function runContractTestsPlaywright(componentName, url, strictness, config
|
|
|
749
705
|
defaultTimeouts.componentReadyTimeoutMs
|
|
750
706
|
);
|
|
751
707
|
const strictnessMode = normalizeStrictness(strictness);
|
|
752
|
-
|
|
753
|
-
if (!contractPath) {
|
|
754
|
-
const contractTyped = contract_default;
|
|
755
|
-
contractPath = contractTyped[componentName]?.path;
|
|
756
|
-
}
|
|
708
|
+
const contractPath = componentConfig?.contractPath;
|
|
757
709
|
if (!contractPath) {
|
|
758
710
|
throw new Error(`Contract path not found for component: ${componentName}`);
|
|
759
711
|
}
|
|
@@ -972,6 +924,52 @@ This usually means:
|
|
|
972
924
|
reporter.reportStaticTest(relDescription, "pass", void 0, relationshipLevel);
|
|
973
925
|
}
|
|
974
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
|
+
}
|
|
975
973
|
const staticAssertionRunner = new AssertionRunner(page, componentContract.selectors, assertionTimeoutMs);
|
|
976
974
|
for (const test of componentContract.static[0]?.assertions || []) {
|
|
977
975
|
if (test.target === "relative") continue;
|
|
@@ -1014,6 +1012,22 @@ This usually means:
|
|
|
1014
1012
|
}
|
|
1015
1013
|
return false;
|
|
1016
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
|
+
}
|
|
1017
1031
|
if (!test.expectedValue) {
|
|
1018
1032
|
const attributes = test.attribute.split(" | ");
|
|
1019
1033
|
let hasAny = false;
|
|
@@ -1047,16 +1061,17 @@ This usually means:
|
|
|
1047
1061
|
reporter.reportStaticTest(staticDescription, "pass", void 0, staticLevel);
|
|
1048
1062
|
}
|
|
1049
1063
|
} else {
|
|
1050
|
-
if (isRedundantCheck(targetSelector, test.attribute,
|
|
1051
|
-
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}).`);
|
|
1052
1066
|
staticPassed += 1;
|
|
1053
1067
|
reporter.reportStaticTest(staticDescription, "pass", void 0, staticLevel);
|
|
1054
1068
|
} else {
|
|
1069
|
+
const valueToCheck = expectedValue ?? "";
|
|
1055
1070
|
const result = await staticAssertionRunner.validateAttribute(
|
|
1056
1071
|
target,
|
|
1057
1072
|
test.target,
|
|
1058
1073
|
test.attribute,
|
|
1059
|
-
|
|
1074
|
+
valueToCheck,
|
|
1060
1075
|
test.failureMessage,
|
|
1061
1076
|
"Static ARIA Test"
|
|
1062
1077
|
);
|
|
@@ -1174,7 +1189,33 @@ This usually means:
|
|
|
1174
1189
|
continue;
|
|
1175
1190
|
}
|
|
1176
1191
|
for (const assertion of assertions) {
|
|
1177
|
-
|
|
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);
|
|
1178
1219
|
if (result.success && result.passMessage) {
|
|
1179
1220
|
passes.push(result.passMessage);
|
|
1180
1221
|
} else if (!result.success && result.failMessage) {
|
package/dist/{contractTestRunnerPlaywright-WNWQYSXZ.js → contractTestRunnerPlaywright-NL3JNJYH.js}
RENAMED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ContractReporter,
|
|
3
|
-
contract_default,
|
|
4
3
|
createTestPage,
|
|
5
4
|
normalizeLevel,
|
|
6
5
|
normalizeStrictness,
|
|
7
6
|
resolveEnforcement
|
|
8
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-4DU5Z5BR.js";
|
|
9
8
|
import {
|
|
10
9
|
test_exports
|
|
11
10
|
} from "./chunk-PK5L2SAF.js";
|
|
11
|
+
import {
|
|
12
|
+
RelativeTargetResolver
|
|
13
|
+
} from "./chunk-GLT43UVH.js";
|
|
12
14
|
import "./chunk-I2KLQ2HA.js";
|
|
13
15
|
|
|
14
16
|
// src/utils/test/src/contractTestRunnerPlaywright.ts
|
|
@@ -45,7 +47,7 @@ var StrategyRegistry = class {
|
|
|
45
47
|
);
|
|
46
48
|
this.builtInStrategies.set(
|
|
47
49
|
"combobox",
|
|
48
|
-
() => import("./ComboboxComponentStrategy-
|
|
50
|
+
() => import("./ComboboxComponentStrategy-DU342VMB.js").then(
|
|
49
51
|
(m) => m.ComboboxComponentStrategy
|
|
50
52
|
)
|
|
51
53
|
);
|
|
@@ -55,12 +57,6 @@ var StrategyRegistry = class {
|
|
|
55
57
|
(m) => m.TabsComponentStrategy
|
|
56
58
|
)
|
|
57
59
|
);
|
|
58
|
-
this.builtInStrategies.set(
|
|
59
|
-
"combobox.listbox",
|
|
60
|
-
() => import("./ComboboxComponentStrategy-OGRVZXAF.js").then(
|
|
61
|
-
(m) => m.ComboboxComponentStrategy
|
|
62
|
-
)
|
|
63
|
-
);
|
|
64
60
|
}
|
|
65
61
|
/**
|
|
66
62
|
* Load a strategy - either from custom path or built-in registry
|
|
@@ -124,11 +120,7 @@ var ComponentDetector = class {
|
|
|
124
120
|
*/
|
|
125
121
|
static async detect(componentName, componentConfig, actionTimeoutMs = 400, assertionTimeoutMs = 400, configBaseDir) {
|
|
126
122
|
const typedComponentConfig = this.isComponentConfig(componentConfig) ? componentConfig : void 0;
|
|
127
|
-
|
|
128
|
-
if (!contractPath) {
|
|
129
|
-
const contractTyped = contract_default;
|
|
130
|
-
contractPath = contractTyped[componentName]?.path;
|
|
131
|
-
}
|
|
123
|
+
const contractPath = typedComponentConfig?.contractPath;
|
|
132
124
|
if (!contractPath) {
|
|
133
125
|
throw new Error(`Contract path not found for component: ${componentName}`);
|
|
134
126
|
}
|
|
@@ -161,7 +153,7 @@ var ComponentDetector = class {
|
|
|
161
153
|
if (!strategyClass) {
|
|
162
154
|
return null;
|
|
163
155
|
}
|
|
164
|
-
const mainSelector = selectors.
|
|
156
|
+
const mainSelector = selectors.main;
|
|
165
157
|
if (componentName === "tabs") {
|
|
166
158
|
return new strategyClass(mainSelector, selectors);
|
|
167
159
|
}
|
|
@@ -174,46 +166,6 @@ var ComponentDetector = class {
|
|
|
174
166
|
}
|
|
175
167
|
};
|
|
176
168
|
|
|
177
|
-
// src/utils/test/src/RelativeTargetResolver.ts
|
|
178
|
-
var RelativeTargetResolver = class {
|
|
179
|
-
/**
|
|
180
|
-
* Resolve a relative target like "first", "second", "last", "next", "previous"
|
|
181
|
-
* @param page Playwright page instance
|
|
182
|
-
* @param selector Base selector to find elements
|
|
183
|
-
* @param relative Relative position (first, second, last, next, previous)
|
|
184
|
-
* @returns The resolved Locator or null if not found
|
|
185
|
-
*/
|
|
186
|
-
static async resolve(page, selector, relative) {
|
|
187
|
-
const items = await page.locator(selector).all();
|
|
188
|
-
switch (relative) {
|
|
189
|
-
case "first":
|
|
190
|
-
return items[0];
|
|
191
|
-
case "second":
|
|
192
|
-
return items[1];
|
|
193
|
-
case "last":
|
|
194
|
-
return items[items.length - 1];
|
|
195
|
-
case "next": {
|
|
196
|
-
const currentIndex = await page.evaluate(([sel]) => {
|
|
197
|
-
const items2 = Array.from(document.querySelectorAll(sel));
|
|
198
|
-
return items2.indexOf(document.activeElement);
|
|
199
|
-
}, [selector]);
|
|
200
|
-
const nextIndex = (currentIndex + 1) % items.length;
|
|
201
|
-
return items[nextIndex];
|
|
202
|
-
}
|
|
203
|
-
case "previous": {
|
|
204
|
-
const currentIndex = await page.evaluate(([sel]) => {
|
|
205
|
-
const items2 = Array.from(document.querySelectorAll(sel));
|
|
206
|
-
return items2.indexOf(document.activeElement);
|
|
207
|
-
}, [selector]);
|
|
208
|
-
const prevIndex = (currentIndex - 1 + items.length) % items.length;
|
|
209
|
-
return items[prevIndex];
|
|
210
|
-
}
|
|
211
|
-
default:
|
|
212
|
-
return null;
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
};
|
|
216
|
-
|
|
217
169
|
// src/utils/test/src/ActionExecutor.ts
|
|
218
170
|
var ActionExecutor = class {
|
|
219
171
|
constructor(page, selectors, timeoutMs = 400) {
|
|
@@ -239,16 +191,16 @@ var ActionExecutor = class {
|
|
|
239
191
|
async focus(target, relativeTarget, virtualId) {
|
|
240
192
|
try {
|
|
241
193
|
if (target === "virtual" && virtualId) {
|
|
242
|
-
const
|
|
243
|
-
if (!
|
|
244
|
-
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.` };
|
|
245
197
|
}
|
|
246
|
-
const
|
|
247
|
-
const exists = await
|
|
198
|
+
const main = this.page.locator(mainSelector).first();
|
|
199
|
+
const exists = await main.count();
|
|
248
200
|
if (!exists) {
|
|
249
|
-
return { success: false, error: `
|
|
201
|
+
return { success: false, error: `Main element not found for virtual focus.` };
|
|
250
202
|
}
|
|
251
|
-
await
|
|
203
|
+
await main.evaluate((el, id) => {
|
|
252
204
|
el.setAttribute("aria-activedescendant", id);
|
|
253
205
|
}, virtualId);
|
|
254
206
|
return { success: true };
|
|
@@ -542,6 +494,10 @@ var AssertionRunner = class {
|
|
|
542
494
|
};
|
|
543
495
|
}
|
|
544
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
|
+
}
|
|
545
501
|
const expectedValues = expectedValue.split(" | ").map((v) => v.trim());
|
|
546
502
|
const attributeValue = await target.getAttribute(attribute);
|
|
547
503
|
if (attributeValue !== null && expectedValues.includes(attributeValue)) {
|
|
@@ -709,7 +665,7 @@ var AssertionRunner = class {
|
|
|
709
665
|
// src/utils/test/src/contractTestRunnerPlaywright.ts
|
|
710
666
|
async function runContractTestsPlaywright(componentName, url, strictness, config, configBaseDir) {
|
|
711
667
|
const componentConfig = config?.test?.components?.find((c) => c.name === componentName);
|
|
712
|
-
const isCustomContract = !!componentConfig?.
|
|
668
|
+
const isCustomContract = !!componentConfig?.contractPath;
|
|
713
669
|
const reporter = new ContractReporter(true, isCustomContract);
|
|
714
670
|
const defaultTimeouts = {
|
|
715
671
|
actionTimeoutMs: 400,
|
|
@@ -749,11 +705,7 @@ async function runContractTestsPlaywright(componentName, url, strictness, config
|
|
|
749
705
|
defaultTimeouts.componentReadyTimeoutMs
|
|
750
706
|
);
|
|
751
707
|
const strictnessMode = normalizeStrictness(strictness);
|
|
752
|
-
|
|
753
|
-
if (!contractPath) {
|
|
754
|
-
const contractTyped = contract_default;
|
|
755
|
-
contractPath = contractTyped[componentName]?.path;
|
|
756
|
-
}
|
|
708
|
+
const contractPath = componentConfig?.contractPath;
|
|
757
709
|
if (!contractPath) {
|
|
758
710
|
throw new Error(`Contract path not found for component: ${componentName}`);
|
|
759
711
|
}
|
|
@@ -972,6 +924,52 @@ This usually means:
|
|
|
972
924
|
reporter.reportStaticTest(relDescription, "pass", void 0, relationshipLevel);
|
|
973
925
|
}
|
|
974
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
|
+
}
|
|
975
973
|
const staticAssertionRunner = new AssertionRunner(page, componentContract.selectors, assertionTimeoutMs);
|
|
976
974
|
for (const test of componentContract.static[0]?.assertions || []) {
|
|
977
975
|
if (test.target === "relative") continue;
|
|
@@ -1014,6 +1012,22 @@ This usually means:
|
|
|
1014
1012
|
}
|
|
1015
1013
|
return false;
|
|
1016
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
|
+
}
|
|
1017
1031
|
if (!test.expectedValue) {
|
|
1018
1032
|
const attributes = test.attribute.split(" | ");
|
|
1019
1033
|
let hasAny = false;
|
|
@@ -1047,16 +1061,17 @@ This usually means:
|
|
|
1047
1061
|
reporter.reportStaticTest(staticDescription, "pass", void 0, staticLevel);
|
|
1048
1062
|
}
|
|
1049
1063
|
} else {
|
|
1050
|
-
if (isRedundantCheck(targetSelector, test.attribute,
|
|
1051
|
-
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}).`);
|
|
1052
1066
|
staticPassed += 1;
|
|
1053
1067
|
reporter.reportStaticTest(staticDescription, "pass", void 0, staticLevel);
|
|
1054
1068
|
} else {
|
|
1069
|
+
const valueToCheck = expectedValue ?? "";
|
|
1055
1070
|
const result = await staticAssertionRunner.validateAttribute(
|
|
1056
1071
|
target,
|
|
1057
1072
|
test.target,
|
|
1058
1073
|
test.attribute,
|
|
1059
|
-
|
|
1074
|
+
valueToCheck,
|
|
1060
1075
|
test.failureMessage,
|
|
1061
1076
|
"Static ARIA Test"
|
|
1062
1077
|
);
|
|
@@ -1174,7 +1189,33 @@ This usually means:
|
|
|
1174
1189
|
continue;
|
|
1175
1190
|
}
|
|
1176
1191
|
for (const assertion of assertions) {
|
|
1177
|
-
|
|
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);
|
|
1178
1219
|
if (result.success && result.passMessage) {
|
|
1179
1220
|
passes.push(result.passMessage);
|
|
1180
1221
|
} else if (!result.success && result.failMessage) {
|