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.
Files changed (37) hide show
  1. package/README.md +3 -3
  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-XERMSYEH.js → chunk-4DU5Z5BR.js} +0 -23
  6. package/dist/{chunk-NI3MQCAS.js → chunk-GJGUY643.js} +2 -2
  7. package/dist/chunk-GLT43UVH.js +43 -0
  8. package/dist/cli.cjs +147 -84
  9. package/dist/cli.js +5 -5
  10. package/dist/{configLoader-DWHOHXHL.js → configLoader-Q7N5XV4P.js} +2 -2
  11. package/dist/{configLoader-UJZHQBYS.js → configLoader-REHK3S3Q.js} +1 -1
  12. package/dist/{contractTestRunnerPlaywright-QDXSK3FE.js → contractTestRunnerPlaywright-H24LQ45R.js} +113 -72
  13. package/dist/{contractTestRunnerPlaywright-WNWQYSXZ.js → contractTestRunnerPlaywright-NL3JNJYH.js} +113 -72
  14. package/dist/index.cjs +248 -112
  15. package/dist/index.d.cts +3 -0
  16. package/dist/index.d.ts +3 -0
  17. package/dist/index.js +124 -41
  18. package/dist/src/combobox/index.cjs +1 -0
  19. package/dist/src/combobox/index.js +1 -0
  20. package/dist/src/utils/test/{ComboboxComponentStrategy-5AECQSRN.js → ComboboxComponentStrategy-XKQ72RFD.js} +7 -7
  21. package/dist/src/utils/test/RelativeTargetResolver-G2XDN2VV.js +1 -0
  22. package/dist/src/utils/test/{chunk-XERMSYEH.js → chunk-4DU5Z5BR.js} +1 -23
  23. package/dist/src/utils/test/chunk-GLT43UVH.js +41 -0
  24. package/dist/src/utils/test/{configLoader-SHJSRG2A.js → configLoader-NA7IBCS3.js} +2 -2
  25. package/dist/src/utils/test/{contractTestRunnerPlaywright-Z2AHXSNM.js → contractTestRunnerPlaywright-5FT6K2WN.js} +111 -71
  26. package/dist/src/utils/test/dsl/index.cjs +106 -29
  27. package/dist/src/utils/test/dsl/index.d.cts +3 -0
  28. package/dist/src/utils/test/dsl/index.d.ts +3 -0
  29. package/dist/src/utils/test/dsl/index.js +106 -29
  30. package/dist/src/utils/test/index.cjs +135 -76
  31. package/dist/src/utils/test/index.js +17 -11
  32. package/dist/{test-O3J4ZPQR.js → test-FYSJXQWO.js} +17 -12
  33. package/package.json +3 -4
  34. package/dist/src/utils/test/aria-contracts/accordion/accordion.contract.json +0 -290
  35. package/dist/src/utils/test/aria-contracts/combobox/combobox.listbox.contract.json +0 -463
  36. package/dist/src/utils/test/aria-contracts/menu/menu.contract.json +0 -562
  37. 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-NI3MQCAS.js";
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-RM6TCZ5C.js");
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-RM6TCZ5C.js");
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-O3J4ZPQR.js");
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-UJZHQBYS.js");
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.path !== void 0 && typeof comp.path !== "string") {
74
- errors.push(`test.components[${idx}].path must be a string when provided`);
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`);
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  loadConfig
3
- } from "./chunk-NI3MQCAS.js";
3
+ } from "./chunk-GJGUY643.js";
4
4
  import "./chunk-I2KLQ2HA.js";
5
5
  export {
6
6
  loadConfig
@@ -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-XERMSYEH.js";
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-OGRVZXAF.js").then(
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
- let contractPath = typedComponentConfig?.path;
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.trigger || selectors.input || selectors.tablist || selectors.container;
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 inputSelector = this.selectors.input;
243
- if (!inputSelector) {
244
- 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.` };
245
197
  }
246
- const input = this.page.locator(inputSelector).first();
247
- const exists = await input.count();
198
+ const main = this.page.locator(mainSelector).first();
199
+ const exists = await main.count();
248
200
  if (!exists) {
249
- return { success: false, error: `Input element not found for virtual focus.` };
201
+ return { success: false, error: `Main element not found for virtual focus.` };
250
202
  }
251
- await input.evaluate((el, id) => {
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?.path;
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
- let contractPath = componentConfig?.path;
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, test.expectedValue)) {
1051
- passes.push(`${test.attribute}="${test.expectedValue}" on ${test.target} verified by selector (already present in: ${targetSelector}).`);
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
- test.expectedValue,
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
- const result = await assertionRunner.validate(assertion, dynamicTest.description);
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) {
@@ -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-XERMSYEH.js";
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-OGRVZXAF.js").then(
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
- let contractPath = typedComponentConfig?.path;
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.trigger || selectors.input || selectors.tablist || selectors.container;
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 inputSelector = this.selectors.input;
243
- if (!inputSelector) {
244
- 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.` };
245
197
  }
246
- const input = this.page.locator(inputSelector).first();
247
- const exists = await input.count();
198
+ const main = this.page.locator(mainSelector).first();
199
+ const exists = await main.count();
248
200
  if (!exists) {
249
- return { success: false, error: `Input element not found for virtual focus.` };
201
+ return { success: false, error: `Main element not found for virtual focus.` };
250
202
  }
251
- await input.evaluate((el, id) => {
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?.path;
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
- let contractPath = componentConfig?.path;
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, test.expectedValue)) {
1051
- passes.push(`${test.attribute}="${test.expectedValue}" on ${test.target} verified by selector (already present in: ${targetSelector}).`);
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
- test.expectedValue,
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
- const result = await assertionRunner.validate(assertion, dynamicTest.description);
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) {