aria-ease 6.12.1 → 6.14.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/dist/{MenuComponentStrategy-JAMTCSNF.js → MenuComponentStrategy-VYCC2XOM.js} +5 -5
- package/dist/{buildContracts-FT6KWUJN.js → buildContracts-VIV6GM56.js} +0 -28
- package/dist/cli.cjs +154 -78
- package/dist/cli.js +2 -2
- package/dist/{contractTestRunnerPlaywright-H24LQ45R.js → contractTestRunnerPlaywright-B2HLZKKK.js} +150 -46
- package/dist/{contractTestRunnerPlaywright-NL3JNJYH.js → contractTestRunnerPlaywright-RWK52C7S.js} +150 -46
- package/dist/index.cjs +564 -85
- package/dist/index.d.cts +24 -1
- package/dist/index.d.ts +24 -1
- package/dist/index.js +411 -36
- package/dist/src/combobox/index.cjs +3 -0
- package/dist/src/combobox/index.js +3 -0
- package/dist/src/utils/test/{MenuComponentStrategy-VKZQYLBE.js → MenuComponentStrategy-6XWU5KLW.js} +5 -5
- package/dist/src/utils/test/{contractTestRunnerPlaywright-5FT6K2WN.js → contractTestRunnerPlaywright-5FIGA5G4.js} +150 -46
- package/dist/src/utils/test/dsl/index.cjs +407 -35
- package/dist/src/utils/test/dsl/index.d.cts +24 -1
- package/dist/src/utils/test/dsl/index.d.ts +24 -1
- package/dist/src/utils/test/dsl/index.js +407 -35
- package/dist/src/utils/test/index.cjs +154 -50
- package/dist/src/utils/test/index.js +1 -1
- package/dist/{test-FYSJXQWO.js → test-WDBS5JWO.js} +1 -1
- package/package.json +1 -1
package/dist/{contractTestRunnerPlaywright-H24LQ45R.js → contractTestRunnerPlaywright-B2HLZKKK.js}
RENAMED
|
@@ -35,7 +35,7 @@ var StrategyRegistry = class {
|
|
|
35
35
|
registerBuiltInStrategies() {
|
|
36
36
|
this.builtInStrategies.set(
|
|
37
37
|
"menu",
|
|
38
|
-
() => import("./MenuComponentStrategy-
|
|
38
|
+
() => import("./MenuComponentStrategy-VYCC2XOM.js").then(
|
|
39
39
|
(m) => m.MenuComponentStrategy
|
|
40
40
|
)
|
|
41
41
|
);
|
|
@@ -558,13 +558,21 @@ var AssertionRunner = class {
|
|
|
558
558
|
/**
|
|
559
559
|
* Validate focus assertion
|
|
560
560
|
*/
|
|
561
|
-
async validateFocus(target, targetName, failureMessage, testDescription) {
|
|
561
|
+
async validateFocus(target, targetName, expectedFocus, failureMessage, testDescription) {
|
|
562
562
|
try {
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
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
|
+
}
|
|
568
576
|
} catch {
|
|
569
577
|
const actualFocus = await this.page.evaluate(() => {
|
|
570
578
|
const focused = document.activeElement;
|
|
@@ -650,7 +658,9 @@ var AssertionRunner = class {
|
|
|
650
658
|
}
|
|
651
659
|
return { success: false, failMessage: "Missing expectedValue for toHaveValue assertion" };
|
|
652
660
|
case "toHaveFocus":
|
|
653
|
-
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);
|
|
654
664
|
case "toHaveRole":
|
|
655
665
|
if (assertion.expectedValue !== void 0) {
|
|
656
666
|
return this.validateRole(target, assertion.target, assertion.expectedValue, assertion.failureMessage || "", testDescription);
|
|
@@ -667,12 +677,7 @@ async function runContractTestsPlaywright(componentName, url, strictness, config
|
|
|
667
677
|
const componentConfig = config?.test?.components?.find((c) => c.name === componentName);
|
|
668
678
|
const isCustomContract = !!componentConfig?.contractPath;
|
|
669
679
|
const reporter = new ContractReporter(true, isCustomContract);
|
|
670
|
-
const defaultTimeouts = {
|
|
671
|
-
actionTimeoutMs: 400,
|
|
672
|
-
assertionTimeoutMs: 400,
|
|
673
|
-
navigationTimeoutMs: 3e4,
|
|
674
|
-
componentReadyTimeoutMs: 5e3
|
|
675
|
-
};
|
|
680
|
+
const defaultTimeouts = { actionTimeoutMs: 400, assertionTimeoutMs: 400, navigationTimeoutMs: 3e4, componentReadyTimeoutMs: 5e3 };
|
|
676
681
|
const globalDisableTimeouts = config?.test?.disableTimeouts === true;
|
|
677
682
|
const componentDisableTimeouts = componentConfig?.disableTimeouts === true;
|
|
678
683
|
const disableTimeouts = componentDisableTimeouts || globalDisableTimeouts;
|
|
@@ -684,11 +689,7 @@ async function runContractTestsPlaywright(componentName, url, strictness, config
|
|
|
684
689
|
}
|
|
685
690
|
return value;
|
|
686
691
|
};
|
|
687
|
-
const actionTimeoutMs = resolveTimeout(
|
|
688
|
-
componentConfig?.actionTimeoutMs,
|
|
689
|
-
config?.test?.actionTimeoutMs,
|
|
690
|
-
defaultTimeouts.actionTimeoutMs
|
|
691
|
-
);
|
|
692
|
+
const actionTimeoutMs = resolveTimeout(componentConfig?.actionTimeoutMs, config?.test?.actionTimeoutMs, defaultTimeouts.actionTimeoutMs);
|
|
692
693
|
const assertionTimeoutMs = resolveTimeout(
|
|
693
694
|
componentConfig?.assertionTimeoutMs,
|
|
694
695
|
config?.test?.assertionTimeoutMs,
|
|
@@ -788,8 +789,8 @@ This usually means:
|
|
|
788
789
|
);
|
|
789
790
|
}
|
|
790
791
|
reporter.start(componentName, totalTests, apgUrl);
|
|
791
|
-
if (componentName === "menu" && componentContract.selectors.
|
|
792
|
-
await page.locator(componentContract.selectors.
|
|
792
|
+
if (componentName === "menu" && componentContract.selectors.main) {
|
|
793
|
+
await page.locator(componentContract.selectors.main).first().waitFor({ state: "visible", timeout: componentReadyTimeoutMs }).catch(() => {
|
|
793
794
|
});
|
|
794
795
|
}
|
|
795
796
|
const hasSubmenuCapability = componentName === "menu" && !!componentContract.selectors.submenuTrigger ? await page.locator(componentContract.selectors.submenuTrigger).count() > 0 : false;
|
|
@@ -798,7 +799,42 @@ This usually means:
|
|
|
798
799
|
let staticFailed = 0;
|
|
799
800
|
let staticWarnings = 0;
|
|
800
801
|
for (const rel of componentContract.relationships || []) {
|
|
802
|
+
if (strategy && typeof strategy.resetState === "function") {
|
|
803
|
+
try {
|
|
804
|
+
await strategy.resetState(page);
|
|
805
|
+
} catch (err) {
|
|
806
|
+
warnings.push(`Warning: resetState failed before relationship test: ${err instanceof Error ? err.message : String(err)}`);
|
|
807
|
+
}
|
|
808
|
+
}
|
|
801
809
|
const relationshipLevel = normalizeLevel(rel.level);
|
|
810
|
+
if (Array.isArray(rel.setup) && rel.setup.length > 0) {
|
|
811
|
+
let isAllowedType2 = function(t) {
|
|
812
|
+
return allowedTypes.includes(t);
|
|
813
|
+
};
|
|
814
|
+
var isAllowedType = isAllowedType2;
|
|
815
|
+
const actionExecutor = new ActionExecutor(page, componentContract.selectors, actionTimeoutMs);
|
|
816
|
+
const relDescription = rel.type === "aria-reference" ? `${rel.from}.${rel.attribute} references ${rel.to}` : `${rel.parent} contains ${rel.child}`;
|
|
817
|
+
const allowedTypes = ["focus", "type", "click", "keypress", "hover"];
|
|
818
|
+
const toSetupAction = (a) => ({
|
|
819
|
+
...a,
|
|
820
|
+
type: isAllowedType2(a.type) ? a.type : "click"
|
|
821
|
+
});
|
|
822
|
+
const setupActions = rel.setup.map(toSetupAction);
|
|
823
|
+
const setupResult = await runSetupActions(setupActions, actionExecutor, strategy, page, relDescription, ["submenu", "submenuTrigger", "submenuItems"]);
|
|
824
|
+
if (setupResult.skip) {
|
|
825
|
+
skipped.push(setupResult.message || "Setup action skipped");
|
|
826
|
+
reporter.reportStaticTest(relDescription, "skip", setupResult.message, relationshipLevel);
|
|
827
|
+
continue;
|
|
828
|
+
}
|
|
829
|
+
if (!setupResult.success) {
|
|
830
|
+
const failure = `Relationship setup failed: ${setupResult.error}`;
|
|
831
|
+
const outcome = classifyFailure(failure, rel.level);
|
|
832
|
+
if (outcome.status === "fail") staticFailed += 1;
|
|
833
|
+
if (outcome.status === "warn") staticWarnings += 1;
|
|
834
|
+
reporter.reportStaticTest(relDescription, outcome.status, outcome.detail, outcome.level);
|
|
835
|
+
continue;
|
|
836
|
+
}
|
|
837
|
+
}
|
|
802
838
|
if (componentName === "menu" && !hasSubmenuCapability) {
|
|
803
839
|
const involvesSubmenu = isSubmenuRelation(rel);
|
|
804
840
|
if (involvesSubmenu) {
|
|
@@ -971,7 +1007,53 @@ This usually means:
|
|
|
971
1007
|
}
|
|
972
1008
|
}
|
|
973
1009
|
const staticAssertionRunner = new AssertionRunner(page, componentContract.selectors, assertionTimeoutMs);
|
|
1010
|
+
async function runSetupActions(setup, actionExecutor, strategy2, page2, description, skipKeywords = []) {
|
|
1011
|
+
if (!Array.isArray(setup) || setup.length === 0) return { success: true };
|
|
1012
|
+
if (strategy2 && typeof strategy2.resetState === "function") {
|
|
1013
|
+
await strategy2.resetState(page2);
|
|
1014
|
+
}
|
|
1015
|
+
for (const setupAct of setup) {
|
|
1016
|
+
let setupResult = { success: true };
|
|
1017
|
+
try {
|
|
1018
|
+
if (setupAct.type === "focus") {
|
|
1019
|
+
if (setupAct.target === "relative" && setupAct.relativeTarget) {
|
|
1020
|
+
setupResult = await actionExecutor.focus("relative", setupAct.relativeTarget);
|
|
1021
|
+
} else {
|
|
1022
|
+
setupResult = await actionExecutor.focus(setupAct.target);
|
|
1023
|
+
}
|
|
1024
|
+
} else if (setupAct.type === "type" && setupAct.value) {
|
|
1025
|
+
setupResult = await actionExecutor.type(setupAct.target, setupAct.value);
|
|
1026
|
+
} else if (setupAct.type === "click") {
|
|
1027
|
+
setupResult = await actionExecutor.click(setupAct.target, setupAct.relativeTarget);
|
|
1028
|
+
} else if (setupAct.type === "keypress" && setupAct.key) {
|
|
1029
|
+
setupResult = await actionExecutor.keypress(setupAct.target, setupAct.key);
|
|
1030
|
+
} else if (setupAct.type === "hover") {
|
|
1031
|
+
setupResult = await actionExecutor.hover(setupAct.target, setupAct.relativeTarget);
|
|
1032
|
+
} else {
|
|
1033
|
+
continue;
|
|
1034
|
+
}
|
|
1035
|
+
} catch (err) {
|
|
1036
|
+
setupResult = { success: false, error: err instanceof Error ? err.message : String(err) };
|
|
1037
|
+
}
|
|
1038
|
+
if (!setupResult.success) {
|
|
1039
|
+
const setupMsg = setupResult.error || "Setup action failed";
|
|
1040
|
+
const isSkip = skipKeywords.some((kw) => description.includes(kw) || setupMsg.includes(kw));
|
|
1041
|
+
if (isSkip) {
|
|
1042
|
+
return { success: false, skip: true, message: `Skipping test - capability not present: ${setupMsg}` };
|
|
1043
|
+
}
|
|
1044
|
+
return { success: false, error: setupMsg };
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
return { success: true };
|
|
1048
|
+
}
|
|
974
1049
|
for (const test of componentContract.static[0]?.assertions || []) {
|
|
1050
|
+
if (strategy && typeof strategy.resetState === "function") {
|
|
1051
|
+
try {
|
|
1052
|
+
await strategy.resetState(page);
|
|
1053
|
+
} catch (err) {
|
|
1054
|
+
warnings.push(`Warning: resetState failed before static test: ${err instanceof Error ? err.message : String(err)}`);
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
975
1057
|
if (test.target === "relative") continue;
|
|
976
1058
|
const staticDescription = `${test.target}${test.attribute ? ` (${test.attribute})` : ""}`;
|
|
977
1059
|
const staticLevel = normalizeLevel(test.level);
|
|
@@ -981,6 +1063,33 @@ This usually means:
|
|
|
981
1063
|
reporter.reportStaticTest(staticDescription, "skip", skipMessage, staticLevel);
|
|
982
1064
|
continue;
|
|
983
1065
|
}
|
|
1066
|
+
if (Array.isArray(test.setup) && test.setup.length > 0) {
|
|
1067
|
+
let isAllowedType2 = function(t) {
|
|
1068
|
+
return allowedTypes.includes(t);
|
|
1069
|
+
};
|
|
1070
|
+
var isAllowedType = isAllowedType2;
|
|
1071
|
+
const actionExecutor = new ActionExecutor(page, componentContract.selectors, actionTimeoutMs);
|
|
1072
|
+
const allowedTypes = ["focus", "type", "click", "keypress", "hover"];
|
|
1073
|
+
const toSetupAction = (a) => ({
|
|
1074
|
+
...a,
|
|
1075
|
+
type: isAllowedType2(a.type) ? a.type : "click"
|
|
1076
|
+
});
|
|
1077
|
+
const setupActions = test.setup.map(toSetupAction);
|
|
1078
|
+
const setupResult = await runSetupActions(setupActions, actionExecutor, strategy, page, staticDescription, ["submenu", "submenuTrigger", "submenuItems"]);
|
|
1079
|
+
if (setupResult.skip) {
|
|
1080
|
+
skipped.push(setupResult.message || "Setup action skipped");
|
|
1081
|
+
reporter.reportStaticTest(staticDescription, "skip", setupResult.message, staticLevel);
|
|
1082
|
+
continue;
|
|
1083
|
+
}
|
|
1084
|
+
if (!setupResult.success) {
|
|
1085
|
+
const failure = `Static setup failed: ${setupResult.error}`;
|
|
1086
|
+
const outcome = classifyFailure(failure, test.level);
|
|
1087
|
+
if (outcome.status === "fail") staticFailed += 1;
|
|
1088
|
+
if (outcome.status === "warn") staticWarnings += 1;
|
|
1089
|
+
reporter.reportStaticTest(staticDescription, outcome.status, outcome.detail, outcome.level);
|
|
1090
|
+
continue;
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
984
1093
|
const targetSelector = componentContract.selectors[test.target];
|
|
985
1094
|
if (!targetSelector) {
|
|
986
1095
|
const failure = `Selector for target ${test.target} not found.`;
|
|
@@ -1107,31 +1216,26 @@ This usually means:
|
|
|
1107
1216
|
const dynamicLevel = normalizeLevel(dynamicTest.level);
|
|
1108
1217
|
const actionExecutor = new ActionExecutor(page, componentContract.selectors, actionTimeoutMs);
|
|
1109
1218
|
if (Array.isArray(setup) && setup.length > 0) {
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
}
|
|
1129
|
-
|
|
1130
|
-
const setupMsg = setupResult.error || "Setup action failed";
|
|
1131
|
-
const outcome = classifyFailure(`Setup failed: ${setupMsg}`, dynamicTest.level);
|
|
1132
|
-
reporter.reportTest({ description: dynamicTest.description, level: dynamicLevel }, outcome.status, outcome.detail);
|
|
1133
|
-
continue;
|
|
1134
|
-
}
|
|
1219
|
+
let isAllowedType2 = function(t) {
|
|
1220
|
+
return allowedTypes.includes(t);
|
|
1221
|
+
};
|
|
1222
|
+
var isAllowedType = isAllowedType2;
|
|
1223
|
+
const allowedTypes = ["focus", "type", "click", "keypress", "hover"];
|
|
1224
|
+
const toSetupAction = (a) => ({
|
|
1225
|
+
...a,
|
|
1226
|
+
type: isAllowedType2(a.type) ? a.type : "click"
|
|
1227
|
+
});
|
|
1228
|
+
const setupActions = setup.map(toSetupAction);
|
|
1229
|
+
const setupResult = await runSetupActions(setupActions, actionExecutor, strategy, page, dynamicTest.description, ["submenu", "submenuTrigger", "submenuItems"]);
|
|
1230
|
+
if (setupResult.skip) {
|
|
1231
|
+
skipped.push(setupResult.message || "Setup action skipped");
|
|
1232
|
+
reporter.reportTest({ description: dynamicTest.description, level: dynamicLevel }, "skip", setupResult.message);
|
|
1233
|
+
continue;
|
|
1234
|
+
}
|
|
1235
|
+
if (!setupResult.success) {
|
|
1236
|
+
const outcome = classifyFailure(`Setup failed: ${setupResult.error}`, dynamicTest.level);
|
|
1237
|
+
reporter.reportTest({ description: dynamicTest.description, level: dynamicLevel }, outcome.status, outcome.detail);
|
|
1238
|
+
continue;
|
|
1135
1239
|
}
|
|
1136
1240
|
}
|
|
1137
1241
|
const failuresBeforeTest = failures.length;
|
package/dist/{contractTestRunnerPlaywright-NL3JNJYH.js → contractTestRunnerPlaywright-RWK52C7S.js}
RENAMED
|
@@ -35,7 +35,7 @@ var StrategyRegistry = class {
|
|
|
35
35
|
registerBuiltInStrategies() {
|
|
36
36
|
this.builtInStrategies.set(
|
|
37
37
|
"menu",
|
|
38
|
-
() => import("./MenuComponentStrategy-
|
|
38
|
+
() => import("./MenuComponentStrategy-VYCC2XOM.js").then(
|
|
39
39
|
(m) => m.MenuComponentStrategy
|
|
40
40
|
)
|
|
41
41
|
);
|
|
@@ -558,13 +558,21 @@ var AssertionRunner = class {
|
|
|
558
558
|
/**
|
|
559
559
|
* Validate focus assertion
|
|
560
560
|
*/
|
|
561
|
-
async validateFocus(target, targetName, failureMessage, testDescription) {
|
|
561
|
+
async validateFocus(target, targetName, expectedFocus, failureMessage, testDescription) {
|
|
562
562
|
try {
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
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
|
+
}
|
|
568
576
|
} catch {
|
|
569
577
|
const actualFocus = await this.page.evaluate(() => {
|
|
570
578
|
const focused = document.activeElement;
|
|
@@ -650,7 +658,9 @@ var AssertionRunner = class {
|
|
|
650
658
|
}
|
|
651
659
|
return { success: false, failMessage: "Missing expectedValue for toHaveValue assertion" };
|
|
652
660
|
case "toHaveFocus":
|
|
653
|
-
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);
|
|
654
664
|
case "toHaveRole":
|
|
655
665
|
if (assertion.expectedValue !== void 0) {
|
|
656
666
|
return this.validateRole(target, assertion.target, assertion.expectedValue, assertion.failureMessage || "", testDescription);
|
|
@@ -667,12 +677,7 @@ async function runContractTestsPlaywright(componentName, url, strictness, config
|
|
|
667
677
|
const componentConfig = config?.test?.components?.find((c) => c.name === componentName);
|
|
668
678
|
const isCustomContract = !!componentConfig?.contractPath;
|
|
669
679
|
const reporter = new ContractReporter(true, isCustomContract);
|
|
670
|
-
const defaultTimeouts = {
|
|
671
|
-
actionTimeoutMs: 400,
|
|
672
|
-
assertionTimeoutMs: 400,
|
|
673
|
-
navigationTimeoutMs: 3e4,
|
|
674
|
-
componentReadyTimeoutMs: 5e3
|
|
675
|
-
};
|
|
680
|
+
const defaultTimeouts = { actionTimeoutMs: 400, assertionTimeoutMs: 400, navigationTimeoutMs: 3e4, componentReadyTimeoutMs: 5e3 };
|
|
676
681
|
const globalDisableTimeouts = config?.test?.disableTimeouts === true;
|
|
677
682
|
const componentDisableTimeouts = componentConfig?.disableTimeouts === true;
|
|
678
683
|
const disableTimeouts = componentDisableTimeouts || globalDisableTimeouts;
|
|
@@ -684,11 +689,7 @@ async function runContractTestsPlaywright(componentName, url, strictness, config
|
|
|
684
689
|
}
|
|
685
690
|
return value;
|
|
686
691
|
};
|
|
687
|
-
const actionTimeoutMs = resolveTimeout(
|
|
688
|
-
componentConfig?.actionTimeoutMs,
|
|
689
|
-
config?.test?.actionTimeoutMs,
|
|
690
|
-
defaultTimeouts.actionTimeoutMs
|
|
691
|
-
);
|
|
692
|
+
const actionTimeoutMs = resolveTimeout(componentConfig?.actionTimeoutMs, config?.test?.actionTimeoutMs, defaultTimeouts.actionTimeoutMs);
|
|
692
693
|
const assertionTimeoutMs = resolveTimeout(
|
|
693
694
|
componentConfig?.assertionTimeoutMs,
|
|
694
695
|
config?.test?.assertionTimeoutMs,
|
|
@@ -788,8 +789,8 @@ This usually means:
|
|
|
788
789
|
);
|
|
789
790
|
}
|
|
790
791
|
reporter.start(componentName, totalTests, apgUrl);
|
|
791
|
-
if (componentName === "menu" && componentContract.selectors.
|
|
792
|
-
await page.locator(componentContract.selectors.
|
|
792
|
+
if (componentName === "menu" && componentContract.selectors.main) {
|
|
793
|
+
await page.locator(componentContract.selectors.main).first().waitFor({ state: "visible", timeout: componentReadyTimeoutMs }).catch(() => {
|
|
793
794
|
});
|
|
794
795
|
}
|
|
795
796
|
const hasSubmenuCapability = componentName === "menu" && !!componentContract.selectors.submenuTrigger ? await page.locator(componentContract.selectors.submenuTrigger).count() > 0 : false;
|
|
@@ -798,7 +799,42 @@ This usually means:
|
|
|
798
799
|
let staticFailed = 0;
|
|
799
800
|
let staticWarnings = 0;
|
|
800
801
|
for (const rel of componentContract.relationships || []) {
|
|
802
|
+
if (strategy && typeof strategy.resetState === "function") {
|
|
803
|
+
try {
|
|
804
|
+
await strategy.resetState(page);
|
|
805
|
+
} catch (err) {
|
|
806
|
+
warnings.push(`Warning: resetState failed before relationship test: ${err instanceof Error ? err.message : String(err)}`);
|
|
807
|
+
}
|
|
808
|
+
}
|
|
801
809
|
const relationshipLevel = normalizeLevel(rel.level);
|
|
810
|
+
if (Array.isArray(rel.setup) && rel.setup.length > 0) {
|
|
811
|
+
let isAllowedType2 = function(t) {
|
|
812
|
+
return allowedTypes.includes(t);
|
|
813
|
+
};
|
|
814
|
+
var isAllowedType = isAllowedType2;
|
|
815
|
+
const actionExecutor = new ActionExecutor(page, componentContract.selectors, actionTimeoutMs);
|
|
816
|
+
const relDescription = rel.type === "aria-reference" ? `${rel.from}.${rel.attribute} references ${rel.to}` : `${rel.parent} contains ${rel.child}`;
|
|
817
|
+
const allowedTypes = ["focus", "type", "click", "keypress", "hover"];
|
|
818
|
+
const toSetupAction = (a) => ({
|
|
819
|
+
...a,
|
|
820
|
+
type: isAllowedType2(a.type) ? a.type : "click"
|
|
821
|
+
});
|
|
822
|
+
const setupActions = rel.setup.map(toSetupAction);
|
|
823
|
+
const setupResult = await runSetupActions(setupActions, actionExecutor, strategy, page, relDescription, ["submenu", "submenuTrigger", "submenuItems"]);
|
|
824
|
+
if (setupResult.skip) {
|
|
825
|
+
skipped.push(setupResult.message || "Setup action skipped");
|
|
826
|
+
reporter.reportStaticTest(relDescription, "skip", setupResult.message, relationshipLevel);
|
|
827
|
+
continue;
|
|
828
|
+
}
|
|
829
|
+
if (!setupResult.success) {
|
|
830
|
+
const failure = `Relationship setup failed: ${setupResult.error}`;
|
|
831
|
+
const outcome = classifyFailure(failure, rel.level);
|
|
832
|
+
if (outcome.status === "fail") staticFailed += 1;
|
|
833
|
+
if (outcome.status === "warn") staticWarnings += 1;
|
|
834
|
+
reporter.reportStaticTest(relDescription, outcome.status, outcome.detail, outcome.level);
|
|
835
|
+
continue;
|
|
836
|
+
}
|
|
837
|
+
}
|
|
802
838
|
if (componentName === "menu" && !hasSubmenuCapability) {
|
|
803
839
|
const involvesSubmenu = isSubmenuRelation(rel);
|
|
804
840
|
if (involvesSubmenu) {
|
|
@@ -971,7 +1007,53 @@ This usually means:
|
|
|
971
1007
|
}
|
|
972
1008
|
}
|
|
973
1009
|
const staticAssertionRunner = new AssertionRunner(page, componentContract.selectors, assertionTimeoutMs);
|
|
1010
|
+
async function runSetupActions(setup, actionExecutor, strategy2, page2, description, skipKeywords = []) {
|
|
1011
|
+
if (!Array.isArray(setup) || setup.length === 0) return { success: true };
|
|
1012
|
+
if (strategy2 && typeof strategy2.resetState === "function") {
|
|
1013
|
+
await strategy2.resetState(page2);
|
|
1014
|
+
}
|
|
1015
|
+
for (const setupAct of setup) {
|
|
1016
|
+
let setupResult = { success: true };
|
|
1017
|
+
try {
|
|
1018
|
+
if (setupAct.type === "focus") {
|
|
1019
|
+
if (setupAct.target === "relative" && setupAct.relativeTarget) {
|
|
1020
|
+
setupResult = await actionExecutor.focus("relative", setupAct.relativeTarget);
|
|
1021
|
+
} else {
|
|
1022
|
+
setupResult = await actionExecutor.focus(setupAct.target);
|
|
1023
|
+
}
|
|
1024
|
+
} else if (setupAct.type === "type" && setupAct.value) {
|
|
1025
|
+
setupResult = await actionExecutor.type(setupAct.target, setupAct.value);
|
|
1026
|
+
} else if (setupAct.type === "click") {
|
|
1027
|
+
setupResult = await actionExecutor.click(setupAct.target, setupAct.relativeTarget);
|
|
1028
|
+
} else if (setupAct.type === "keypress" && setupAct.key) {
|
|
1029
|
+
setupResult = await actionExecutor.keypress(setupAct.target, setupAct.key);
|
|
1030
|
+
} else if (setupAct.type === "hover") {
|
|
1031
|
+
setupResult = await actionExecutor.hover(setupAct.target, setupAct.relativeTarget);
|
|
1032
|
+
} else {
|
|
1033
|
+
continue;
|
|
1034
|
+
}
|
|
1035
|
+
} catch (err) {
|
|
1036
|
+
setupResult = { success: false, error: err instanceof Error ? err.message : String(err) };
|
|
1037
|
+
}
|
|
1038
|
+
if (!setupResult.success) {
|
|
1039
|
+
const setupMsg = setupResult.error || "Setup action failed";
|
|
1040
|
+
const isSkip = skipKeywords.some((kw) => description.includes(kw) || setupMsg.includes(kw));
|
|
1041
|
+
if (isSkip) {
|
|
1042
|
+
return { success: false, skip: true, message: `Skipping test - capability not present: ${setupMsg}` };
|
|
1043
|
+
}
|
|
1044
|
+
return { success: false, error: setupMsg };
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
return { success: true };
|
|
1048
|
+
}
|
|
974
1049
|
for (const test of componentContract.static[0]?.assertions || []) {
|
|
1050
|
+
if (strategy && typeof strategy.resetState === "function") {
|
|
1051
|
+
try {
|
|
1052
|
+
await strategy.resetState(page);
|
|
1053
|
+
} catch (err) {
|
|
1054
|
+
warnings.push(`Warning: resetState failed before static test: ${err instanceof Error ? err.message : String(err)}`);
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
975
1057
|
if (test.target === "relative") continue;
|
|
976
1058
|
const staticDescription = `${test.target}${test.attribute ? ` (${test.attribute})` : ""}`;
|
|
977
1059
|
const staticLevel = normalizeLevel(test.level);
|
|
@@ -981,6 +1063,33 @@ This usually means:
|
|
|
981
1063
|
reporter.reportStaticTest(staticDescription, "skip", skipMessage, staticLevel);
|
|
982
1064
|
continue;
|
|
983
1065
|
}
|
|
1066
|
+
if (Array.isArray(test.setup) && test.setup.length > 0) {
|
|
1067
|
+
let isAllowedType2 = function(t) {
|
|
1068
|
+
return allowedTypes.includes(t);
|
|
1069
|
+
};
|
|
1070
|
+
var isAllowedType = isAllowedType2;
|
|
1071
|
+
const actionExecutor = new ActionExecutor(page, componentContract.selectors, actionTimeoutMs);
|
|
1072
|
+
const allowedTypes = ["focus", "type", "click", "keypress", "hover"];
|
|
1073
|
+
const toSetupAction = (a) => ({
|
|
1074
|
+
...a,
|
|
1075
|
+
type: isAllowedType2(a.type) ? a.type : "click"
|
|
1076
|
+
});
|
|
1077
|
+
const setupActions = test.setup.map(toSetupAction);
|
|
1078
|
+
const setupResult = await runSetupActions(setupActions, actionExecutor, strategy, page, staticDescription, ["submenu", "submenuTrigger", "submenuItems"]);
|
|
1079
|
+
if (setupResult.skip) {
|
|
1080
|
+
skipped.push(setupResult.message || "Setup action skipped");
|
|
1081
|
+
reporter.reportStaticTest(staticDescription, "skip", setupResult.message, staticLevel);
|
|
1082
|
+
continue;
|
|
1083
|
+
}
|
|
1084
|
+
if (!setupResult.success) {
|
|
1085
|
+
const failure = `Static setup failed: ${setupResult.error}`;
|
|
1086
|
+
const outcome = classifyFailure(failure, test.level);
|
|
1087
|
+
if (outcome.status === "fail") staticFailed += 1;
|
|
1088
|
+
if (outcome.status === "warn") staticWarnings += 1;
|
|
1089
|
+
reporter.reportStaticTest(staticDescription, outcome.status, outcome.detail, outcome.level);
|
|
1090
|
+
continue;
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
984
1093
|
const targetSelector = componentContract.selectors[test.target];
|
|
985
1094
|
if (!targetSelector) {
|
|
986
1095
|
const failure = `Selector for target ${test.target} not found.`;
|
|
@@ -1107,31 +1216,26 @@ This usually means:
|
|
|
1107
1216
|
const dynamicLevel = normalizeLevel(dynamicTest.level);
|
|
1108
1217
|
const actionExecutor = new ActionExecutor(page, componentContract.selectors, actionTimeoutMs);
|
|
1109
1218
|
if (Array.isArray(setup) && setup.length > 0) {
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
}
|
|
1129
|
-
|
|
1130
|
-
const setupMsg = setupResult.error || "Setup action failed";
|
|
1131
|
-
const outcome = classifyFailure(`Setup failed: ${setupMsg}`, dynamicTest.level);
|
|
1132
|
-
reporter.reportTest({ description: dynamicTest.description, level: dynamicLevel }, outcome.status, outcome.detail);
|
|
1133
|
-
continue;
|
|
1134
|
-
}
|
|
1219
|
+
let isAllowedType2 = function(t) {
|
|
1220
|
+
return allowedTypes.includes(t);
|
|
1221
|
+
};
|
|
1222
|
+
var isAllowedType = isAllowedType2;
|
|
1223
|
+
const allowedTypes = ["focus", "type", "click", "keypress", "hover"];
|
|
1224
|
+
const toSetupAction = (a) => ({
|
|
1225
|
+
...a,
|
|
1226
|
+
type: isAllowedType2(a.type) ? a.type : "click"
|
|
1227
|
+
});
|
|
1228
|
+
const setupActions = setup.map(toSetupAction);
|
|
1229
|
+
const setupResult = await runSetupActions(setupActions, actionExecutor, strategy, page, dynamicTest.description, ["submenu", "submenuTrigger", "submenuItems"]);
|
|
1230
|
+
if (setupResult.skip) {
|
|
1231
|
+
skipped.push(setupResult.message || "Setup action skipped");
|
|
1232
|
+
reporter.reportTest({ description: dynamicTest.description, level: dynamicLevel }, "skip", setupResult.message);
|
|
1233
|
+
continue;
|
|
1234
|
+
}
|
|
1235
|
+
if (!setupResult.success) {
|
|
1236
|
+
const outcome = classifyFailure(`Setup failed: ${setupResult.error}`, dynamicTest.level);
|
|
1237
|
+
reporter.reportTest({ description: dynamicTest.description, level: dynamicLevel }, outcome.status, outcome.detail);
|
|
1238
|
+
continue;
|
|
1135
1239
|
}
|
|
1136
1240
|
}
|
|
1137
1241
|
const failuresBeforeTest = failures.length;
|