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
|
@@ -35,29 +35,29 @@ var init_contract = __esm({
|
|
|
35
35
|
"src/utils/test/contract/contract.json"() {
|
|
36
36
|
contract_default = {
|
|
37
37
|
menu: {
|
|
38
|
-
path: "./contracts/
|
|
38
|
+
path: "./aria-contracts/menu/menu.contract.json",
|
|
39
39
|
component: "menu"
|
|
40
40
|
},
|
|
41
|
-
combobox: {
|
|
42
|
-
path: "./contracts/
|
|
43
|
-
component: "combobox"
|
|
41
|
+
"combobox.listbox": {
|
|
42
|
+
path: "./aria-contracts/combobox/combobox.listbox.contract.json",
|
|
43
|
+
component: "combobox.listbox"
|
|
44
44
|
},
|
|
45
45
|
accordion: {
|
|
46
|
-
path: "./contracts/
|
|
46
|
+
path: "./aria-contracts/accordion/accordion.contract.json",
|
|
47
47
|
component: "accordion"
|
|
48
48
|
},
|
|
49
49
|
tabs: {
|
|
50
|
-
path: "./contracts/
|
|
50
|
+
path: "./aria-contracts/tabs/tabs.contract.json",
|
|
51
51
|
component: "tabs"
|
|
52
52
|
}
|
|
53
53
|
};
|
|
54
54
|
}
|
|
55
55
|
});
|
|
56
56
|
|
|
57
|
-
// src/utils/test/
|
|
57
|
+
// src/utils/test/src/ContractReporter.ts
|
|
58
58
|
var ContractReporter;
|
|
59
59
|
var init_ContractReporter = __esm({
|
|
60
|
-
"src/utils/test/
|
|
60
|
+
"src/utils/test/src/ContractReporter.ts"() {
|
|
61
61
|
ContractReporter = class {
|
|
62
62
|
startTime = 0;
|
|
63
63
|
componentName = "";
|
|
@@ -69,6 +69,8 @@ var init_ContractReporter = __esm({
|
|
|
69
69
|
optionalSuggestions = 0;
|
|
70
70
|
isPlaywright = false;
|
|
71
71
|
apgUrl = "https://www.w3.org/WAI/ARIA/apg/";
|
|
72
|
+
hasPrintedStaticSection = false;
|
|
73
|
+
hasPrintedDynamicSection = false;
|
|
72
74
|
constructor(isPlaywright = false) {
|
|
73
75
|
this.isPlaywright = isPlaywright;
|
|
74
76
|
}
|
|
@@ -79,30 +81,32 @@ var init_ContractReporter = __esm({
|
|
|
79
81
|
this.startTime = Date.now();
|
|
80
82
|
this.componentName = componentName;
|
|
81
83
|
this.totalTests = totalTests;
|
|
84
|
+
this.hasPrintedStaticSection = false;
|
|
85
|
+
this.hasPrintedDynamicSection = false;
|
|
82
86
|
if (apgUrl) {
|
|
83
87
|
this.apgUrl = apgUrl;
|
|
84
88
|
}
|
|
85
89
|
const mode = this.isPlaywright ? "Playwright (Real Browser)" : "jsdom (Fast)";
|
|
86
90
|
this.log(`
|
|
87
91
|
${"\u2550".repeat(60)}`);
|
|
88
|
-
this.log(`\u{1F50D} Testing ${componentName} Component - ${mode}`);
|
|
92
|
+
this.log(`\u{1F50D} Testing ${componentName.charAt(0).toUpperCase() + componentName.slice(1)} Component - ${mode}`);
|
|
89
93
|
this.log(`${"\u2550".repeat(60)}
|
|
90
94
|
`);
|
|
91
95
|
}
|
|
92
96
|
reportStatic(passes, failures) {
|
|
93
97
|
this.staticPasses = passes;
|
|
94
98
|
this.staticFailures = failures;
|
|
95
|
-
const icon = failures === 0 ? "\u2705" : "\u274C";
|
|
96
|
-
const status = failures === 0 ? "PASS" : "FAIL";
|
|
97
|
-
this.log("");
|
|
98
|
-
this.log(`${icon} Static ARIA Tests: ${status}`);
|
|
99
|
-
this.log(` ${passes}/${passes + failures} required attributes present
|
|
100
|
-
`);
|
|
101
99
|
}
|
|
102
100
|
/**
|
|
103
101
|
* Report individual static test pass
|
|
104
102
|
*/
|
|
105
103
|
reportStaticTest(description, passed, failureMessage) {
|
|
104
|
+
if (!this.hasPrintedStaticSection) {
|
|
105
|
+
this.log(`${"\u2500".repeat(60)}`);
|
|
106
|
+
this.log(`\u{1F9EA} Static Assertions`);
|
|
107
|
+
this.log(`${"\u2500".repeat(60)}`);
|
|
108
|
+
this.hasPrintedStaticSection = true;
|
|
109
|
+
}
|
|
106
110
|
const icon = passed ? "\u2713" : "\u2717";
|
|
107
111
|
this.log(` ${icon} ${description}`);
|
|
108
112
|
if (!passed && failureMessage) {
|
|
@@ -113,13 +117,20 @@ ${"\u2550".repeat(60)}`);
|
|
|
113
117
|
* Report individual dynamic test result
|
|
114
118
|
*/
|
|
115
119
|
reportTest(test, status, failureMessage) {
|
|
120
|
+
if (!this.hasPrintedDynamicSection) {
|
|
121
|
+
this.log("");
|
|
122
|
+
this.log(`${"\u2500".repeat(60)}`);
|
|
123
|
+
this.log(`\u2328\uFE0F Dynamic Interaction Tests`);
|
|
124
|
+
this.log(`${"\u2500".repeat(60)}`);
|
|
125
|
+
this.hasPrintedDynamicSection = true;
|
|
126
|
+
}
|
|
116
127
|
const result = {
|
|
117
128
|
description: test.description,
|
|
118
129
|
status,
|
|
119
130
|
failureMessage,
|
|
120
131
|
isOptional: test.isOptional
|
|
121
132
|
};
|
|
122
|
-
if (status === "skip"
|
|
133
|
+
if (status === "skip") {
|
|
123
134
|
result.skipReason = "Requires real browser (addEventListener events)";
|
|
124
135
|
}
|
|
125
136
|
this.dynamicResults.push(result);
|
|
@@ -199,7 +210,7 @@ ${"\u2500".repeat(60)}`);
|
|
|
199
210
|
});
|
|
200
211
|
this.log(`
|
|
201
212
|
\u{1F4A1} Run with Playwright for full validation:`);
|
|
202
|
-
this.log(` testUiComponent('${this.componentName}',
|
|
213
|
+
this.log(` testUiComponent('${this.componentName}', null, 'http://localhost:5173/test-harness?component=component_name')
|
|
203
214
|
`);
|
|
204
215
|
}
|
|
205
216
|
/**
|
|
@@ -223,9 +234,14 @@ ${"\u2500".repeat(60)}`);
|
|
|
223
234
|
${"\u2550".repeat(60)}`);
|
|
224
235
|
this.log(`\u{1F4CA} Summary
|
|
225
236
|
`);
|
|
237
|
+
const staticIcon = this.staticFailures === 0 ? "\u2705" : "\u274C";
|
|
238
|
+
const staticStatus = this.staticFailures === 0 ? "PASS" : "FAIL";
|
|
239
|
+
this.log(`${staticIcon} Static ARIA Tests: ${staticStatus}`);
|
|
240
|
+
this.log(` ${this.staticPasses}/${this.staticPasses + this.staticFailures} required attributes present`);
|
|
241
|
+
this.log("");
|
|
226
242
|
if (totalFailures === 0 && this.skipped === 0 && this.optionalSuggestions === 0) {
|
|
227
243
|
this.log(`\u2705 All ${totalRun} tests passed!`);
|
|
228
|
-
this.log(` ${this.componentName} component meets WAI-ARIA expectations for Roles, States, Properties, and Keyboard
|
|
244
|
+
this.log(` ${this.componentName.charAt(0).toUpperCase()}${this.componentName.slice(1)} component meets WAI-ARIA expectations for Roles, States, Properties, and Keyboard Interactions \u2713`);
|
|
229
245
|
} else if (totalFailures === 0) {
|
|
230
246
|
this.log(`\u2705 ${totalPasses}/${totalRun} required tests passed`);
|
|
231
247
|
if (this.skipped > 0) {
|
|
@@ -234,7 +250,7 @@ ${"\u2550".repeat(60)}`);
|
|
|
234
250
|
if (this.optionalSuggestions > 0) {
|
|
235
251
|
this.log(`\u{1F4A1} ${this.optionalSuggestions} optional enhancement${this.optionalSuggestions > 1 ? "s" : ""} suggested`);
|
|
236
252
|
}
|
|
237
|
-
this.log(` ${this.componentName} component meets WAI-ARIA expectations for Roles, States, Properties, and Keyboard
|
|
253
|
+
this.log(` ${this.componentName.charAt(0).toUpperCase()}${this.componentName.slice(1)} component meets WAI-ARIA expectations for Roles, States, Properties, and Keyboard Interactions \u2713`);
|
|
238
254
|
} else {
|
|
239
255
|
this.log(`\u274C ${totalFailures} test${totalFailures > 1 ? "s" : ""} failed`);
|
|
240
256
|
this.log(`\u2705 ${totalPasses} test${totalPasses > 1 ? "s" : ""} passed`);
|
|
@@ -320,13 +336,761 @@ async function closeSharedBrowser() {
|
|
|
320
336
|
}
|
|
321
337
|
var sharedBrowser, sharedContext;
|
|
322
338
|
var init_playwrightTestHarness = __esm({
|
|
323
|
-
"src/utils/test/
|
|
339
|
+
"src/utils/test/src/playwrightTestHarness.ts"() {
|
|
324
340
|
sharedBrowser = null;
|
|
325
341
|
sharedContext = null;
|
|
326
342
|
}
|
|
327
343
|
});
|
|
344
|
+
var ComboboxComponentStrategy;
|
|
345
|
+
var init_ComboboxComponentStrategy = __esm({
|
|
346
|
+
"src/utils/test/src/component-strategies/ComboboxComponentStrategy.ts"() {
|
|
347
|
+
ComboboxComponentStrategy = class {
|
|
348
|
+
constructor(mainSelector, selectors, actionTimeoutMs = 400, assertionTimeoutMs = 400) {
|
|
349
|
+
this.mainSelector = mainSelector;
|
|
350
|
+
this.selectors = selectors;
|
|
351
|
+
this.actionTimeoutMs = actionTimeoutMs;
|
|
352
|
+
this.assertionTimeoutMs = assertionTimeoutMs;
|
|
353
|
+
}
|
|
354
|
+
async resetState(page) {
|
|
355
|
+
if (!this.selectors.popup) return;
|
|
356
|
+
const popupSelector = this.selectors.popup;
|
|
357
|
+
const popupElement = page.locator(popupSelector).first();
|
|
358
|
+
const isPopupVisible = await popupElement.isVisible().catch(() => false);
|
|
359
|
+
if (!isPopupVisible) return;
|
|
360
|
+
let menuClosed = false;
|
|
361
|
+
let closeSelector = this.selectors.input;
|
|
362
|
+
if (!closeSelector && this.selectors.focusable) {
|
|
363
|
+
closeSelector = this.selectors.focusable;
|
|
364
|
+
} else if (!closeSelector) {
|
|
365
|
+
closeSelector = this.selectors.trigger;
|
|
366
|
+
}
|
|
367
|
+
if (closeSelector) {
|
|
368
|
+
const closeElement = page.locator(closeSelector).first();
|
|
369
|
+
await closeElement.focus();
|
|
370
|
+
await page.keyboard.press("Escape");
|
|
371
|
+
menuClosed = await test.expect(popupElement).toBeHidden({ timeout: this.assertionTimeoutMs }).then(() => true).catch(() => false);
|
|
372
|
+
}
|
|
373
|
+
if (!menuClosed && this.selectors.trigger) {
|
|
374
|
+
const triggerElement = page.locator(this.selectors.trigger).first();
|
|
375
|
+
await triggerElement.click({ timeout: this.actionTimeoutMs });
|
|
376
|
+
menuClosed = await test.expect(popupElement).toBeHidden({ timeout: this.assertionTimeoutMs }).then(() => true).catch(() => false);
|
|
377
|
+
}
|
|
378
|
+
if (!menuClosed) {
|
|
379
|
+
await page.mouse.click(10, 10);
|
|
380
|
+
menuClosed = await test.expect(popupElement).toBeHidden({ timeout: this.assertionTimeoutMs }).then(() => true).catch(() => false);
|
|
381
|
+
}
|
|
382
|
+
if (!menuClosed) {
|
|
383
|
+
throw new Error(
|
|
384
|
+
`\u274C FATAL: Cannot close combobox popup between tests. Popup remains visible after trying:
|
|
385
|
+
1. Escape key
|
|
386
|
+
2. Clicking trigger
|
|
387
|
+
3. Clicking outside
|
|
388
|
+
This indicates a problem with the combobox component's close functionality.`
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
if (this.selectors.input) {
|
|
392
|
+
await page.locator(this.selectors.input).first().clear();
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
async shouldSkipTest() {
|
|
396
|
+
return false;
|
|
397
|
+
}
|
|
398
|
+
getMainSelector() {
|
|
399
|
+
return this.mainSelector;
|
|
400
|
+
}
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
});
|
|
404
|
+
var AccordionComponentStrategy;
|
|
405
|
+
var init_AccordionComponentStrategy = __esm({
|
|
406
|
+
"src/utils/test/src/component-strategies/AccordionComponentStrategy.ts"() {
|
|
407
|
+
AccordionComponentStrategy = class {
|
|
408
|
+
constructor(mainSelector, selectors, actionTimeoutMs = 400, assertionTimeoutMs = 400) {
|
|
409
|
+
this.mainSelector = mainSelector;
|
|
410
|
+
this.selectors = selectors;
|
|
411
|
+
this.actionTimeoutMs = actionTimeoutMs;
|
|
412
|
+
this.assertionTimeoutMs = assertionTimeoutMs;
|
|
413
|
+
}
|
|
414
|
+
async resetState(page) {
|
|
415
|
+
if (!this.selectors.panel || !this.selectors.trigger || this.selectors.popup) {
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
const triggerSelector = this.selectors.trigger;
|
|
419
|
+
const panelSelector = this.selectors.panel;
|
|
420
|
+
if (!triggerSelector || !panelSelector) return;
|
|
421
|
+
const allTriggers = await page.locator(triggerSelector).all();
|
|
422
|
+
for (const trigger of allTriggers) {
|
|
423
|
+
const isExpanded = await trigger.getAttribute("aria-expanded") === "true";
|
|
424
|
+
const triggerPanel = await trigger.getAttribute("aria-controls");
|
|
425
|
+
if (isExpanded && triggerPanel) {
|
|
426
|
+
await trigger.click({ timeout: this.actionTimeoutMs });
|
|
427
|
+
const panel = page.locator(`#${triggerPanel}`);
|
|
428
|
+
await test.expect(panel).toBeHidden({ timeout: this.assertionTimeoutMs }).catch(() => {
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
async shouldSkipTest() {
|
|
434
|
+
return false;
|
|
435
|
+
}
|
|
436
|
+
getMainSelector() {
|
|
437
|
+
return this.mainSelector;
|
|
438
|
+
}
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
});
|
|
442
|
+
var MenuComponentStrategy;
|
|
443
|
+
var init_MenuComponentStrategy = __esm({
|
|
444
|
+
"src/utils/test/src/component-strategies/MenuComponentStrategy.ts"() {
|
|
445
|
+
MenuComponentStrategy = class {
|
|
446
|
+
constructor(mainSelector, selectors, actionTimeoutMs = 400, assertionTimeoutMs = 400) {
|
|
447
|
+
this.mainSelector = mainSelector;
|
|
448
|
+
this.selectors = selectors;
|
|
449
|
+
this.actionTimeoutMs = actionTimeoutMs;
|
|
450
|
+
this.assertionTimeoutMs = assertionTimeoutMs;
|
|
451
|
+
}
|
|
452
|
+
async resetState(page) {
|
|
453
|
+
if (!this.selectors.popup) return;
|
|
454
|
+
const popupSelector = this.selectors.popup;
|
|
455
|
+
const popupElement = page.locator(popupSelector).first();
|
|
456
|
+
const isPopupVisible = await popupElement.isVisible().catch(() => false);
|
|
457
|
+
if (!isPopupVisible) return;
|
|
458
|
+
let menuClosed = false;
|
|
459
|
+
let closeSelector = this.selectors.input;
|
|
460
|
+
if (!closeSelector && this.selectors.focusable) {
|
|
461
|
+
closeSelector = this.selectors.focusable;
|
|
462
|
+
} else if (!closeSelector) {
|
|
463
|
+
closeSelector = this.selectors.trigger;
|
|
464
|
+
}
|
|
465
|
+
if (closeSelector) {
|
|
466
|
+
const closeElement = page.locator(closeSelector).first();
|
|
467
|
+
await closeElement.focus();
|
|
468
|
+
await page.keyboard.press("Escape");
|
|
469
|
+
menuClosed = await test.expect(popupElement).toBeHidden({ timeout: this.assertionTimeoutMs }).then(() => true).catch(() => false);
|
|
470
|
+
}
|
|
471
|
+
if (!menuClosed && this.selectors.trigger) {
|
|
472
|
+
const triggerElement = page.locator(this.selectors.trigger).first();
|
|
473
|
+
await triggerElement.click({ timeout: this.actionTimeoutMs });
|
|
474
|
+
menuClosed = await test.expect(popupElement).toBeHidden({ timeout: this.assertionTimeoutMs }).then(() => true).catch(() => false);
|
|
475
|
+
}
|
|
476
|
+
if (!menuClosed) {
|
|
477
|
+
await page.mouse.click(10, 10);
|
|
478
|
+
menuClosed = await test.expect(popupElement).toBeHidden({ timeout: this.assertionTimeoutMs }).then(() => true).catch(() => false);
|
|
479
|
+
}
|
|
480
|
+
if (!menuClosed) {
|
|
481
|
+
throw new Error(
|
|
482
|
+
`\u274C FATAL: Cannot close menu between tests. Menu remains visible after trying:
|
|
483
|
+
1. Escape key
|
|
484
|
+
2. Clicking trigger
|
|
485
|
+
3. Clicking outside
|
|
486
|
+
This indicates a problem with the menu component's close functionality.`
|
|
487
|
+
);
|
|
488
|
+
}
|
|
489
|
+
if (this.selectors.input) {
|
|
490
|
+
await page.locator(this.selectors.input).first().clear();
|
|
491
|
+
}
|
|
492
|
+
if (this.selectors.trigger) {
|
|
493
|
+
const triggerElement = page.locator(this.selectors.trigger).first();
|
|
494
|
+
await triggerElement.focus();
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
async shouldSkipTest(test, page) {
|
|
498
|
+
for (const act of test.action) {
|
|
499
|
+
if (act.type === "keypress" && (act.target === "submenuTrigger" || act.target === "submenu")) {
|
|
500
|
+
const submenuSelector = this.selectors[act.target];
|
|
501
|
+
if (submenuSelector) {
|
|
502
|
+
const submenuCount = await page.locator(submenuSelector).count();
|
|
503
|
+
if (submenuCount === 0) {
|
|
504
|
+
return true;
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
for (const assertion of test.assertions) {
|
|
510
|
+
if (assertion.target === "submenu" || assertion.target === "submenuTrigger") {
|
|
511
|
+
const submenuSelector = this.selectors[assertion.target];
|
|
512
|
+
if (submenuSelector) {
|
|
513
|
+
const submenuCount = await page.locator(submenuSelector).count();
|
|
514
|
+
if (submenuCount === 0) {
|
|
515
|
+
return true;
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
return false;
|
|
521
|
+
}
|
|
522
|
+
getMainSelector() {
|
|
523
|
+
return this.mainSelector;
|
|
524
|
+
}
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
// src/utils/test/src/component-strategies/TabsComponentStrategy.ts
|
|
530
|
+
var TabsComponentStrategy;
|
|
531
|
+
var init_TabsComponentStrategy = __esm({
|
|
532
|
+
"src/utils/test/src/component-strategies/TabsComponentStrategy.ts"() {
|
|
533
|
+
TabsComponentStrategy = class {
|
|
534
|
+
constructor(mainSelector, selectors) {
|
|
535
|
+
this.mainSelector = mainSelector;
|
|
536
|
+
this.selectors = selectors;
|
|
537
|
+
}
|
|
538
|
+
async resetState() {
|
|
539
|
+
}
|
|
540
|
+
async shouldSkipTest(test, page) {
|
|
541
|
+
if (test.isVertical !== void 0 && this.selectors.tablist) {
|
|
542
|
+
const tablistSelector = this.selectors.tablist;
|
|
543
|
+
const tablist = page.locator(tablistSelector).first();
|
|
544
|
+
const orientation = await tablist.getAttribute("aria-orientation");
|
|
545
|
+
const isVertical = orientation === "vertical";
|
|
546
|
+
if (test.isVertical !== isVertical) {
|
|
547
|
+
return true;
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
return false;
|
|
551
|
+
}
|
|
552
|
+
getMainSelector() {
|
|
553
|
+
return this.mainSelector;
|
|
554
|
+
}
|
|
555
|
+
};
|
|
556
|
+
}
|
|
557
|
+
});
|
|
558
|
+
var ComponentDetector;
|
|
559
|
+
var init_ComponentDetector = __esm({
|
|
560
|
+
"src/utils/test/src/ComponentDetector.ts"() {
|
|
561
|
+
init_ComboboxComponentStrategy();
|
|
562
|
+
init_AccordionComponentStrategy();
|
|
563
|
+
init_MenuComponentStrategy();
|
|
564
|
+
init_TabsComponentStrategy();
|
|
565
|
+
init_contract();
|
|
566
|
+
ComponentDetector = class {
|
|
567
|
+
static detect(componentName, actionTimeoutMs = 400, assertionTimeoutMs = 400) {
|
|
568
|
+
const contractTyped = contract_default;
|
|
569
|
+
const contractPath = contractTyped[componentName]?.path;
|
|
570
|
+
if (!contractPath) {
|
|
571
|
+
throw new Error(`Contract path not found for component: ${componentName}`);
|
|
572
|
+
}
|
|
573
|
+
const resolvedPath = new URL(contractPath, (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href))).pathname;
|
|
574
|
+
const contractData = fs.readFileSync(resolvedPath, "utf-8");
|
|
575
|
+
const componentContract = JSON.parse(contractData);
|
|
576
|
+
const selectors = componentContract.selectors;
|
|
577
|
+
if (componentName.includes("combobox")) {
|
|
578
|
+
const mainSelector = selectors.input || selectors.container;
|
|
579
|
+
return new ComboboxComponentStrategy(mainSelector, selectors, actionTimeoutMs, assertionTimeoutMs);
|
|
580
|
+
}
|
|
581
|
+
if (componentName === "accordion") {
|
|
582
|
+
const mainSelector = selectors.trigger || selectors.container;
|
|
583
|
+
return new AccordionComponentStrategy(mainSelector, selectors, actionTimeoutMs, assertionTimeoutMs);
|
|
584
|
+
}
|
|
585
|
+
if (componentName === "menu") {
|
|
586
|
+
const mainSelector = selectors.trigger || selectors.container;
|
|
587
|
+
return new MenuComponentStrategy(mainSelector, selectors, actionTimeoutMs, assertionTimeoutMs);
|
|
588
|
+
}
|
|
589
|
+
if (componentName === "tabs") {
|
|
590
|
+
const mainSelector = selectors.tablist || selectors.tab;
|
|
591
|
+
return new TabsComponentStrategy(mainSelector, selectors);
|
|
592
|
+
}
|
|
593
|
+
return null;
|
|
594
|
+
}
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
});
|
|
598
|
+
|
|
599
|
+
// src/utils/test/src/RelativeTargetResolver.ts
|
|
600
|
+
var RelativeTargetResolver;
|
|
601
|
+
var init_RelativeTargetResolver = __esm({
|
|
602
|
+
"src/utils/test/src/RelativeTargetResolver.ts"() {
|
|
603
|
+
RelativeTargetResolver = class {
|
|
604
|
+
/**
|
|
605
|
+
* Resolve a relative target like "first", "second", "last", "next", "previous"
|
|
606
|
+
* @param page Playwright page instance
|
|
607
|
+
* @param selector Base selector to find elements
|
|
608
|
+
* @param relative Relative position (first, second, last, next, previous)
|
|
609
|
+
* @returns The resolved Locator or null if not found
|
|
610
|
+
*/
|
|
611
|
+
static async resolve(page, selector, relative) {
|
|
612
|
+
const items = await page.locator(selector).all();
|
|
613
|
+
switch (relative) {
|
|
614
|
+
case "first":
|
|
615
|
+
return items[0];
|
|
616
|
+
case "second":
|
|
617
|
+
return items[1];
|
|
618
|
+
case "last":
|
|
619
|
+
return items[items.length - 1];
|
|
620
|
+
case "next": {
|
|
621
|
+
const currentIndex = await page.evaluate(([sel]) => {
|
|
622
|
+
const items2 = Array.from(document.querySelectorAll(sel));
|
|
623
|
+
return items2.indexOf(document.activeElement);
|
|
624
|
+
}, [selector]);
|
|
625
|
+
const nextIndex = (currentIndex + 1) % items.length;
|
|
626
|
+
return items[nextIndex];
|
|
627
|
+
}
|
|
628
|
+
case "previous": {
|
|
629
|
+
const currentIndex = await page.evaluate(([sel]) => {
|
|
630
|
+
const items2 = Array.from(document.querySelectorAll(sel));
|
|
631
|
+
return items2.indexOf(document.activeElement);
|
|
632
|
+
}, [selector]);
|
|
633
|
+
const prevIndex = (currentIndex - 1 + items.length) % items.length;
|
|
634
|
+
return items[prevIndex];
|
|
635
|
+
}
|
|
636
|
+
default:
|
|
637
|
+
return null;
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
});
|
|
328
643
|
|
|
329
|
-
// src/utils/test/
|
|
644
|
+
// src/utils/test/src/ActionExecutor.ts
|
|
645
|
+
var ActionExecutor;
|
|
646
|
+
var init_ActionExecutor = __esm({
|
|
647
|
+
"src/utils/test/src/ActionExecutor.ts"() {
|
|
648
|
+
init_RelativeTargetResolver();
|
|
649
|
+
ActionExecutor = class {
|
|
650
|
+
constructor(page, selectors, timeoutMs = 400) {
|
|
651
|
+
this.page = page;
|
|
652
|
+
this.selectors = selectors;
|
|
653
|
+
this.timeoutMs = timeoutMs;
|
|
654
|
+
}
|
|
655
|
+
/**
|
|
656
|
+
* Check if error is due to browser/page being closed
|
|
657
|
+
*/
|
|
658
|
+
isBrowserClosedError(error) {
|
|
659
|
+
return error instanceof Error && error.message.includes("Target page, context or browser has been closed");
|
|
660
|
+
}
|
|
661
|
+
/**
|
|
662
|
+
* Execute focus action
|
|
663
|
+
*/
|
|
664
|
+
async focus(target) {
|
|
665
|
+
try {
|
|
666
|
+
const selector = this.selectors[target];
|
|
667
|
+
if (!selector) {
|
|
668
|
+
return { success: false, error: `Selector for focus target ${target} not found.` };
|
|
669
|
+
}
|
|
670
|
+
await this.page.locator(selector).first().focus({ timeout: this.timeoutMs });
|
|
671
|
+
return { success: true };
|
|
672
|
+
} catch (error) {
|
|
673
|
+
if (this.isBrowserClosedError(error)) {
|
|
674
|
+
return {
|
|
675
|
+
success: false,
|
|
676
|
+
error: `CRITICAL: Browser/page closed during test execution. Remaining actions skipped.`,
|
|
677
|
+
shouldBreak: true
|
|
678
|
+
};
|
|
679
|
+
}
|
|
680
|
+
return {
|
|
681
|
+
success: false,
|
|
682
|
+
error: `Failed to focus ${target}: ${error instanceof Error ? error.message : String(error)}`
|
|
683
|
+
};
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
/**
|
|
687
|
+
* Execute type/fill action
|
|
688
|
+
*/
|
|
689
|
+
async type(target, value) {
|
|
690
|
+
try {
|
|
691
|
+
const selector = this.selectors[target];
|
|
692
|
+
if (!selector) {
|
|
693
|
+
return { success: false, error: `Selector for type target ${target} not found.` };
|
|
694
|
+
}
|
|
695
|
+
await this.page.locator(selector).first().fill(value, { timeout: this.timeoutMs });
|
|
696
|
+
return { success: true };
|
|
697
|
+
} catch (error) {
|
|
698
|
+
if (this.isBrowserClosedError(error)) {
|
|
699
|
+
return {
|
|
700
|
+
success: false,
|
|
701
|
+
error: `CRITICAL: Browser/page closed during test execution. Remaining actions skipped.`,
|
|
702
|
+
shouldBreak: true
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
return {
|
|
706
|
+
success: false,
|
|
707
|
+
error: `Failed to type into ${target}: ${error instanceof Error ? error.message : String(error)}`
|
|
708
|
+
};
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
/**
|
|
712
|
+
* Execute click action
|
|
713
|
+
*/
|
|
714
|
+
async click(target, relativeTarget) {
|
|
715
|
+
try {
|
|
716
|
+
if (target === "document") {
|
|
717
|
+
await this.page.mouse.click(10, 10);
|
|
718
|
+
return { success: true };
|
|
719
|
+
}
|
|
720
|
+
if (target === "relative" && relativeTarget) {
|
|
721
|
+
const relativeSelector = this.selectors.relative;
|
|
722
|
+
if (!relativeSelector) {
|
|
723
|
+
return { success: false, error: `Relative selector not defined for click action.` };
|
|
724
|
+
}
|
|
725
|
+
const element = await RelativeTargetResolver.resolve(this.page, relativeSelector, relativeTarget);
|
|
726
|
+
if (!element) {
|
|
727
|
+
return { success: false, error: `Could not resolve relative target ${relativeTarget} for click.` };
|
|
728
|
+
}
|
|
729
|
+
await element.click({ timeout: this.timeoutMs });
|
|
730
|
+
return { success: true };
|
|
731
|
+
}
|
|
732
|
+
const selector = this.selectors[target];
|
|
733
|
+
if (!selector) {
|
|
734
|
+
return { success: false, error: `Selector for action target ${target} not found.` };
|
|
735
|
+
}
|
|
736
|
+
await this.page.locator(selector).first().click({ timeout: this.timeoutMs });
|
|
737
|
+
return { success: true };
|
|
738
|
+
} catch (error) {
|
|
739
|
+
if (this.isBrowserClosedError(error)) {
|
|
740
|
+
return {
|
|
741
|
+
success: false,
|
|
742
|
+
error: `CRITICAL: Browser/page closed during test execution. Remaining actions skipped.`,
|
|
743
|
+
shouldBreak: true
|
|
744
|
+
};
|
|
745
|
+
}
|
|
746
|
+
return {
|
|
747
|
+
success: false,
|
|
748
|
+
error: `Failed to click ${target}: ${error instanceof Error ? error.message : String(error)}`
|
|
749
|
+
};
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
/**
|
|
753
|
+
* Execute keypress action
|
|
754
|
+
*/
|
|
755
|
+
async keypress(target, key) {
|
|
756
|
+
try {
|
|
757
|
+
const keyMap = {
|
|
758
|
+
"Space": "Space",
|
|
759
|
+
"Enter": "Enter",
|
|
760
|
+
"Escape": "Escape",
|
|
761
|
+
"Arrow Up": "ArrowUp",
|
|
762
|
+
"Arrow Down": "ArrowDown",
|
|
763
|
+
"Arrow Left": "ArrowLeft",
|
|
764
|
+
"Arrow Right": "ArrowRight",
|
|
765
|
+
"Home": "Home",
|
|
766
|
+
"End": "End",
|
|
767
|
+
"Tab": "Tab"
|
|
768
|
+
};
|
|
769
|
+
let keyValue = keyMap[key] || key;
|
|
770
|
+
if (keyValue === "Space") {
|
|
771
|
+
keyValue = " ";
|
|
772
|
+
} else if (keyValue.includes(" ")) {
|
|
773
|
+
keyValue = keyValue.replace(/ /g, "");
|
|
774
|
+
}
|
|
775
|
+
if (target === "focusable" && ["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight", "Escape"].includes(keyValue)) {
|
|
776
|
+
await this.page.keyboard.press(keyValue);
|
|
777
|
+
return { success: true };
|
|
778
|
+
}
|
|
779
|
+
const selector = this.selectors[target];
|
|
780
|
+
if (!selector) {
|
|
781
|
+
return { success: false, error: `Selector for keypress target ${target} not found.` };
|
|
782
|
+
}
|
|
783
|
+
const locator = this.page.locator(selector).first();
|
|
784
|
+
const elementCount = await locator.count();
|
|
785
|
+
if (elementCount === 0) {
|
|
786
|
+
return {
|
|
787
|
+
success: false,
|
|
788
|
+
error: `${target} element not found (optional submenu test)`,
|
|
789
|
+
shouldBreak: true
|
|
790
|
+
// Signal to skip this test
|
|
791
|
+
};
|
|
792
|
+
}
|
|
793
|
+
await locator.press(keyValue, { timeout: this.timeoutMs });
|
|
794
|
+
return { success: true };
|
|
795
|
+
} catch (error) {
|
|
796
|
+
if (this.isBrowserClosedError(error)) {
|
|
797
|
+
return {
|
|
798
|
+
success: false,
|
|
799
|
+
error: `CRITICAL: Browser/page closed during test execution. Remaining actions skipped.`,
|
|
800
|
+
shouldBreak: true
|
|
801
|
+
};
|
|
802
|
+
}
|
|
803
|
+
return {
|
|
804
|
+
success: false,
|
|
805
|
+
error: `Failed to press ${key} on ${target}: ${error instanceof Error ? error.message : String(error)}`
|
|
806
|
+
};
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
/**
|
|
810
|
+
* Execute hover action
|
|
811
|
+
*/
|
|
812
|
+
async hover(target, relativeTarget) {
|
|
813
|
+
try {
|
|
814
|
+
if (target === "relative" && relativeTarget) {
|
|
815
|
+
const relativeSelector = this.selectors.relative;
|
|
816
|
+
if (!relativeSelector) {
|
|
817
|
+
return { success: false, error: `Relative selector not defined for hover action.` };
|
|
818
|
+
}
|
|
819
|
+
const element = await RelativeTargetResolver.resolve(this.page, relativeSelector, relativeTarget);
|
|
820
|
+
if (!element) {
|
|
821
|
+
return { success: false, error: `Could not resolve relative target ${relativeTarget} for hover.` };
|
|
822
|
+
}
|
|
823
|
+
await element.hover({ timeout: this.timeoutMs });
|
|
824
|
+
return { success: true };
|
|
825
|
+
}
|
|
826
|
+
const selector = this.selectors[target];
|
|
827
|
+
if (!selector) {
|
|
828
|
+
return { success: false, error: `Selector for hover target ${target} not found.` };
|
|
829
|
+
}
|
|
830
|
+
await this.page.locator(selector).first().hover({ timeout: this.timeoutMs });
|
|
831
|
+
return { success: true };
|
|
832
|
+
} catch (error) {
|
|
833
|
+
if (this.isBrowserClosedError(error)) {
|
|
834
|
+
return {
|
|
835
|
+
success: false,
|
|
836
|
+
error: `CRITICAL: Browser/page closed during test execution. Remaining actions skipped.`,
|
|
837
|
+
shouldBreak: true
|
|
838
|
+
};
|
|
839
|
+
}
|
|
840
|
+
return {
|
|
841
|
+
success: false,
|
|
842
|
+
error: `Failed to hover ${target}: ${error instanceof Error ? error.message : String(error)}`
|
|
843
|
+
};
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
};
|
|
847
|
+
}
|
|
848
|
+
});
|
|
849
|
+
var AssertionRunner;
|
|
850
|
+
var init_AssertionRunner = __esm({
|
|
851
|
+
"src/utils/test/src/AssertionRunner.ts"() {
|
|
852
|
+
init_RelativeTargetResolver();
|
|
853
|
+
AssertionRunner = class {
|
|
854
|
+
constructor(page, selectors, timeoutMs = 400) {
|
|
855
|
+
this.page = page;
|
|
856
|
+
this.selectors = selectors;
|
|
857
|
+
this.timeoutMs = timeoutMs;
|
|
858
|
+
}
|
|
859
|
+
/**
|
|
860
|
+
* Resolve the target element for an assertion
|
|
861
|
+
*/
|
|
862
|
+
async resolveTarget(targetName, relativeTarget) {
|
|
863
|
+
try {
|
|
864
|
+
if (targetName === "relative") {
|
|
865
|
+
const relativeSelector = this.selectors.relative;
|
|
866
|
+
if (!relativeSelector) {
|
|
867
|
+
return { target: null, error: "Relative selector is not defined in the contract." };
|
|
868
|
+
}
|
|
869
|
+
if (!relativeTarget) {
|
|
870
|
+
return { target: null, error: "Relative target or expected value is not defined." };
|
|
871
|
+
}
|
|
872
|
+
const target = await RelativeTargetResolver.resolve(this.page, relativeSelector, relativeTarget);
|
|
873
|
+
if (!target) {
|
|
874
|
+
return { target: null, error: `Target ${targetName} not found.` };
|
|
875
|
+
}
|
|
876
|
+
return { target };
|
|
877
|
+
}
|
|
878
|
+
const selector = this.selectors[targetName];
|
|
879
|
+
if (!selector) {
|
|
880
|
+
return { target: null, error: `Selector for assertion target ${targetName} not found.` };
|
|
881
|
+
}
|
|
882
|
+
return { target: this.page.locator(selector).first() };
|
|
883
|
+
} catch (error) {
|
|
884
|
+
return {
|
|
885
|
+
target: null,
|
|
886
|
+
error: `Failed to resolve target ${targetName}: ${error instanceof Error ? error.message : String(error)}`
|
|
887
|
+
};
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
/**
|
|
891
|
+
* Validate visibility assertion
|
|
892
|
+
*/
|
|
893
|
+
async validateVisibility(target, targetName, expectedVisible, failureMessage, testDescription) {
|
|
894
|
+
try {
|
|
895
|
+
if (expectedVisible) {
|
|
896
|
+
await test.expect(target).toBeVisible({ timeout: this.timeoutMs });
|
|
897
|
+
return {
|
|
898
|
+
success: true,
|
|
899
|
+
passMessage: `${targetName} is visible as expected. Test: "${testDescription}".`
|
|
900
|
+
};
|
|
901
|
+
} else {
|
|
902
|
+
await test.expect(target).toBeHidden({ timeout: this.timeoutMs });
|
|
903
|
+
return {
|
|
904
|
+
success: true,
|
|
905
|
+
passMessage: `${targetName} is not visible as expected. Test: "${testDescription}".`
|
|
906
|
+
};
|
|
907
|
+
}
|
|
908
|
+
} catch {
|
|
909
|
+
const selector = this.selectors[targetName] || "";
|
|
910
|
+
const debugState = await this.page.evaluate((sel) => {
|
|
911
|
+
const el = sel ? document.querySelector(sel) : null;
|
|
912
|
+
if (!el) return "element not found";
|
|
913
|
+
const styles = window.getComputedStyle(el);
|
|
914
|
+
return `display:${styles.display}, visibility:${styles.visibility}, opacity:${styles.opacity}`;
|
|
915
|
+
}, selector);
|
|
916
|
+
if (expectedVisible) {
|
|
917
|
+
return {
|
|
918
|
+
success: false,
|
|
919
|
+
failMessage: `${failureMessage} (actual: ${debugState})`
|
|
920
|
+
};
|
|
921
|
+
} else {
|
|
922
|
+
return {
|
|
923
|
+
success: false,
|
|
924
|
+
failMessage: `${failureMessage} ${targetName} is still visible (actual: ${debugState}).`
|
|
925
|
+
};
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
/**
|
|
930
|
+
* Validate attribute assertion
|
|
931
|
+
*/
|
|
932
|
+
async validateAttribute(target, targetName, attribute, expectedValue, failureMessage, testDescription) {
|
|
933
|
+
if (expectedValue === "!empty") {
|
|
934
|
+
const attributeValue2 = await target.getAttribute(attribute);
|
|
935
|
+
if (attributeValue2 && attributeValue2.trim() !== "") {
|
|
936
|
+
return {
|
|
937
|
+
success: true,
|
|
938
|
+
passMessage: `${targetName} has non-empty "${attribute}". Test: "${testDescription}".`
|
|
939
|
+
};
|
|
940
|
+
} else {
|
|
941
|
+
return {
|
|
942
|
+
success: false,
|
|
943
|
+
failMessage: `${failureMessage} ${targetName} "${attribute}" should not be empty, found "${attributeValue2}".`
|
|
944
|
+
};
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
const expectedValues = expectedValue.split(" | ").map((v) => v.trim());
|
|
948
|
+
const attributeValue = await target.getAttribute(attribute);
|
|
949
|
+
if (attributeValue !== null && expectedValues.includes(attributeValue)) {
|
|
950
|
+
return {
|
|
951
|
+
success: true,
|
|
952
|
+
passMessage: `${targetName} has expected "${attribute}". Test: "${testDescription}".`
|
|
953
|
+
};
|
|
954
|
+
} else {
|
|
955
|
+
return {
|
|
956
|
+
success: false,
|
|
957
|
+
failMessage: `${failureMessage} ${targetName} "${attribute}" should be "${expectedValue}", found "${attributeValue}".`
|
|
958
|
+
};
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
/**
|
|
962
|
+
* Validate input value assertion
|
|
963
|
+
*/
|
|
964
|
+
async validateValue(target, targetName, expectedValue, failureMessage, testDescription) {
|
|
965
|
+
const inputValue = await target.inputValue().catch(() => "");
|
|
966
|
+
if (expectedValue === "!empty") {
|
|
967
|
+
if (inputValue && inputValue.trim() !== "") {
|
|
968
|
+
return {
|
|
969
|
+
success: true,
|
|
970
|
+
passMessage: `${targetName} has non-empty value. Test: "${testDescription}".`
|
|
971
|
+
};
|
|
972
|
+
} else {
|
|
973
|
+
return {
|
|
974
|
+
success: false,
|
|
975
|
+
failMessage: `${failureMessage} ${targetName} value should not be empty, found "${inputValue}".`
|
|
976
|
+
};
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
if (expectedValue === "") {
|
|
980
|
+
if (inputValue === "") {
|
|
981
|
+
return {
|
|
982
|
+
success: true,
|
|
983
|
+
passMessage: `${targetName} has empty value. Test: "${testDescription}".`
|
|
984
|
+
};
|
|
985
|
+
} else {
|
|
986
|
+
return {
|
|
987
|
+
success: false,
|
|
988
|
+
failMessage: `${failureMessage} ${targetName} value should be empty, found "${inputValue}".`
|
|
989
|
+
};
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
if (inputValue === expectedValue) {
|
|
993
|
+
return {
|
|
994
|
+
success: true,
|
|
995
|
+
passMessage: `${targetName} has expected value. Test: "${testDescription}".`
|
|
996
|
+
};
|
|
997
|
+
} else {
|
|
998
|
+
return {
|
|
999
|
+
success: false,
|
|
1000
|
+
failMessage: `${failureMessage} ${targetName} value should be "${expectedValue}", found "${inputValue}".`
|
|
1001
|
+
};
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
/**
|
|
1005
|
+
* Validate focus assertion
|
|
1006
|
+
*/
|
|
1007
|
+
async validateFocus(target, targetName, failureMessage, testDescription) {
|
|
1008
|
+
try {
|
|
1009
|
+
await test.expect(target).toBeFocused({ timeout: this.timeoutMs });
|
|
1010
|
+
return {
|
|
1011
|
+
success: true,
|
|
1012
|
+
passMessage: `${targetName} has focus as expected. Test: "${testDescription}".`
|
|
1013
|
+
};
|
|
1014
|
+
} catch {
|
|
1015
|
+
const actualFocus = await this.page.evaluate(() => {
|
|
1016
|
+
const focused = document.activeElement;
|
|
1017
|
+
return focused ? `${focused.tagName}#${focused.id || "no-id"}.${focused.className || "no-class"}` : "no element focused";
|
|
1018
|
+
});
|
|
1019
|
+
return {
|
|
1020
|
+
success: false,
|
|
1021
|
+
failMessage: `${failureMessage} (actual focus: ${actualFocus})`
|
|
1022
|
+
};
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
/**
|
|
1026
|
+
* Validate role assertion
|
|
1027
|
+
*/
|
|
1028
|
+
async validateRole(target, targetName, expectedRole, failureMessage, testDescription) {
|
|
1029
|
+
const roleValue = await target.getAttribute("role");
|
|
1030
|
+
if (roleValue === expectedRole) {
|
|
1031
|
+
return {
|
|
1032
|
+
success: true,
|
|
1033
|
+
passMessage: `${targetName} has role "${expectedRole}". Test: "${testDescription}".`
|
|
1034
|
+
};
|
|
1035
|
+
} else {
|
|
1036
|
+
return {
|
|
1037
|
+
success: false,
|
|
1038
|
+
failMessage: `${failureMessage} Expected role "${expectedRole}", found "${roleValue}".`
|
|
1039
|
+
};
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
/**
|
|
1043
|
+
* Main validation method - routes to specific validators
|
|
1044
|
+
*/
|
|
1045
|
+
async validate(assertion, testDescription) {
|
|
1046
|
+
if (this.page.isClosed()) {
|
|
1047
|
+
return {
|
|
1048
|
+
success: false,
|
|
1049
|
+
failMessage: `CRITICAL: Browser/page closed before completing all tests. Increase test timeout or reduce test complexity.`
|
|
1050
|
+
};
|
|
1051
|
+
}
|
|
1052
|
+
const { target, error } = await this.resolveTarget(assertion.target, assertion.relativeTarget || assertion.expectedValue);
|
|
1053
|
+
if (error || !target) {
|
|
1054
|
+
return { success: false, failMessage: error || `Target ${assertion.target} not found.`, target: null };
|
|
1055
|
+
}
|
|
1056
|
+
switch (assertion.assertion) {
|
|
1057
|
+
case "toBeVisible":
|
|
1058
|
+
return this.validateVisibility(target, assertion.target, true, assertion.failureMessage || "", testDescription);
|
|
1059
|
+
case "notToBeVisible":
|
|
1060
|
+
return this.validateVisibility(target, assertion.target, false, assertion.failureMessage || "", testDescription);
|
|
1061
|
+
case "toHaveAttribute":
|
|
1062
|
+
if (assertion.attribute && assertion.expectedValue !== void 0) {
|
|
1063
|
+
return this.validateAttribute(
|
|
1064
|
+
target,
|
|
1065
|
+
assertion.target,
|
|
1066
|
+
assertion.attribute,
|
|
1067
|
+
assertion.expectedValue,
|
|
1068
|
+
assertion.failureMessage || "",
|
|
1069
|
+
testDescription
|
|
1070
|
+
);
|
|
1071
|
+
}
|
|
1072
|
+
return { success: false, failMessage: "Missing attribute or expectedValue for toHaveAttribute assertion" };
|
|
1073
|
+
case "toHaveValue":
|
|
1074
|
+
if (assertion.expectedValue !== void 0) {
|
|
1075
|
+
return this.validateValue(target, assertion.target, assertion.expectedValue, assertion.failureMessage || "", testDescription);
|
|
1076
|
+
}
|
|
1077
|
+
return { success: false, failMessage: "Missing expectedValue for toHaveValue assertion" };
|
|
1078
|
+
case "toHaveFocus":
|
|
1079
|
+
return this.validateFocus(target, assertion.target, assertion.failureMessage || "", testDescription);
|
|
1080
|
+
case "toHaveRole":
|
|
1081
|
+
if (assertion.expectedValue !== void 0) {
|
|
1082
|
+
return this.validateRole(target, assertion.target, assertion.expectedValue, assertion.failureMessage || "", testDescription);
|
|
1083
|
+
}
|
|
1084
|
+
return { success: false, failMessage: "Missing expectedValue for toHaveRole assertion" };
|
|
1085
|
+
default:
|
|
1086
|
+
return { success: false, failMessage: `Unknown assertion type: ${assertion.assertion}` };
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
};
|
|
1090
|
+
}
|
|
1091
|
+
});
|
|
1092
|
+
|
|
1093
|
+
// src/utils/test/src/contractTestRunnerPlaywright.ts
|
|
330
1094
|
var contractTestRunnerPlaywright_exports = {};
|
|
331
1095
|
__export(contractTestRunnerPlaywright_exports, {
|
|
332
1096
|
runContractTestsPlaywright: () => runContractTestsPlaywright
|
|
@@ -335,20 +1099,13 @@ async function runContractTestsPlaywright(componentName, url) {
|
|
|
335
1099
|
const reporter = new ContractReporter(true);
|
|
336
1100
|
const actionTimeoutMs = 400;
|
|
337
1101
|
const assertionTimeoutMs = 400;
|
|
338
|
-
function isBrowserClosedError(error) {
|
|
339
|
-
return error instanceof Error && error.message.includes("Target page, context or browser has been closed");
|
|
340
|
-
}
|
|
341
1102
|
const contractTyped = contract_default;
|
|
342
1103
|
const contractPath = contractTyped[componentName]?.path;
|
|
343
|
-
if (!contractPath) {
|
|
344
|
-
throw new Error(`Contract path not found for component: ${componentName}`);
|
|
345
|
-
}
|
|
346
1104
|
const resolvedPath = new URL(contractPath, (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href))).pathname;
|
|
347
1105
|
const contractData = fs.readFileSync(resolvedPath, "utf-8");
|
|
348
1106
|
const componentContract = JSON.parse(contractData);
|
|
349
1107
|
const totalTests = componentContract.static[0].assertions.length + componentContract.dynamic.length;
|
|
350
1108
|
const apgUrl = componentContract.meta?.source?.apg;
|
|
351
|
-
reporter.start(componentName, totalTests, apgUrl);
|
|
352
1109
|
const failures = [];
|
|
353
1110
|
const passes = [];
|
|
354
1111
|
const skipped = [];
|
|
@@ -368,68 +1125,53 @@ async function runContractTestsPlaywright(componentName, url) {
|
|
|
368
1125
|
}
|
|
369
1126
|
await page.addStyleTag({ content: `* { transition: none !important; animation: none !important; }` });
|
|
370
1127
|
}
|
|
371
|
-
const
|
|
1128
|
+
const strategy = ComponentDetector.detect(componentName, actionTimeoutMs, assertionTimeoutMs);
|
|
1129
|
+
if (!strategy) {
|
|
1130
|
+
throw new Error(`Unsupported component: ${componentName}`);
|
|
1131
|
+
}
|
|
1132
|
+
const mainSelector = strategy.getMainSelector();
|
|
372
1133
|
if (!mainSelector) {
|
|
373
|
-
throw new Error(`CRITICAL: No
|
|
1134
|
+
throw new Error(`CRITICAL: No selector found in contract for ${componentName}`);
|
|
374
1135
|
}
|
|
375
1136
|
try {
|
|
376
1137
|
await page.locator(mainSelector).first().waitFor({ state: "attached", timeout: 3e4 });
|
|
377
1138
|
} catch (error) {
|
|
378
1139
|
throw new Error(
|
|
379
|
-
`
|
|
1140
|
+
`
|
|
1141
|
+
\u274C CRITICAL: Component not found on page!
|
|
1142
|
+
This usually means:
|
|
1143
|
+
- The component didn't render
|
|
1144
|
+
- The URL is incorrect
|
|
1145
|
+
- The component selector '${mainSelector}' in the contract is wrong
|
|
1146
|
+
- Original error: ${error}`
|
|
380
1147
|
);
|
|
381
1148
|
}
|
|
1149
|
+
reporter.start(componentName, totalTests, apgUrl);
|
|
382
1150
|
if (componentName === "menu" && componentContract.selectors.trigger) {
|
|
383
1151
|
await page.locator(componentContract.selectors.trigger).first().waitFor({
|
|
384
|
-
state: "
|
|
1152
|
+
state: "attached",
|
|
385
1153
|
timeout: 5e3
|
|
386
1154
|
}).catch(() => {
|
|
387
|
-
console.warn("Menu trigger not visible, continuing with tests...");
|
|
388
1155
|
});
|
|
389
1156
|
}
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
throw new Error("Page is not initialized");
|
|
393
|
-
}
|
|
394
|
-
const items = await page.locator(selector).all();
|
|
395
|
-
switch (relative) {
|
|
396
|
-
case "first":
|
|
397
|
-
return items[0];
|
|
398
|
-
case "second":
|
|
399
|
-
return items[1];
|
|
400
|
-
case "last":
|
|
401
|
-
return items[items.length - 1];
|
|
402
|
-
case "next": {
|
|
403
|
-
const currentIndex = await page.evaluate(([sel]) => {
|
|
404
|
-
const items2 = Array.from(document.querySelectorAll(sel));
|
|
405
|
-
return items2.indexOf(document.activeElement);
|
|
406
|
-
}, [selector]);
|
|
407
|
-
const nextIndex = (currentIndex + 1) % items.length;
|
|
408
|
-
return items[nextIndex];
|
|
409
|
-
}
|
|
410
|
-
case "previous": {
|
|
411
|
-
const currentIndex = await page.evaluate(([sel]) => {
|
|
412
|
-
const items2 = Array.from(document.querySelectorAll(sel));
|
|
413
|
-
return items2.indexOf(document.activeElement);
|
|
414
|
-
}, [selector]);
|
|
415
|
-
const prevIndex = (currentIndex - 1 + items.length) % items.length;
|
|
416
|
-
return items[prevIndex];
|
|
417
|
-
}
|
|
418
|
-
default:
|
|
419
|
-
return null;
|
|
420
|
-
}
|
|
421
|
-
}
|
|
1157
|
+
const failuresBeforeStatic = failures.length;
|
|
1158
|
+
const staticAssertionRunner = new AssertionRunner(page, componentContract.selectors, assertionTimeoutMs);
|
|
422
1159
|
for (const test of componentContract.static[0]?.assertions || []) {
|
|
423
1160
|
if (test.target === "relative") continue;
|
|
1161
|
+
const staticDescription = `${test.target}${test.attribute ? ` (${test.attribute})` : ""}`;
|
|
424
1162
|
const targetSelector = componentContract.selectors[test.target];
|
|
425
1163
|
if (!targetSelector) {
|
|
426
|
-
|
|
1164
|
+
const failure = `Selector for target ${test.target} not found.`;
|
|
1165
|
+
failures.push(failure);
|
|
1166
|
+
reporter.reportStaticTest(staticDescription, false, failure);
|
|
427
1167
|
continue;
|
|
428
1168
|
}
|
|
429
1169
|
const target = page.locator(targetSelector).first();
|
|
430
1170
|
const exists = await target.count() > 0;
|
|
431
1171
|
if (!exists) {
|
|
432
|
-
|
|
1172
|
+
const failure = `Target ${test.target} not found.`;
|
|
1173
|
+
failures.push(failure);
|
|
1174
|
+
reporter.reportStaticTest(staticDescription, false, failure);
|
|
433
1175
|
continue;
|
|
434
1176
|
}
|
|
435
1177
|
const isRedundantCheck = (selector, attrName, expectedVal) => {
|
|
@@ -463,20 +1205,34 @@ async function runContractTestsPlaywright(componentName, url) {
|
|
|
463
1205
|
}
|
|
464
1206
|
}
|
|
465
1207
|
if (!hasAny && !allRedundant) {
|
|
466
|
-
|
|
1208
|
+
const failure = test.failureMessage + ` None of the attributes "${test.attribute}" are present.`;
|
|
1209
|
+
failures.push(failure);
|
|
1210
|
+
reporter.reportStaticTest(staticDescription, false, failure);
|
|
467
1211
|
} else if (!allRedundant && hasAny) {
|
|
468
1212
|
passes.push(`At least one of the attributes "${test.attribute}" exists on the element.`);
|
|
1213
|
+
reporter.reportStaticTest(staticDescription, true);
|
|
1214
|
+
} else {
|
|
1215
|
+
reporter.reportStaticTest(staticDescription, true);
|
|
469
1216
|
}
|
|
470
1217
|
} else {
|
|
471
1218
|
if (isRedundantCheck(targetSelector, test.attribute, test.expectedValue)) {
|
|
472
1219
|
passes.push(`${test.attribute}="${test.expectedValue}" on ${test.target} verified by selector (already present in: ${targetSelector}).`);
|
|
1220
|
+
reporter.reportStaticTest(staticDescription, true);
|
|
473
1221
|
} else {
|
|
474
|
-
const
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
1222
|
+
const result = await staticAssertionRunner.validateAttribute(
|
|
1223
|
+
target,
|
|
1224
|
+
test.target,
|
|
1225
|
+
test.attribute,
|
|
1226
|
+
test.expectedValue,
|
|
1227
|
+
test.failureMessage,
|
|
1228
|
+
"Static ARIA Test"
|
|
1229
|
+
);
|
|
1230
|
+
if (result.success && result.passMessage) {
|
|
1231
|
+
passes.push(result.passMessage);
|
|
1232
|
+
reporter.reportStaticTest(staticDescription, true);
|
|
1233
|
+
} else if (!result.success && result.failMessage) {
|
|
1234
|
+
failures.push(result.failMessage);
|
|
1235
|
+
reporter.reportStaticTest(staticDescription, false, result.failMessage);
|
|
480
1236
|
}
|
|
481
1237
|
}
|
|
482
1238
|
}
|
|
@@ -491,383 +1247,58 @@ async function runContractTestsPlaywright(componentName, url) {
|
|
|
491
1247
|
}
|
|
492
1248
|
const { action, assertions } = dynamicTest;
|
|
493
1249
|
const failuresBeforeTest = failures.length;
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
const
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
let menuClosed = false;
|
|
501
|
-
let closeSelector = componentContract.selectors.input;
|
|
502
|
-
if (!closeSelector && componentContract.selectors.focusable) {
|
|
503
|
-
closeSelector = componentContract.selectors.focusable;
|
|
504
|
-
} else if (!closeSelector) {
|
|
505
|
-
closeSelector = componentContract.selectors.trigger;
|
|
506
|
-
}
|
|
507
|
-
if (closeSelector) {
|
|
508
|
-
const closeElement = page.locator(closeSelector).first();
|
|
509
|
-
await closeElement.focus();
|
|
510
|
-
await page.keyboard.press("Escape");
|
|
511
|
-
menuClosed = await test.expect(popupElement).toBeHidden({ timeout: assertionTimeoutMs }).then(() => true).catch(() => false);
|
|
512
|
-
}
|
|
513
|
-
if (!menuClosed && componentContract.selectors.trigger) {
|
|
514
|
-
const triggerElement = page.locator(componentContract.selectors.trigger).first();
|
|
515
|
-
await triggerElement.click({ timeout: actionTimeoutMs });
|
|
516
|
-
menuClosed = await test.expect(popupElement).toBeHidden({ timeout: assertionTimeoutMs }).then(() => true).catch(() => false);
|
|
517
|
-
}
|
|
518
|
-
if (!menuClosed) {
|
|
519
|
-
await page.mouse.click(10, 10);
|
|
520
|
-
menuClosed = await test.expect(popupElement).toBeHidden({ timeout: assertionTimeoutMs }).then(() => true).catch(() => false);
|
|
521
|
-
}
|
|
522
|
-
if (!menuClosed) {
|
|
523
|
-
throw new Error(
|
|
524
|
-
`\u274C FATAL: Cannot close menu between tests. Menu remains visible after trying:
|
|
525
|
-
1. Escape key
|
|
526
|
-
2. Clicking trigger
|
|
527
|
-
3. Clicking outside
|
|
528
|
-
This indicates a problem with the menu component's close functionality.`
|
|
529
|
-
);
|
|
530
|
-
}
|
|
531
|
-
if (componentContract.selectors.input) {
|
|
532
|
-
await page.locator(componentContract.selectors.input).first().clear();
|
|
533
|
-
}
|
|
534
|
-
if (componentName === "menu" && componentContract.selectors.trigger) {
|
|
535
|
-
const triggerElement = page.locator(componentContract.selectors.trigger).first();
|
|
536
|
-
await triggerElement.focus();
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
}
|
|
540
|
-
if (componentContract.selectors.panel && componentContract.selectors.trigger && !componentContract.selectors.popup) {
|
|
541
|
-
const triggerSelector = componentContract.selectors.trigger;
|
|
542
|
-
const panelSelector = componentContract.selectors.panel;
|
|
543
|
-
if (triggerSelector && panelSelector) {
|
|
544
|
-
const allTriggers = await page.locator(triggerSelector).all();
|
|
545
|
-
for (const trigger of allTriggers) {
|
|
546
|
-
const isExpanded = await trigger.getAttribute("aria-expanded") === "true";
|
|
547
|
-
const triggerPanel = await trigger.getAttribute("aria-controls");
|
|
548
|
-
if (isExpanded && triggerPanel) {
|
|
549
|
-
await trigger.click({ timeout: actionTimeoutMs });
|
|
550
|
-
const panel = page.locator(`#${triggerPanel}`);
|
|
551
|
-
await test.expect(panel).toBeHidden({ timeout: assertionTimeoutMs }).catch(() => {
|
|
552
|
-
});
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
}
|
|
556
|
-
}
|
|
557
|
-
let shouldSkipTest = false;
|
|
558
|
-
for (const act of action) {
|
|
559
|
-
if (act.type === "keypress" && (act.target === "submenuTrigger" || act.target === "submenu")) {
|
|
560
|
-
const submenuSelector = componentContract.selectors[act.target];
|
|
561
|
-
if (submenuSelector) {
|
|
562
|
-
const submenuCount = await page.locator(submenuSelector).count();
|
|
563
|
-
if (submenuCount === 0) {
|
|
564
|
-
reporter.reportTest(dynamicTest, "skip", `Skipping test - ${act.target} element not found (optional submenu test)`);
|
|
565
|
-
shouldSkipTest = true;
|
|
566
|
-
break;
|
|
567
|
-
}
|
|
568
|
-
}
|
|
569
|
-
}
|
|
570
|
-
}
|
|
571
|
-
if (!shouldSkipTest) {
|
|
572
|
-
for (const assertion of assertions) {
|
|
573
|
-
if (assertion.target === "submenu" || assertion.target === "submenuTrigger") {
|
|
574
|
-
const submenuSelector = componentContract.selectors[assertion.target];
|
|
575
|
-
if (submenuSelector) {
|
|
576
|
-
const submenuCount = await page.locator(submenuSelector).count();
|
|
577
|
-
if (submenuCount === 0) {
|
|
578
|
-
reporter.reportTest(dynamicTest, "skip", `Skipping test - ${assertion.target} element not found (optional submenu test)`);
|
|
579
|
-
shouldSkipTest = true;
|
|
580
|
-
break;
|
|
581
|
-
}
|
|
582
|
-
}
|
|
583
|
-
}
|
|
584
|
-
}
|
|
1250
|
+
try {
|
|
1251
|
+
await strategy.resetState(page);
|
|
1252
|
+
} catch (error) {
|
|
1253
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1254
|
+
reporter.error(errorMessage);
|
|
1255
|
+
throw error;
|
|
585
1256
|
}
|
|
1257
|
+
const shouldSkipTest = await strategy.shouldSkipTest(dynamicTest, page);
|
|
586
1258
|
if (shouldSkipTest) {
|
|
1259
|
+
reporter.reportTest(dynamicTest, "skip", `Skipping test - component-specific conditions not met`);
|
|
587
1260
|
continue;
|
|
588
1261
|
}
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
const tablistSelector = componentContract.selectors.tablist;
|
|
592
|
-
const tablist = page.locator(tablistSelector).first();
|
|
593
|
-
const orientation = await tablist.getAttribute("aria-orientation");
|
|
594
|
-
const isVertical = orientation === "vertical";
|
|
595
|
-
if (dynamicTest.isVertical !== isVertical) {
|
|
596
|
-
const skipReason = dynamicTest.isVertical ? `Skipping vertical tabs test - component has horizontal orientation` : `Skipping horizontal tabs test - component has vertical orientation`;
|
|
597
|
-
reporter.reportTest(dynamicTest, "skip", skipReason);
|
|
598
|
-
continue;
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
|
-
}
|
|
1262
|
+
const actionExecutor = new ActionExecutor(page, componentContract.selectors, actionTimeoutMs);
|
|
1263
|
+
const assertionRunner = new AssertionRunner(page, componentContract.selectors, assertionTimeoutMs);
|
|
602
1264
|
for (const act of action) {
|
|
603
1265
|
if (!page || page.isClosed()) {
|
|
604
1266
|
failures.push(`CRITICAL: Browser/page closed during test execution. Remaining actions skipped.`);
|
|
605
1267
|
break;
|
|
606
1268
|
}
|
|
1269
|
+
let result;
|
|
607
1270
|
if (act.type === "focus") {
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
}
|
|
620
|
-
failures.push(`Failed to focus ${act.target}: ${error instanceof Error ? error.message : String(error)}`);
|
|
621
|
-
continue;
|
|
622
|
-
}
|
|
623
|
-
}
|
|
624
|
-
if (act.type === "type" && act.value) {
|
|
625
|
-
try {
|
|
626
|
-
const typeSelector = componentContract.selectors[act.target];
|
|
627
|
-
if (!typeSelector) {
|
|
628
|
-
failures.push(`Selector for type target ${act.target} not found.`);
|
|
629
|
-
continue;
|
|
630
|
-
}
|
|
631
|
-
await page.locator(typeSelector).first().fill(act.value, { timeout: actionTimeoutMs });
|
|
632
|
-
} catch (error) {
|
|
633
|
-
if (isBrowserClosedError(error)) {
|
|
634
|
-
failures.push(`CRITICAL: Browser/page closed during test execution. Remaining actions skipped.`);
|
|
635
|
-
break;
|
|
636
|
-
}
|
|
637
|
-
failures.push(`Failed to type into ${act.target}: ${error instanceof Error ? error.message : String(error)}`);
|
|
638
|
-
continue;
|
|
639
|
-
}
|
|
1271
|
+
result = await actionExecutor.focus(act.target);
|
|
1272
|
+
} else if (act.type === "type" && act.value) {
|
|
1273
|
+
result = await actionExecutor.type(act.target, act.value);
|
|
1274
|
+
} else if (act.type === "click") {
|
|
1275
|
+
result = await actionExecutor.click(act.target, act.relativeTarget);
|
|
1276
|
+
} else if (act.type === "keypress" && act.key) {
|
|
1277
|
+
result = await actionExecutor.keypress(act.target, act.key);
|
|
1278
|
+
} else if (act.type === "hover") {
|
|
1279
|
+
result = await actionExecutor.hover(act.target, act.relativeTarget);
|
|
1280
|
+
} else {
|
|
1281
|
+
continue;
|
|
640
1282
|
}
|
|
641
|
-
if (
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
await page.mouse.click(10, 10);
|
|
645
|
-
} else if (act.target === "relative" && act.relativeTarget) {
|
|
646
|
-
const relativeSelector = componentContract.selectors.relative;
|
|
647
|
-
if (!relativeSelector) {
|
|
648
|
-
failures.push(`Relative selector not defined for click action.`);
|
|
649
|
-
continue;
|
|
650
|
-
}
|
|
651
|
-
const relativeElement = await resolveRelativeTarget(relativeSelector, act.relativeTarget);
|
|
652
|
-
if (!relativeElement) {
|
|
653
|
-
failures.push(`Could not resolve relative target ${act.relativeTarget} for click.`);
|
|
654
|
-
continue;
|
|
655
|
-
}
|
|
656
|
-
await relativeElement.click({ timeout: actionTimeoutMs });
|
|
657
|
-
} else {
|
|
658
|
-
const actionSelector = componentContract.selectors[act.target];
|
|
659
|
-
if (!actionSelector) {
|
|
660
|
-
failures.push(`Selector for action target ${act.target} not found.`);
|
|
661
|
-
continue;
|
|
662
|
-
}
|
|
663
|
-
await page.locator(actionSelector).first().click({ timeout: actionTimeoutMs });
|
|
664
|
-
}
|
|
665
|
-
} catch (error) {
|
|
666
|
-
if (isBrowserClosedError(error)) {
|
|
667
|
-
failures.push(`CRITICAL: Browser/page closed during test execution. Remaining actions skipped.`);
|
|
668
|
-
break;
|
|
669
|
-
}
|
|
670
|
-
failures.push(`Failed to click ${act.target}: ${error instanceof Error ? error.message : String(error)}`);
|
|
671
|
-
continue;
|
|
1283
|
+
if (!result.success) {
|
|
1284
|
+
if (result.error) {
|
|
1285
|
+
failures.push(result.error);
|
|
672
1286
|
}
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
const keyMap = {
|
|
677
|
-
"Space": "Space",
|
|
678
|
-
"Enter": "Enter",
|
|
679
|
-
"Escape": "Escape",
|
|
680
|
-
"Arrow Up": "ArrowUp",
|
|
681
|
-
"Arrow Down": "ArrowDown",
|
|
682
|
-
"Arrow Left": "ArrowLeft",
|
|
683
|
-
"Arrow Right": "ArrowRight",
|
|
684
|
-
"Home": "Home",
|
|
685
|
-
"End": "End",
|
|
686
|
-
"Tab": "Tab"
|
|
687
|
-
};
|
|
688
|
-
let keyValue = keyMap[act.key] || act.key;
|
|
689
|
-
if (keyValue === "Space") {
|
|
690
|
-
keyValue = " ";
|
|
691
|
-
} else if (keyValue.includes(" ")) {
|
|
692
|
-
keyValue = keyValue.replace(/ /g, "");
|
|
1287
|
+
if (result.shouldBreak) {
|
|
1288
|
+
if (result.error?.includes("optional submenu test")) {
|
|
1289
|
+
reporter.reportTest(dynamicTest, "skip", result.error);
|
|
693
1290
|
}
|
|
694
|
-
|
|
695
|
-
await page.keyboard.press(keyValue);
|
|
696
|
-
} else {
|
|
697
|
-
const keypressSelector = componentContract.selectors[act.target];
|
|
698
|
-
if (!keypressSelector) {
|
|
699
|
-
failures.push(`Selector for keypress target ${act.target} not found.`);
|
|
700
|
-
continue;
|
|
701
|
-
}
|
|
702
|
-
const target = page.locator(keypressSelector).first();
|
|
703
|
-
const elementCount = await target.count();
|
|
704
|
-
if (elementCount === 0) {
|
|
705
|
-
reporter.reportTest(dynamicTest, "skip", `Skipping test - ${act.target} element not found (optional submenu test)`);
|
|
706
|
-
break;
|
|
707
|
-
}
|
|
708
|
-
await target.press(keyValue, { timeout: actionTimeoutMs });
|
|
709
|
-
}
|
|
710
|
-
} catch (error) {
|
|
711
|
-
if (isBrowserClosedError(error)) {
|
|
712
|
-
failures.push(`CRITICAL: Browser/page closed during test execution. Remaining actions skipped.`);
|
|
713
|
-
break;
|
|
714
|
-
}
|
|
715
|
-
failures.push(`Failed to press ${act.key} on ${act.target}: ${error instanceof Error ? error.message : String(error)}`);
|
|
716
|
-
continue;
|
|
717
|
-
}
|
|
718
|
-
}
|
|
719
|
-
if (act.type === "hover") {
|
|
720
|
-
try {
|
|
721
|
-
if (act.target === "relative" && act.relativeTarget) {
|
|
722
|
-
const relativeSelector = componentContract.selectors.relative;
|
|
723
|
-
if (!relativeSelector) {
|
|
724
|
-
failures.push(`Relative selector not defined for hover action.`);
|
|
725
|
-
continue;
|
|
726
|
-
}
|
|
727
|
-
const relativeElement = await resolveRelativeTarget(relativeSelector, act.relativeTarget);
|
|
728
|
-
if (!relativeElement) {
|
|
729
|
-
failures.push(`Could not resolve relative target ${act.relativeTarget} for hover.`);
|
|
730
|
-
continue;
|
|
731
|
-
}
|
|
732
|
-
await relativeElement.hover({ timeout: actionTimeoutMs });
|
|
733
|
-
} else {
|
|
734
|
-
const hoverSelector = componentContract.selectors[act.target];
|
|
735
|
-
if (!hoverSelector) {
|
|
736
|
-
failures.push(`Selector for hover target ${act.target} not found.`);
|
|
737
|
-
continue;
|
|
738
|
-
}
|
|
739
|
-
await page.locator(hoverSelector).first().hover({ timeout: actionTimeoutMs });
|
|
740
|
-
}
|
|
741
|
-
} catch (error) {
|
|
742
|
-
if (isBrowserClosedError(error)) {
|
|
743
|
-
failures.push(`CRITICAL: Browser/page closed during test execution. Remaining actions skipped.`);
|
|
744
|
-
break;
|
|
745
|
-
}
|
|
746
|
-
failures.push(`Failed to hover ${act.target}: ${error instanceof Error ? error.message : String(error)}`);
|
|
747
|
-
continue;
|
|
1291
|
+
break;
|
|
748
1292
|
}
|
|
1293
|
+
continue;
|
|
749
1294
|
}
|
|
750
1295
|
}
|
|
751
1296
|
for (const assertion of assertions) {
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
}
|
|
756
|
-
|
|
757
|
-
try {
|
|
758
|
-
if (assertion.target === "relative") {
|
|
759
|
-
const relativeSelector = componentContract.selectors.relative;
|
|
760
|
-
if (!relativeSelector) {
|
|
761
|
-
failures.push("Relative selector is not defined in the contract.");
|
|
762
|
-
continue;
|
|
763
|
-
}
|
|
764
|
-
const relativeTargetValue = assertion.relativeTarget || assertion.expectedValue;
|
|
765
|
-
if (!relativeTargetValue) {
|
|
766
|
-
failures.push("Relative target or expected value is not defined.");
|
|
767
|
-
continue;
|
|
768
|
-
}
|
|
769
|
-
target = await resolveRelativeTarget(relativeSelector, relativeTargetValue);
|
|
770
|
-
} else {
|
|
771
|
-
const assertionSelector = componentContract.selectors[assertion.target];
|
|
772
|
-
if (!assertionSelector) {
|
|
773
|
-
failures.push(`Selector for assertion target ${assertion.target} not found.`);
|
|
774
|
-
continue;
|
|
775
|
-
}
|
|
776
|
-
target = page.locator(assertionSelector).first();
|
|
777
|
-
}
|
|
778
|
-
if (!target) {
|
|
779
|
-
failures.push(`Target ${assertion.target} not found.`);
|
|
780
|
-
continue;
|
|
781
|
-
}
|
|
782
|
-
} catch (error) {
|
|
783
|
-
failures.push(`Failed to resolve target ${assertion.target}: ${error instanceof Error ? error.message : String(error)}`);
|
|
784
|
-
continue;
|
|
785
|
-
}
|
|
786
|
-
if (assertion.assertion === "toBeVisible") {
|
|
787
|
-
try {
|
|
788
|
-
await test.expect(target).toBeVisible({ timeout: assertionTimeoutMs });
|
|
789
|
-
passes.push(`${assertion.target} is visible as expected. Test: "${dynamicTest.description}".`);
|
|
790
|
-
} catch {
|
|
791
|
-
const debugState = await page.evaluate((sel) => {
|
|
792
|
-
const el = sel ? document.querySelector(sel) : null;
|
|
793
|
-
if (!el) return "element not found";
|
|
794
|
-
const styles = window.getComputedStyle(el);
|
|
795
|
-
return `display:${styles.display}, visibility:${styles.visibility}, opacity:${styles.opacity}`;
|
|
796
|
-
}, componentContract.selectors[assertion.target] || "");
|
|
797
|
-
failures.push(`${assertion.failureMessage} (actual: ${debugState})`);
|
|
798
|
-
}
|
|
799
|
-
}
|
|
800
|
-
if (assertion.assertion === "notToBeVisible") {
|
|
801
|
-
try {
|
|
802
|
-
await test.expect(target).toBeHidden({ timeout: assertionTimeoutMs });
|
|
803
|
-
passes.push(`${assertion.target} is not visible as expected. Test: "${dynamicTest.description}".`);
|
|
804
|
-
} catch {
|
|
805
|
-
const debugState = await page.evaluate((sel) => {
|
|
806
|
-
const el = sel ? document.querySelector(sel) : null;
|
|
807
|
-
if (!el) return "element not found";
|
|
808
|
-
const styles = window.getComputedStyle(el);
|
|
809
|
-
return `display:${styles.display}, visibility:${styles.visibility}, opacity:${styles.opacity}`;
|
|
810
|
-
}, componentContract.selectors[assertion.target] || "");
|
|
811
|
-
failures.push(assertion.failureMessage + ` ${assertion.target} is still visible (actual: ${debugState}).`);
|
|
812
|
-
}
|
|
813
|
-
}
|
|
814
|
-
if (assertion.assertion === "toHaveAttribute" && assertion.attribute && assertion.expectedValue) {
|
|
815
|
-
try {
|
|
816
|
-
if (assertion.expectedValue === "!empty") {
|
|
817
|
-
const attributeValue = await target.getAttribute(assertion.attribute);
|
|
818
|
-
if (attributeValue && attributeValue.trim() !== "") {
|
|
819
|
-
passes.push(`${assertion.target} has non-empty "${assertion.attribute}". Test: "${dynamicTest.description}".`);
|
|
820
|
-
} else {
|
|
821
|
-
failures.push(assertion.failureMessage + ` ${assertion.target} "${assertion.attribute}" should not be empty, found "${attributeValue}".`);
|
|
822
|
-
}
|
|
823
|
-
} else {
|
|
824
|
-
await test.expect(target).toHaveAttribute(assertion.attribute, assertion.expectedValue, { timeout: assertionTimeoutMs });
|
|
825
|
-
passes.push(`${assertion.target} has expected "${assertion.attribute}". Test: "${dynamicTest.description}".`);
|
|
826
|
-
}
|
|
827
|
-
} catch {
|
|
828
|
-
const attributeValue = await target.getAttribute(assertion.attribute);
|
|
829
|
-
failures.push(assertion.failureMessage + ` ${assertion.target} "${assertion.attribute}" should be "${assertion.expectedValue}", found "${attributeValue}".`);
|
|
830
|
-
}
|
|
831
|
-
}
|
|
832
|
-
if (assertion.assertion === "toHaveValue") {
|
|
833
|
-
const inputValue = await target.inputValue().catch(() => "");
|
|
834
|
-
if (assertion.expectedValue === "!empty") {
|
|
835
|
-
if (inputValue && inputValue.trim() !== "") {
|
|
836
|
-
passes.push(`${assertion.target} has non-empty value. Test: "${dynamicTest.description}".`);
|
|
837
|
-
} else {
|
|
838
|
-
failures.push(assertion.failureMessage + ` ${assertion.target} value should not be empty, found "${inputValue}".`);
|
|
839
|
-
}
|
|
840
|
-
} else if (assertion.expectedValue === "") {
|
|
841
|
-
if (inputValue === "") {
|
|
842
|
-
passes.push(`${assertion.target} has empty value. Test: "${dynamicTest.description}".`);
|
|
843
|
-
} else {
|
|
844
|
-
failures.push(assertion.failureMessage + ` ${assertion.target} value should be empty, found "${inputValue}".`);
|
|
845
|
-
}
|
|
846
|
-
} else if (inputValue === assertion.expectedValue) {
|
|
847
|
-
passes.push(`${assertion.target} has expected value. Test: "${dynamicTest.description}".`);
|
|
848
|
-
} else {
|
|
849
|
-
failures.push(assertion.failureMessage + ` ${assertion.target} value should be "${assertion.expectedValue}", found "${inputValue}".`);
|
|
850
|
-
}
|
|
851
|
-
}
|
|
852
|
-
if (assertion.assertion === "toHaveFocus") {
|
|
853
|
-
try {
|
|
854
|
-
await test.expect(target).toBeFocused({ timeout: assertionTimeoutMs });
|
|
855
|
-
passes.push(`${assertion.target} has focus as expected. Test: "${dynamicTest.description}".`);
|
|
856
|
-
} catch {
|
|
857
|
-
const actualFocus = await page.evaluate(() => {
|
|
858
|
-
const focused = document.activeElement;
|
|
859
|
-
return focused ? `${focused.tagName}#${focused.id || "no-id"}.${focused.className || "no-class"}` : "no element focused";
|
|
860
|
-
});
|
|
861
|
-
failures.push(`${assertion.failureMessage} (actual focus: ${actualFocus})`);
|
|
862
|
-
}
|
|
863
|
-
}
|
|
864
|
-
if (assertion.assertion === "toHaveRole" && assertion.expectedValue) {
|
|
865
|
-
const roleValue = await target.getAttribute("role");
|
|
866
|
-
if (roleValue === assertion.expectedValue) {
|
|
867
|
-
passes.push(`${assertion.target} has role "${assertion.expectedValue}". Test: "${dynamicTest.description}".`);
|
|
868
|
-
} else {
|
|
869
|
-
failures.push(assertion.failureMessage + ` Expected role "${assertion.expectedValue}", found "${roleValue}".`);
|
|
870
|
-
}
|
|
1297
|
+
const result = await assertionRunner.validate(assertion, dynamicTest.description);
|
|
1298
|
+
if (result.success && result.passMessage) {
|
|
1299
|
+
passes.push(result.passMessage);
|
|
1300
|
+
} else if (!result.success && result.failMessage) {
|
|
1301
|
+
failures.push(result.failMessage);
|
|
871
1302
|
}
|
|
872
1303
|
}
|
|
873
1304
|
const failuresAfterTest = failures.length;
|
|
@@ -880,54 +1311,29 @@ This indicates a problem with the menu component's close functionality.`
|
|
|
880
1311
|
reporter.reportTest(dynamicTest, testPassed ? "pass" : "fail", failureMessage);
|
|
881
1312
|
}
|
|
882
1313
|
}
|
|
883
|
-
const
|
|
884
|
-
const staticFailed =
|
|
1314
|
+
const staticTotal = componentContract.static[0].assertions.length;
|
|
1315
|
+
const staticFailed = failures.length - failuresBeforeStatic;
|
|
1316
|
+
const staticPassed = Math.max(0, staticTotal - staticFailed);
|
|
885
1317
|
reporter.reportStatic(staticPassed, staticFailed);
|
|
886
1318
|
reporter.summary(failures);
|
|
887
1319
|
} catch (error) {
|
|
888
1320
|
if (error instanceof Error) {
|
|
889
1321
|
if (error.message.includes("Executable doesn't exist") || error.message.includes("browserType.launch")) {
|
|
890
|
-
|
|
891
|
-
console.log("\u{1F4E6} Run: npx playwright install chromium\n");
|
|
892
|
-
failures.push("CRITICAL: Playwright browser not installed. Run: npx playwright install chromium");
|
|
1322
|
+
throw new Error("\n\u274C CRITICAL: Playwright browsers not found!\n\u{1F4E6} Run: npx playwright install chromium");
|
|
893
1323
|
} else if (error.message.includes("net::ERR_CONNECTION_REFUSED") || error.message.includes("NS_ERROR_CONNECTION_REFUSED")) {
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
`);
|
|
897
|
-
failures.push(`CRITICAL: Dev server not running at ${url}`);
|
|
1324
|
+
throw new Error(`
|
|
1325
|
+
\u274C CRITICAL: Cannot connect to dev server!
|
|
1326
|
+
Make sure your dev server is running at ${url}`);
|
|
898
1327
|
} else if (error.message.includes("Timeout") && error.message.includes("waitFor")) {
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
console.log(` This usually means:
|
|
903
|
-
`);
|
|
904
|
-
console.log(` - The component didn't render
|
|
905
|
-
`);
|
|
906
|
-
console.log(` - The URL is incorrect
|
|
907
|
-
`);
|
|
908
|
-
console.log(` - The component selector in the contract is wrong
|
|
909
|
-
`);
|
|
910
|
-
failures.push(`CRITICAL: Component element not found on page - ${error.message}`);
|
|
1328
|
+
throw new Error(
|
|
1329
|
+
"\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"
|
|
1330
|
+
);
|
|
911
1331
|
} else if (error.message.includes("Target page, context or browser has been closed")) {
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
console.log(` - The test timeout was too short
|
|
916
|
-
`);
|
|
917
|
-
console.log(` - The browser crashed
|
|
918
|
-
`);
|
|
919
|
-
console.log(` - An external process killed the browser
|
|
920
|
-
`);
|
|
921
|
-
failures.push(`CRITICAL: Browser/page closed unexpectedly - ${error.message}`);
|
|
922
|
-
} else if (error.message.includes("FATAL")) {
|
|
923
|
-
console.error(`
|
|
924
|
-
${error.message}
|
|
925
|
-
`);
|
|
926
|
-
failures.push(error.message);
|
|
1332
|
+
throw new Error(
|
|
1333
|
+
"\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"
|
|
1334
|
+
);
|
|
927
1335
|
} else {
|
|
928
|
-
|
|
929
|
-
console.error("Stack:", error.stack);
|
|
930
|
-
failures.push(`UNEXPECTED ERROR: ${error.message}`);
|
|
1336
|
+
throw error;
|
|
931
1337
|
}
|
|
932
1338
|
}
|
|
933
1339
|
} finally {
|
|
@@ -936,10 +1342,13 @@ ${error.message}
|
|
|
936
1342
|
return { passes, failures, skipped };
|
|
937
1343
|
}
|
|
938
1344
|
var init_contractTestRunnerPlaywright = __esm({
|
|
939
|
-
"src/utils/test/
|
|
1345
|
+
"src/utils/test/src/contractTestRunnerPlaywright.ts"() {
|
|
940
1346
|
init_contract();
|
|
941
|
-
init_ContractReporter();
|
|
942
1347
|
init_playwrightTestHarness();
|
|
1348
|
+
init_ComponentDetector();
|
|
1349
|
+
init_ContractReporter();
|
|
1350
|
+
init_ActionExecutor();
|
|
1351
|
+
init_AssertionRunner();
|
|
943
1352
|
}
|
|
944
1353
|
});
|
|
945
1354
|
|
|
@@ -1052,7 +1461,7 @@ var init_badgeHelper = __esm({
|
|
|
1052
1461
|
}
|
|
1053
1462
|
});
|
|
1054
1463
|
|
|
1055
|
-
// src/utils/test/
|
|
1464
|
+
// src/utils/test/src/contractTestRunner.ts
|
|
1056
1465
|
init_contract();
|
|
1057
1466
|
init_ContractReporter();
|
|
1058
1467
|
async function runContractTests(componentName, component) {
|
|
@@ -1070,6 +1479,7 @@ async function runContractTests(componentName, component) {
|
|
|
1070
1479
|
const failures = [];
|
|
1071
1480
|
const passes = [];
|
|
1072
1481
|
const skipped = [];
|
|
1482
|
+
const failuresBeforeStatic = failures.length;
|
|
1073
1483
|
for (const test of componentContract.static[0].assertions) {
|
|
1074
1484
|
if (test.target !== "relative") {
|
|
1075
1485
|
const selector = componentContract.selectors[test.target];
|
|
@@ -1108,8 +1518,9 @@ async function runContractTests(componentName, component) {
|
|
|
1108
1518
|
skipped.push(dynamicTest.description);
|
|
1109
1519
|
reporter.reportTest(dynamicTest, "skip");
|
|
1110
1520
|
}
|
|
1111
|
-
const
|
|
1112
|
-
const staticFailed =
|
|
1521
|
+
const staticTotal = componentContract.static[0].assertions.length;
|
|
1522
|
+
const staticFailed = failures.length - failuresBeforeStatic;
|
|
1523
|
+
const staticPassed = Math.max(0, staticTotal - staticFailed);
|
|
1113
1524
|
reporter.reportStatic(staticPassed, staticFailed);
|
|
1114
1525
|
reporter.summary(failures);
|
|
1115
1526
|
return { passes, failures, skipped };
|
|
@@ -1215,6 +1626,11 @@ ${violationDetails}
|
|
|
1215
1626
|
return result;
|
|
1216
1627
|
}
|
|
1217
1628
|
exports.runTest = async () => {
|
|
1629
|
+
return {
|
|
1630
|
+
passes: [],
|
|
1631
|
+
failures: [],
|
|
1632
|
+
skipped: []
|
|
1633
|
+
};
|
|
1218
1634
|
};
|
|
1219
1635
|
if (typeof window === "undefined") {
|
|
1220
1636
|
exports.runTest = async () => {
|
|
@@ -1222,36 +1638,36 @@ if (typeof window === "undefined") {
|
|
|
1222
1638
|
`);
|
|
1223
1639
|
const { exec } = await import('child_process');
|
|
1224
1640
|
const chalk2 = (await import('chalk')).default;
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
if (stdout) {
|
|
1641
|
+
return new Promise((resolve, reject) => {
|
|
1642
|
+
exec(
|
|
1643
|
+
`npx vitest --run --reporter verbose`,
|
|
1644
|
+
async (error, stdout, stderr) => {
|
|
1230
1645
|
console.log(stdout);
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1646
|
+
if (stderr) console.error(stderr);
|
|
1647
|
+
const testsPassed = !error || error.code === 0;
|
|
1648
|
+
if (testsPassed) {
|
|
1649
|
+
try {
|
|
1650
|
+
const { displayBadgeInfo: displayBadgeInfo2, promptAddBadge: promptAddBadge2 } = await Promise.resolve().then(() => (init_badgeHelper(), badgeHelper_exports));
|
|
1651
|
+
displayBadgeInfo2("component");
|
|
1652
|
+
await promptAddBadge2("component", process.cwd());
|
|
1653
|
+
console.log(chalk2.dim("\n" + "\u2500".repeat(60)));
|
|
1654
|
+
console.log(chalk2.cyan("\u{1F499} Found aria-ease helpful?"));
|
|
1655
|
+
console.log(chalk2.white(" \u2022 Star us on GitHub: ") + chalk2.blue.underline("https://github.com/aria-ease/aria-ease"));
|
|
1656
|
+
console.log(chalk2.white(" \u2022 Share feedback: ") + chalk2.blue.underline("https://github.com/aria-ease/aria-ease/discussions"));
|
|
1657
|
+
console.log(chalk2.dim("\u2500".repeat(60) + "\n"));
|
|
1658
|
+
} catch (badgeError) {
|
|
1659
|
+
console.error("Warning: Could not display badge prompt:", badgeError);
|
|
1660
|
+
}
|
|
1661
|
+
resolve({ passes: [], failures: [], skipped: [] });
|
|
1662
|
+
process.exit(0);
|
|
1663
|
+
} else {
|
|
1664
|
+
const exitCode = error?.code || 1;
|
|
1665
|
+
reject(new Error(`Tests failed with code ${exitCode}`));
|
|
1666
|
+
process.exit(exitCode);
|
|
1247
1667
|
}
|
|
1248
|
-
process.exit(0);
|
|
1249
|
-
} else {
|
|
1250
|
-
const exitCode = error?.code || 1;
|
|
1251
|
-
process.exit(exitCode);
|
|
1252
1668
|
}
|
|
1253
|
-
|
|
1254
|
-
);
|
|
1669
|
+
);
|
|
1670
|
+
});
|
|
1255
1671
|
};
|
|
1256
1672
|
}
|
|
1257
1673
|
async function cleanupTests() {
|