aria-ease 6.9.0 → 6.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/bin/{buildContracts-GBOY7UXG.js → buildContracts-S22V7AGV.js} +28 -0
- package/bin/{chunk-LMSKLN5O.js → chunk-NI3MQCAS.js} +34 -0
- package/bin/cli.cjs +235 -20
- package/bin/cli.js +4 -4
- package/bin/{configLoader-Q6A4JLKW.js → configLoader-UJZHQBYS.js} +1 -1
- package/{dist/contractTestRunnerPlaywright-XBWJZMR3.js → bin/contractTestRunnerPlaywright-QDXSK3FE.js} +173 -20
- package/bin/{test-OND56UUL.js → test-O3J4ZPQR.js} +2 -2
- package/dist/{configLoader-WTGJAP4Z.js → configLoader-DWHOHXHL.js} +34 -0
- package/{bin/contractTestRunnerPlaywright-ZZNWDUYP.js → dist/contractTestRunnerPlaywright-WNWQYSXZ.js} +173 -20
- package/dist/index.cjs +506 -312
- package/dist/index.d.cts +54 -54
- package/dist/index.d.ts +54 -54
- package/dist/index.js +298 -291
- package/dist/src/{Types.d-DYfYR3Vc.d.cts → Types.d-yGC2bBaB.d.cts} +1 -1
- package/dist/src/{Types.d-DYfYR3Vc.d.ts → Types.d-yGC2bBaB.d.ts} +1 -1
- package/dist/src/accordion/index.d.cts +1 -1
- package/dist/src/accordion/index.d.ts +1 -1
- package/dist/src/block/index.d.cts +1 -1
- package/dist/src/block/index.d.ts +1 -1
- package/dist/src/checkbox/index.d.cts +1 -1
- package/dist/src/checkbox/index.d.ts +1 -1
- package/dist/src/combobox/index.cjs +21 -7
- package/dist/src/combobox/index.d.cts +1 -1
- package/dist/src/combobox/index.d.ts +1 -1
- package/dist/src/combobox/index.js +21 -7
- package/dist/src/menu/index.d.cts +1 -1
- package/dist/src/menu/index.d.ts +1 -1
- package/dist/src/radio/index.d.cts +1 -1
- package/dist/src/radio/index.d.ts +1 -1
- package/dist/src/tabs/index.d.cts +1 -1
- package/dist/src/tabs/index.d.ts +1 -1
- package/dist/src/toggle/index.d.cts +1 -1
- package/dist/src/toggle/index.d.ts +1 -1
- package/dist/src/utils/test/{configLoader-YE2CYGDG.js → configLoader-SHJSRG2A.js} +34 -0
- package/dist/src/utils/test/{contractTestRunnerPlaywright-LC5OAVXB.js → contractTestRunnerPlaywright-Z2AHXSNM.js} +173 -20
- package/dist/src/utils/test/dsl/index.cjs +313 -0
- package/dist/src/utils/test/dsl/index.d.cts +136 -0
- package/dist/src/utils/test/dsl/index.d.ts +136 -0
- package/dist/src/utils/test/dsl/index.js +311 -0
- package/dist/src/utils/test/index.cjs +207 -20
- package/dist/src/utils/test/index.js +2 -2
- package/package.json +7 -6
|
@@ -230,8 +230,41 @@ var ActionExecutor = class {
|
|
|
230
230
|
/**
|
|
231
231
|
* Execute focus action
|
|
232
232
|
*/
|
|
233
|
-
|
|
233
|
+
/**
|
|
234
|
+
* Execute focus action (supports absolute, relative, and virtual focus)
|
|
235
|
+
* @param target - selector key (e.g. "input", "button", "relative", or "virtual")
|
|
236
|
+
* @param relativeTarget - for relative focus (e.g. "first", "last")
|
|
237
|
+
* @param virtualId - for virtual focus (aria-activedescendant value)
|
|
238
|
+
*/
|
|
239
|
+
async focus(target, relativeTarget, virtualId) {
|
|
234
240
|
try {
|
|
241
|
+
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.` };
|
|
245
|
+
}
|
|
246
|
+
const input = this.page.locator(inputSelector).first();
|
|
247
|
+
const exists = await input.count();
|
|
248
|
+
if (!exists) {
|
|
249
|
+
return { success: false, error: `Input element not found for virtual focus.` };
|
|
250
|
+
}
|
|
251
|
+
await input.evaluate((el, id) => {
|
|
252
|
+
el.setAttribute("aria-activedescendant", id);
|
|
253
|
+
}, virtualId);
|
|
254
|
+
return { success: true };
|
|
255
|
+
}
|
|
256
|
+
if (target === "relative" && relativeTarget) {
|
|
257
|
+
const relativeSelector = this.selectors.relative;
|
|
258
|
+
if (!relativeSelector) {
|
|
259
|
+
return { success: false, error: `Relative selector not defined for focus action.` };
|
|
260
|
+
}
|
|
261
|
+
const element = await RelativeTargetResolver.resolve(this.page, relativeSelector, relativeTarget);
|
|
262
|
+
if (!element) {
|
|
263
|
+
return { success: false, error: `Could not resolve relative target ${relativeTarget} for focus.` };
|
|
264
|
+
}
|
|
265
|
+
await element.focus({ timeout: this.timeoutMs });
|
|
266
|
+
return { success: true };
|
|
267
|
+
}
|
|
235
268
|
const selector = this.selectors[target];
|
|
236
269
|
if (!selector) {
|
|
237
270
|
return { success: false, error: `Selector for focus target ${target} not found.` };
|
|
@@ -424,10 +457,10 @@ var AssertionRunner = class {
|
|
|
424
457
|
/**
|
|
425
458
|
* Resolve the target element for an assertion
|
|
426
459
|
*/
|
|
427
|
-
async resolveTarget(targetName, relativeTarget) {
|
|
460
|
+
async resolveTarget(targetName, relativeTarget, selectorKey) {
|
|
428
461
|
try {
|
|
429
462
|
if (targetName === "relative") {
|
|
430
|
-
const relativeSelector = this.selectors.relative;
|
|
463
|
+
const relativeSelector = selectorKey ? this.selectors[selectorKey] : this.selectors.relative;
|
|
431
464
|
if (!relativeSelector) {
|
|
432
465
|
return { target: null, error: "Relative selector is not defined in the contract." };
|
|
433
466
|
}
|
|
@@ -614,10 +647,30 @@ var AssertionRunner = class {
|
|
|
614
647
|
failMessage: `CRITICAL: Browser/page closed before completing all tests. Increase test timeout or reduce test complexity.`
|
|
615
648
|
};
|
|
616
649
|
}
|
|
617
|
-
const { target, error } = await this.resolveTarget(
|
|
650
|
+
const { target, error } = await this.resolveTarget(
|
|
651
|
+
assertion.target,
|
|
652
|
+
assertion.relativeTarget || assertion.expectedValue,
|
|
653
|
+
assertion.selectorKey
|
|
654
|
+
);
|
|
618
655
|
if (error || !target) {
|
|
619
656
|
return { success: false, failMessage: error || `Target ${assertion.target} not found.`, target: null };
|
|
620
657
|
}
|
|
658
|
+
if (assertion.target === "input" && assertion.attribute === "aria-activedescendant" && assertion.expectedValue === "!empty" && assertion.relativeTarget && assertion.selectorKey) {
|
|
659
|
+
const optionLocator = await RelativeTargetResolver.resolve(this.page, this.selectors[assertion.selectorKey], assertion.relativeTarget);
|
|
660
|
+
const optionId = optionLocator ? await optionLocator.getAttribute("id") : null;
|
|
661
|
+
const inputId = await target.getAttribute("aria-activedescendant");
|
|
662
|
+
if (optionId && inputId === optionId) {
|
|
663
|
+
return {
|
|
664
|
+
success: true,
|
|
665
|
+
passMessage: `input[aria-activedescendant] matches id of ${assertion.relativeTarget}(${assertion.selectorKey}). Test: "${testDescription}".`
|
|
666
|
+
};
|
|
667
|
+
} else {
|
|
668
|
+
return {
|
|
669
|
+
success: false,
|
|
670
|
+
failMessage: `input[aria-activedescendant] should match id of ${assertion.relativeTarget}(${assertion.selectorKey}), found "${inputId}".`
|
|
671
|
+
};
|
|
672
|
+
}
|
|
673
|
+
}
|
|
621
674
|
switch (assertion.assertion) {
|
|
622
675
|
case "toBeVisible":
|
|
623
676
|
return this.validateVisibility(target, assertion.target, true, assertion.failureMessage || "", testDescription);
|
|
@@ -658,8 +711,43 @@ async function runContractTestsPlaywright(componentName, url, strictness, config
|
|
|
658
711
|
const componentConfig = config?.test?.components?.find((c) => c.name === componentName);
|
|
659
712
|
const isCustomContract = !!componentConfig?.path;
|
|
660
713
|
const reporter = new ContractReporter(true, isCustomContract);
|
|
661
|
-
const
|
|
662
|
-
|
|
714
|
+
const defaultTimeouts = {
|
|
715
|
+
actionTimeoutMs: 400,
|
|
716
|
+
assertionTimeoutMs: 400,
|
|
717
|
+
navigationTimeoutMs: 3e4,
|
|
718
|
+
componentReadyTimeoutMs: 5e3
|
|
719
|
+
};
|
|
720
|
+
const globalDisableTimeouts = config?.test?.disableTimeouts === true;
|
|
721
|
+
const componentDisableTimeouts = componentConfig?.disableTimeouts === true;
|
|
722
|
+
const disableTimeouts = componentDisableTimeouts || globalDisableTimeouts;
|
|
723
|
+
const resolveTimeout = (componentValue, globalValue, fallback) => {
|
|
724
|
+
if (disableTimeouts) return 0;
|
|
725
|
+
const value = componentValue ?? globalValue;
|
|
726
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
|
|
727
|
+
return fallback;
|
|
728
|
+
}
|
|
729
|
+
return value;
|
|
730
|
+
};
|
|
731
|
+
const actionTimeoutMs = resolveTimeout(
|
|
732
|
+
componentConfig?.actionTimeoutMs,
|
|
733
|
+
config?.test?.actionTimeoutMs,
|
|
734
|
+
defaultTimeouts.actionTimeoutMs
|
|
735
|
+
);
|
|
736
|
+
const assertionTimeoutMs = resolveTimeout(
|
|
737
|
+
componentConfig?.assertionTimeoutMs,
|
|
738
|
+
config?.test?.assertionTimeoutMs,
|
|
739
|
+
defaultTimeouts.assertionTimeoutMs
|
|
740
|
+
);
|
|
741
|
+
const navigationTimeoutMs = resolveTimeout(
|
|
742
|
+
componentConfig?.navigationTimeoutMs,
|
|
743
|
+
config?.test?.navigationTimeoutMs,
|
|
744
|
+
defaultTimeouts.navigationTimeoutMs
|
|
745
|
+
);
|
|
746
|
+
const componentReadyTimeoutMs = resolveTimeout(
|
|
747
|
+
componentConfig?.componentReadyTimeoutMs,
|
|
748
|
+
config?.test?.componentReadyTimeoutMs,
|
|
749
|
+
defaultTimeouts.componentReadyTimeoutMs
|
|
750
|
+
);
|
|
663
751
|
const strictnessMode = normalizeStrictness(strictness);
|
|
664
752
|
let contractPath = componentConfig?.path;
|
|
665
753
|
if (!contractPath) {
|
|
@@ -717,7 +805,7 @@ async function runContractTestsPlaywright(componentName, url, strictness, config
|
|
|
717
805
|
try {
|
|
718
806
|
await page.goto(url, {
|
|
719
807
|
waitUntil: "domcontentloaded",
|
|
720
|
-
timeout:
|
|
808
|
+
timeout: navigationTimeoutMs
|
|
721
809
|
});
|
|
722
810
|
} catch (error) {
|
|
723
811
|
throw new Error(
|
|
@@ -735,7 +823,7 @@ async function runContractTestsPlaywright(componentName, url, strictness, config
|
|
|
735
823
|
throw new Error(`CRITICAL: No selector found in contract for ${componentName}`);
|
|
736
824
|
}
|
|
737
825
|
try {
|
|
738
|
-
await page.locator(mainSelector).first().waitFor({ state: "attached", timeout:
|
|
826
|
+
await page.locator(mainSelector).first().waitFor({ state: "attached", timeout: componentReadyTimeoutMs });
|
|
739
827
|
} catch (error) {
|
|
740
828
|
throw new Error(
|
|
741
829
|
`
|
|
@@ -749,18 +837,26 @@ This usually means:
|
|
|
749
837
|
}
|
|
750
838
|
reporter.start(componentName, totalTests, apgUrl);
|
|
751
839
|
if (componentName === "menu" && componentContract.selectors.trigger) {
|
|
752
|
-
await page.locator(componentContract.selectors.trigger).first().waitFor({
|
|
753
|
-
state: "attached",
|
|
754
|
-
timeout: 5e3
|
|
755
|
-
}).catch(() => {
|
|
840
|
+
await page.locator(componentContract.selectors.trigger).first().waitFor({ state: "attached", timeout: componentReadyTimeoutMs }).catch(() => {
|
|
756
841
|
});
|
|
757
842
|
}
|
|
758
843
|
const hasSubmenuCapability = componentName === "menu" && !!componentContract.selectors.submenuTrigger ? await page.locator(componentContract.selectors.submenuTrigger).count() > 0 : false;
|
|
844
|
+
const isSubmenuRelation = (rel) => rel.type === "aria-reference" && [rel.from, rel.to].some((name) => ["submenu", "submenuTrigger", "submenuItems"].includes(name || "")) || rel.type === "contains" && [rel.parent, rel.child].some((name) => ["submenu", "submenuTrigger", "submenuItems"].includes(name || ""));
|
|
759
845
|
let staticPassed = 0;
|
|
760
846
|
let staticFailed = 0;
|
|
761
847
|
let staticWarnings = 0;
|
|
762
848
|
for (const rel of componentContract.relationships || []) {
|
|
763
849
|
const relationshipLevel = normalizeLevel(rel.level);
|
|
850
|
+
if (componentName === "menu" && !hasSubmenuCapability) {
|
|
851
|
+
const involvesSubmenu = isSubmenuRelation(rel);
|
|
852
|
+
if (involvesSubmenu) {
|
|
853
|
+
const relDescription = rel.type === "aria-reference" ? `${rel.from}.${rel.attribute} references ${rel.to}` : `${rel.parent} contains ${rel.child}`;
|
|
854
|
+
const skipMessage = `Skipping submenu relationship assertion: no submenu capability detected in rendered component.`;
|
|
855
|
+
skipped.push(skipMessage);
|
|
856
|
+
reporter.reportStaticTest(relDescription, "skip", skipMessage, relationshipLevel);
|
|
857
|
+
continue;
|
|
858
|
+
}
|
|
859
|
+
}
|
|
764
860
|
if (rel.type === "aria-reference") {
|
|
765
861
|
const relDescription = `${rel.from}.${rel.attribute} references ${rel.to}`;
|
|
766
862
|
const fromSelector = componentContract.selectors[rel.from];
|
|
@@ -780,6 +876,12 @@ This usually means:
|
|
|
780
876
|
const fromExists = await fromTarget.count() > 0;
|
|
781
877
|
const toExists = await toTarget.count() > 0;
|
|
782
878
|
if (!fromExists || !toExists) {
|
|
879
|
+
if (componentName === "menu" && isSubmenuRelation(rel)) {
|
|
880
|
+
const skipMessage = "Skipping submenu relationship assertion in static phase: submenu elements are not present until submenu is opened.";
|
|
881
|
+
skipped.push(skipMessage);
|
|
882
|
+
reporter.reportStaticTest(relDescription, "skip", skipMessage, relationshipLevel);
|
|
883
|
+
continue;
|
|
884
|
+
}
|
|
783
885
|
const outcome = classifyFailure(
|
|
784
886
|
`Relationship target not found: ${!fromExists ? rel.from : rel.to}.`,
|
|
785
887
|
rel.level
|
|
@@ -835,6 +937,12 @@ This usually means:
|
|
|
835
937
|
const parent = page.locator(parentSelector).first();
|
|
836
938
|
const parentExists = await parent.count() > 0;
|
|
837
939
|
if (!parentExists) {
|
|
940
|
+
if (componentName === "menu" && isSubmenuRelation(rel)) {
|
|
941
|
+
const skipMessage = "Skipping submenu relationship assertion in static phase: submenu container is not present until submenu is opened.";
|
|
942
|
+
skipped.push(skipMessage);
|
|
943
|
+
reporter.reportStaticTest(relDescription, "skip", skipMessage, relationshipLevel);
|
|
944
|
+
continue;
|
|
945
|
+
}
|
|
838
946
|
const outcome = classifyFailure(`Relationship parent target not found: ${rel.parent}.`, rel.level);
|
|
839
947
|
if (outcome.status === "fail") staticFailed += 1;
|
|
840
948
|
if (outcome.status === "warn") staticWarnings += 1;
|
|
@@ -844,6 +952,12 @@ This usually means:
|
|
|
844
952
|
const descendants = parent.locator(childSelector);
|
|
845
953
|
const descendantCount = await descendants.count();
|
|
846
954
|
if (descendantCount < 1) {
|
|
955
|
+
if (componentName === "menu" && isSubmenuRelation(rel)) {
|
|
956
|
+
const skipMessage = "Skipping submenu relationship assertion in static phase: submenu descendants are not present until submenu is opened.";
|
|
957
|
+
skipped.push(skipMessage);
|
|
958
|
+
reporter.reportStaticTest(relDescription, "skip", skipMessage, relationshipLevel);
|
|
959
|
+
continue;
|
|
960
|
+
}
|
|
847
961
|
const outcome = classifyFailure(
|
|
848
962
|
`Expected ${rel.parent} to contain descendant matching selector for ${rel.child}.`,
|
|
849
963
|
rel.level
|
|
@@ -967,11 +1081,6 @@ This usually means:
|
|
|
967
1081
|
failures.push(`CRITICAL: Browser/page closed before completing all tests. ${componentContract.dynamic.length - componentContract.dynamic.indexOf(dynamicTest)} tests skipped.`);
|
|
968
1082
|
break;
|
|
969
1083
|
}
|
|
970
|
-
const { action, assertions } = dynamicTest;
|
|
971
|
-
const failuresBeforeTest = failures.length;
|
|
972
|
-
const warningsBeforeTest = warnings.length;
|
|
973
|
-
const skippedBeforeTest = skipped.length;
|
|
974
|
-
const dynamicLevel = normalizeLevel(dynamicTest.level);
|
|
975
1084
|
try {
|
|
976
1085
|
await strategy.resetState(page);
|
|
977
1086
|
} catch (error) {
|
|
@@ -979,6 +1088,40 @@ This usually means:
|
|
|
979
1088
|
reporter.error(errorMessage);
|
|
980
1089
|
throw error;
|
|
981
1090
|
}
|
|
1091
|
+
const { setup = [], action, assertions } = dynamicTest;
|
|
1092
|
+
const dynamicLevel = normalizeLevel(dynamicTest.level);
|
|
1093
|
+
const actionExecutor = new ActionExecutor(page, componentContract.selectors, actionTimeoutMs);
|
|
1094
|
+
if (Array.isArray(setup) && setup.length > 0) {
|
|
1095
|
+
for (const setupAct of setup) {
|
|
1096
|
+
let setupResult;
|
|
1097
|
+
if (setupAct.type === "focus") {
|
|
1098
|
+
if (setupAct.target === "relative" && setupAct.relativeTarget) {
|
|
1099
|
+
setupResult = await actionExecutor.focus("relative", setupAct.relativeTarget);
|
|
1100
|
+
} else {
|
|
1101
|
+
setupResult = await actionExecutor.focus(setupAct.target);
|
|
1102
|
+
}
|
|
1103
|
+
} else if (setupAct.type === "type" && setupAct.value) {
|
|
1104
|
+
setupResult = await actionExecutor.type(setupAct.target, setupAct.value);
|
|
1105
|
+
} else if (setupAct.type === "click") {
|
|
1106
|
+
setupResult = await actionExecutor.click(setupAct.target, setupAct.relativeTarget);
|
|
1107
|
+
} else if (setupAct.type === "keypress" && setupAct.key) {
|
|
1108
|
+
setupResult = await actionExecutor.keypress(setupAct.target, setupAct.key);
|
|
1109
|
+
} else if (setupAct.type === "hover") {
|
|
1110
|
+
setupResult = await actionExecutor.hover(setupAct.target, setupAct.relativeTarget);
|
|
1111
|
+
} else {
|
|
1112
|
+
continue;
|
|
1113
|
+
}
|
|
1114
|
+
if (!setupResult.success) {
|
|
1115
|
+
const setupMsg = setupResult.error || "Setup action failed";
|
|
1116
|
+
const outcome = classifyFailure(`Setup failed: ${setupMsg}`, dynamicTest.level);
|
|
1117
|
+
reporter.reportTest({ description: dynamicTest.description, level: dynamicLevel }, outcome.status, outcome.detail);
|
|
1118
|
+
continue;
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
const failuresBeforeTest = failures.length;
|
|
1123
|
+
const warningsBeforeTest = warnings.length;
|
|
1124
|
+
const skippedBeforeTest = skipped.length;
|
|
982
1125
|
const shouldSkipTest = await strategy.shouldSkipTest(dynamicTest, page);
|
|
983
1126
|
if (shouldSkipTest) {
|
|
984
1127
|
const skipMessage = `Skipping test - component-specific conditions not met`;
|
|
@@ -986,7 +1129,6 @@ This usually means:
|
|
|
986
1129
|
reporter.reportTest({ description: dynamicTest.description, level: dynamicLevel }, "skip", skipMessage);
|
|
987
1130
|
continue;
|
|
988
1131
|
}
|
|
989
|
-
const actionExecutor = new ActionExecutor(page, componentContract.selectors, actionTimeoutMs);
|
|
990
1132
|
const assertionRunner = new AssertionRunner(page, componentContract.selectors, assertionTimeoutMs);
|
|
991
1133
|
let shouldAbortCurrentTest = false;
|
|
992
1134
|
let actionOutcome = null;
|
|
@@ -998,7 +1140,11 @@ This usually means:
|
|
|
998
1140
|
}
|
|
999
1141
|
let result;
|
|
1000
1142
|
if (act.type === "focus") {
|
|
1001
|
-
|
|
1143
|
+
if (act.target === "relative" && act.relativeTarget) {
|
|
1144
|
+
result = await actionExecutor.focus("relative", act.relativeTarget);
|
|
1145
|
+
} else {
|
|
1146
|
+
result = await actionExecutor.focus(act.target);
|
|
1147
|
+
}
|
|
1002
1148
|
} else if (act.type === "type" && act.value) {
|
|
1003
1149
|
result = await actionExecutor.type(act.target, act.value);
|
|
1004
1150
|
} else if (act.type === "click") {
|
|
@@ -1076,7 +1222,14 @@ This usually means:
|
|
|
1076
1222
|
Make sure your dev server is running at ${url}`);
|
|
1077
1223
|
} else if (error.message.includes("Timeout") && error.message.includes("waitFor")) {
|
|
1078
1224
|
throw new Error(
|
|
1079
|
-
|
|
1225
|
+
`
|
|
1226
|
+
\u274C CRITICAL: Component not found on page!
|
|
1227
|
+
The component selector could not be found within ${componentReadyTimeoutMs}ms.
|
|
1228
|
+
This usually means:
|
|
1229
|
+
- The component didn't render
|
|
1230
|
+
- The URL is incorrect
|
|
1231
|
+
- The component selector was not provided to the component utility, or a wrong selector was used
|
|
1232
|
+
`
|
|
1080
1233
|
);
|
|
1081
1234
|
} else if (error.message.includes("Target page, context or browser has been closed")) {
|
|
1082
1235
|
throw new Error(
|
|
@@ -222,7 +222,7 @@ Error: ${error instanceof Error ? error.message : String(error)}`
|
|
|
222
222
|
let configBaseDir = typeof process !== "undefined" ? process.cwd() : "";
|
|
223
223
|
if (typeof process !== "undefined" && typeof process.cwd === "function") {
|
|
224
224
|
try {
|
|
225
|
-
const { loadConfig } = await import("./configLoader-
|
|
225
|
+
const { loadConfig } = await import("./configLoader-UJZHQBYS.js");
|
|
226
226
|
const result2 = await loadConfig(process.cwd());
|
|
227
227
|
config = result2.config;
|
|
228
228
|
if (result2.configPath) {
|
|
@@ -244,7 +244,7 @@ Error: ${error instanceof Error ? error.message : String(error)}`
|
|
|
244
244
|
const devServerUrl = await checkDevServer(url);
|
|
245
245
|
if (devServerUrl) {
|
|
246
246
|
console.log(`\u{1F3AD} Running Playwright tests on ${devServerUrl}`);
|
|
247
|
-
const { runContractTestsPlaywright } = await import("./contractTestRunnerPlaywright-
|
|
247
|
+
const { runContractTestsPlaywright } = await import("./contractTestRunnerPlaywright-QDXSK3FE.js");
|
|
248
248
|
contract = await runContractTestsPlaywright(componentName, devServerUrl, strictness, config, configBaseDir);
|
|
249
249
|
} else {
|
|
250
250
|
throw new Error(
|
|
@@ -42,6 +42,23 @@ function validateConfig(config) {
|
|
|
42
42
|
if (typeof cfg.test !== "object" || cfg.test === null) {
|
|
43
43
|
errors.push("test must be an object");
|
|
44
44
|
} else {
|
|
45
|
+
if (cfg.test.disableTimeouts !== void 0 && typeof cfg.test.disableTimeouts !== "boolean") {
|
|
46
|
+
errors.push("test.disableTimeouts must be a boolean when provided");
|
|
47
|
+
}
|
|
48
|
+
const testTimeoutFields = [
|
|
49
|
+
"actionTimeoutMs",
|
|
50
|
+
"assertionTimeoutMs",
|
|
51
|
+
"navigationTimeoutMs",
|
|
52
|
+
"componentReadyTimeoutMs"
|
|
53
|
+
];
|
|
54
|
+
for (const field of testTimeoutFields) {
|
|
55
|
+
const value = cfg.test[field];
|
|
56
|
+
if (value !== void 0) {
|
|
57
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
|
|
58
|
+
errors.push(`test.${field} must be a non-negative number when provided`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
45
62
|
if (cfg.test.components !== void 0) {
|
|
46
63
|
if (!Array.isArray(cfg.test.components)) {
|
|
47
64
|
errors.push("test.components must be an array");
|
|
@@ -62,6 +79,23 @@ function validateConfig(config) {
|
|
|
62
79
|
if (comp.strictness !== void 0 && !["minimal", "balanced", "strict", "paranoid"].includes(comp.strictness)) {
|
|
63
80
|
errors.push(`test.components[${idx}].strictness must be one of: minimal, balanced, strict, paranoid`);
|
|
64
81
|
}
|
|
82
|
+
if (comp.disableTimeouts !== void 0 && typeof comp.disableTimeouts !== "boolean") {
|
|
83
|
+
errors.push(`test.components[${idx}].disableTimeouts must be a boolean when provided`);
|
|
84
|
+
}
|
|
85
|
+
const componentTimeoutFields = [
|
|
86
|
+
"actionTimeoutMs",
|
|
87
|
+
"assertionTimeoutMs",
|
|
88
|
+
"navigationTimeoutMs",
|
|
89
|
+
"componentReadyTimeoutMs"
|
|
90
|
+
];
|
|
91
|
+
for (const field of componentTimeoutFields) {
|
|
92
|
+
const value = comp[field];
|
|
93
|
+
if (value !== void 0) {
|
|
94
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
|
|
95
|
+
errors.push(`test.components[${idx}].${field} must be a non-negative number when provided`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
65
99
|
}
|
|
66
100
|
});
|
|
67
101
|
}
|