aria-ease 6.4.8 → 6.5.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 +15 -27
- package/bin/{chunk-TQBS54MM.js → chunk-AUJAN4RK.js} +35 -19
- package/bin/cli.cjs +952 -513
- package/bin/cli.js +1 -1
- package/bin/contractTestRunnerPlaywright-7F756CFB.js +984 -0
- package/bin/{test-WICJJ62P.js → test-C3CMRHSI.js} +39 -32
- package/dist/{chunk-TQBS54MM.js → chunk-AUJAN4RK.js} +35 -19
- package/dist/contractTestRunnerPlaywright-7F756CFB.js +984 -0
- package/dist/index.cjs +958 -519
- package/dist/index.js +46 -39
- 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.d.cts +1 -1
- package/dist/src/combobox/index.d.ts +1 -1
- package/dist/src/menu/index.cjs +7 -7
- package/dist/src/menu/index.d.cts +1 -1
- package/dist/src/menu/index.d.ts +1 -1
- package/dist/src/menu/index.js +7 -7
- 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/{contracts/AccordionContract.json → aria-contracts/accordion/accordion.contract.json} +20 -7
- package/dist/src/utils/test/{contracts/ComboboxContract.json → aria-contracts/combobox/combobox.listbox.contract.json} +18 -17
- package/dist/src/utils/test/{contracts/MenuContract.json → aria-contracts/menu/menu.contract.json} +42 -1
- package/dist/src/utils/test/{contracts/TabsContract.json → aria-contracts/tabs/tabs.contract.json} +20 -7
- package/dist/src/utils/test/{chunk-TQBS54MM.js → chunk-AUJAN4RK.js} +34 -18
- package/dist/src/utils/test/contractTestRunnerPlaywright-HL73FADJ.js +955 -0
- package/dist/src/utils/test/index.cjs +921 -505
- package/dist/src/utils/test/index.d.cts +6 -1
- package/dist/src/utils/test/index.d.ts +6 -1
- package/dist/src/utils/test/index.js +38 -31
- package/package.json +2 -2
- package/bin/contractTestRunnerPlaywright-D57V4RSU.js +0 -628
- package/dist/contractTestRunnerPlaywright-D57V4RSU.js +0 -628
- package/dist/src/utils/test/contractTestRunnerPlaywright-HV4EIRDH.js +0 -610
package/bin/cli.cjs
CHANGED
|
@@ -403,29 +403,29 @@ var init_contract = __esm({
|
|
|
403
403
|
"src/utils/test/contract/contract.json"() {
|
|
404
404
|
contract_default = {
|
|
405
405
|
menu: {
|
|
406
|
-
path: "./contracts/
|
|
406
|
+
path: "./aria-contracts/menu/menu.contract.json",
|
|
407
407
|
component: "menu"
|
|
408
408
|
},
|
|
409
|
-
combobox: {
|
|
410
|
-
path: "./contracts/
|
|
411
|
-
component: "combobox"
|
|
409
|
+
"combobox.listbox": {
|
|
410
|
+
path: "./aria-contracts/combobox/combobox.listbox.contract.json",
|
|
411
|
+
component: "combobox.listbox"
|
|
412
412
|
},
|
|
413
413
|
accordion: {
|
|
414
|
-
path: "./contracts/
|
|
414
|
+
path: "./aria-contracts/accordion/accordion.contract.json",
|
|
415
415
|
component: "accordion"
|
|
416
416
|
},
|
|
417
417
|
tabs: {
|
|
418
|
-
path: "./contracts/
|
|
418
|
+
path: "./aria-contracts/tabs/tabs.contract.json",
|
|
419
419
|
component: "tabs"
|
|
420
420
|
}
|
|
421
421
|
};
|
|
422
422
|
}
|
|
423
423
|
});
|
|
424
424
|
|
|
425
|
-
// src/utils/test/
|
|
425
|
+
// src/utils/test/src/ContractReporter.ts
|
|
426
426
|
var ContractReporter;
|
|
427
427
|
var init_ContractReporter = __esm({
|
|
428
|
-
"src/utils/test/
|
|
428
|
+
"src/utils/test/src/ContractReporter.ts"() {
|
|
429
429
|
"use strict";
|
|
430
430
|
ContractReporter = class {
|
|
431
431
|
startTime = 0;
|
|
@@ -438,6 +438,8 @@ var init_ContractReporter = __esm({
|
|
|
438
438
|
optionalSuggestions = 0;
|
|
439
439
|
isPlaywright = false;
|
|
440
440
|
apgUrl = "https://www.w3.org/WAI/ARIA/apg/";
|
|
441
|
+
hasPrintedStaticSection = false;
|
|
442
|
+
hasPrintedDynamicSection = false;
|
|
441
443
|
constructor(isPlaywright = false) {
|
|
442
444
|
this.isPlaywright = isPlaywright;
|
|
443
445
|
}
|
|
@@ -448,30 +450,32 @@ var init_ContractReporter = __esm({
|
|
|
448
450
|
this.startTime = Date.now();
|
|
449
451
|
this.componentName = componentName;
|
|
450
452
|
this.totalTests = totalTests;
|
|
453
|
+
this.hasPrintedStaticSection = false;
|
|
454
|
+
this.hasPrintedDynamicSection = false;
|
|
451
455
|
if (apgUrl) {
|
|
452
456
|
this.apgUrl = apgUrl;
|
|
453
457
|
}
|
|
454
458
|
const mode = this.isPlaywright ? "Playwright (Real Browser)" : "jsdom (Fast)";
|
|
455
459
|
this.log(`
|
|
456
460
|
${"\u2550".repeat(60)}`);
|
|
457
|
-
this.log(`\u{1F50D} Testing ${componentName} Component - ${mode}`);
|
|
461
|
+
this.log(`\u{1F50D} Testing ${componentName.charAt(0).toUpperCase() + componentName.slice(1)} Component - ${mode}`);
|
|
458
462
|
this.log(`${"\u2550".repeat(60)}
|
|
459
463
|
`);
|
|
460
464
|
}
|
|
461
465
|
reportStatic(passes, failures) {
|
|
462
466
|
this.staticPasses = passes;
|
|
463
467
|
this.staticFailures = failures;
|
|
464
|
-
const icon = failures === 0 ? "\u2705" : "\u274C";
|
|
465
|
-
const status = failures === 0 ? "PASS" : "FAIL";
|
|
466
|
-
this.log("");
|
|
467
|
-
this.log(`${icon} Static ARIA Tests: ${status}`);
|
|
468
|
-
this.log(` ${passes}/${passes + failures} required attributes present
|
|
469
|
-
`);
|
|
470
468
|
}
|
|
471
469
|
/**
|
|
472
470
|
* Report individual static test pass
|
|
473
471
|
*/
|
|
474
472
|
reportStaticTest(description, passed, failureMessage) {
|
|
473
|
+
if (!this.hasPrintedStaticSection) {
|
|
474
|
+
this.log(`${"\u2500".repeat(60)}`);
|
|
475
|
+
this.log(`\u{1F9EA} Static Assertions`);
|
|
476
|
+
this.log(`${"\u2500".repeat(60)}`);
|
|
477
|
+
this.hasPrintedStaticSection = true;
|
|
478
|
+
}
|
|
475
479
|
const icon = passed ? "\u2713" : "\u2717";
|
|
476
480
|
this.log(` ${icon} ${description}`);
|
|
477
481
|
if (!passed && failureMessage) {
|
|
@@ -482,13 +486,20 @@ ${"\u2550".repeat(60)}`);
|
|
|
482
486
|
* Report individual dynamic test result
|
|
483
487
|
*/
|
|
484
488
|
reportTest(test, status, failureMessage) {
|
|
489
|
+
if (!this.hasPrintedDynamicSection) {
|
|
490
|
+
this.log("");
|
|
491
|
+
this.log(`${"\u2500".repeat(60)}`);
|
|
492
|
+
this.log(`\u2328\uFE0F Dynamic Interaction Tests`);
|
|
493
|
+
this.log(`${"\u2500".repeat(60)}`);
|
|
494
|
+
this.hasPrintedDynamicSection = true;
|
|
495
|
+
}
|
|
485
496
|
const result = {
|
|
486
497
|
description: test.description,
|
|
487
498
|
status,
|
|
488
499
|
failureMessage,
|
|
489
500
|
isOptional: test.isOptional
|
|
490
501
|
};
|
|
491
|
-
if (status === "skip"
|
|
502
|
+
if (status === "skip") {
|
|
492
503
|
result.skipReason = "Requires real browser (addEventListener events)";
|
|
493
504
|
}
|
|
494
505
|
this.dynamicResults.push(result);
|
|
@@ -568,7 +579,7 @@ ${"\u2500".repeat(60)}`);
|
|
|
568
579
|
});
|
|
569
580
|
this.log(`
|
|
570
581
|
\u{1F4A1} Run with Playwright for full validation:`);
|
|
571
|
-
this.log(` testUiComponent('${this.componentName}',
|
|
582
|
+
this.log(` testUiComponent('${this.componentName}', null, 'http://localhost:5173/test-harness?component=component_name')
|
|
572
583
|
`);
|
|
573
584
|
}
|
|
574
585
|
/**
|
|
@@ -592,9 +603,14 @@ ${"\u2500".repeat(60)}`);
|
|
|
592
603
|
${"\u2550".repeat(60)}`);
|
|
593
604
|
this.log(`\u{1F4CA} Summary
|
|
594
605
|
`);
|
|
606
|
+
const staticIcon = this.staticFailures === 0 ? "\u2705" : "\u274C";
|
|
607
|
+
const staticStatus = this.staticFailures === 0 ? "PASS" : "FAIL";
|
|
608
|
+
this.log(`${staticIcon} Static ARIA Tests: ${staticStatus}`);
|
|
609
|
+
this.log(` ${this.staticPasses}/${this.staticPasses + this.staticFailures} required attributes present`);
|
|
610
|
+
this.log("");
|
|
595
611
|
if (totalFailures === 0 && this.skipped === 0 && this.optionalSuggestions === 0) {
|
|
596
612
|
this.log(`\u2705 All ${totalRun} tests passed!`);
|
|
597
|
-
this.log(` ${this.componentName} component meets WAI-ARIA expectations for Roles, States, Properties, and Keyboard
|
|
613
|
+
this.log(` ${this.componentName.charAt(0).toUpperCase()}${this.componentName.slice(1)} component meets WAI-ARIA expectations for Roles, States, Properties, and Keyboard Interactions \u2713`);
|
|
598
614
|
} else if (totalFailures === 0) {
|
|
599
615
|
this.log(`\u2705 ${totalPasses}/${totalRun} required tests passed`);
|
|
600
616
|
if (this.skipped > 0) {
|
|
@@ -603,7 +619,7 @@ ${"\u2550".repeat(60)}`);
|
|
|
603
619
|
if (this.optionalSuggestions > 0) {
|
|
604
620
|
this.log(`\u{1F4A1} ${this.optionalSuggestions} optional enhancement${this.optionalSuggestions > 1 ? "s" : ""} suggested`);
|
|
605
621
|
}
|
|
606
|
-
this.log(` ${this.componentName} component meets WAI-ARIA expectations for Roles, States, Properties, and Keyboard
|
|
622
|
+
this.log(` ${this.componentName.charAt(0).toUpperCase()}${this.componentName.slice(1)} component meets WAI-ARIA expectations for Roles, States, Properties, and Keyboard Interactions \u2713`);
|
|
607
623
|
} else {
|
|
608
624
|
this.log(`\u274C ${totalFailures} test${totalFailures > 1 ? "s" : ""} failed`);
|
|
609
625
|
this.log(`\u2705 ${totalPasses} test${totalPasses > 1 ? "s" : ""} passed`);
|
|
@@ -649,7 +665,7 @@ ${"\u2550".repeat(60)}`);
|
|
|
649
665
|
}
|
|
650
666
|
});
|
|
651
667
|
|
|
652
|
-
// src/utils/test/
|
|
668
|
+
// src/utils/test/src/contractTestRunner.ts
|
|
653
669
|
async function runContractTests(componentName, component) {
|
|
654
670
|
const reporter = new ContractReporter(false);
|
|
655
671
|
const contractTyped = contract_default;
|
|
@@ -665,6 +681,7 @@ async function runContractTests(componentName, component) {
|
|
|
665
681
|
const failures = [];
|
|
666
682
|
const passes = [];
|
|
667
683
|
const skipped = [];
|
|
684
|
+
const failuresBeforeStatic = failures.length;
|
|
668
685
|
for (const test of componentContract.static[0].assertions) {
|
|
669
686
|
if (test.target !== "relative") {
|
|
670
687
|
const selector = componentContract.selectors[test.target];
|
|
@@ -703,15 +720,16 @@ async function runContractTests(componentName, component) {
|
|
|
703
720
|
skipped.push(dynamicTest.description);
|
|
704
721
|
reporter.reportTest(dynamicTest, "skip");
|
|
705
722
|
}
|
|
706
|
-
const
|
|
707
|
-
const staticFailed =
|
|
723
|
+
const staticTotal = componentContract.static[0].assertions.length;
|
|
724
|
+
const staticFailed = failures.length - failuresBeforeStatic;
|
|
725
|
+
const staticPassed = Math.max(0, staticTotal - staticFailed);
|
|
708
726
|
reporter.reportStatic(staticPassed, staticFailed);
|
|
709
727
|
reporter.summary(failures);
|
|
710
728
|
return { passes, failures, skipped };
|
|
711
729
|
}
|
|
712
730
|
var import_promises, import_meta;
|
|
713
731
|
var init_contractTestRunner = __esm({
|
|
714
|
-
"src/utils/test/
|
|
732
|
+
"src/utils/test/src/contractTestRunner.ts"() {
|
|
715
733
|
"use strict";
|
|
716
734
|
init_contract();
|
|
717
735
|
import_promises = __toESM(require("fs/promises"), 1);
|
|
@@ -720,7 +738,7 @@ var init_contractTestRunner = __esm({
|
|
|
720
738
|
}
|
|
721
739
|
});
|
|
722
740
|
|
|
723
|
-
// src/utils/test/
|
|
741
|
+
// src/utils/test/src/playwrightTestHarness.ts
|
|
724
742
|
async function getOrCreateBrowser() {
|
|
725
743
|
if (!sharedBrowser) {
|
|
726
744
|
sharedBrowser = await import_playwright3.chromium.launch({
|
|
@@ -762,7 +780,7 @@ async function closeSharedBrowser() {
|
|
|
762
780
|
}
|
|
763
781
|
var import_playwright3, sharedBrowser, sharedContext;
|
|
764
782
|
var init_playwrightTestHarness = __esm({
|
|
765
|
-
"src/utils/test/
|
|
783
|
+
"src/utils/test/src/playwrightTestHarness.ts"() {
|
|
766
784
|
"use strict";
|
|
767
785
|
import_playwright3 = require("playwright");
|
|
768
786
|
sharedBrowser = null;
|
|
@@ -784,7 +802,779 @@ var init_test = __esm({
|
|
|
784
802
|
}
|
|
785
803
|
});
|
|
786
804
|
|
|
787
|
-
// src/utils/test/
|
|
805
|
+
// src/utils/test/src/component-strategies/ComboboxComponentStrategy.ts
|
|
806
|
+
var ComboboxComponentStrategy;
|
|
807
|
+
var init_ComboboxComponentStrategy = __esm({
|
|
808
|
+
"src/utils/test/src/component-strategies/ComboboxComponentStrategy.ts"() {
|
|
809
|
+
"use strict";
|
|
810
|
+
init_test();
|
|
811
|
+
ComboboxComponentStrategy = class {
|
|
812
|
+
constructor(mainSelector, selectors, actionTimeoutMs = 400, assertionTimeoutMs = 400) {
|
|
813
|
+
this.mainSelector = mainSelector;
|
|
814
|
+
this.selectors = selectors;
|
|
815
|
+
this.actionTimeoutMs = actionTimeoutMs;
|
|
816
|
+
this.assertionTimeoutMs = assertionTimeoutMs;
|
|
817
|
+
}
|
|
818
|
+
async resetState(page) {
|
|
819
|
+
if (!this.selectors.popup) return;
|
|
820
|
+
const popupSelector = this.selectors.popup;
|
|
821
|
+
const popupElement = page.locator(popupSelector).first();
|
|
822
|
+
const isPopupVisible = await popupElement.isVisible().catch(() => false);
|
|
823
|
+
if (!isPopupVisible) return;
|
|
824
|
+
let menuClosed = false;
|
|
825
|
+
let closeSelector = this.selectors.input;
|
|
826
|
+
if (!closeSelector && this.selectors.focusable) {
|
|
827
|
+
closeSelector = this.selectors.focusable;
|
|
828
|
+
} else if (!closeSelector) {
|
|
829
|
+
closeSelector = this.selectors.trigger;
|
|
830
|
+
}
|
|
831
|
+
if (closeSelector) {
|
|
832
|
+
const closeElement = page.locator(closeSelector).first();
|
|
833
|
+
await closeElement.focus();
|
|
834
|
+
await page.keyboard.press("Escape");
|
|
835
|
+
menuClosed = await (0, test_exports.expect)(popupElement).toBeHidden({ timeout: this.assertionTimeoutMs }).then(() => true).catch(() => false);
|
|
836
|
+
}
|
|
837
|
+
if (!menuClosed && this.selectors.trigger) {
|
|
838
|
+
const triggerElement = page.locator(this.selectors.trigger).first();
|
|
839
|
+
await triggerElement.click({ timeout: this.actionTimeoutMs });
|
|
840
|
+
menuClosed = await (0, test_exports.expect)(popupElement).toBeHidden({ timeout: this.assertionTimeoutMs }).then(() => true).catch(() => false);
|
|
841
|
+
}
|
|
842
|
+
if (!menuClosed) {
|
|
843
|
+
await page.mouse.click(10, 10);
|
|
844
|
+
menuClosed = await (0, test_exports.expect)(popupElement).toBeHidden({ timeout: this.assertionTimeoutMs }).then(() => true).catch(() => false);
|
|
845
|
+
}
|
|
846
|
+
if (!menuClosed) {
|
|
847
|
+
throw new Error(
|
|
848
|
+
`\u274C FATAL: Cannot close combobox popup between tests. Popup remains visible after trying:
|
|
849
|
+
1. Escape key
|
|
850
|
+
2. Clicking trigger
|
|
851
|
+
3. Clicking outside
|
|
852
|
+
This indicates a problem with the combobox component's close functionality.`
|
|
853
|
+
);
|
|
854
|
+
}
|
|
855
|
+
if (this.selectors.input) {
|
|
856
|
+
await page.locator(this.selectors.input).first().clear();
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
async shouldSkipTest() {
|
|
860
|
+
return false;
|
|
861
|
+
}
|
|
862
|
+
getMainSelector() {
|
|
863
|
+
return this.mainSelector;
|
|
864
|
+
}
|
|
865
|
+
};
|
|
866
|
+
}
|
|
867
|
+
});
|
|
868
|
+
|
|
869
|
+
// src/utils/test/src/component-strategies/AccordionComponentStrategy.ts
|
|
870
|
+
var AccordionComponentStrategy;
|
|
871
|
+
var init_AccordionComponentStrategy = __esm({
|
|
872
|
+
"src/utils/test/src/component-strategies/AccordionComponentStrategy.ts"() {
|
|
873
|
+
"use strict";
|
|
874
|
+
init_test();
|
|
875
|
+
AccordionComponentStrategy = class {
|
|
876
|
+
constructor(mainSelector, selectors, actionTimeoutMs = 400, assertionTimeoutMs = 400) {
|
|
877
|
+
this.mainSelector = mainSelector;
|
|
878
|
+
this.selectors = selectors;
|
|
879
|
+
this.actionTimeoutMs = actionTimeoutMs;
|
|
880
|
+
this.assertionTimeoutMs = assertionTimeoutMs;
|
|
881
|
+
}
|
|
882
|
+
async resetState(page) {
|
|
883
|
+
if (!this.selectors.panel || !this.selectors.trigger || this.selectors.popup) {
|
|
884
|
+
return;
|
|
885
|
+
}
|
|
886
|
+
const triggerSelector = this.selectors.trigger;
|
|
887
|
+
const panelSelector = this.selectors.panel;
|
|
888
|
+
if (!triggerSelector || !panelSelector) return;
|
|
889
|
+
const allTriggers = await page.locator(triggerSelector).all();
|
|
890
|
+
for (const trigger of allTriggers) {
|
|
891
|
+
const isExpanded = await trigger.getAttribute("aria-expanded") === "true";
|
|
892
|
+
const triggerPanel = await trigger.getAttribute("aria-controls");
|
|
893
|
+
if (isExpanded && triggerPanel) {
|
|
894
|
+
await trigger.click({ timeout: this.actionTimeoutMs });
|
|
895
|
+
const panel = page.locator(`#${triggerPanel}`);
|
|
896
|
+
await (0, test_exports.expect)(panel).toBeHidden({ timeout: this.assertionTimeoutMs }).catch(() => {
|
|
897
|
+
});
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
async shouldSkipTest() {
|
|
902
|
+
return false;
|
|
903
|
+
}
|
|
904
|
+
getMainSelector() {
|
|
905
|
+
return this.mainSelector;
|
|
906
|
+
}
|
|
907
|
+
};
|
|
908
|
+
}
|
|
909
|
+
});
|
|
910
|
+
|
|
911
|
+
// src/utils/test/src/component-strategies/MenuComponentStrategy.ts
|
|
912
|
+
var MenuComponentStrategy;
|
|
913
|
+
var init_MenuComponentStrategy = __esm({
|
|
914
|
+
"src/utils/test/src/component-strategies/MenuComponentStrategy.ts"() {
|
|
915
|
+
"use strict";
|
|
916
|
+
init_test();
|
|
917
|
+
MenuComponentStrategy = class {
|
|
918
|
+
constructor(mainSelector, selectors, actionTimeoutMs = 400, assertionTimeoutMs = 400) {
|
|
919
|
+
this.mainSelector = mainSelector;
|
|
920
|
+
this.selectors = selectors;
|
|
921
|
+
this.actionTimeoutMs = actionTimeoutMs;
|
|
922
|
+
this.assertionTimeoutMs = assertionTimeoutMs;
|
|
923
|
+
}
|
|
924
|
+
async resetState(page) {
|
|
925
|
+
if (!this.selectors.popup) return;
|
|
926
|
+
const popupSelector = this.selectors.popup;
|
|
927
|
+
const popupElement = page.locator(popupSelector).first();
|
|
928
|
+
const isPopupVisible = await popupElement.isVisible().catch(() => false);
|
|
929
|
+
if (!isPopupVisible) return;
|
|
930
|
+
let menuClosed = false;
|
|
931
|
+
let closeSelector = this.selectors.input;
|
|
932
|
+
if (!closeSelector && this.selectors.focusable) {
|
|
933
|
+
closeSelector = this.selectors.focusable;
|
|
934
|
+
} else if (!closeSelector) {
|
|
935
|
+
closeSelector = this.selectors.trigger;
|
|
936
|
+
}
|
|
937
|
+
if (closeSelector) {
|
|
938
|
+
const closeElement = page.locator(closeSelector).first();
|
|
939
|
+
await closeElement.focus();
|
|
940
|
+
await page.keyboard.press("Escape");
|
|
941
|
+
menuClosed = await (0, test_exports.expect)(popupElement).toBeHidden({ timeout: this.assertionTimeoutMs }).then(() => true).catch(() => false);
|
|
942
|
+
}
|
|
943
|
+
if (!menuClosed && this.selectors.trigger) {
|
|
944
|
+
const triggerElement = page.locator(this.selectors.trigger).first();
|
|
945
|
+
await triggerElement.click({ timeout: this.actionTimeoutMs });
|
|
946
|
+
menuClosed = await (0, test_exports.expect)(popupElement).toBeHidden({ timeout: this.assertionTimeoutMs }).then(() => true).catch(() => false);
|
|
947
|
+
}
|
|
948
|
+
if (!menuClosed) {
|
|
949
|
+
await page.mouse.click(10, 10);
|
|
950
|
+
menuClosed = await (0, test_exports.expect)(popupElement).toBeHidden({ timeout: this.assertionTimeoutMs }).then(() => true).catch(() => false);
|
|
951
|
+
}
|
|
952
|
+
if (!menuClosed) {
|
|
953
|
+
throw new Error(
|
|
954
|
+
`\u274C FATAL: Cannot close menu between tests. Menu remains visible after trying:
|
|
955
|
+
1. Escape key
|
|
956
|
+
2. Clicking trigger
|
|
957
|
+
3. Clicking outside
|
|
958
|
+
This indicates a problem with the menu component's close functionality.`
|
|
959
|
+
);
|
|
960
|
+
}
|
|
961
|
+
if (this.selectors.input) {
|
|
962
|
+
await page.locator(this.selectors.input).first().clear();
|
|
963
|
+
}
|
|
964
|
+
if (this.selectors.trigger) {
|
|
965
|
+
const triggerElement = page.locator(this.selectors.trigger).first();
|
|
966
|
+
await triggerElement.focus();
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
async shouldSkipTest(test, page) {
|
|
970
|
+
for (const act of test.action) {
|
|
971
|
+
if (act.type === "keypress" && (act.target === "submenuTrigger" || act.target === "submenu")) {
|
|
972
|
+
const submenuSelector = this.selectors[act.target];
|
|
973
|
+
if (submenuSelector) {
|
|
974
|
+
const submenuCount = await page.locator(submenuSelector).count();
|
|
975
|
+
if (submenuCount === 0) {
|
|
976
|
+
return true;
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
for (const assertion of test.assertions) {
|
|
982
|
+
if (assertion.target === "submenu" || assertion.target === "submenuTrigger") {
|
|
983
|
+
const submenuSelector = this.selectors[assertion.target];
|
|
984
|
+
if (submenuSelector) {
|
|
985
|
+
const submenuCount = await page.locator(submenuSelector).count();
|
|
986
|
+
if (submenuCount === 0) {
|
|
987
|
+
return true;
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
return false;
|
|
993
|
+
}
|
|
994
|
+
getMainSelector() {
|
|
995
|
+
return this.mainSelector;
|
|
996
|
+
}
|
|
997
|
+
};
|
|
998
|
+
}
|
|
999
|
+
});
|
|
1000
|
+
|
|
1001
|
+
// src/utils/test/src/component-strategies/TabsComponentStrategy.ts
|
|
1002
|
+
var TabsComponentStrategy;
|
|
1003
|
+
var init_TabsComponentStrategy = __esm({
|
|
1004
|
+
"src/utils/test/src/component-strategies/TabsComponentStrategy.ts"() {
|
|
1005
|
+
"use strict";
|
|
1006
|
+
TabsComponentStrategy = class {
|
|
1007
|
+
constructor(mainSelector, selectors) {
|
|
1008
|
+
this.mainSelector = mainSelector;
|
|
1009
|
+
this.selectors = selectors;
|
|
1010
|
+
}
|
|
1011
|
+
async resetState() {
|
|
1012
|
+
}
|
|
1013
|
+
async shouldSkipTest(test, page) {
|
|
1014
|
+
if (test.isVertical !== void 0 && this.selectors.tablist) {
|
|
1015
|
+
const tablistSelector = this.selectors.tablist;
|
|
1016
|
+
const tablist = page.locator(tablistSelector).first();
|
|
1017
|
+
const orientation = await tablist.getAttribute("aria-orientation");
|
|
1018
|
+
const isVertical = orientation === "vertical";
|
|
1019
|
+
if (test.isVertical !== isVertical) {
|
|
1020
|
+
return true;
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
return false;
|
|
1024
|
+
}
|
|
1025
|
+
getMainSelector() {
|
|
1026
|
+
return this.mainSelector;
|
|
1027
|
+
}
|
|
1028
|
+
};
|
|
1029
|
+
}
|
|
1030
|
+
});
|
|
1031
|
+
|
|
1032
|
+
// src/utils/test/src/ComponentDetector.ts
|
|
1033
|
+
var import_fs, import_meta2, ComponentDetector;
|
|
1034
|
+
var init_ComponentDetector = __esm({
|
|
1035
|
+
"src/utils/test/src/ComponentDetector.ts"() {
|
|
1036
|
+
"use strict";
|
|
1037
|
+
init_ComboboxComponentStrategy();
|
|
1038
|
+
init_AccordionComponentStrategy();
|
|
1039
|
+
init_MenuComponentStrategy();
|
|
1040
|
+
init_TabsComponentStrategy();
|
|
1041
|
+
import_fs = require("fs");
|
|
1042
|
+
init_contract();
|
|
1043
|
+
import_meta2 = {};
|
|
1044
|
+
ComponentDetector = class {
|
|
1045
|
+
static detect(componentName, actionTimeoutMs = 400, assertionTimeoutMs = 400) {
|
|
1046
|
+
const contractTyped = contract_default;
|
|
1047
|
+
const contractPath = contractTyped[componentName]?.path;
|
|
1048
|
+
if (!contractPath) {
|
|
1049
|
+
throw new Error(`Contract path not found for component: ${componentName}`);
|
|
1050
|
+
}
|
|
1051
|
+
const resolvedPath = new URL(contractPath, import_meta2.url).pathname;
|
|
1052
|
+
const contractData = (0, import_fs.readFileSync)(resolvedPath, "utf-8");
|
|
1053
|
+
const componentContract = JSON.parse(contractData);
|
|
1054
|
+
const selectors = componentContract.selectors;
|
|
1055
|
+
if (componentName.includes("combobox")) {
|
|
1056
|
+
const mainSelector = selectors.input || selectors.container;
|
|
1057
|
+
return new ComboboxComponentStrategy(mainSelector, selectors, actionTimeoutMs, assertionTimeoutMs);
|
|
1058
|
+
}
|
|
1059
|
+
if (componentName === "accordion") {
|
|
1060
|
+
const mainSelector = selectors.trigger || selectors.container;
|
|
1061
|
+
return new AccordionComponentStrategy(mainSelector, selectors, actionTimeoutMs, assertionTimeoutMs);
|
|
1062
|
+
}
|
|
1063
|
+
if (componentName === "menu") {
|
|
1064
|
+
const mainSelector = selectors.trigger || selectors.container;
|
|
1065
|
+
return new MenuComponentStrategy(mainSelector, selectors, actionTimeoutMs, assertionTimeoutMs);
|
|
1066
|
+
}
|
|
1067
|
+
if (componentName === "tabs") {
|
|
1068
|
+
const mainSelector = selectors.tablist || selectors.tab;
|
|
1069
|
+
return new TabsComponentStrategy(mainSelector, selectors);
|
|
1070
|
+
}
|
|
1071
|
+
return null;
|
|
1072
|
+
}
|
|
1073
|
+
};
|
|
1074
|
+
}
|
|
1075
|
+
});
|
|
1076
|
+
|
|
1077
|
+
// src/utils/test/src/RelativeTargetResolver.ts
|
|
1078
|
+
var RelativeTargetResolver;
|
|
1079
|
+
var init_RelativeTargetResolver = __esm({
|
|
1080
|
+
"src/utils/test/src/RelativeTargetResolver.ts"() {
|
|
1081
|
+
"use strict";
|
|
1082
|
+
RelativeTargetResolver = class {
|
|
1083
|
+
/**
|
|
1084
|
+
* Resolve a relative target like "first", "second", "last", "next", "previous"
|
|
1085
|
+
* @param page Playwright page instance
|
|
1086
|
+
* @param selector Base selector to find elements
|
|
1087
|
+
* @param relative Relative position (first, second, last, next, previous)
|
|
1088
|
+
* @returns The resolved Locator or null if not found
|
|
1089
|
+
*/
|
|
1090
|
+
static async resolve(page, selector, relative) {
|
|
1091
|
+
const items = await page.locator(selector).all();
|
|
1092
|
+
switch (relative) {
|
|
1093
|
+
case "first":
|
|
1094
|
+
return items[0];
|
|
1095
|
+
case "second":
|
|
1096
|
+
return items[1];
|
|
1097
|
+
case "last":
|
|
1098
|
+
return items[items.length - 1];
|
|
1099
|
+
case "next": {
|
|
1100
|
+
const currentIndex = await page.evaluate(([sel]) => {
|
|
1101
|
+
const items2 = Array.from(document.querySelectorAll(sel));
|
|
1102
|
+
return items2.indexOf(document.activeElement);
|
|
1103
|
+
}, [selector]);
|
|
1104
|
+
const nextIndex = (currentIndex + 1) % items.length;
|
|
1105
|
+
return items[nextIndex];
|
|
1106
|
+
}
|
|
1107
|
+
case "previous": {
|
|
1108
|
+
const currentIndex = await page.evaluate(([sel]) => {
|
|
1109
|
+
const items2 = Array.from(document.querySelectorAll(sel));
|
|
1110
|
+
return items2.indexOf(document.activeElement);
|
|
1111
|
+
}, [selector]);
|
|
1112
|
+
const prevIndex = (currentIndex - 1 + items.length) % items.length;
|
|
1113
|
+
return items[prevIndex];
|
|
1114
|
+
}
|
|
1115
|
+
default:
|
|
1116
|
+
return null;
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
};
|
|
1120
|
+
}
|
|
1121
|
+
});
|
|
1122
|
+
|
|
1123
|
+
// src/utils/test/src/ActionExecutor.ts
|
|
1124
|
+
var ActionExecutor;
|
|
1125
|
+
var init_ActionExecutor = __esm({
|
|
1126
|
+
"src/utils/test/src/ActionExecutor.ts"() {
|
|
1127
|
+
"use strict";
|
|
1128
|
+
init_RelativeTargetResolver();
|
|
1129
|
+
ActionExecutor = class {
|
|
1130
|
+
constructor(page, selectors, timeoutMs = 400) {
|
|
1131
|
+
this.page = page;
|
|
1132
|
+
this.selectors = selectors;
|
|
1133
|
+
this.timeoutMs = timeoutMs;
|
|
1134
|
+
}
|
|
1135
|
+
/**
|
|
1136
|
+
* Check if error is due to browser/page being closed
|
|
1137
|
+
*/
|
|
1138
|
+
isBrowserClosedError(error) {
|
|
1139
|
+
return error instanceof Error && error.message.includes("Target page, context or browser has been closed");
|
|
1140
|
+
}
|
|
1141
|
+
/**
|
|
1142
|
+
* Execute focus action
|
|
1143
|
+
*/
|
|
1144
|
+
async focus(target) {
|
|
1145
|
+
try {
|
|
1146
|
+
const selector = this.selectors[target];
|
|
1147
|
+
if (!selector) {
|
|
1148
|
+
return { success: false, error: `Selector for focus target ${target} not found.` };
|
|
1149
|
+
}
|
|
1150
|
+
await this.page.locator(selector).first().focus({ timeout: this.timeoutMs });
|
|
1151
|
+
return { success: true };
|
|
1152
|
+
} catch (error) {
|
|
1153
|
+
if (this.isBrowserClosedError(error)) {
|
|
1154
|
+
return {
|
|
1155
|
+
success: false,
|
|
1156
|
+
error: `CRITICAL: Browser/page closed during test execution. Remaining actions skipped.`,
|
|
1157
|
+
shouldBreak: true
|
|
1158
|
+
};
|
|
1159
|
+
}
|
|
1160
|
+
return {
|
|
1161
|
+
success: false,
|
|
1162
|
+
error: `Failed to focus ${target}: ${error instanceof Error ? error.message : String(error)}`
|
|
1163
|
+
};
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
/**
|
|
1167
|
+
* Execute type/fill action
|
|
1168
|
+
*/
|
|
1169
|
+
async type(target, value) {
|
|
1170
|
+
try {
|
|
1171
|
+
const selector = this.selectors[target];
|
|
1172
|
+
if (!selector) {
|
|
1173
|
+
return { success: false, error: `Selector for type target ${target} not found.` };
|
|
1174
|
+
}
|
|
1175
|
+
await this.page.locator(selector).first().fill(value, { timeout: this.timeoutMs });
|
|
1176
|
+
return { success: true };
|
|
1177
|
+
} catch (error) {
|
|
1178
|
+
if (this.isBrowserClosedError(error)) {
|
|
1179
|
+
return {
|
|
1180
|
+
success: false,
|
|
1181
|
+
error: `CRITICAL: Browser/page closed during test execution. Remaining actions skipped.`,
|
|
1182
|
+
shouldBreak: true
|
|
1183
|
+
};
|
|
1184
|
+
}
|
|
1185
|
+
return {
|
|
1186
|
+
success: false,
|
|
1187
|
+
error: `Failed to type into ${target}: ${error instanceof Error ? error.message : String(error)}`
|
|
1188
|
+
};
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
/**
|
|
1192
|
+
* Execute click action
|
|
1193
|
+
*/
|
|
1194
|
+
async click(target, relativeTarget) {
|
|
1195
|
+
try {
|
|
1196
|
+
if (target === "document") {
|
|
1197
|
+
await this.page.mouse.click(10, 10);
|
|
1198
|
+
return { success: true };
|
|
1199
|
+
}
|
|
1200
|
+
if (target === "relative" && relativeTarget) {
|
|
1201
|
+
const relativeSelector = this.selectors.relative;
|
|
1202
|
+
if (!relativeSelector) {
|
|
1203
|
+
return { success: false, error: `Relative selector not defined for click action.` };
|
|
1204
|
+
}
|
|
1205
|
+
const element = await RelativeTargetResolver.resolve(this.page, relativeSelector, relativeTarget);
|
|
1206
|
+
if (!element) {
|
|
1207
|
+
return { success: false, error: `Could not resolve relative target ${relativeTarget} for click.` };
|
|
1208
|
+
}
|
|
1209
|
+
await element.click({ timeout: this.timeoutMs });
|
|
1210
|
+
return { success: true };
|
|
1211
|
+
}
|
|
1212
|
+
const selector = this.selectors[target];
|
|
1213
|
+
if (!selector) {
|
|
1214
|
+
return { success: false, error: `Selector for action target ${target} not found.` };
|
|
1215
|
+
}
|
|
1216
|
+
await this.page.locator(selector).first().click({ timeout: this.timeoutMs });
|
|
1217
|
+
return { success: true };
|
|
1218
|
+
} catch (error) {
|
|
1219
|
+
if (this.isBrowserClosedError(error)) {
|
|
1220
|
+
return {
|
|
1221
|
+
success: false,
|
|
1222
|
+
error: `CRITICAL: Browser/page closed during test execution. Remaining actions skipped.`,
|
|
1223
|
+
shouldBreak: true
|
|
1224
|
+
};
|
|
1225
|
+
}
|
|
1226
|
+
return {
|
|
1227
|
+
success: false,
|
|
1228
|
+
error: `Failed to click ${target}: ${error instanceof Error ? error.message : String(error)}`
|
|
1229
|
+
};
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
/**
|
|
1233
|
+
* Execute keypress action
|
|
1234
|
+
*/
|
|
1235
|
+
async keypress(target, key) {
|
|
1236
|
+
try {
|
|
1237
|
+
const keyMap = {
|
|
1238
|
+
"Space": "Space",
|
|
1239
|
+
"Enter": "Enter",
|
|
1240
|
+
"Escape": "Escape",
|
|
1241
|
+
"Arrow Up": "ArrowUp",
|
|
1242
|
+
"Arrow Down": "ArrowDown",
|
|
1243
|
+
"Arrow Left": "ArrowLeft",
|
|
1244
|
+
"Arrow Right": "ArrowRight",
|
|
1245
|
+
"Home": "Home",
|
|
1246
|
+
"End": "End",
|
|
1247
|
+
"Tab": "Tab"
|
|
1248
|
+
};
|
|
1249
|
+
let keyValue = keyMap[key] || key;
|
|
1250
|
+
if (keyValue === "Space") {
|
|
1251
|
+
keyValue = " ";
|
|
1252
|
+
} else if (keyValue.includes(" ")) {
|
|
1253
|
+
keyValue = keyValue.replace(/ /g, "");
|
|
1254
|
+
}
|
|
1255
|
+
if (target === "focusable" && ["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight", "Escape"].includes(keyValue)) {
|
|
1256
|
+
await this.page.keyboard.press(keyValue);
|
|
1257
|
+
return { success: true };
|
|
1258
|
+
}
|
|
1259
|
+
const selector = this.selectors[target];
|
|
1260
|
+
if (!selector) {
|
|
1261
|
+
return { success: false, error: `Selector for keypress target ${target} not found.` };
|
|
1262
|
+
}
|
|
1263
|
+
const locator = this.page.locator(selector).first();
|
|
1264
|
+
const elementCount = await locator.count();
|
|
1265
|
+
if (elementCount === 0) {
|
|
1266
|
+
return {
|
|
1267
|
+
success: false,
|
|
1268
|
+
error: `${target} element not found (optional submenu test)`,
|
|
1269
|
+
shouldBreak: true
|
|
1270
|
+
// Signal to skip this test
|
|
1271
|
+
};
|
|
1272
|
+
}
|
|
1273
|
+
await locator.press(keyValue, { timeout: this.timeoutMs });
|
|
1274
|
+
return { success: true };
|
|
1275
|
+
} catch (error) {
|
|
1276
|
+
if (this.isBrowserClosedError(error)) {
|
|
1277
|
+
return {
|
|
1278
|
+
success: false,
|
|
1279
|
+
error: `CRITICAL: Browser/page closed during test execution. Remaining actions skipped.`,
|
|
1280
|
+
shouldBreak: true
|
|
1281
|
+
};
|
|
1282
|
+
}
|
|
1283
|
+
return {
|
|
1284
|
+
success: false,
|
|
1285
|
+
error: `Failed to press ${key} on ${target}: ${error instanceof Error ? error.message : String(error)}`
|
|
1286
|
+
};
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
/**
|
|
1290
|
+
* Execute hover action
|
|
1291
|
+
*/
|
|
1292
|
+
async hover(target, relativeTarget) {
|
|
1293
|
+
try {
|
|
1294
|
+
if (target === "relative" && relativeTarget) {
|
|
1295
|
+
const relativeSelector = this.selectors.relative;
|
|
1296
|
+
if (!relativeSelector) {
|
|
1297
|
+
return { success: false, error: `Relative selector not defined for hover action.` };
|
|
1298
|
+
}
|
|
1299
|
+
const element = await RelativeTargetResolver.resolve(this.page, relativeSelector, relativeTarget);
|
|
1300
|
+
if (!element) {
|
|
1301
|
+
return { success: false, error: `Could not resolve relative target ${relativeTarget} for hover.` };
|
|
1302
|
+
}
|
|
1303
|
+
await element.hover({ timeout: this.timeoutMs });
|
|
1304
|
+
return { success: true };
|
|
1305
|
+
}
|
|
1306
|
+
const selector = this.selectors[target];
|
|
1307
|
+
if (!selector) {
|
|
1308
|
+
return { success: false, error: `Selector for hover target ${target} not found.` };
|
|
1309
|
+
}
|
|
1310
|
+
await this.page.locator(selector).first().hover({ timeout: this.timeoutMs });
|
|
1311
|
+
return { success: true };
|
|
1312
|
+
} catch (error) {
|
|
1313
|
+
if (this.isBrowserClosedError(error)) {
|
|
1314
|
+
return {
|
|
1315
|
+
success: false,
|
|
1316
|
+
error: `CRITICAL: Browser/page closed during test execution. Remaining actions skipped.`,
|
|
1317
|
+
shouldBreak: true
|
|
1318
|
+
};
|
|
1319
|
+
}
|
|
1320
|
+
return {
|
|
1321
|
+
success: false,
|
|
1322
|
+
error: `Failed to hover ${target}: ${error instanceof Error ? error.message : String(error)}`
|
|
1323
|
+
};
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
};
|
|
1327
|
+
}
|
|
1328
|
+
});
|
|
1329
|
+
|
|
1330
|
+
// src/utils/test/src/AssertionRunner.ts
|
|
1331
|
+
var AssertionRunner;
|
|
1332
|
+
var init_AssertionRunner = __esm({
|
|
1333
|
+
"src/utils/test/src/AssertionRunner.ts"() {
|
|
1334
|
+
"use strict";
|
|
1335
|
+
init_test();
|
|
1336
|
+
init_RelativeTargetResolver();
|
|
1337
|
+
AssertionRunner = class {
|
|
1338
|
+
constructor(page, selectors, timeoutMs = 400) {
|
|
1339
|
+
this.page = page;
|
|
1340
|
+
this.selectors = selectors;
|
|
1341
|
+
this.timeoutMs = timeoutMs;
|
|
1342
|
+
}
|
|
1343
|
+
/**
|
|
1344
|
+
* Resolve the target element for an assertion
|
|
1345
|
+
*/
|
|
1346
|
+
async resolveTarget(targetName, relativeTarget) {
|
|
1347
|
+
try {
|
|
1348
|
+
if (targetName === "relative") {
|
|
1349
|
+
const relativeSelector = this.selectors.relative;
|
|
1350
|
+
if (!relativeSelector) {
|
|
1351
|
+
return { target: null, error: "Relative selector is not defined in the contract." };
|
|
1352
|
+
}
|
|
1353
|
+
if (!relativeTarget) {
|
|
1354
|
+
return { target: null, error: "Relative target or expected value is not defined." };
|
|
1355
|
+
}
|
|
1356
|
+
const target = await RelativeTargetResolver.resolve(this.page, relativeSelector, relativeTarget);
|
|
1357
|
+
if (!target) {
|
|
1358
|
+
return { target: null, error: `Target ${targetName} not found.` };
|
|
1359
|
+
}
|
|
1360
|
+
return { target };
|
|
1361
|
+
}
|
|
1362
|
+
const selector = this.selectors[targetName];
|
|
1363
|
+
if (!selector) {
|
|
1364
|
+
return { target: null, error: `Selector for assertion target ${targetName} not found.` };
|
|
1365
|
+
}
|
|
1366
|
+
return { target: this.page.locator(selector).first() };
|
|
1367
|
+
} catch (error) {
|
|
1368
|
+
return {
|
|
1369
|
+
target: null,
|
|
1370
|
+
error: `Failed to resolve target ${targetName}: ${error instanceof Error ? error.message : String(error)}`
|
|
1371
|
+
};
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
/**
|
|
1375
|
+
* Validate visibility assertion
|
|
1376
|
+
*/
|
|
1377
|
+
async validateVisibility(target, targetName, expectedVisible, failureMessage, testDescription) {
|
|
1378
|
+
try {
|
|
1379
|
+
if (expectedVisible) {
|
|
1380
|
+
await (0, test_exports.expect)(target).toBeVisible({ timeout: this.timeoutMs });
|
|
1381
|
+
return {
|
|
1382
|
+
success: true,
|
|
1383
|
+
passMessage: `${targetName} is visible as expected. Test: "${testDescription}".`
|
|
1384
|
+
};
|
|
1385
|
+
} else {
|
|
1386
|
+
await (0, test_exports.expect)(target).toBeHidden({ timeout: this.timeoutMs });
|
|
1387
|
+
return {
|
|
1388
|
+
success: true,
|
|
1389
|
+
passMessage: `${targetName} is not visible as expected. Test: "${testDescription}".`
|
|
1390
|
+
};
|
|
1391
|
+
}
|
|
1392
|
+
} catch {
|
|
1393
|
+
const selector = this.selectors[targetName] || "";
|
|
1394
|
+
const debugState = await this.page.evaluate((sel) => {
|
|
1395
|
+
const el = sel ? document.querySelector(sel) : null;
|
|
1396
|
+
if (!el) return "element not found";
|
|
1397
|
+
const styles = window.getComputedStyle(el);
|
|
1398
|
+
return `display:${styles.display}, visibility:${styles.visibility}, opacity:${styles.opacity}`;
|
|
1399
|
+
}, selector);
|
|
1400
|
+
if (expectedVisible) {
|
|
1401
|
+
return {
|
|
1402
|
+
success: false,
|
|
1403
|
+
failMessage: `${failureMessage} (actual: ${debugState})`
|
|
1404
|
+
};
|
|
1405
|
+
} else {
|
|
1406
|
+
return {
|
|
1407
|
+
success: false,
|
|
1408
|
+
failMessage: `${failureMessage} ${targetName} is still visible (actual: ${debugState}).`
|
|
1409
|
+
};
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
/**
|
|
1414
|
+
* Validate attribute assertion
|
|
1415
|
+
*/
|
|
1416
|
+
async validateAttribute(target, targetName, attribute, expectedValue, failureMessage, testDescription) {
|
|
1417
|
+
if (expectedValue === "!empty") {
|
|
1418
|
+
const attributeValue2 = await target.getAttribute(attribute);
|
|
1419
|
+
if (attributeValue2 && attributeValue2.trim() !== "") {
|
|
1420
|
+
return {
|
|
1421
|
+
success: true,
|
|
1422
|
+
passMessage: `${targetName} has non-empty "${attribute}". Test: "${testDescription}".`
|
|
1423
|
+
};
|
|
1424
|
+
} else {
|
|
1425
|
+
return {
|
|
1426
|
+
success: false,
|
|
1427
|
+
failMessage: `${failureMessage} ${targetName} "${attribute}" should not be empty, found "${attributeValue2}".`
|
|
1428
|
+
};
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
const expectedValues = expectedValue.split(" | ").map((v) => v.trim());
|
|
1432
|
+
const attributeValue = await target.getAttribute(attribute);
|
|
1433
|
+
if (attributeValue !== null && expectedValues.includes(attributeValue)) {
|
|
1434
|
+
return {
|
|
1435
|
+
success: true,
|
|
1436
|
+
passMessage: `${targetName} has expected "${attribute}". Test: "${testDescription}".`
|
|
1437
|
+
};
|
|
1438
|
+
} else {
|
|
1439
|
+
return {
|
|
1440
|
+
success: false,
|
|
1441
|
+
failMessage: `${failureMessage} ${targetName} "${attribute}" should be "${expectedValue}", found "${attributeValue}".`
|
|
1442
|
+
};
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1445
|
+
/**
|
|
1446
|
+
* Validate input value assertion
|
|
1447
|
+
*/
|
|
1448
|
+
async validateValue(target, targetName, expectedValue, failureMessage, testDescription) {
|
|
1449
|
+
const inputValue = await target.inputValue().catch(() => "");
|
|
1450
|
+
if (expectedValue === "!empty") {
|
|
1451
|
+
if (inputValue && inputValue.trim() !== "") {
|
|
1452
|
+
return {
|
|
1453
|
+
success: true,
|
|
1454
|
+
passMessage: `${targetName} has non-empty value. Test: "${testDescription}".`
|
|
1455
|
+
};
|
|
1456
|
+
} else {
|
|
1457
|
+
return {
|
|
1458
|
+
success: false,
|
|
1459
|
+
failMessage: `${failureMessage} ${targetName} value should not be empty, found "${inputValue}".`
|
|
1460
|
+
};
|
|
1461
|
+
}
|
|
1462
|
+
}
|
|
1463
|
+
if (expectedValue === "") {
|
|
1464
|
+
if (inputValue === "") {
|
|
1465
|
+
return {
|
|
1466
|
+
success: true,
|
|
1467
|
+
passMessage: `${targetName} has empty value. Test: "${testDescription}".`
|
|
1468
|
+
};
|
|
1469
|
+
} else {
|
|
1470
|
+
return {
|
|
1471
|
+
success: false,
|
|
1472
|
+
failMessage: `${failureMessage} ${targetName} value should be empty, found "${inputValue}".`
|
|
1473
|
+
};
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1476
|
+
if (inputValue === expectedValue) {
|
|
1477
|
+
return {
|
|
1478
|
+
success: true,
|
|
1479
|
+
passMessage: `${targetName} has expected value. Test: "${testDescription}".`
|
|
1480
|
+
};
|
|
1481
|
+
} else {
|
|
1482
|
+
return {
|
|
1483
|
+
success: false,
|
|
1484
|
+
failMessage: `${failureMessage} ${targetName} value should be "${expectedValue}", found "${inputValue}".`
|
|
1485
|
+
};
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1488
|
+
/**
|
|
1489
|
+
* Validate focus assertion
|
|
1490
|
+
*/
|
|
1491
|
+
async validateFocus(target, targetName, failureMessage, testDescription) {
|
|
1492
|
+
try {
|
|
1493
|
+
await (0, test_exports.expect)(target).toBeFocused({ timeout: this.timeoutMs });
|
|
1494
|
+
return {
|
|
1495
|
+
success: true,
|
|
1496
|
+
passMessage: `${targetName} has focus as expected. Test: "${testDescription}".`
|
|
1497
|
+
};
|
|
1498
|
+
} catch {
|
|
1499
|
+
const actualFocus = await this.page.evaluate(() => {
|
|
1500
|
+
const focused = document.activeElement;
|
|
1501
|
+
return focused ? `${focused.tagName}#${focused.id || "no-id"}.${focused.className || "no-class"}` : "no element focused";
|
|
1502
|
+
});
|
|
1503
|
+
return {
|
|
1504
|
+
success: false,
|
|
1505
|
+
failMessage: `${failureMessage} (actual focus: ${actualFocus})`
|
|
1506
|
+
};
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
1509
|
+
/**
|
|
1510
|
+
* Validate role assertion
|
|
1511
|
+
*/
|
|
1512
|
+
async validateRole(target, targetName, expectedRole, failureMessage, testDescription) {
|
|
1513
|
+
const roleValue = await target.getAttribute("role");
|
|
1514
|
+
if (roleValue === expectedRole) {
|
|
1515
|
+
return {
|
|
1516
|
+
success: true,
|
|
1517
|
+
passMessage: `${targetName} has role "${expectedRole}". Test: "${testDescription}".`
|
|
1518
|
+
};
|
|
1519
|
+
} else {
|
|
1520
|
+
return {
|
|
1521
|
+
success: false,
|
|
1522
|
+
failMessage: `${failureMessage} Expected role "${expectedRole}", found "${roleValue}".`
|
|
1523
|
+
};
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1526
|
+
/**
|
|
1527
|
+
* Main validation method - routes to specific validators
|
|
1528
|
+
*/
|
|
1529
|
+
async validate(assertion, testDescription) {
|
|
1530
|
+
if (this.page.isClosed()) {
|
|
1531
|
+
return {
|
|
1532
|
+
success: false,
|
|
1533
|
+
failMessage: `CRITICAL: Browser/page closed before completing all tests. Increase test timeout or reduce test complexity.`
|
|
1534
|
+
};
|
|
1535
|
+
}
|
|
1536
|
+
const { target, error } = await this.resolveTarget(assertion.target, assertion.relativeTarget || assertion.expectedValue);
|
|
1537
|
+
if (error || !target) {
|
|
1538
|
+
return { success: false, failMessage: error || `Target ${assertion.target} not found.`, target: null };
|
|
1539
|
+
}
|
|
1540
|
+
switch (assertion.assertion) {
|
|
1541
|
+
case "toBeVisible":
|
|
1542
|
+
return this.validateVisibility(target, assertion.target, true, assertion.failureMessage || "", testDescription);
|
|
1543
|
+
case "notToBeVisible":
|
|
1544
|
+
return this.validateVisibility(target, assertion.target, false, assertion.failureMessage || "", testDescription);
|
|
1545
|
+
case "toHaveAttribute":
|
|
1546
|
+
if (assertion.attribute && assertion.expectedValue !== void 0) {
|
|
1547
|
+
return this.validateAttribute(
|
|
1548
|
+
target,
|
|
1549
|
+
assertion.target,
|
|
1550
|
+
assertion.attribute,
|
|
1551
|
+
assertion.expectedValue,
|
|
1552
|
+
assertion.failureMessage || "",
|
|
1553
|
+
testDescription
|
|
1554
|
+
);
|
|
1555
|
+
}
|
|
1556
|
+
return { success: false, failMessage: "Missing attribute or expectedValue for toHaveAttribute assertion" };
|
|
1557
|
+
case "toHaveValue":
|
|
1558
|
+
if (assertion.expectedValue !== void 0) {
|
|
1559
|
+
return this.validateValue(target, assertion.target, assertion.expectedValue, assertion.failureMessage || "", testDescription);
|
|
1560
|
+
}
|
|
1561
|
+
return { success: false, failMessage: "Missing expectedValue for toHaveValue assertion" };
|
|
1562
|
+
case "toHaveFocus":
|
|
1563
|
+
return this.validateFocus(target, assertion.target, assertion.failureMessage || "", testDescription);
|
|
1564
|
+
case "toHaveRole":
|
|
1565
|
+
if (assertion.expectedValue !== void 0) {
|
|
1566
|
+
return this.validateRole(target, assertion.target, assertion.expectedValue, assertion.failureMessage || "", testDescription);
|
|
1567
|
+
}
|
|
1568
|
+
return { success: false, failMessage: "Missing expectedValue for toHaveRole assertion" };
|
|
1569
|
+
default:
|
|
1570
|
+
return { success: false, failMessage: `Unknown assertion type: ${assertion.assertion}` };
|
|
1571
|
+
}
|
|
1572
|
+
}
|
|
1573
|
+
};
|
|
1574
|
+
}
|
|
1575
|
+
});
|
|
1576
|
+
|
|
1577
|
+
// src/utils/test/src/contractTestRunnerPlaywright.ts
|
|
788
1578
|
var contractTestRunnerPlaywright_exports = {};
|
|
789
1579
|
__export(contractTestRunnerPlaywright_exports, {
|
|
790
1580
|
runContractTestsPlaywright: () => runContractTestsPlaywright
|
|
@@ -793,20 +1583,13 @@ async function runContractTestsPlaywright(componentName, url) {
|
|
|
793
1583
|
const reporter = new ContractReporter(true);
|
|
794
1584
|
const actionTimeoutMs = 400;
|
|
795
1585
|
const assertionTimeoutMs = 400;
|
|
796
|
-
function isBrowserClosedError(error) {
|
|
797
|
-
return error instanceof Error && error.message.includes("Target page, context or browser has been closed");
|
|
798
|
-
}
|
|
799
1586
|
const contractTyped = contract_default;
|
|
800
1587
|
const contractPath = contractTyped[componentName]?.path;
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
}
|
|
804
|
-
const resolvedPath = new URL(contractPath, import_meta2.url).pathname;
|
|
805
|
-
const contractData = (0, import_fs.readFileSync)(resolvedPath, "utf-8");
|
|
1588
|
+
const resolvedPath = new URL(contractPath, import_meta3.url).pathname;
|
|
1589
|
+
const contractData = (0, import_fs2.readFileSync)(resolvedPath, "utf-8");
|
|
806
1590
|
const componentContract = JSON.parse(contractData);
|
|
807
1591
|
const totalTests = componentContract.static[0].assertions.length + componentContract.dynamic.length;
|
|
808
1592
|
const apgUrl = componentContract.meta?.source?.apg;
|
|
809
|
-
reporter.start(componentName, totalTests, apgUrl);
|
|
810
1593
|
const failures = [];
|
|
811
1594
|
const passes = [];
|
|
812
1595
|
const skipped = [];
|
|
@@ -826,68 +1609,53 @@ async function runContractTestsPlaywright(componentName, url) {
|
|
|
826
1609
|
}
|
|
827
1610
|
await page.addStyleTag({ content: `* { transition: none !important; animation: none !important; }` });
|
|
828
1611
|
}
|
|
829
|
-
const
|
|
1612
|
+
const strategy = ComponentDetector.detect(componentName, actionTimeoutMs, assertionTimeoutMs);
|
|
1613
|
+
if (!strategy) {
|
|
1614
|
+
throw new Error(`Unsupported component: ${componentName}`);
|
|
1615
|
+
}
|
|
1616
|
+
const mainSelector = strategy.getMainSelector();
|
|
830
1617
|
if (!mainSelector) {
|
|
831
|
-
throw new Error(`CRITICAL: No
|
|
1618
|
+
throw new Error(`CRITICAL: No selector found in contract for ${componentName}`);
|
|
832
1619
|
}
|
|
833
1620
|
try {
|
|
834
1621
|
await page.locator(mainSelector).first().waitFor({ state: "attached", timeout: 3e4 });
|
|
835
1622
|
} catch (error) {
|
|
836
1623
|
throw new Error(
|
|
837
|
-
`
|
|
1624
|
+
`
|
|
1625
|
+
\u274C CRITICAL: Component not found on page!
|
|
1626
|
+
This usually means:
|
|
1627
|
+
- The component didn't render
|
|
1628
|
+
- The URL is incorrect
|
|
1629
|
+
- The component selector '${mainSelector}' in the contract is wrong
|
|
1630
|
+
- Original error: ${error}`
|
|
838
1631
|
);
|
|
839
1632
|
}
|
|
1633
|
+
reporter.start(componentName, totalTests, apgUrl);
|
|
840
1634
|
if (componentName === "menu" && componentContract.selectors.trigger) {
|
|
841
1635
|
await page.locator(componentContract.selectors.trigger).first().waitFor({
|
|
842
|
-
state: "
|
|
1636
|
+
state: "attached",
|
|
843
1637
|
timeout: 5e3
|
|
844
1638
|
}).catch(() => {
|
|
845
|
-
console.warn("Menu trigger not visible, continuing with tests...");
|
|
846
1639
|
});
|
|
847
1640
|
}
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
throw new Error("Page is not initialized");
|
|
851
|
-
}
|
|
852
|
-
const items = await page.locator(selector).all();
|
|
853
|
-
switch (relative) {
|
|
854
|
-
case "first":
|
|
855
|
-
return items[0];
|
|
856
|
-
case "second":
|
|
857
|
-
return items[1];
|
|
858
|
-
case "last":
|
|
859
|
-
return items[items.length - 1];
|
|
860
|
-
case "next": {
|
|
861
|
-
const currentIndex = await page.evaluate(([sel]) => {
|
|
862
|
-
const items2 = Array.from(document.querySelectorAll(sel));
|
|
863
|
-
return items2.indexOf(document.activeElement);
|
|
864
|
-
}, [selector]);
|
|
865
|
-
const nextIndex = (currentIndex + 1) % items.length;
|
|
866
|
-
return items[nextIndex];
|
|
867
|
-
}
|
|
868
|
-
case "previous": {
|
|
869
|
-
const currentIndex = await page.evaluate(([sel]) => {
|
|
870
|
-
const items2 = Array.from(document.querySelectorAll(sel));
|
|
871
|
-
return items2.indexOf(document.activeElement);
|
|
872
|
-
}, [selector]);
|
|
873
|
-
const prevIndex = (currentIndex - 1 + items.length) % items.length;
|
|
874
|
-
return items[prevIndex];
|
|
875
|
-
}
|
|
876
|
-
default:
|
|
877
|
-
return null;
|
|
878
|
-
}
|
|
879
|
-
}
|
|
1641
|
+
const failuresBeforeStatic = failures.length;
|
|
1642
|
+
const staticAssertionRunner = new AssertionRunner(page, componentContract.selectors, assertionTimeoutMs);
|
|
880
1643
|
for (const test of componentContract.static[0]?.assertions || []) {
|
|
881
1644
|
if (test.target === "relative") continue;
|
|
1645
|
+
const staticDescription = `${test.target}${test.attribute ? ` (${test.attribute})` : ""}`;
|
|
882
1646
|
const targetSelector = componentContract.selectors[test.target];
|
|
883
1647
|
if (!targetSelector) {
|
|
884
|
-
|
|
1648
|
+
const failure = `Selector for target ${test.target} not found.`;
|
|
1649
|
+
failures.push(failure);
|
|
1650
|
+
reporter.reportStaticTest(staticDescription, false, failure);
|
|
885
1651
|
continue;
|
|
886
1652
|
}
|
|
887
1653
|
const target = page.locator(targetSelector).first();
|
|
888
1654
|
const exists = await target.count() > 0;
|
|
889
1655
|
if (!exists) {
|
|
890
|
-
|
|
1656
|
+
const failure = `Target ${test.target} not found.`;
|
|
1657
|
+
failures.push(failure);
|
|
1658
|
+
reporter.reportStaticTest(staticDescription, false, failure);
|
|
891
1659
|
continue;
|
|
892
1660
|
}
|
|
893
1661
|
const isRedundantCheck = (selector, attrName, expectedVal) => {
|
|
@@ -921,20 +1689,34 @@ async function runContractTestsPlaywright(componentName, url) {
|
|
|
921
1689
|
}
|
|
922
1690
|
}
|
|
923
1691
|
if (!hasAny && !allRedundant) {
|
|
924
|
-
|
|
1692
|
+
const failure = test.failureMessage + ` None of the attributes "${test.attribute}" are present.`;
|
|
1693
|
+
failures.push(failure);
|
|
1694
|
+
reporter.reportStaticTest(staticDescription, false, failure);
|
|
925
1695
|
} else if (!allRedundant && hasAny) {
|
|
926
1696
|
passes.push(`At least one of the attributes "${test.attribute}" exists on the element.`);
|
|
1697
|
+
reporter.reportStaticTest(staticDescription, true);
|
|
1698
|
+
} else {
|
|
1699
|
+
reporter.reportStaticTest(staticDescription, true);
|
|
927
1700
|
}
|
|
928
1701
|
} else {
|
|
929
1702
|
if (isRedundantCheck(targetSelector, test.attribute, test.expectedValue)) {
|
|
930
1703
|
passes.push(`${test.attribute}="${test.expectedValue}" on ${test.target} verified by selector (already present in: ${targetSelector}).`);
|
|
1704
|
+
reporter.reportStaticTest(staticDescription, true);
|
|
931
1705
|
} else {
|
|
932
|
-
const
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
1706
|
+
const result = await staticAssertionRunner.validateAttribute(
|
|
1707
|
+
target,
|
|
1708
|
+
test.target,
|
|
1709
|
+
test.attribute,
|
|
1710
|
+
test.expectedValue,
|
|
1711
|
+
test.failureMessage,
|
|
1712
|
+
"Static ARIA Test"
|
|
1713
|
+
);
|
|
1714
|
+
if (result.success && result.passMessage) {
|
|
1715
|
+
passes.push(result.passMessage);
|
|
1716
|
+
reporter.reportStaticTest(staticDescription, true);
|
|
1717
|
+
} else if (!result.success && result.failMessage) {
|
|
1718
|
+
failures.push(result.failMessage);
|
|
1719
|
+
reporter.reportStaticTest(staticDescription, false, result.failMessage);
|
|
938
1720
|
}
|
|
939
1721
|
}
|
|
940
1722
|
}
|
|
@@ -949,383 +1731,58 @@ async function runContractTestsPlaywright(componentName, url) {
|
|
|
949
1731
|
}
|
|
950
1732
|
const { action, assertions } = dynamicTest;
|
|
951
1733
|
const failuresBeforeTest = failures.length;
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
const
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
let menuClosed = false;
|
|
959
|
-
let closeSelector = componentContract.selectors.input;
|
|
960
|
-
if (!closeSelector && componentContract.selectors.focusable) {
|
|
961
|
-
closeSelector = componentContract.selectors.focusable;
|
|
962
|
-
} else if (!closeSelector) {
|
|
963
|
-
closeSelector = componentContract.selectors.trigger;
|
|
964
|
-
}
|
|
965
|
-
if (closeSelector) {
|
|
966
|
-
const closeElement = page.locator(closeSelector).first();
|
|
967
|
-
await closeElement.focus();
|
|
968
|
-
await page.keyboard.press("Escape");
|
|
969
|
-
menuClosed = await (0, test_exports.expect)(popupElement).toBeHidden({ timeout: assertionTimeoutMs }).then(() => true).catch(() => false);
|
|
970
|
-
}
|
|
971
|
-
if (!menuClosed && componentContract.selectors.trigger) {
|
|
972
|
-
const triggerElement = page.locator(componentContract.selectors.trigger).first();
|
|
973
|
-
await triggerElement.click({ timeout: actionTimeoutMs });
|
|
974
|
-
menuClosed = await (0, test_exports.expect)(popupElement).toBeHidden({ timeout: assertionTimeoutMs }).then(() => true).catch(() => false);
|
|
975
|
-
}
|
|
976
|
-
if (!menuClosed) {
|
|
977
|
-
await page.mouse.click(10, 10);
|
|
978
|
-
menuClosed = await (0, test_exports.expect)(popupElement).toBeHidden({ timeout: assertionTimeoutMs }).then(() => true).catch(() => false);
|
|
979
|
-
}
|
|
980
|
-
if (!menuClosed) {
|
|
981
|
-
throw new Error(
|
|
982
|
-
`\u274C FATAL: Cannot close menu between tests. Menu remains visible after trying:
|
|
983
|
-
1. Escape key
|
|
984
|
-
2. Clicking trigger
|
|
985
|
-
3. Clicking outside
|
|
986
|
-
This indicates a problem with the menu component's close functionality.`
|
|
987
|
-
);
|
|
988
|
-
}
|
|
989
|
-
if (componentContract.selectors.input) {
|
|
990
|
-
await page.locator(componentContract.selectors.input).first().clear();
|
|
991
|
-
}
|
|
992
|
-
if (componentName === "menu" && componentContract.selectors.trigger) {
|
|
993
|
-
const triggerElement = page.locator(componentContract.selectors.trigger).first();
|
|
994
|
-
await triggerElement.focus();
|
|
995
|
-
}
|
|
996
|
-
}
|
|
997
|
-
}
|
|
998
|
-
if (componentContract.selectors.panel && componentContract.selectors.trigger && !componentContract.selectors.popup) {
|
|
999
|
-
const triggerSelector = componentContract.selectors.trigger;
|
|
1000
|
-
const panelSelector = componentContract.selectors.panel;
|
|
1001
|
-
if (triggerSelector && panelSelector) {
|
|
1002
|
-
const allTriggers = await page.locator(triggerSelector).all();
|
|
1003
|
-
for (const trigger of allTriggers) {
|
|
1004
|
-
const isExpanded = await trigger.getAttribute("aria-expanded") === "true";
|
|
1005
|
-
const triggerPanel = await trigger.getAttribute("aria-controls");
|
|
1006
|
-
if (isExpanded && triggerPanel) {
|
|
1007
|
-
await trigger.click({ timeout: actionTimeoutMs });
|
|
1008
|
-
const panel = page.locator(`#${triggerPanel}`);
|
|
1009
|
-
await (0, test_exports.expect)(panel).toBeHidden({ timeout: assertionTimeoutMs }).catch(() => {
|
|
1010
|
-
});
|
|
1011
|
-
}
|
|
1012
|
-
}
|
|
1013
|
-
}
|
|
1014
|
-
}
|
|
1015
|
-
let shouldSkipTest = false;
|
|
1016
|
-
for (const act of action) {
|
|
1017
|
-
if (act.type === "keypress" && (act.target === "submenuTrigger" || act.target === "submenu")) {
|
|
1018
|
-
const submenuSelector = componentContract.selectors[act.target];
|
|
1019
|
-
if (submenuSelector) {
|
|
1020
|
-
const submenuCount = await page.locator(submenuSelector).count();
|
|
1021
|
-
if (submenuCount === 0) {
|
|
1022
|
-
reporter.reportTest(dynamicTest, "skip", `Skipping test - ${act.target} element not found (optional submenu test)`);
|
|
1023
|
-
shouldSkipTest = true;
|
|
1024
|
-
break;
|
|
1025
|
-
}
|
|
1026
|
-
}
|
|
1027
|
-
}
|
|
1028
|
-
}
|
|
1029
|
-
if (!shouldSkipTest) {
|
|
1030
|
-
for (const assertion of assertions) {
|
|
1031
|
-
if (assertion.target === "submenu" || assertion.target === "submenuTrigger") {
|
|
1032
|
-
const submenuSelector = componentContract.selectors[assertion.target];
|
|
1033
|
-
if (submenuSelector) {
|
|
1034
|
-
const submenuCount = await page.locator(submenuSelector).count();
|
|
1035
|
-
if (submenuCount === 0) {
|
|
1036
|
-
reporter.reportTest(dynamicTest, "skip", `Skipping test - ${assertion.target} element not found (optional submenu test)`);
|
|
1037
|
-
shouldSkipTest = true;
|
|
1038
|
-
break;
|
|
1039
|
-
}
|
|
1040
|
-
}
|
|
1041
|
-
}
|
|
1042
|
-
}
|
|
1734
|
+
try {
|
|
1735
|
+
await strategy.resetState(page);
|
|
1736
|
+
} catch (error) {
|
|
1737
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1738
|
+
reporter.error(errorMessage);
|
|
1739
|
+
throw error;
|
|
1043
1740
|
}
|
|
1741
|
+
const shouldSkipTest = await strategy.shouldSkipTest(dynamicTest, page);
|
|
1044
1742
|
if (shouldSkipTest) {
|
|
1743
|
+
reporter.reportTest(dynamicTest, "skip", `Skipping test - component-specific conditions not met`);
|
|
1045
1744
|
continue;
|
|
1046
1745
|
}
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
const tablistSelector = componentContract.selectors.tablist;
|
|
1050
|
-
const tablist = page.locator(tablistSelector).first();
|
|
1051
|
-
const orientation = await tablist.getAttribute("aria-orientation");
|
|
1052
|
-
const isVertical = orientation === "vertical";
|
|
1053
|
-
if (dynamicTest.isVertical !== isVertical) {
|
|
1054
|
-
const skipReason = dynamicTest.isVertical ? `Skipping vertical tabs test - component has horizontal orientation` : `Skipping horizontal tabs test - component has vertical orientation`;
|
|
1055
|
-
reporter.reportTest(dynamicTest, "skip", skipReason);
|
|
1056
|
-
continue;
|
|
1057
|
-
}
|
|
1058
|
-
}
|
|
1059
|
-
}
|
|
1746
|
+
const actionExecutor = new ActionExecutor(page, componentContract.selectors, actionTimeoutMs);
|
|
1747
|
+
const assertionRunner = new AssertionRunner(page, componentContract.selectors, assertionTimeoutMs);
|
|
1060
1748
|
for (const act of action) {
|
|
1061
1749
|
if (!page || page.isClosed()) {
|
|
1062
1750
|
failures.push(`CRITICAL: Browser/page closed during test execution. Remaining actions skipped.`);
|
|
1063
1751
|
break;
|
|
1064
1752
|
}
|
|
1753
|
+
let result;
|
|
1065
1754
|
if (act.type === "focus") {
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
}
|
|
1078
|
-
failures.push(`Failed to focus ${act.target}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1079
|
-
continue;
|
|
1080
|
-
}
|
|
1755
|
+
result = await actionExecutor.focus(act.target);
|
|
1756
|
+
} else if (act.type === "type" && act.value) {
|
|
1757
|
+
result = await actionExecutor.type(act.target, act.value);
|
|
1758
|
+
} else if (act.type === "click") {
|
|
1759
|
+
result = await actionExecutor.click(act.target, act.relativeTarget);
|
|
1760
|
+
} else if (act.type === "keypress" && act.key) {
|
|
1761
|
+
result = await actionExecutor.keypress(act.target, act.key);
|
|
1762
|
+
} else if (act.type === "hover") {
|
|
1763
|
+
result = await actionExecutor.hover(act.target, act.relativeTarget);
|
|
1764
|
+
} else {
|
|
1765
|
+
continue;
|
|
1081
1766
|
}
|
|
1082
|
-
if (
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
if (!typeSelector) {
|
|
1086
|
-
failures.push(`Selector for type target ${act.target} not found.`);
|
|
1087
|
-
continue;
|
|
1088
|
-
}
|
|
1089
|
-
await page.locator(typeSelector).first().fill(act.value, { timeout: actionTimeoutMs });
|
|
1090
|
-
} catch (error) {
|
|
1091
|
-
if (isBrowserClosedError(error)) {
|
|
1092
|
-
failures.push(`CRITICAL: Browser/page closed during test execution. Remaining actions skipped.`);
|
|
1093
|
-
break;
|
|
1094
|
-
}
|
|
1095
|
-
failures.push(`Failed to type into ${act.target}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1096
|
-
continue;
|
|
1767
|
+
if (!result.success) {
|
|
1768
|
+
if (result.error) {
|
|
1769
|
+
failures.push(result.error);
|
|
1097
1770
|
}
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
if (act.target === "document") {
|
|
1102
|
-
await page.mouse.click(10, 10);
|
|
1103
|
-
} else if (act.target === "relative" && act.relativeTarget) {
|
|
1104
|
-
const relativeSelector = componentContract.selectors.relative;
|
|
1105
|
-
if (!relativeSelector) {
|
|
1106
|
-
failures.push(`Relative selector not defined for click action.`);
|
|
1107
|
-
continue;
|
|
1108
|
-
}
|
|
1109
|
-
const relativeElement = await resolveRelativeTarget(relativeSelector, act.relativeTarget);
|
|
1110
|
-
if (!relativeElement) {
|
|
1111
|
-
failures.push(`Could not resolve relative target ${act.relativeTarget} for click.`);
|
|
1112
|
-
continue;
|
|
1113
|
-
}
|
|
1114
|
-
await relativeElement.click({ timeout: actionTimeoutMs });
|
|
1115
|
-
} else {
|
|
1116
|
-
const actionSelector = componentContract.selectors[act.target];
|
|
1117
|
-
if (!actionSelector) {
|
|
1118
|
-
failures.push(`Selector for action target ${act.target} not found.`);
|
|
1119
|
-
continue;
|
|
1120
|
-
}
|
|
1121
|
-
await page.locator(actionSelector).first().click({ timeout: actionTimeoutMs });
|
|
1122
|
-
}
|
|
1123
|
-
} catch (error) {
|
|
1124
|
-
if (isBrowserClosedError(error)) {
|
|
1125
|
-
failures.push(`CRITICAL: Browser/page closed during test execution. Remaining actions skipped.`);
|
|
1126
|
-
break;
|
|
1127
|
-
}
|
|
1128
|
-
failures.push(`Failed to click ${act.target}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1129
|
-
continue;
|
|
1130
|
-
}
|
|
1131
|
-
}
|
|
1132
|
-
if (act.type === "keypress" && act.key) {
|
|
1133
|
-
try {
|
|
1134
|
-
const keyMap = {
|
|
1135
|
-
"Space": "Space",
|
|
1136
|
-
"Enter": "Enter",
|
|
1137
|
-
"Escape": "Escape",
|
|
1138
|
-
"Arrow Up": "ArrowUp",
|
|
1139
|
-
"Arrow Down": "ArrowDown",
|
|
1140
|
-
"Arrow Left": "ArrowLeft",
|
|
1141
|
-
"Arrow Right": "ArrowRight",
|
|
1142
|
-
"Home": "Home",
|
|
1143
|
-
"End": "End",
|
|
1144
|
-
"Tab": "Tab"
|
|
1145
|
-
};
|
|
1146
|
-
let keyValue = keyMap[act.key] || act.key;
|
|
1147
|
-
if (keyValue === "Space") {
|
|
1148
|
-
keyValue = " ";
|
|
1149
|
-
} else if (keyValue.includes(" ")) {
|
|
1150
|
-
keyValue = keyValue.replace(/ /g, "");
|
|
1151
|
-
}
|
|
1152
|
-
if (act.target === "focusable" && ["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight", "Escape"].includes(keyValue)) {
|
|
1153
|
-
await page.keyboard.press(keyValue);
|
|
1154
|
-
} else {
|
|
1155
|
-
const keypressSelector = componentContract.selectors[act.target];
|
|
1156
|
-
if (!keypressSelector) {
|
|
1157
|
-
failures.push(`Selector for keypress target ${act.target} not found.`);
|
|
1158
|
-
continue;
|
|
1159
|
-
}
|
|
1160
|
-
const target = page.locator(keypressSelector).first();
|
|
1161
|
-
const elementCount = await target.count();
|
|
1162
|
-
if (elementCount === 0) {
|
|
1163
|
-
reporter.reportTest(dynamicTest, "skip", `Skipping test - ${act.target} element not found (optional submenu test)`);
|
|
1164
|
-
break;
|
|
1165
|
-
}
|
|
1166
|
-
await target.press(keyValue, { timeout: actionTimeoutMs });
|
|
1771
|
+
if (result.shouldBreak) {
|
|
1772
|
+
if (result.error?.includes("optional submenu test")) {
|
|
1773
|
+
reporter.reportTest(dynamicTest, "skip", result.error);
|
|
1167
1774
|
}
|
|
1168
|
-
|
|
1169
|
-
if (isBrowserClosedError(error)) {
|
|
1170
|
-
failures.push(`CRITICAL: Browser/page closed during test execution. Remaining actions skipped.`);
|
|
1171
|
-
break;
|
|
1172
|
-
}
|
|
1173
|
-
failures.push(`Failed to press ${act.key} on ${act.target}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1174
|
-
continue;
|
|
1175
|
-
}
|
|
1176
|
-
}
|
|
1177
|
-
if (act.type === "hover") {
|
|
1178
|
-
try {
|
|
1179
|
-
if (act.target === "relative" && act.relativeTarget) {
|
|
1180
|
-
const relativeSelector = componentContract.selectors.relative;
|
|
1181
|
-
if (!relativeSelector) {
|
|
1182
|
-
failures.push(`Relative selector not defined for hover action.`);
|
|
1183
|
-
continue;
|
|
1184
|
-
}
|
|
1185
|
-
const relativeElement = await resolveRelativeTarget(relativeSelector, act.relativeTarget);
|
|
1186
|
-
if (!relativeElement) {
|
|
1187
|
-
failures.push(`Could not resolve relative target ${act.relativeTarget} for hover.`);
|
|
1188
|
-
continue;
|
|
1189
|
-
}
|
|
1190
|
-
await relativeElement.hover({ timeout: actionTimeoutMs });
|
|
1191
|
-
} else {
|
|
1192
|
-
const hoverSelector = componentContract.selectors[act.target];
|
|
1193
|
-
if (!hoverSelector) {
|
|
1194
|
-
failures.push(`Selector for hover target ${act.target} not found.`);
|
|
1195
|
-
continue;
|
|
1196
|
-
}
|
|
1197
|
-
await page.locator(hoverSelector).first().hover({ timeout: actionTimeoutMs });
|
|
1198
|
-
}
|
|
1199
|
-
} catch (error) {
|
|
1200
|
-
if (isBrowserClosedError(error)) {
|
|
1201
|
-
failures.push(`CRITICAL: Browser/page closed during test execution. Remaining actions skipped.`);
|
|
1202
|
-
break;
|
|
1203
|
-
}
|
|
1204
|
-
failures.push(`Failed to hover ${act.target}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1205
|
-
continue;
|
|
1775
|
+
break;
|
|
1206
1776
|
}
|
|
1777
|
+
continue;
|
|
1207
1778
|
}
|
|
1208
1779
|
}
|
|
1209
1780
|
for (const assertion of assertions) {
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
}
|
|
1214
|
-
|
|
1215
|
-
try {
|
|
1216
|
-
if (assertion.target === "relative") {
|
|
1217
|
-
const relativeSelector = componentContract.selectors.relative;
|
|
1218
|
-
if (!relativeSelector) {
|
|
1219
|
-
failures.push("Relative selector is not defined in the contract.");
|
|
1220
|
-
continue;
|
|
1221
|
-
}
|
|
1222
|
-
const relativeTargetValue = assertion.relativeTarget || assertion.expectedValue;
|
|
1223
|
-
if (!relativeTargetValue) {
|
|
1224
|
-
failures.push("Relative target or expected value is not defined.");
|
|
1225
|
-
continue;
|
|
1226
|
-
}
|
|
1227
|
-
target = await resolveRelativeTarget(relativeSelector, relativeTargetValue);
|
|
1228
|
-
} else {
|
|
1229
|
-
const assertionSelector = componentContract.selectors[assertion.target];
|
|
1230
|
-
if (!assertionSelector) {
|
|
1231
|
-
failures.push(`Selector for assertion target ${assertion.target} not found.`);
|
|
1232
|
-
continue;
|
|
1233
|
-
}
|
|
1234
|
-
target = page.locator(assertionSelector).first();
|
|
1235
|
-
}
|
|
1236
|
-
if (!target) {
|
|
1237
|
-
failures.push(`Target ${assertion.target} not found.`);
|
|
1238
|
-
continue;
|
|
1239
|
-
}
|
|
1240
|
-
} catch (error) {
|
|
1241
|
-
failures.push(`Failed to resolve target ${assertion.target}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1242
|
-
continue;
|
|
1243
|
-
}
|
|
1244
|
-
if (assertion.assertion === "toBeVisible") {
|
|
1245
|
-
try {
|
|
1246
|
-
await (0, test_exports.expect)(target).toBeVisible({ timeout: assertionTimeoutMs });
|
|
1247
|
-
passes.push(`${assertion.target} is visible as expected. Test: "${dynamicTest.description}".`);
|
|
1248
|
-
} catch {
|
|
1249
|
-
const debugState = await page.evaluate((sel) => {
|
|
1250
|
-
const el = sel ? document.querySelector(sel) : null;
|
|
1251
|
-
if (!el) return "element not found";
|
|
1252
|
-
const styles = window.getComputedStyle(el);
|
|
1253
|
-
return `display:${styles.display}, visibility:${styles.visibility}, opacity:${styles.opacity}`;
|
|
1254
|
-
}, componentContract.selectors[assertion.target] || "");
|
|
1255
|
-
failures.push(`${assertion.failureMessage} (actual: ${debugState})`);
|
|
1256
|
-
}
|
|
1257
|
-
}
|
|
1258
|
-
if (assertion.assertion === "notToBeVisible") {
|
|
1259
|
-
try {
|
|
1260
|
-
await (0, test_exports.expect)(target).toBeHidden({ timeout: assertionTimeoutMs });
|
|
1261
|
-
passes.push(`${assertion.target} is not visible as expected. Test: "${dynamicTest.description}".`);
|
|
1262
|
-
} catch {
|
|
1263
|
-
const debugState = await page.evaluate((sel) => {
|
|
1264
|
-
const el = sel ? document.querySelector(sel) : null;
|
|
1265
|
-
if (!el) return "element not found";
|
|
1266
|
-
const styles = window.getComputedStyle(el);
|
|
1267
|
-
return `display:${styles.display}, visibility:${styles.visibility}, opacity:${styles.opacity}`;
|
|
1268
|
-
}, componentContract.selectors[assertion.target] || "");
|
|
1269
|
-
failures.push(assertion.failureMessage + ` ${assertion.target} is still visible (actual: ${debugState}).`);
|
|
1270
|
-
}
|
|
1271
|
-
}
|
|
1272
|
-
if (assertion.assertion === "toHaveAttribute" && assertion.attribute && assertion.expectedValue) {
|
|
1273
|
-
try {
|
|
1274
|
-
if (assertion.expectedValue === "!empty") {
|
|
1275
|
-
const attributeValue = await target.getAttribute(assertion.attribute);
|
|
1276
|
-
if (attributeValue && attributeValue.trim() !== "") {
|
|
1277
|
-
passes.push(`${assertion.target} has non-empty "${assertion.attribute}". Test: "${dynamicTest.description}".`);
|
|
1278
|
-
} else {
|
|
1279
|
-
failures.push(assertion.failureMessage + ` ${assertion.target} "${assertion.attribute}" should not be empty, found "${attributeValue}".`);
|
|
1280
|
-
}
|
|
1281
|
-
} else {
|
|
1282
|
-
await (0, test_exports.expect)(target).toHaveAttribute(assertion.attribute, assertion.expectedValue, { timeout: assertionTimeoutMs });
|
|
1283
|
-
passes.push(`${assertion.target} has expected "${assertion.attribute}". Test: "${dynamicTest.description}".`);
|
|
1284
|
-
}
|
|
1285
|
-
} catch {
|
|
1286
|
-
const attributeValue = await target.getAttribute(assertion.attribute);
|
|
1287
|
-
failures.push(assertion.failureMessage + ` ${assertion.target} "${assertion.attribute}" should be "${assertion.expectedValue}", found "${attributeValue}".`);
|
|
1288
|
-
}
|
|
1289
|
-
}
|
|
1290
|
-
if (assertion.assertion === "toHaveValue") {
|
|
1291
|
-
const inputValue = await target.inputValue().catch(() => "");
|
|
1292
|
-
if (assertion.expectedValue === "!empty") {
|
|
1293
|
-
if (inputValue && inputValue.trim() !== "") {
|
|
1294
|
-
passes.push(`${assertion.target} has non-empty value. Test: "${dynamicTest.description}".`);
|
|
1295
|
-
} else {
|
|
1296
|
-
failures.push(assertion.failureMessage + ` ${assertion.target} value should not be empty, found "${inputValue}".`);
|
|
1297
|
-
}
|
|
1298
|
-
} else if (assertion.expectedValue === "") {
|
|
1299
|
-
if (inputValue === "") {
|
|
1300
|
-
passes.push(`${assertion.target} has empty value. Test: "${dynamicTest.description}".`);
|
|
1301
|
-
} else {
|
|
1302
|
-
failures.push(assertion.failureMessage + ` ${assertion.target} value should be empty, found "${inputValue}".`);
|
|
1303
|
-
}
|
|
1304
|
-
} else if (inputValue === assertion.expectedValue) {
|
|
1305
|
-
passes.push(`${assertion.target} has expected value. Test: "${dynamicTest.description}".`);
|
|
1306
|
-
} else {
|
|
1307
|
-
failures.push(assertion.failureMessage + ` ${assertion.target} value should be "${assertion.expectedValue}", found "${inputValue}".`);
|
|
1308
|
-
}
|
|
1309
|
-
}
|
|
1310
|
-
if (assertion.assertion === "toHaveFocus") {
|
|
1311
|
-
try {
|
|
1312
|
-
await (0, test_exports.expect)(target).toBeFocused({ timeout: assertionTimeoutMs });
|
|
1313
|
-
passes.push(`${assertion.target} has focus as expected. Test: "${dynamicTest.description}".`);
|
|
1314
|
-
} catch {
|
|
1315
|
-
const actualFocus = await page.evaluate(() => {
|
|
1316
|
-
const focused = document.activeElement;
|
|
1317
|
-
return focused ? `${focused.tagName}#${focused.id || "no-id"}.${focused.className || "no-class"}` : "no element focused";
|
|
1318
|
-
});
|
|
1319
|
-
failures.push(`${assertion.failureMessage} (actual focus: ${actualFocus})`);
|
|
1320
|
-
}
|
|
1321
|
-
}
|
|
1322
|
-
if (assertion.assertion === "toHaveRole" && assertion.expectedValue) {
|
|
1323
|
-
const roleValue = await target.getAttribute("role");
|
|
1324
|
-
if (roleValue === assertion.expectedValue) {
|
|
1325
|
-
passes.push(`${assertion.target} has role "${assertion.expectedValue}". Test: "${dynamicTest.description}".`);
|
|
1326
|
-
} else {
|
|
1327
|
-
failures.push(assertion.failureMessage + ` Expected role "${assertion.expectedValue}", found "${roleValue}".`);
|
|
1328
|
-
}
|
|
1781
|
+
const result = await assertionRunner.validate(assertion, dynamicTest.description);
|
|
1782
|
+
if (result.success && result.passMessage) {
|
|
1783
|
+
passes.push(result.passMessage);
|
|
1784
|
+
} else if (!result.success && result.failMessage) {
|
|
1785
|
+
failures.push(result.failMessage);
|
|
1329
1786
|
}
|
|
1330
1787
|
}
|
|
1331
1788
|
const failuresAfterTest = failures.length;
|
|
@@ -1338,54 +1795,29 @@ This indicates a problem with the menu component's close functionality.`
|
|
|
1338
1795
|
reporter.reportTest(dynamicTest, testPassed ? "pass" : "fail", failureMessage);
|
|
1339
1796
|
}
|
|
1340
1797
|
}
|
|
1341
|
-
const
|
|
1342
|
-
const staticFailed =
|
|
1798
|
+
const staticTotal = componentContract.static[0].assertions.length;
|
|
1799
|
+
const staticFailed = failures.length - failuresBeforeStatic;
|
|
1800
|
+
const staticPassed = Math.max(0, staticTotal - staticFailed);
|
|
1343
1801
|
reporter.reportStatic(staticPassed, staticFailed);
|
|
1344
1802
|
reporter.summary(failures);
|
|
1345
1803
|
} catch (error) {
|
|
1346
1804
|
if (error instanceof Error) {
|
|
1347
1805
|
if (error.message.includes("Executable doesn't exist") || error.message.includes("browserType.launch")) {
|
|
1348
|
-
|
|
1349
|
-
console.log("\u{1F4E6} Run: npx playwright install chromium\n");
|
|
1350
|
-
failures.push("CRITICAL: Playwright browser not installed. Run: npx playwright install chromium");
|
|
1806
|
+
throw new Error("\n\u274C CRITICAL: Playwright browsers not found!\n\u{1F4E6} Run: npx playwright install chromium");
|
|
1351
1807
|
} else if (error.message.includes("net::ERR_CONNECTION_REFUSED") || error.message.includes("NS_ERROR_CONNECTION_REFUSED")) {
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
`);
|
|
1355
|
-
failures.push(`CRITICAL: Dev server not running at ${url}`);
|
|
1808
|
+
throw new Error(`
|
|
1809
|
+
\u274C CRITICAL: Cannot connect to dev server!
|
|
1810
|
+
Make sure your dev server is running at ${url}`);
|
|
1356
1811
|
} else if (error.message.includes("Timeout") && error.message.includes("waitFor")) {
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
console.log(` This usually means:
|
|
1361
|
-
`);
|
|
1362
|
-
console.log(` - The component didn't render
|
|
1363
|
-
`);
|
|
1364
|
-
console.log(` - The URL is incorrect
|
|
1365
|
-
`);
|
|
1366
|
-
console.log(` - The component selector in the contract is wrong
|
|
1367
|
-
`);
|
|
1368
|
-
failures.push(`CRITICAL: Component element not found on page - ${error.message}`);
|
|
1812
|
+
throw new Error(
|
|
1813
|
+
"\n\u274C CRITICAL: Component not found on page!\nThe component selector could not be found within 30 seconds.\nThis usually means:\n - The component didn't render\n - The URL is incorrect\n - The component selector was not provided to the component utility, or a wrong selector was used\n"
|
|
1814
|
+
);
|
|
1369
1815
|
} else if (error.message.includes("Target page, context or browser has been closed")) {
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
console.log(` - The test timeout was too short
|
|
1374
|
-
`);
|
|
1375
|
-
console.log(` - The browser crashed
|
|
1376
|
-
`);
|
|
1377
|
-
console.log(` - An external process killed the browser
|
|
1378
|
-
`);
|
|
1379
|
-
failures.push(`CRITICAL: Browser/page closed unexpectedly - ${error.message}`);
|
|
1380
|
-
} else if (error.message.includes("FATAL")) {
|
|
1381
|
-
console.error(`
|
|
1382
|
-
${error.message}
|
|
1383
|
-
`);
|
|
1384
|
-
failures.push(error.message);
|
|
1816
|
+
throw new Error(
|
|
1817
|
+
"\n\u274C CRITICAL: Browser/page was closed unexpectedly!\nThis usually means:\n - The test timeout was too short\n - The browser crashed\n - An external process killed the browser"
|
|
1818
|
+
);
|
|
1385
1819
|
} else {
|
|
1386
|
-
|
|
1387
|
-
console.error("Stack:", error.stack);
|
|
1388
|
-
failures.push(`UNEXPECTED ERROR: ${error.message}`);
|
|
1820
|
+
throw error;
|
|
1389
1821
|
}
|
|
1390
1822
|
}
|
|
1391
1823
|
} finally {
|
|
@@ -1393,16 +1825,18 @@ ${error.message}
|
|
|
1393
1825
|
}
|
|
1394
1826
|
return { passes, failures, skipped };
|
|
1395
1827
|
}
|
|
1396
|
-
var
|
|
1828
|
+
var import_fs2, import_meta3;
|
|
1397
1829
|
var init_contractTestRunnerPlaywright = __esm({
|
|
1398
|
-
"src/utils/test/
|
|
1830
|
+
"src/utils/test/src/contractTestRunnerPlaywright.ts"() {
|
|
1399
1831
|
"use strict";
|
|
1400
|
-
|
|
1401
|
-
import_fs = require("fs");
|
|
1832
|
+
import_fs2 = require("fs");
|
|
1402
1833
|
init_contract();
|
|
1403
|
-
init_ContractReporter();
|
|
1404
1834
|
init_playwrightTestHarness();
|
|
1405
|
-
|
|
1835
|
+
init_ComponentDetector();
|
|
1836
|
+
init_ContractReporter();
|
|
1837
|
+
init_ActionExecutor();
|
|
1838
|
+
init_AssertionRunner();
|
|
1839
|
+
import_meta3 = {};
|
|
1406
1840
|
}
|
|
1407
1841
|
});
|
|
1408
1842
|
|
|
@@ -1515,6 +1949,11 @@ var init_test2 = __esm({
|
|
|
1515
1949
|
init_contractTestRunner();
|
|
1516
1950
|
init_playwrightTestHarness();
|
|
1517
1951
|
runTest = async () => {
|
|
1952
|
+
return {
|
|
1953
|
+
passes: [],
|
|
1954
|
+
failures: [],
|
|
1955
|
+
skipped: []
|
|
1956
|
+
};
|
|
1518
1957
|
};
|
|
1519
1958
|
if (typeof window === "undefined") {
|
|
1520
1959
|
runTest = async () => {
|
|
@@ -1522,36 +1961,36 @@ var init_test2 = __esm({
|
|
|
1522
1961
|
`);
|
|
1523
1962
|
const { exec } = await import("child_process");
|
|
1524
1963
|
const chalk3 = (await import("chalk")).default;
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
if (stdout) {
|
|
1964
|
+
return new Promise((resolve, reject) => {
|
|
1965
|
+
exec(
|
|
1966
|
+
`npx vitest --run --reporter verbose`,
|
|
1967
|
+
async (error, stdout, stderr) => {
|
|
1530
1968
|
console.log(stdout);
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1969
|
+
if (stderr) console.error(stderr);
|
|
1970
|
+
const testsPassed = !error || error.code === 0;
|
|
1971
|
+
if (testsPassed) {
|
|
1972
|
+
try {
|
|
1973
|
+
const { displayBadgeInfo: displayBadgeInfo2, promptAddBadge: promptAddBadge2 } = await Promise.resolve().then(() => (init_badgeHelper(), badgeHelper_exports));
|
|
1974
|
+
displayBadgeInfo2("component");
|
|
1975
|
+
await promptAddBadge2("component", process.cwd());
|
|
1976
|
+
console.log(chalk3.dim("\n" + "\u2500".repeat(60)));
|
|
1977
|
+
console.log(chalk3.cyan("\u{1F499} Found aria-ease helpful?"));
|
|
1978
|
+
console.log(chalk3.white(" \u2022 Star us on GitHub: ") + chalk3.blue.underline("https://github.com/aria-ease/aria-ease"));
|
|
1979
|
+
console.log(chalk3.white(" \u2022 Share feedback: ") + chalk3.blue.underline("https://github.com/aria-ease/aria-ease/discussions"));
|
|
1980
|
+
console.log(chalk3.dim("\u2500".repeat(60) + "\n"));
|
|
1981
|
+
} catch (badgeError) {
|
|
1982
|
+
console.error("Warning: Could not display badge prompt:", badgeError);
|
|
1983
|
+
}
|
|
1984
|
+
resolve({ passes: [], failures: [], skipped: [] });
|
|
1985
|
+
process.exit(0);
|
|
1986
|
+
} else {
|
|
1987
|
+
const exitCode = error?.code || 1;
|
|
1988
|
+
reject(new Error(`Tests failed with code ${exitCode}`));
|
|
1989
|
+
process.exit(exitCode);
|
|
1547
1990
|
}
|
|
1548
|
-
process.exit(0);
|
|
1549
|
-
} else {
|
|
1550
|
-
const exitCode = error?.code || 1;
|
|
1551
|
-
process.exit(exitCode);
|
|
1552
1991
|
}
|
|
1553
|
-
|
|
1554
|
-
);
|
|
1992
|
+
);
|
|
1993
|
+
});
|
|
1555
1994
|
};
|
|
1556
1995
|
}
|
|
1557
1996
|
}
|