aria-ease 6.12.0 → 6.12.2
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 +138 -28
- package/dist/cli.js +3 -3
- package/dist/{contractTestRunnerPlaywright-EWAWQVHT.js → contractTestRunnerPlaywright-47DCBO4A.js} +126 -60
- package/dist/{contractTestRunnerPlaywright-DIXP5DQ3.js → contractTestRunnerPlaywright-UJKXRXBS.js} +126 -60
- package/dist/index.cjs +256 -53
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +126 -28
- package/dist/src/combobox/index.cjs +4 -0
- package/dist/src/combobox/index.js +4 -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-AZ4QKLYT.js} +124 -60
- package/dist/src/utils/test/dsl/index.cjs +119 -25
- package/dist/src/utils/test/dsl/index.d.cts +1 -1
- package/dist/src/utils/test/dsl/index.d.ts +1 -1
- package/dist/src/utils/test/dsl/index.js +119 -25
- package/dist/src/utils/test/index.cjs +133 -28
- package/dist/src/utils/test/index.js +3 -3
- package/dist/{test-HBPCSYH5.js → test-6Y4CIQOM.js} +3 -3
- package/package.json +2 -2
package/dist/{contractTestRunnerPlaywright-EWAWQVHT.js → contractTestRunnerPlaywright-47DCBO4A.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)) {
|
|
@@ -591,13 +558,21 @@ var AssertionRunner = class {
|
|
|
591
558
|
/**
|
|
592
559
|
* Validate focus assertion
|
|
593
560
|
*/
|
|
594
|
-
async validateFocus(target, targetName, failureMessage, testDescription) {
|
|
561
|
+
async validateFocus(target, targetName, expectedFocus, failureMessage, testDescription) {
|
|
595
562
|
try {
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
563
|
+
if (expectedFocus) {
|
|
564
|
+
await (0, test_exports.expect)(target).toBeFocused({ timeout: this.timeoutMs });
|
|
565
|
+
return {
|
|
566
|
+
success: true,
|
|
567
|
+
passMessage: `${targetName} has focus as expected. Test: "${testDescription}".`
|
|
568
|
+
};
|
|
569
|
+
} else {
|
|
570
|
+
await (0, test_exports.expect)(target).not.toBeFocused({ timeout: this.timeoutMs });
|
|
571
|
+
return {
|
|
572
|
+
success: true,
|
|
573
|
+
passMessage: `${targetName} does not have focus as expected. Test: "${testDescription}".`
|
|
574
|
+
};
|
|
575
|
+
}
|
|
601
576
|
} catch {
|
|
602
577
|
const actualFocus = await this.page.evaluate(() => {
|
|
603
578
|
const focused = document.activeElement;
|
|
@@ -683,7 +658,9 @@ var AssertionRunner = class {
|
|
|
683
658
|
}
|
|
684
659
|
return { success: false, failMessage: "Missing expectedValue for toHaveValue assertion" };
|
|
685
660
|
case "toHaveFocus":
|
|
686
|
-
return this.validateFocus(target, assertion.target, assertion.failureMessage || "", testDescription);
|
|
661
|
+
return this.validateFocus(target, assertion.target, true, assertion.failureMessage || "", testDescription);
|
|
662
|
+
case "notToHaveFocus":
|
|
663
|
+
return this.validateFocus(target, assertion.target, false, assertion.failureMessage || "", testDescription);
|
|
687
664
|
case "toHaveRole":
|
|
688
665
|
if (assertion.expectedValue !== void 0) {
|
|
689
666
|
return this.validateRole(target, assertion.target, assertion.expectedValue, assertion.failureMessage || "", testDescription);
|
|
@@ -957,6 +934,52 @@ This usually means:
|
|
|
957
934
|
reporter.reportStaticTest(relDescription, "pass", void 0, relationshipLevel);
|
|
958
935
|
}
|
|
959
936
|
}
|
|
937
|
+
async function resolveExpectedValue(expectedValue, selectors, page2, context = {}) {
|
|
938
|
+
if (!expectedValue || typeof expectedValue !== "object" || !("ref" in expectedValue)) return expectedValue;
|
|
939
|
+
let refSelector;
|
|
940
|
+
if (expectedValue.ref === "relative") {
|
|
941
|
+
if (!expectedValue.relativeTarget || !context.relativeBaseSelector) return void 0;
|
|
942
|
+
const baseLocator = page2.locator(context.relativeBaseSelector);
|
|
943
|
+
const count = await baseLocator.count();
|
|
944
|
+
let idx = 0;
|
|
945
|
+
if (expectedValue.relativeTarget === "first") idx = 0;
|
|
946
|
+
else if (expectedValue.relativeTarget === "second") idx = 1;
|
|
947
|
+
else if (expectedValue.relativeTarget === "last") idx = count - 1;
|
|
948
|
+
else if (!isNaN(Number(expectedValue.relativeTarget))) idx = Number(expectedValue.relativeTarget);
|
|
949
|
+
else idx = 0;
|
|
950
|
+
if (idx < 0 || idx >= count) return void 0;
|
|
951
|
+
const relElem = baseLocator.nth(idx);
|
|
952
|
+
return await getPropertyFromLocator(relElem, expectedValue.property || expectedValue.attribute);
|
|
953
|
+
} else {
|
|
954
|
+
refSelector = selectors[expectedValue.ref];
|
|
955
|
+
if (!refSelector) throw new Error(`Selector for ref '${expectedValue.ref}' not found in contract selectors.`);
|
|
956
|
+
const refLocator = page2.locator(refSelector).first();
|
|
957
|
+
return await getPropertyFromLocator(refLocator, expectedValue.property || expectedValue.attribute);
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
async function getPropertyFromLocator(locator, property) {
|
|
961
|
+
if (!locator) return void 0;
|
|
962
|
+
if (!property || property === "id") {
|
|
963
|
+
return await locator.getAttribute("id") ?? void 0;
|
|
964
|
+
} else if (property === "class") {
|
|
965
|
+
return await locator.getAttribute("class") ?? void 0;
|
|
966
|
+
} else if (property === "textContent") {
|
|
967
|
+
return await locator.evaluate((el) => el.textContent ?? void 0);
|
|
968
|
+
} else if (property.startsWith("aria-")) {
|
|
969
|
+
return await locator.getAttribute(property) ?? void 0;
|
|
970
|
+
} else if (property.endsWith("*")) {
|
|
971
|
+
const attrs = await locator.evaluate((el) => {
|
|
972
|
+
const out = [];
|
|
973
|
+
for (const attr of Array.from(el.attributes)) {
|
|
974
|
+
if (attr.name.startsWith("aria-")) out.push(`${attr.name}=${attr.value}`);
|
|
975
|
+
}
|
|
976
|
+
return out.join(";");
|
|
977
|
+
});
|
|
978
|
+
return attrs;
|
|
979
|
+
} else {
|
|
980
|
+
return await locator.getAttribute(property) ?? void 0;
|
|
981
|
+
}
|
|
982
|
+
}
|
|
960
983
|
const staticAssertionRunner = new AssertionRunner(page, componentContract.selectors, assertionTimeoutMs);
|
|
961
984
|
for (const test of componentContract.static[0]?.assertions || []) {
|
|
962
985
|
if (test.target === "relative") continue;
|
|
@@ -999,6 +1022,22 @@ This usually means:
|
|
|
999
1022
|
}
|
|
1000
1023
|
return false;
|
|
1001
1024
|
};
|
|
1025
|
+
let expectedValue = test.expectedValue;
|
|
1026
|
+
if (test.expectedValue && typeof test.expectedValue === "object" && "ref" in test.expectedValue) {
|
|
1027
|
+
const context = {};
|
|
1028
|
+
const relTarget = test.relativeTarget;
|
|
1029
|
+
if (test.expectedValue.ref === "relative" && test.target === "relative" && relTarget) {
|
|
1030
|
+
const baseSel = componentContract.selectors[relTarget];
|
|
1031
|
+
if (!baseSel) throw new Error(`Selector for relativeTarget '${relTarget}' not found in contract selectors.`);
|
|
1032
|
+
context.relativeBaseSelector = baseSel;
|
|
1033
|
+
} else if (test.expectedValue.ref === "relative" && relTarget) {
|
|
1034
|
+
const baseSel = componentContract.selectors[relTarget];
|
|
1035
|
+
if (!baseSel) throw new Error(`Selector for relativeTarget '${relTarget}' not found in contract selectors.`);
|
|
1036
|
+
context.relativeBaseSelector = baseSel;
|
|
1037
|
+
}
|
|
1038
|
+
expectedValue = await resolveExpectedValue(test.expectedValue, componentContract.selectors, page, context);
|
|
1039
|
+
console.log("Expected value in static check", expectedValue);
|
|
1040
|
+
}
|
|
1002
1041
|
if (!test.expectedValue) {
|
|
1003
1042
|
const attributes = test.attribute.split(" | ");
|
|
1004
1043
|
let hasAny = false;
|
|
@@ -1032,16 +1071,17 @@ This usually means:
|
|
|
1032
1071
|
reporter.reportStaticTest(staticDescription, "pass", void 0, staticLevel);
|
|
1033
1072
|
}
|
|
1034
1073
|
} else {
|
|
1035
|
-
if (isRedundantCheck(targetSelector, test.attribute,
|
|
1036
|
-
passes.push(`${test.attribute}="${
|
|
1074
|
+
if (isRedundantCheck(targetSelector, test.attribute, typeof expectedValue === "string" ? expectedValue : void 0)) {
|
|
1075
|
+
passes.push(`${test.attribute}="${expectedValue}" on ${test.target} verified by selector (already present in: ${targetSelector}).`);
|
|
1037
1076
|
staticPassed += 1;
|
|
1038
1077
|
reporter.reportStaticTest(staticDescription, "pass", void 0, staticLevel);
|
|
1039
1078
|
} else {
|
|
1079
|
+
const valueToCheck = expectedValue ?? "";
|
|
1040
1080
|
const result = await staticAssertionRunner.validateAttribute(
|
|
1041
1081
|
target,
|
|
1042
1082
|
test.target,
|
|
1043
1083
|
test.attribute,
|
|
1044
|
-
|
|
1084
|
+
valueToCheck,
|
|
1045
1085
|
test.failureMessage,
|
|
1046
1086
|
"Static ARIA Test"
|
|
1047
1087
|
);
|
|
@@ -1159,7 +1199,33 @@ This usually means:
|
|
|
1159
1199
|
continue;
|
|
1160
1200
|
}
|
|
1161
1201
|
for (const assertion of assertions) {
|
|
1162
|
-
|
|
1202
|
+
let expectedValue;
|
|
1203
|
+
if (assertion.expectedValue && typeof assertion.expectedValue === "object" && "ref" in assertion.expectedValue) {
|
|
1204
|
+
if (assertion.expectedValue.ref === "relative") {
|
|
1205
|
+
const { RelativeTargetResolver: RelativeTargetResolver2 } = await import("./RelativeTargetResolver-DJAITO6D.js");
|
|
1206
|
+
const relativeSelector = componentContract.selectors.relative;
|
|
1207
|
+
if (!relativeSelector) throw new Error("Relative selector not defined in contract selectors.");
|
|
1208
|
+
const relTarget = assertion.relativeTarget || "first";
|
|
1209
|
+
const relElem = await RelativeTargetResolver2.resolve(page, relativeSelector, relTarget);
|
|
1210
|
+
if (!relElem) throw new Error(`Could not resolve relative target '${relTarget}' for expectedValue.`);
|
|
1211
|
+
const prop = assertion.expectedValue.property || assertion.expectedValue.attribute || "id";
|
|
1212
|
+
if (prop === "textContent") {
|
|
1213
|
+
expectedValue = await relElem.evaluate((el) => el.textContent ?? void 0);
|
|
1214
|
+
} else {
|
|
1215
|
+
const attr = await relElem.getAttribute(prop);
|
|
1216
|
+
expectedValue = attr === null ? void 0 : attr;
|
|
1217
|
+
}
|
|
1218
|
+
} else {
|
|
1219
|
+
expectedValue = await resolveExpectedValue(assertion.expectedValue, componentContract.selectors, page, {});
|
|
1220
|
+
}
|
|
1221
|
+
} else if (typeof assertion.expectedValue === "string" || typeof assertion.expectedValue === "undefined") {
|
|
1222
|
+
expectedValue = assertion.expectedValue;
|
|
1223
|
+
} else {
|
|
1224
|
+
expectedValue = "";
|
|
1225
|
+
}
|
|
1226
|
+
const assertionToRun = { ...assertion, expectedValue };
|
|
1227
|
+
const valueToCheck = expectedValue ?? "";
|
|
1228
|
+
const result = await assertionRunner.validate({ ...assertionToRun, expectedValue: valueToCheck }, dynamicTest.description);
|
|
1163
1229
|
if (result.success && result.passMessage) {
|
|
1164
1230
|
passes.push(result.passMessage);
|
|
1165
1231
|
} else if (!result.success && result.failMessage) {
|
package/dist/{contractTestRunnerPlaywright-DIXP5DQ3.js → contractTestRunnerPlaywright-UJKXRXBS.js}
RENAMED
|
@@ -4,10 +4,13 @@ import {
|
|
|
4
4
|
normalizeLevel,
|
|
5
5
|
normalizeStrictness,
|
|
6
6
|
resolveEnforcement
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-4DU5Z5BR.js";
|
|
8
8
|
import {
|
|
9
9
|
test_exports
|
|
10
10
|
} from "./chunk-PK5L2SAF.js";
|
|
11
|
+
import {
|
|
12
|
+
RelativeTargetResolver
|
|
13
|
+
} from "./chunk-GLT43UVH.js";
|
|
11
14
|
import "./chunk-I2KLQ2HA.js";
|
|
12
15
|
|
|
13
16
|
// src/utils/test/src/contractTestRunnerPlaywright.ts
|
|
@@ -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)) {
|
|
@@ -591,13 +558,21 @@ var AssertionRunner = class {
|
|
|
591
558
|
/**
|
|
592
559
|
* Validate focus assertion
|
|
593
560
|
*/
|
|
594
|
-
async validateFocus(target, targetName, failureMessage, testDescription) {
|
|
561
|
+
async validateFocus(target, targetName, expectedFocus, failureMessage, testDescription) {
|
|
595
562
|
try {
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
563
|
+
if (expectedFocus) {
|
|
564
|
+
await (0, test_exports.expect)(target).toBeFocused({ timeout: this.timeoutMs });
|
|
565
|
+
return {
|
|
566
|
+
success: true,
|
|
567
|
+
passMessage: `${targetName} has focus as expected. Test: "${testDescription}".`
|
|
568
|
+
};
|
|
569
|
+
} else {
|
|
570
|
+
await (0, test_exports.expect)(target).not.toBeFocused({ timeout: this.timeoutMs });
|
|
571
|
+
return {
|
|
572
|
+
success: true,
|
|
573
|
+
passMessage: `${targetName} does not have focus as expected. Test: "${testDescription}".`
|
|
574
|
+
};
|
|
575
|
+
}
|
|
601
576
|
} catch {
|
|
602
577
|
const actualFocus = await this.page.evaluate(() => {
|
|
603
578
|
const focused = document.activeElement;
|
|
@@ -683,7 +658,9 @@ var AssertionRunner = class {
|
|
|
683
658
|
}
|
|
684
659
|
return { success: false, failMessage: "Missing expectedValue for toHaveValue assertion" };
|
|
685
660
|
case "toHaveFocus":
|
|
686
|
-
return this.validateFocus(target, assertion.target, assertion.failureMessage || "", testDescription);
|
|
661
|
+
return this.validateFocus(target, assertion.target, true, assertion.failureMessage || "", testDescription);
|
|
662
|
+
case "notToHaveFocus":
|
|
663
|
+
return this.validateFocus(target, assertion.target, false, assertion.failureMessage || "", testDescription);
|
|
687
664
|
case "toHaveRole":
|
|
688
665
|
if (assertion.expectedValue !== void 0) {
|
|
689
666
|
return this.validateRole(target, assertion.target, assertion.expectedValue, assertion.failureMessage || "", testDescription);
|
|
@@ -957,6 +934,52 @@ This usually means:
|
|
|
957
934
|
reporter.reportStaticTest(relDescription, "pass", void 0, relationshipLevel);
|
|
958
935
|
}
|
|
959
936
|
}
|
|
937
|
+
async function resolveExpectedValue(expectedValue, selectors, page2, context = {}) {
|
|
938
|
+
if (!expectedValue || typeof expectedValue !== "object" || !("ref" in expectedValue)) return expectedValue;
|
|
939
|
+
let refSelector;
|
|
940
|
+
if (expectedValue.ref === "relative") {
|
|
941
|
+
if (!expectedValue.relativeTarget || !context.relativeBaseSelector) return void 0;
|
|
942
|
+
const baseLocator = page2.locator(context.relativeBaseSelector);
|
|
943
|
+
const count = await baseLocator.count();
|
|
944
|
+
let idx = 0;
|
|
945
|
+
if (expectedValue.relativeTarget === "first") idx = 0;
|
|
946
|
+
else if (expectedValue.relativeTarget === "second") idx = 1;
|
|
947
|
+
else if (expectedValue.relativeTarget === "last") idx = count - 1;
|
|
948
|
+
else if (!isNaN(Number(expectedValue.relativeTarget))) idx = Number(expectedValue.relativeTarget);
|
|
949
|
+
else idx = 0;
|
|
950
|
+
if (idx < 0 || idx >= count) return void 0;
|
|
951
|
+
const relElem = baseLocator.nth(idx);
|
|
952
|
+
return await getPropertyFromLocator(relElem, expectedValue.property || expectedValue.attribute);
|
|
953
|
+
} else {
|
|
954
|
+
refSelector = selectors[expectedValue.ref];
|
|
955
|
+
if (!refSelector) throw new Error(`Selector for ref '${expectedValue.ref}' not found in contract selectors.`);
|
|
956
|
+
const refLocator = page2.locator(refSelector).first();
|
|
957
|
+
return await getPropertyFromLocator(refLocator, expectedValue.property || expectedValue.attribute);
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
async function getPropertyFromLocator(locator, property) {
|
|
961
|
+
if (!locator) return void 0;
|
|
962
|
+
if (!property || property === "id") {
|
|
963
|
+
return await locator.getAttribute("id") ?? void 0;
|
|
964
|
+
} else if (property === "class") {
|
|
965
|
+
return await locator.getAttribute("class") ?? void 0;
|
|
966
|
+
} else if (property === "textContent") {
|
|
967
|
+
return await locator.evaluate((el) => el.textContent ?? void 0);
|
|
968
|
+
} else if (property.startsWith("aria-")) {
|
|
969
|
+
return await locator.getAttribute(property) ?? void 0;
|
|
970
|
+
} else if (property.endsWith("*")) {
|
|
971
|
+
const attrs = await locator.evaluate((el) => {
|
|
972
|
+
const out = [];
|
|
973
|
+
for (const attr of Array.from(el.attributes)) {
|
|
974
|
+
if (attr.name.startsWith("aria-")) out.push(`${attr.name}=${attr.value}`);
|
|
975
|
+
}
|
|
976
|
+
return out.join(";");
|
|
977
|
+
});
|
|
978
|
+
return attrs;
|
|
979
|
+
} else {
|
|
980
|
+
return await locator.getAttribute(property) ?? void 0;
|
|
981
|
+
}
|
|
982
|
+
}
|
|
960
983
|
const staticAssertionRunner = new AssertionRunner(page, componentContract.selectors, assertionTimeoutMs);
|
|
961
984
|
for (const test of componentContract.static[0]?.assertions || []) {
|
|
962
985
|
if (test.target === "relative") continue;
|
|
@@ -999,6 +1022,22 @@ This usually means:
|
|
|
999
1022
|
}
|
|
1000
1023
|
return false;
|
|
1001
1024
|
};
|
|
1025
|
+
let expectedValue = test.expectedValue;
|
|
1026
|
+
if (test.expectedValue && typeof test.expectedValue === "object" && "ref" in test.expectedValue) {
|
|
1027
|
+
const context = {};
|
|
1028
|
+
const relTarget = test.relativeTarget;
|
|
1029
|
+
if (test.expectedValue.ref === "relative" && test.target === "relative" && relTarget) {
|
|
1030
|
+
const baseSel = componentContract.selectors[relTarget];
|
|
1031
|
+
if (!baseSel) throw new Error(`Selector for relativeTarget '${relTarget}' not found in contract selectors.`);
|
|
1032
|
+
context.relativeBaseSelector = baseSel;
|
|
1033
|
+
} else if (test.expectedValue.ref === "relative" && relTarget) {
|
|
1034
|
+
const baseSel = componentContract.selectors[relTarget];
|
|
1035
|
+
if (!baseSel) throw new Error(`Selector for relativeTarget '${relTarget}' not found in contract selectors.`);
|
|
1036
|
+
context.relativeBaseSelector = baseSel;
|
|
1037
|
+
}
|
|
1038
|
+
expectedValue = await resolveExpectedValue(test.expectedValue, componentContract.selectors, page, context);
|
|
1039
|
+
console.log("Expected value in static check", expectedValue);
|
|
1040
|
+
}
|
|
1002
1041
|
if (!test.expectedValue) {
|
|
1003
1042
|
const attributes = test.attribute.split(" | ");
|
|
1004
1043
|
let hasAny = false;
|
|
@@ -1032,16 +1071,17 @@ This usually means:
|
|
|
1032
1071
|
reporter.reportStaticTest(staticDescription, "pass", void 0, staticLevel);
|
|
1033
1072
|
}
|
|
1034
1073
|
} else {
|
|
1035
|
-
if (isRedundantCheck(targetSelector, test.attribute,
|
|
1036
|
-
passes.push(`${test.attribute}="${
|
|
1074
|
+
if (isRedundantCheck(targetSelector, test.attribute, typeof expectedValue === "string" ? expectedValue : void 0)) {
|
|
1075
|
+
passes.push(`${test.attribute}="${expectedValue}" on ${test.target} verified by selector (already present in: ${targetSelector}).`);
|
|
1037
1076
|
staticPassed += 1;
|
|
1038
1077
|
reporter.reportStaticTest(staticDescription, "pass", void 0, staticLevel);
|
|
1039
1078
|
} else {
|
|
1079
|
+
const valueToCheck = expectedValue ?? "";
|
|
1040
1080
|
const result = await staticAssertionRunner.validateAttribute(
|
|
1041
1081
|
target,
|
|
1042
1082
|
test.target,
|
|
1043
1083
|
test.attribute,
|
|
1044
|
-
|
|
1084
|
+
valueToCheck,
|
|
1045
1085
|
test.failureMessage,
|
|
1046
1086
|
"Static ARIA Test"
|
|
1047
1087
|
);
|
|
@@ -1159,7 +1199,33 @@ This usually means:
|
|
|
1159
1199
|
continue;
|
|
1160
1200
|
}
|
|
1161
1201
|
for (const assertion of assertions) {
|
|
1162
|
-
|
|
1202
|
+
let expectedValue;
|
|
1203
|
+
if (assertion.expectedValue && typeof assertion.expectedValue === "object" && "ref" in assertion.expectedValue) {
|
|
1204
|
+
if (assertion.expectedValue.ref === "relative") {
|
|
1205
|
+
const { RelativeTargetResolver: RelativeTargetResolver2 } = await import("./RelativeTargetResolver-DJAITO6D.js");
|
|
1206
|
+
const relativeSelector = componentContract.selectors.relative;
|
|
1207
|
+
if (!relativeSelector) throw new Error("Relative selector not defined in contract selectors.");
|
|
1208
|
+
const relTarget = assertion.relativeTarget || "first";
|
|
1209
|
+
const relElem = await RelativeTargetResolver2.resolve(page, relativeSelector, relTarget);
|
|
1210
|
+
if (!relElem) throw new Error(`Could not resolve relative target '${relTarget}' for expectedValue.`);
|
|
1211
|
+
const prop = assertion.expectedValue.property || assertion.expectedValue.attribute || "id";
|
|
1212
|
+
if (prop === "textContent") {
|
|
1213
|
+
expectedValue = await relElem.evaluate((el) => el.textContent ?? void 0);
|
|
1214
|
+
} else {
|
|
1215
|
+
const attr = await relElem.getAttribute(prop);
|
|
1216
|
+
expectedValue = attr === null ? void 0 : attr;
|
|
1217
|
+
}
|
|
1218
|
+
} else {
|
|
1219
|
+
expectedValue = await resolveExpectedValue(assertion.expectedValue, componentContract.selectors, page, {});
|
|
1220
|
+
}
|
|
1221
|
+
} else if (typeof assertion.expectedValue === "string" || typeof assertion.expectedValue === "undefined") {
|
|
1222
|
+
expectedValue = assertion.expectedValue;
|
|
1223
|
+
} else {
|
|
1224
|
+
expectedValue = "";
|
|
1225
|
+
}
|
|
1226
|
+
const assertionToRun = { ...assertion, expectedValue };
|
|
1227
|
+
const valueToCheck = expectedValue ?? "";
|
|
1228
|
+
const result = await assertionRunner.validate({ ...assertionToRun, expectedValue: valueToCheck }, dynamicTest.description);
|
|
1163
1229
|
if (result.success && result.passMessage) {
|
|
1164
1230
|
passes.push(result.passMessage);
|
|
1165
1231
|
} else if (!result.success && result.failMessage) {
|