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.
Files changed (29) hide show
  1. package/README.md +2 -2
  2. package/dist/{ComboboxComponentStrategy-OGRVZXAF.js → ComboboxComponentStrategy-DU342VMB.js} +7 -7
  3. package/dist/RelativeTargetResolver-DJAITO6D.js +7 -0
  4. package/dist/{audit-RM6TCZ5C.js → audit-JYEPKLHR.js} +5 -0
  5. package/dist/{chunk-FZ7GMIJB.js → chunk-4DU5Z5BR.js} +0 -2
  6. package/dist/chunk-GLT43UVH.js +43 -0
  7. package/dist/cli.cjs +138 -28
  8. package/dist/cli.js +3 -3
  9. package/dist/{contractTestRunnerPlaywright-EWAWQVHT.js → contractTestRunnerPlaywright-47DCBO4A.js} +126 -60
  10. package/dist/{contractTestRunnerPlaywright-DIXP5DQ3.js → contractTestRunnerPlaywright-UJKXRXBS.js} +126 -60
  11. package/dist/index.cjs +256 -53
  12. package/dist/index.d.cts +1 -1
  13. package/dist/index.d.ts +1 -1
  14. package/dist/index.js +126 -28
  15. package/dist/src/combobox/index.cjs +4 -0
  16. package/dist/src/combobox/index.js +4 -0
  17. package/dist/src/utils/test/{ComboboxComponentStrategy-5AECQSRN.js → ComboboxComponentStrategy-XKQ72RFD.js} +7 -7
  18. package/dist/src/utils/test/RelativeTargetResolver-G2XDN2VV.js +1 -0
  19. package/dist/src/utils/test/{chunk-FZ7GMIJB.js → chunk-4DU5Z5BR.js} +0 -2
  20. package/dist/src/utils/test/chunk-GLT43UVH.js +41 -0
  21. package/dist/src/utils/test/{contractTestRunnerPlaywright-CIZOXYRW.js → contractTestRunnerPlaywright-AZ4QKLYT.js} +124 -60
  22. package/dist/src/utils/test/dsl/index.cjs +119 -25
  23. package/dist/src/utils/test/dsl/index.d.cts +1 -1
  24. package/dist/src/utils/test/dsl/index.d.ts +1 -1
  25. package/dist/src/utils/test/dsl/index.js +119 -25
  26. package/dist/src/utils/test/index.cjs +133 -28
  27. package/dist/src/utils/test/index.js +3 -3
  28. package/dist/{test-HBPCSYH5.js → test-6Y4CIQOM.js} +3 -3
  29. package/package.json +2 -2
@@ -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-FZ7GMIJB.js";
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-OGRVZXAF.js").then(
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 inputSelector = this.selectors.input;
232
- if (!inputSelector) {
233
- return { success: false, error: `Input selector not defined for virtual focus.` };
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 input = this.page.locator(inputSelector).first();
236
- const exists = await input.count();
198
+ const main = this.page.locator(mainSelector).first();
199
+ const exists = await main.count();
237
200
  if (!exists) {
238
- return { success: false, error: `Input element not found for virtual focus.` };
201
+ return { success: false, error: `Main element not found for virtual focus.` };
239
202
  }
240
- await input.evaluate((el, id) => {
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
- await (0, test_exports.expect)(target).toBeFocused({ timeout: this.timeoutMs });
597
- return {
598
- success: true,
599
- passMessage: `${targetName} has focus as expected. Test: "${testDescription}".`
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, test.expectedValue)) {
1036
- passes.push(`${test.attribute}="${test.expectedValue}" on ${test.target} verified by selector (already present in: ${targetSelector}).`);
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
- test.expectedValue,
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
- const result = await assertionRunner.validate(assertion, dynamicTest.description);
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) {
@@ -4,10 +4,13 @@ import {
4
4
  normalizeLevel,
5
5
  normalizeStrictness,
6
6
  resolveEnforcement
7
- } from "./chunk-FZ7GMIJB.js";
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-OGRVZXAF.js").then(
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 inputSelector = this.selectors.input;
232
- if (!inputSelector) {
233
- return { success: false, error: `Input selector not defined for virtual focus.` };
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 input = this.page.locator(inputSelector).first();
236
- const exists = await input.count();
198
+ const main = this.page.locator(mainSelector).first();
199
+ const exists = await main.count();
237
200
  if (!exists) {
238
- return { success: false, error: `Input element not found for virtual focus.` };
201
+ return { success: false, error: `Main element not found for virtual focus.` };
239
202
  }
240
- await input.evaluate((el, id) => {
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
- await (0, test_exports.expect)(target).toBeFocused({ timeout: this.timeoutMs });
597
- return {
598
- success: true,
599
- passMessage: `${targetName} has focus as expected. Test: "${testDescription}".`
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, test.expectedValue)) {
1036
- passes.push(`${test.attribute}="${test.expectedValue}" on ${test.target} verified by selector (already present in: ${targetSelector}).`);
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
- test.expectedValue,
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
- const result = await assertionRunner.validate(assertion, dynamicTest.description);
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) {