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/dist/index.cjs
CHANGED
|
@@ -37,29 +37,29 @@ var init_contract = __esm({
|
|
|
37
37
|
"src/utils/test/contract/contract.json"() {
|
|
38
38
|
contract_default = {
|
|
39
39
|
menu: {
|
|
40
|
-
path: "./contracts/
|
|
40
|
+
path: "./aria-contracts/menu/menu.contract.json",
|
|
41
41
|
component: "menu"
|
|
42
42
|
},
|
|
43
|
-
combobox: {
|
|
44
|
-
path: "./contracts/
|
|
45
|
-
component: "combobox"
|
|
43
|
+
"combobox.listbox": {
|
|
44
|
+
path: "./aria-contracts/combobox/combobox.listbox.contract.json",
|
|
45
|
+
component: "combobox.listbox"
|
|
46
46
|
},
|
|
47
47
|
accordion: {
|
|
48
|
-
path: "./contracts/
|
|
48
|
+
path: "./aria-contracts/accordion/accordion.contract.json",
|
|
49
49
|
component: "accordion"
|
|
50
50
|
},
|
|
51
51
|
tabs: {
|
|
52
|
-
path: "./contracts/
|
|
52
|
+
path: "./aria-contracts/tabs/tabs.contract.json",
|
|
53
53
|
component: "tabs"
|
|
54
54
|
}
|
|
55
55
|
};
|
|
56
56
|
}
|
|
57
57
|
});
|
|
58
58
|
|
|
59
|
-
// src/utils/test/
|
|
59
|
+
// src/utils/test/src/ContractReporter.ts
|
|
60
60
|
var ContractReporter;
|
|
61
61
|
var init_ContractReporter = __esm({
|
|
62
|
-
"src/utils/test/
|
|
62
|
+
"src/utils/test/src/ContractReporter.ts"() {
|
|
63
63
|
"use strict";
|
|
64
64
|
ContractReporter = class {
|
|
65
65
|
startTime = 0;
|
|
@@ -72,6 +72,8 @@ var init_ContractReporter = __esm({
|
|
|
72
72
|
optionalSuggestions = 0;
|
|
73
73
|
isPlaywright = false;
|
|
74
74
|
apgUrl = "https://www.w3.org/WAI/ARIA/apg/";
|
|
75
|
+
hasPrintedStaticSection = false;
|
|
76
|
+
hasPrintedDynamicSection = false;
|
|
75
77
|
constructor(isPlaywright = false) {
|
|
76
78
|
this.isPlaywright = isPlaywright;
|
|
77
79
|
}
|
|
@@ -82,30 +84,32 @@ var init_ContractReporter = __esm({
|
|
|
82
84
|
this.startTime = Date.now();
|
|
83
85
|
this.componentName = componentName;
|
|
84
86
|
this.totalTests = totalTests;
|
|
87
|
+
this.hasPrintedStaticSection = false;
|
|
88
|
+
this.hasPrintedDynamicSection = false;
|
|
85
89
|
if (apgUrl) {
|
|
86
90
|
this.apgUrl = apgUrl;
|
|
87
91
|
}
|
|
88
92
|
const mode = this.isPlaywright ? "Playwright (Real Browser)" : "jsdom (Fast)";
|
|
89
93
|
this.log(`
|
|
90
94
|
${"\u2550".repeat(60)}`);
|
|
91
|
-
this.log(`\u{1F50D} Testing ${componentName} Component - ${mode}`);
|
|
95
|
+
this.log(`\u{1F50D} Testing ${componentName.charAt(0).toUpperCase() + componentName.slice(1)} Component - ${mode}`);
|
|
92
96
|
this.log(`${"\u2550".repeat(60)}
|
|
93
97
|
`);
|
|
94
98
|
}
|
|
95
99
|
reportStatic(passes, failures) {
|
|
96
100
|
this.staticPasses = passes;
|
|
97
101
|
this.staticFailures = failures;
|
|
98
|
-
const icon = failures === 0 ? "\u2705" : "\u274C";
|
|
99
|
-
const status = failures === 0 ? "PASS" : "FAIL";
|
|
100
|
-
this.log("");
|
|
101
|
-
this.log(`${icon} Static ARIA Tests: ${status}`);
|
|
102
|
-
this.log(` ${passes}/${passes + failures} required attributes present
|
|
103
|
-
`);
|
|
104
102
|
}
|
|
105
103
|
/**
|
|
106
104
|
* Report individual static test pass
|
|
107
105
|
*/
|
|
108
106
|
reportStaticTest(description, passed, failureMessage) {
|
|
107
|
+
if (!this.hasPrintedStaticSection) {
|
|
108
|
+
this.log(`${"\u2500".repeat(60)}`);
|
|
109
|
+
this.log(`\u{1F9EA} Static Assertions`);
|
|
110
|
+
this.log(`${"\u2500".repeat(60)}`);
|
|
111
|
+
this.hasPrintedStaticSection = true;
|
|
112
|
+
}
|
|
109
113
|
const icon = passed ? "\u2713" : "\u2717";
|
|
110
114
|
this.log(` ${icon} ${description}`);
|
|
111
115
|
if (!passed && failureMessage) {
|
|
@@ -116,13 +120,20 @@ ${"\u2550".repeat(60)}`);
|
|
|
116
120
|
* Report individual dynamic test result
|
|
117
121
|
*/
|
|
118
122
|
reportTest(test, status, failureMessage) {
|
|
123
|
+
if (!this.hasPrintedDynamicSection) {
|
|
124
|
+
this.log("");
|
|
125
|
+
this.log(`${"\u2500".repeat(60)}`);
|
|
126
|
+
this.log(`\u2328\uFE0F Dynamic Interaction Tests`);
|
|
127
|
+
this.log(`${"\u2500".repeat(60)}`);
|
|
128
|
+
this.hasPrintedDynamicSection = true;
|
|
129
|
+
}
|
|
119
130
|
const result = {
|
|
120
131
|
description: test.description,
|
|
121
132
|
status,
|
|
122
133
|
failureMessage,
|
|
123
134
|
isOptional: test.isOptional
|
|
124
135
|
};
|
|
125
|
-
if (status === "skip"
|
|
136
|
+
if (status === "skip") {
|
|
126
137
|
result.skipReason = "Requires real browser (addEventListener events)";
|
|
127
138
|
}
|
|
128
139
|
this.dynamicResults.push(result);
|
|
@@ -202,7 +213,7 @@ ${"\u2500".repeat(60)}`);
|
|
|
202
213
|
});
|
|
203
214
|
this.log(`
|
|
204
215
|
\u{1F4A1} Run with Playwright for full validation:`);
|
|
205
|
-
this.log(` testUiComponent('${this.componentName}',
|
|
216
|
+
this.log(` testUiComponent('${this.componentName}', null, 'http://localhost:5173/test-harness?component=component_name')
|
|
206
217
|
`);
|
|
207
218
|
}
|
|
208
219
|
/**
|
|
@@ -226,9 +237,14 @@ ${"\u2500".repeat(60)}`);
|
|
|
226
237
|
${"\u2550".repeat(60)}`);
|
|
227
238
|
this.log(`\u{1F4CA} Summary
|
|
228
239
|
`);
|
|
240
|
+
const staticIcon = this.staticFailures === 0 ? "\u2705" : "\u274C";
|
|
241
|
+
const staticStatus = this.staticFailures === 0 ? "PASS" : "FAIL";
|
|
242
|
+
this.log(`${staticIcon} Static ARIA Tests: ${staticStatus}`);
|
|
243
|
+
this.log(` ${this.staticPasses}/${this.staticPasses + this.staticFailures} required attributes present`);
|
|
244
|
+
this.log("");
|
|
229
245
|
if (totalFailures === 0 && this.skipped === 0 && this.optionalSuggestions === 0) {
|
|
230
246
|
this.log(`\u2705 All ${totalRun} tests passed!`);
|
|
231
|
-
this.log(` ${this.componentName} component meets WAI-ARIA expectations for Roles, States, Properties, and Keyboard
|
|
247
|
+
this.log(` ${this.componentName.charAt(0).toUpperCase()}${this.componentName.slice(1)} component meets WAI-ARIA expectations for Roles, States, Properties, and Keyboard Interactions \u2713`);
|
|
232
248
|
} else if (totalFailures === 0) {
|
|
233
249
|
this.log(`\u2705 ${totalPasses}/${totalRun} required tests passed`);
|
|
234
250
|
if (this.skipped > 0) {
|
|
@@ -237,7 +253,7 @@ ${"\u2550".repeat(60)}`);
|
|
|
237
253
|
if (this.optionalSuggestions > 0) {
|
|
238
254
|
this.log(`\u{1F4A1} ${this.optionalSuggestions} optional enhancement${this.optionalSuggestions > 1 ? "s" : ""} suggested`);
|
|
239
255
|
}
|
|
240
|
-
this.log(` ${this.componentName} component meets WAI-ARIA expectations for Roles, States, Properties, and Keyboard
|
|
256
|
+
this.log(` ${this.componentName.charAt(0).toUpperCase()}${this.componentName.slice(1)} component meets WAI-ARIA expectations for Roles, States, Properties, and Keyboard Interactions \u2713`);
|
|
241
257
|
} else {
|
|
242
258
|
this.log(`\u274C ${totalFailures} test${totalFailures > 1 ? "s" : ""} failed`);
|
|
243
259
|
this.log(`\u2705 ${totalPasses} test${totalPasses > 1 ? "s" : ""} passed`);
|
|
@@ -283,7 +299,7 @@ ${"\u2550".repeat(60)}`);
|
|
|
283
299
|
}
|
|
284
300
|
});
|
|
285
301
|
|
|
286
|
-
// src/utils/test/
|
|
302
|
+
// src/utils/test/src/playwrightTestHarness.ts
|
|
287
303
|
async function getOrCreateBrowser() {
|
|
288
304
|
if (!sharedBrowser) {
|
|
289
305
|
sharedBrowser = await import_playwright.chromium.launch({
|
|
@@ -325,7 +341,7 @@ async function closeSharedBrowser() {
|
|
|
325
341
|
}
|
|
326
342
|
var import_playwright, sharedBrowser, sharedContext;
|
|
327
343
|
var init_playwrightTestHarness = __esm({
|
|
328
|
-
"src/utils/test/
|
|
344
|
+
"src/utils/test/src/playwrightTestHarness.ts"() {
|
|
329
345
|
"use strict";
|
|
330
346
|
import_playwright = require("playwright");
|
|
331
347
|
sharedBrowser = null;
|
|
@@ -347,7 +363,779 @@ var init_test = __esm({
|
|
|
347
363
|
}
|
|
348
364
|
});
|
|
349
365
|
|
|
350
|
-
// src/utils/test/
|
|
366
|
+
// src/utils/test/src/component-strategies/ComboboxComponentStrategy.ts
|
|
367
|
+
var ComboboxComponentStrategy;
|
|
368
|
+
var init_ComboboxComponentStrategy = __esm({
|
|
369
|
+
"src/utils/test/src/component-strategies/ComboboxComponentStrategy.ts"() {
|
|
370
|
+
"use strict";
|
|
371
|
+
init_test();
|
|
372
|
+
ComboboxComponentStrategy = class {
|
|
373
|
+
constructor(mainSelector, selectors, actionTimeoutMs = 400, assertionTimeoutMs = 400) {
|
|
374
|
+
this.mainSelector = mainSelector;
|
|
375
|
+
this.selectors = selectors;
|
|
376
|
+
this.actionTimeoutMs = actionTimeoutMs;
|
|
377
|
+
this.assertionTimeoutMs = assertionTimeoutMs;
|
|
378
|
+
}
|
|
379
|
+
async resetState(page) {
|
|
380
|
+
if (!this.selectors.popup) return;
|
|
381
|
+
const popupSelector = this.selectors.popup;
|
|
382
|
+
const popupElement = page.locator(popupSelector).first();
|
|
383
|
+
const isPopupVisible = await popupElement.isVisible().catch(() => false);
|
|
384
|
+
if (!isPopupVisible) return;
|
|
385
|
+
let menuClosed = false;
|
|
386
|
+
let closeSelector = this.selectors.input;
|
|
387
|
+
if (!closeSelector && this.selectors.focusable) {
|
|
388
|
+
closeSelector = this.selectors.focusable;
|
|
389
|
+
} else if (!closeSelector) {
|
|
390
|
+
closeSelector = this.selectors.trigger;
|
|
391
|
+
}
|
|
392
|
+
if (closeSelector) {
|
|
393
|
+
const closeElement = page.locator(closeSelector).first();
|
|
394
|
+
await closeElement.focus();
|
|
395
|
+
await page.keyboard.press("Escape");
|
|
396
|
+
menuClosed = await (0, test_exports.expect)(popupElement).toBeHidden({ timeout: this.assertionTimeoutMs }).then(() => true).catch(() => false);
|
|
397
|
+
}
|
|
398
|
+
if (!menuClosed && this.selectors.trigger) {
|
|
399
|
+
const triggerElement = page.locator(this.selectors.trigger).first();
|
|
400
|
+
await triggerElement.click({ timeout: this.actionTimeoutMs });
|
|
401
|
+
menuClosed = await (0, test_exports.expect)(popupElement).toBeHidden({ timeout: this.assertionTimeoutMs }).then(() => true).catch(() => false);
|
|
402
|
+
}
|
|
403
|
+
if (!menuClosed) {
|
|
404
|
+
await page.mouse.click(10, 10);
|
|
405
|
+
menuClosed = await (0, test_exports.expect)(popupElement).toBeHidden({ timeout: this.assertionTimeoutMs }).then(() => true).catch(() => false);
|
|
406
|
+
}
|
|
407
|
+
if (!menuClosed) {
|
|
408
|
+
throw new Error(
|
|
409
|
+
`\u274C FATAL: Cannot close combobox popup between tests. Popup remains visible after trying:
|
|
410
|
+
1. Escape key
|
|
411
|
+
2. Clicking trigger
|
|
412
|
+
3. Clicking outside
|
|
413
|
+
This indicates a problem with the combobox component's close functionality.`
|
|
414
|
+
);
|
|
415
|
+
}
|
|
416
|
+
if (this.selectors.input) {
|
|
417
|
+
await page.locator(this.selectors.input).first().clear();
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
async shouldSkipTest() {
|
|
421
|
+
return false;
|
|
422
|
+
}
|
|
423
|
+
getMainSelector() {
|
|
424
|
+
return this.mainSelector;
|
|
425
|
+
}
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
// src/utils/test/src/component-strategies/AccordionComponentStrategy.ts
|
|
431
|
+
var AccordionComponentStrategy;
|
|
432
|
+
var init_AccordionComponentStrategy = __esm({
|
|
433
|
+
"src/utils/test/src/component-strategies/AccordionComponentStrategy.ts"() {
|
|
434
|
+
"use strict";
|
|
435
|
+
init_test();
|
|
436
|
+
AccordionComponentStrategy = class {
|
|
437
|
+
constructor(mainSelector, selectors, actionTimeoutMs = 400, assertionTimeoutMs = 400) {
|
|
438
|
+
this.mainSelector = mainSelector;
|
|
439
|
+
this.selectors = selectors;
|
|
440
|
+
this.actionTimeoutMs = actionTimeoutMs;
|
|
441
|
+
this.assertionTimeoutMs = assertionTimeoutMs;
|
|
442
|
+
}
|
|
443
|
+
async resetState(page) {
|
|
444
|
+
if (!this.selectors.panel || !this.selectors.trigger || this.selectors.popup) {
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
const triggerSelector = this.selectors.trigger;
|
|
448
|
+
const panelSelector = this.selectors.panel;
|
|
449
|
+
if (!triggerSelector || !panelSelector) return;
|
|
450
|
+
const allTriggers = await page.locator(triggerSelector).all();
|
|
451
|
+
for (const trigger of allTriggers) {
|
|
452
|
+
const isExpanded = await trigger.getAttribute("aria-expanded") === "true";
|
|
453
|
+
const triggerPanel = await trigger.getAttribute("aria-controls");
|
|
454
|
+
if (isExpanded && triggerPanel) {
|
|
455
|
+
await trigger.click({ timeout: this.actionTimeoutMs });
|
|
456
|
+
const panel = page.locator(`#${triggerPanel}`);
|
|
457
|
+
await (0, test_exports.expect)(panel).toBeHidden({ timeout: this.assertionTimeoutMs }).catch(() => {
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
async shouldSkipTest() {
|
|
463
|
+
return false;
|
|
464
|
+
}
|
|
465
|
+
getMainSelector() {
|
|
466
|
+
return this.mainSelector;
|
|
467
|
+
}
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
// src/utils/test/src/component-strategies/MenuComponentStrategy.ts
|
|
473
|
+
var MenuComponentStrategy;
|
|
474
|
+
var init_MenuComponentStrategy = __esm({
|
|
475
|
+
"src/utils/test/src/component-strategies/MenuComponentStrategy.ts"() {
|
|
476
|
+
"use strict";
|
|
477
|
+
init_test();
|
|
478
|
+
MenuComponentStrategy = class {
|
|
479
|
+
constructor(mainSelector, selectors, actionTimeoutMs = 400, assertionTimeoutMs = 400) {
|
|
480
|
+
this.mainSelector = mainSelector;
|
|
481
|
+
this.selectors = selectors;
|
|
482
|
+
this.actionTimeoutMs = actionTimeoutMs;
|
|
483
|
+
this.assertionTimeoutMs = assertionTimeoutMs;
|
|
484
|
+
}
|
|
485
|
+
async resetState(page) {
|
|
486
|
+
if (!this.selectors.popup) return;
|
|
487
|
+
const popupSelector = this.selectors.popup;
|
|
488
|
+
const popupElement = page.locator(popupSelector).first();
|
|
489
|
+
const isPopupVisible = await popupElement.isVisible().catch(() => false);
|
|
490
|
+
if (!isPopupVisible) return;
|
|
491
|
+
let menuClosed = false;
|
|
492
|
+
let closeSelector = this.selectors.input;
|
|
493
|
+
if (!closeSelector && this.selectors.focusable) {
|
|
494
|
+
closeSelector = this.selectors.focusable;
|
|
495
|
+
} else if (!closeSelector) {
|
|
496
|
+
closeSelector = this.selectors.trigger;
|
|
497
|
+
}
|
|
498
|
+
if (closeSelector) {
|
|
499
|
+
const closeElement = page.locator(closeSelector).first();
|
|
500
|
+
await closeElement.focus();
|
|
501
|
+
await page.keyboard.press("Escape");
|
|
502
|
+
menuClosed = await (0, test_exports.expect)(popupElement).toBeHidden({ timeout: this.assertionTimeoutMs }).then(() => true).catch(() => false);
|
|
503
|
+
}
|
|
504
|
+
if (!menuClosed && this.selectors.trigger) {
|
|
505
|
+
const triggerElement = page.locator(this.selectors.trigger).first();
|
|
506
|
+
await triggerElement.click({ timeout: this.actionTimeoutMs });
|
|
507
|
+
menuClosed = await (0, test_exports.expect)(popupElement).toBeHidden({ timeout: this.assertionTimeoutMs }).then(() => true).catch(() => false);
|
|
508
|
+
}
|
|
509
|
+
if (!menuClosed) {
|
|
510
|
+
await page.mouse.click(10, 10);
|
|
511
|
+
menuClosed = await (0, test_exports.expect)(popupElement).toBeHidden({ timeout: this.assertionTimeoutMs }).then(() => true).catch(() => false);
|
|
512
|
+
}
|
|
513
|
+
if (!menuClosed) {
|
|
514
|
+
throw new Error(
|
|
515
|
+
`\u274C FATAL: Cannot close menu between tests. Menu remains visible after trying:
|
|
516
|
+
1. Escape key
|
|
517
|
+
2. Clicking trigger
|
|
518
|
+
3. Clicking outside
|
|
519
|
+
This indicates a problem with the menu component's close functionality.`
|
|
520
|
+
);
|
|
521
|
+
}
|
|
522
|
+
if (this.selectors.input) {
|
|
523
|
+
await page.locator(this.selectors.input).first().clear();
|
|
524
|
+
}
|
|
525
|
+
if (this.selectors.trigger) {
|
|
526
|
+
const triggerElement = page.locator(this.selectors.trigger).first();
|
|
527
|
+
await triggerElement.focus();
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
async shouldSkipTest(test, page) {
|
|
531
|
+
for (const act of test.action) {
|
|
532
|
+
if (act.type === "keypress" && (act.target === "submenuTrigger" || act.target === "submenu")) {
|
|
533
|
+
const submenuSelector = this.selectors[act.target];
|
|
534
|
+
if (submenuSelector) {
|
|
535
|
+
const submenuCount = await page.locator(submenuSelector).count();
|
|
536
|
+
if (submenuCount === 0) {
|
|
537
|
+
return true;
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
for (const assertion of test.assertions) {
|
|
543
|
+
if (assertion.target === "submenu" || assertion.target === "submenuTrigger") {
|
|
544
|
+
const submenuSelector = this.selectors[assertion.target];
|
|
545
|
+
if (submenuSelector) {
|
|
546
|
+
const submenuCount = await page.locator(submenuSelector).count();
|
|
547
|
+
if (submenuCount === 0) {
|
|
548
|
+
return true;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
return false;
|
|
554
|
+
}
|
|
555
|
+
getMainSelector() {
|
|
556
|
+
return this.mainSelector;
|
|
557
|
+
}
|
|
558
|
+
};
|
|
559
|
+
}
|
|
560
|
+
});
|
|
561
|
+
|
|
562
|
+
// src/utils/test/src/component-strategies/TabsComponentStrategy.ts
|
|
563
|
+
var TabsComponentStrategy;
|
|
564
|
+
var init_TabsComponentStrategy = __esm({
|
|
565
|
+
"src/utils/test/src/component-strategies/TabsComponentStrategy.ts"() {
|
|
566
|
+
"use strict";
|
|
567
|
+
TabsComponentStrategy = class {
|
|
568
|
+
constructor(mainSelector, selectors) {
|
|
569
|
+
this.mainSelector = mainSelector;
|
|
570
|
+
this.selectors = selectors;
|
|
571
|
+
}
|
|
572
|
+
async resetState() {
|
|
573
|
+
}
|
|
574
|
+
async shouldSkipTest(test, page) {
|
|
575
|
+
if (test.isVertical !== void 0 && this.selectors.tablist) {
|
|
576
|
+
const tablistSelector = this.selectors.tablist;
|
|
577
|
+
const tablist = page.locator(tablistSelector).first();
|
|
578
|
+
const orientation = await tablist.getAttribute("aria-orientation");
|
|
579
|
+
const isVertical = orientation === "vertical";
|
|
580
|
+
if (test.isVertical !== isVertical) {
|
|
581
|
+
return true;
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
return false;
|
|
585
|
+
}
|
|
586
|
+
getMainSelector() {
|
|
587
|
+
return this.mainSelector;
|
|
588
|
+
}
|
|
589
|
+
};
|
|
590
|
+
}
|
|
591
|
+
});
|
|
592
|
+
|
|
593
|
+
// src/utils/test/src/ComponentDetector.ts
|
|
594
|
+
var import_fs, import_meta2, ComponentDetector;
|
|
595
|
+
var init_ComponentDetector = __esm({
|
|
596
|
+
"src/utils/test/src/ComponentDetector.ts"() {
|
|
597
|
+
"use strict";
|
|
598
|
+
init_ComboboxComponentStrategy();
|
|
599
|
+
init_AccordionComponentStrategy();
|
|
600
|
+
init_MenuComponentStrategy();
|
|
601
|
+
init_TabsComponentStrategy();
|
|
602
|
+
import_fs = require("fs");
|
|
603
|
+
init_contract();
|
|
604
|
+
import_meta2 = {};
|
|
605
|
+
ComponentDetector = class {
|
|
606
|
+
static detect(componentName, actionTimeoutMs = 400, assertionTimeoutMs = 400) {
|
|
607
|
+
const contractTyped = contract_default;
|
|
608
|
+
const contractPath = contractTyped[componentName]?.path;
|
|
609
|
+
if (!contractPath) {
|
|
610
|
+
throw new Error(`Contract path not found for component: ${componentName}`);
|
|
611
|
+
}
|
|
612
|
+
const resolvedPath = new URL(contractPath, import_meta2.url).pathname;
|
|
613
|
+
const contractData = (0, import_fs.readFileSync)(resolvedPath, "utf-8");
|
|
614
|
+
const componentContract = JSON.parse(contractData);
|
|
615
|
+
const selectors = componentContract.selectors;
|
|
616
|
+
if (componentName.includes("combobox")) {
|
|
617
|
+
const mainSelector = selectors.input || selectors.container;
|
|
618
|
+
return new ComboboxComponentStrategy(mainSelector, selectors, actionTimeoutMs, assertionTimeoutMs);
|
|
619
|
+
}
|
|
620
|
+
if (componentName === "accordion") {
|
|
621
|
+
const mainSelector = selectors.trigger || selectors.container;
|
|
622
|
+
return new AccordionComponentStrategy(mainSelector, selectors, actionTimeoutMs, assertionTimeoutMs);
|
|
623
|
+
}
|
|
624
|
+
if (componentName === "menu") {
|
|
625
|
+
const mainSelector = selectors.trigger || selectors.container;
|
|
626
|
+
return new MenuComponentStrategy(mainSelector, selectors, actionTimeoutMs, assertionTimeoutMs);
|
|
627
|
+
}
|
|
628
|
+
if (componentName === "tabs") {
|
|
629
|
+
const mainSelector = selectors.tablist || selectors.tab;
|
|
630
|
+
return new TabsComponentStrategy(mainSelector, selectors);
|
|
631
|
+
}
|
|
632
|
+
return null;
|
|
633
|
+
}
|
|
634
|
+
};
|
|
635
|
+
}
|
|
636
|
+
});
|
|
637
|
+
|
|
638
|
+
// src/utils/test/src/RelativeTargetResolver.ts
|
|
639
|
+
var RelativeTargetResolver;
|
|
640
|
+
var init_RelativeTargetResolver = __esm({
|
|
641
|
+
"src/utils/test/src/RelativeTargetResolver.ts"() {
|
|
642
|
+
"use strict";
|
|
643
|
+
RelativeTargetResolver = class {
|
|
644
|
+
/**
|
|
645
|
+
* Resolve a relative target like "first", "second", "last", "next", "previous"
|
|
646
|
+
* @param page Playwright page instance
|
|
647
|
+
* @param selector Base selector to find elements
|
|
648
|
+
* @param relative Relative position (first, second, last, next, previous)
|
|
649
|
+
* @returns The resolved Locator or null if not found
|
|
650
|
+
*/
|
|
651
|
+
static async resolve(page, selector, relative) {
|
|
652
|
+
const items = await page.locator(selector).all();
|
|
653
|
+
switch (relative) {
|
|
654
|
+
case "first":
|
|
655
|
+
return items[0];
|
|
656
|
+
case "second":
|
|
657
|
+
return items[1];
|
|
658
|
+
case "last":
|
|
659
|
+
return items[items.length - 1];
|
|
660
|
+
case "next": {
|
|
661
|
+
const currentIndex = await page.evaluate(([sel]) => {
|
|
662
|
+
const items2 = Array.from(document.querySelectorAll(sel));
|
|
663
|
+
return items2.indexOf(document.activeElement);
|
|
664
|
+
}, [selector]);
|
|
665
|
+
const nextIndex = (currentIndex + 1) % items.length;
|
|
666
|
+
return items[nextIndex];
|
|
667
|
+
}
|
|
668
|
+
case "previous": {
|
|
669
|
+
const currentIndex = await page.evaluate(([sel]) => {
|
|
670
|
+
const items2 = Array.from(document.querySelectorAll(sel));
|
|
671
|
+
return items2.indexOf(document.activeElement);
|
|
672
|
+
}, [selector]);
|
|
673
|
+
const prevIndex = (currentIndex - 1 + items.length) % items.length;
|
|
674
|
+
return items[prevIndex];
|
|
675
|
+
}
|
|
676
|
+
default:
|
|
677
|
+
return null;
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
};
|
|
681
|
+
}
|
|
682
|
+
});
|
|
683
|
+
|
|
684
|
+
// src/utils/test/src/ActionExecutor.ts
|
|
685
|
+
var ActionExecutor;
|
|
686
|
+
var init_ActionExecutor = __esm({
|
|
687
|
+
"src/utils/test/src/ActionExecutor.ts"() {
|
|
688
|
+
"use strict";
|
|
689
|
+
init_RelativeTargetResolver();
|
|
690
|
+
ActionExecutor = class {
|
|
691
|
+
constructor(page, selectors, timeoutMs = 400) {
|
|
692
|
+
this.page = page;
|
|
693
|
+
this.selectors = selectors;
|
|
694
|
+
this.timeoutMs = timeoutMs;
|
|
695
|
+
}
|
|
696
|
+
/**
|
|
697
|
+
* Check if error is due to browser/page being closed
|
|
698
|
+
*/
|
|
699
|
+
isBrowserClosedError(error) {
|
|
700
|
+
return error instanceof Error && error.message.includes("Target page, context or browser has been closed");
|
|
701
|
+
}
|
|
702
|
+
/**
|
|
703
|
+
* Execute focus action
|
|
704
|
+
*/
|
|
705
|
+
async focus(target) {
|
|
706
|
+
try {
|
|
707
|
+
const selector = this.selectors[target];
|
|
708
|
+
if (!selector) {
|
|
709
|
+
return { success: false, error: `Selector for focus target ${target} not found.` };
|
|
710
|
+
}
|
|
711
|
+
await this.page.locator(selector).first().focus({ timeout: this.timeoutMs });
|
|
712
|
+
return { success: true };
|
|
713
|
+
} catch (error) {
|
|
714
|
+
if (this.isBrowserClosedError(error)) {
|
|
715
|
+
return {
|
|
716
|
+
success: false,
|
|
717
|
+
error: `CRITICAL: Browser/page closed during test execution. Remaining actions skipped.`,
|
|
718
|
+
shouldBreak: true
|
|
719
|
+
};
|
|
720
|
+
}
|
|
721
|
+
return {
|
|
722
|
+
success: false,
|
|
723
|
+
error: `Failed to focus ${target}: ${error instanceof Error ? error.message : String(error)}`
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
/**
|
|
728
|
+
* Execute type/fill action
|
|
729
|
+
*/
|
|
730
|
+
async type(target, value) {
|
|
731
|
+
try {
|
|
732
|
+
const selector = this.selectors[target];
|
|
733
|
+
if (!selector) {
|
|
734
|
+
return { success: false, error: `Selector for type target ${target} not found.` };
|
|
735
|
+
}
|
|
736
|
+
await this.page.locator(selector).first().fill(value, { 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 type into ${target}: ${error instanceof Error ? error.message : String(error)}`
|
|
749
|
+
};
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
/**
|
|
753
|
+
* Execute click action
|
|
754
|
+
*/
|
|
755
|
+
async click(target, relativeTarget) {
|
|
756
|
+
try {
|
|
757
|
+
if (target === "document") {
|
|
758
|
+
await this.page.mouse.click(10, 10);
|
|
759
|
+
return { success: true };
|
|
760
|
+
}
|
|
761
|
+
if (target === "relative" && relativeTarget) {
|
|
762
|
+
const relativeSelector = this.selectors.relative;
|
|
763
|
+
if (!relativeSelector) {
|
|
764
|
+
return { success: false, error: `Relative selector not defined for click action.` };
|
|
765
|
+
}
|
|
766
|
+
const element = await RelativeTargetResolver.resolve(this.page, relativeSelector, relativeTarget);
|
|
767
|
+
if (!element) {
|
|
768
|
+
return { success: false, error: `Could not resolve relative target ${relativeTarget} for click.` };
|
|
769
|
+
}
|
|
770
|
+
await element.click({ timeout: this.timeoutMs });
|
|
771
|
+
return { success: true };
|
|
772
|
+
}
|
|
773
|
+
const selector = this.selectors[target];
|
|
774
|
+
if (!selector) {
|
|
775
|
+
return { success: false, error: `Selector for action target ${target} not found.` };
|
|
776
|
+
}
|
|
777
|
+
await this.page.locator(selector).first().click({ timeout: this.timeoutMs });
|
|
778
|
+
return { success: true };
|
|
779
|
+
} catch (error) {
|
|
780
|
+
if (this.isBrowserClosedError(error)) {
|
|
781
|
+
return {
|
|
782
|
+
success: false,
|
|
783
|
+
error: `CRITICAL: Browser/page closed during test execution. Remaining actions skipped.`,
|
|
784
|
+
shouldBreak: true
|
|
785
|
+
};
|
|
786
|
+
}
|
|
787
|
+
return {
|
|
788
|
+
success: false,
|
|
789
|
+
error: `Failed to click ${target}: ${error instanceof Error ? error.message : String(error)}`
|
|
790
|
+
};
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
/**
|
|
794
|
+
* Execute keypress action
|
|
795
|
+
*/
|
|
796
|
+
async keypress(target, key) {
|
|
797
|
+
try {
|
|
798
|
+
const keyMap = {
|
|
799
|
+
"Space": "Space",
|
|
800
|
+
"Enter": "Enter",
|
|
801
|
+
"Escape": "Escape",
|
|
802
|
+
"Arrow Up": "ArrowUp",
|
|
803
|
+
"Arrow Down": "ArrowDown",
|
|
804
|
+
"Arrow Left": "ArrowLeft",
|
|
805
|
+
"Arrow Right": "ArrowRight",
|
|
806
|
+
"Home": "Home",
|
|
807
|
+
"End": "End",
|
|
808
|
+
"Tab": "Tab"
|
|
809
|
+
};
|
|
810
|
+
let keyValue = keyMap[key] || key;
|
|
811
|
+
if (keyValue === "Space") {
|
|
812
|
+
keyValue = " ";
|
|
813
|
+
} else if (keyValue.includes(" ")) {
|
|
814
|
+
keyValue = keyValue.replace(/ /g, "");
|
|
815
|
+
}
|
|
816
|
+
if (target === "focusable" && ["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight", "Escape"].includes(keyValue)) {
|
|
817
|
+
await this.page.keyboard.press(keyValue);
|
|
818
|
+
return { success: true };
|
|
819
|
+
}
|
|
820
|
+
const selector = this.selectors[target];
|
|
821
|
+
if (!selector) {
|
|
822
|
+
return { success: false, error: `Selector for keypress target ${target} not found.` };
|
|
823
|
+
}
|
|
824
|
+
const locator = this.page.locator(selector).first();
|
|
825
|
+
const elementCount = await locator.count();
|
|
826
|
+
if (elementCount === 0) {
|
|
827
|
+
return {
|
|
828
|
+
success: false,
|
|
829
|
+
error: `${target} element not found (optional submenu test)`,
|
|
830
|
+
shouldBreak: true
|
|
831
|
+
// Signal to skip this test
|
|
832
|
+
};
|
|
833
|
+
}
|
|
834
|
+
await locator.press(keyValue, { timeout: this.timeoutMs });
|
|
835
|
+
return { success: true };
|
|
836
|
+
} catch (error) {
|
|
837
|
+
if (this.isBrowserClosedError(error)) {
|
|
838
|
+
return {
|
|
839
|
+
success: false,
|
|
840
|
+
error: `CRITICAL: Browser/page closed during test execution. Remaining actions skipped.`,
|
|
841
|
+
shouldBreak: true
|
|
842
|
+
};
|
|
843
|
+
}
|
|
844
|
+
return {
|
|
845
|
+
success: false,
|
|
846
|
+
error: `Failed to press ${key} on ${target}: ${error instanceof Error ? error.message : String(error)}`
|
|
847
|
+
};
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
/**
|
|
851
|
+
* Execute hover action
|
|
852
|
+
*/
|
|
853
|
+
async hover(target, relativeTarget) {
|
|
854
|
+
try {
|
|
855
|
+
if (target === "relative" && relativeTarget) {
|
|
856
|
+
const relativeSelector = this.selectors.relative;
|
|
857
|
+
if (!relativeSelector) {
|
|
858
|
+
return { success: false, error: `Relative selector not defined for hover action.` };
|
|
859
|
+
}
|
|
860
|
+
const element = await RelativeTargetResolver.resolve(this.page, relativeSelector, relativeTarget);
|
|
861
|
+
if (!element) {
|
|
862
|
+
return { success: false, error: `Could not resolve relative target ${relativeTarget} for hover.` };
|
|
863
|
+
}
|
|
864
|
+
await element.hover({ timeout: this.timeoutMs });
|
|
865
|
+
return { success: true };
|
|
866
|
+
}
|
|
867
|
+
const selector = this.selectors[target];
|
|
868
|
+
if (!selector) {
|
|
869
|
+
return { success: false, error: `Selector for hover target ${target} not found.` };
|
|
870
|
+
}
|
|
871
|
+
await this.page.locator(selector).first().hover({ timeout: this.timeoutMs });
|
|
872
|
+
return { success: true };
|
|
873
|
+
} catch (error) {
|
|
874
|
+
if (this.isBrowserClosedError(error)) {
|
|
875
|
+
return {
|
|
876
|
+
success: false,
|
|
877
|
+
error: `CRITICAL: Browser/page closed during test execution. Remaining actions skipped.`,
|
|
878
|
+
shouldBreak: true
|
|
879
|
+
};
|
|
880
|
+
}
|
|
881
|
+
return {
|
|
882
|
+
success: false,
|
|
883
|
+
error: `Failed to hover ${target}: ${error instanceof Error ? error.message : String(error)}`
|
|
884
|
+
};
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
};
|
|
888
|
+
}
|
|
889
|
+
});
|
|
890
|
+
|
|
891
|
+
// src/utils/test/src/AssertionRunner.ts
|
|
892
|
+
var AssertionRunner;
|
|
893
|
+
var init_AssertionRunner = __esm({
|
|
894
|
+
"src/utils/test/src/AssertionRunner.ts"() {
|
|
895
|
+
"use strict";
|
|
896
|
+
init_test();
|
|
897
|
+
init_RelativeTargetResolver();
|
|
898
|
+
AssertionRunner = class {
|
|
899
|
+
constructor(page, selectors, timeoutMs = 400) {
|
|
900
|
+
this.page = page;
|
|
901
|
+
this.selectors = selectors;
|
|
902
|
+
this.timeoutMs = timeoutMs;
|
|
903
|
+
}
|
|
904
|
+
/**
|
|
905
|
+
* Resolve the target element for an assertion
|
|
906
|
+
*/
|
|
907
|
+
async resolveTarget(targetName, relativeTarget) {
|
|
908
|
+
try {
|
|
909
|
+
if (targetName === "relative") {
|
|
910
|
+
const relativeSelector = this.selectors.relative;
|
|
911
|
+
if (!relativeSelector) {
|
|
912
|
+
return { target: null, error: "Relative selector is not defined in the contract." };
|
|
913
|
+
}
|
|
914
|
+
if (!relativeTarget) {
|
|
915
|
+
return { target: null, error: "Relative target or expected value is not defined." };
|
|
916
|
+
}
|
|
917
|
+
const target = await RelativeTargetResolver.resolve(this.page, relativeSelector, relativeTarget);
|
|
918
|
+
if (!target) {
|
|
919
|
+
return { target: null, error: `Target ${targetName} not found.` };
|
|
920
|
+
}
|
|
921
|
+
return { target };
|
|
922
|
+
}
|
|
923
|
+
const selector = this.selectors[targetName];
|
|
924
|
+
if (!selector) {
|
|
925
|
+
return { target: null, error: `Selector for assertion target ${targetName} not found.` };
|
|
926
|
+
}
|
|
927
|
+
return { target: this.page.locator(selector).first() };
|
|
928
|
+
} catch (error) {
|
|
929
|
+
return {
|
|
930
|
+
target: null,
|
|
931
|
+
error: `Failed to resolve target ${targetName}: ${error instanceof Error ? error.message : String(error)}`
|
|
932
|
+
};
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
/**
|
|
936
|
+
* Validate visibility assertion
|
|
937
|
+
*/
|
|
938
|
+
async validateVisibility(target, targetName, expectedVisible, failureMessage, testDescription) {
|
|
939
|
+
try {
|
|
940
|
+
if (expectedVisible) {
|
|
941
|
+
await (0, test_exports.expect)(target).toBeVisible({ timeout: this.timeoutMs });
|
|
942
|
+
return {
|
|
943
|
+
success: true,
|
|
944
|
+
passMessage: `${targetName} is visible as expected. Test: "${testDescription}".`
|
|
945
|
+
};
|
|
946
|
+
} else {
|
|
947
|
+
await (0, test_exports.expect)(target).toBeHidden({ timeout: this.timeoutMs });
|
|
948
|
+
return {
|
|
949
|
+
success: true,
|
|
950
|
+
passMessage: `${targetName} is not visible as expected. Test: "${testDescription}".`
|
|
951
|
+
};
|
|
952
|
+
}
|
|
953
|
+
} catch {
|
|
954
|
+
const selector = this.selectors[targetName] || "";
|
|
955
|
+
const debugState = await this.page.evaluate((sel) => {
|
|
956
|
+
const el = sel ? document.querySelector(sel) : null;
|
|
957
|
+
if (!el) return "element not found";
|
|
958
|
+
const styles = window.getComputedStyle(el);
|
|
959
|
+
return `display:${styles.display}, visibility:${styles.visibility}, opacity:${styles.opacity}`;
|
|
960
|
+
}, selector);
|
|
961
|
+
if (expectedVisible) {
|
|
962
|
+
return {
|
|
963
|
+
success: false,
|
|
964
|
+
failMessage: `${failureMessage} (actual: ${debugState})`
|
|
965
|
+
};
|
|
966
|
+
} else {
|
|
967
|
+
return {
|
|
968
|
+
success: false,
|
|
969
|
+
failMessage: `${failureMessage} ${targetName} is still visible (actual: ${debugState}).`
|
|
970
|
+
};
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
/**
|
|
975
|
+
* Validate attribute assertion
|
|
976
|
+
*/
|
|
977
|
+
async validateAttribute(target, targetName, attribute, expectedValue, failureMessage, testDescription) {
|
|
978
|
+
if (expectedValue === "!empty") {
|
|
979
|
+
const attributeValue2 = await target.getAttribute(attribute);
|
|
980
|
+
if (attributeValue2 && attributeValue2.trim() !== "") {
|
|
981
|
+
return {
|
|
982
|
+
success: true,
|
|
983
|
+
passMessage: `${targetName} has non-empty "${attribute}". Test: "${testDescription}".`
|
|
984
|
+
};
|
|
985
|
+
} else {
|
|
986
|
+
return {
|
|
987
|
+
success: false,
|
|
988
|
+
failMessage: `${failureMessage} ${targetName} "${attribute}" should not be empty, found "${attributeValue2}".`
|
|
989
|
+
};
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
const expectedValues = expectedValue.split(" | ").map((v) => v.trim());
|
|
993
|
+
const attributeValue = await target.getAttribute(attribute);
|
|
994
|
+
if (attributeValue !== null && expectedValues.includes(attributeValue)) {
|
|
995
|
+
return {
|
|
996
|
+
success: true,
|
|
997
|
+
passMessage: `${targetName} has expected "${attribute}". Test: "${testDescription}".`
|
|
998
|
+
};
|
|
999
|
+
} else {
|
|
1000
|
+
return {
|
|
1001
|
+
success: false,
|
|
1002
|
+
failMessage: `${failureMessage} ${targetName} "${attribute}" should be "${expectedValue}", found "${attributeValue}".`
|
|
1003
|
+
};
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
/**
|
|
1007
|
+
* Validate input value assertion
|
|
1008
|
+
*/
|
|
1009
|
+
async validateValue(target, targetName, expectedValue, failureMessage, testDescription) {
|
|
1010
|
+
const inputValue = await target.inputValue().catch(() => "");
|
|
1011
|
+
if (expectedValue === "!empty") {
|
|
1012
|
+
if (inputValue && inputValue.trim() !== "") {
|
|
1013
|
+
return {
|
|
1014
|
+
success: true,
|
|
1015
|
+
passMessage: `${targetName} has non-empty value. Test: "${testDescription}".`
|
|
1016
|
+
};
|
|
1017
|
+
} else {
|
|
1018
|
+
return {
|
|
1019
|
+
success: false,
|
|
1020
|
+
failMessage: `${failureMessage} ${targetName} value should not be empty, found "${inputValue}".`
|
|
1021
|
+
};
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
if (expectedValue === "") {
|
|
1025
|
+
if (inputValue === "") {
|
|
1026
|
+
return {
|
|
1027
|
+
success: true,
|
|
1028
|
+
passMessage: `${targetName} has empty value. Test: "${testDescription}".`
|
|
1029
|
+
};
|
|
1030
|
+
} else {
|
|
1031
|
+
return {
|
|
1032
|
+
success: false,
|
|
1033
|
+
failMessage: `${failureMessage} ${targetName} value should be empty, found "${inputValue}".`
|
|
1034
|
+
};
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
if (inputValue === expectedValue) {
|
|
1038
|
+
return {
|
|
1039
|
+
success: true,
|
|
1040
|
+
passMessage: `${targetName} has expected value. Test: "${testDescription}".`
|
|
1041
|
+
};
|
|
1042
|
+
} else {
|
|
1043
|
+
return {
|
|
1044
|
+
success: false,
|
|
1045
|
+
failMessage: `${failureMessage} ${targetName} value should be "${expectedValue}", found "${inputValue}".`
|
|
1046
|
+
};
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
/**
|
|
1050
|
+
* Validate focus assertion
|
|
1051
|
+
*/
|
|
1052
|
+
async validateFocus(target, targetName, failureMessage, testDescription) {
|
|
1053
|
+
try {
|
|
1054
|
+
await (0, test_exports.expect)(target).toBeFocused({ timeout: this.timeoutMs });
|
|
1055
|
+
return {
|
|
1056
|
+
success: true,
|
|
1057
|
+
passMessage: `${targetName} has focus as expected. Test: "${testDescription}".`
|
|
1058
|
+
};
|
|
1059
|
+
} catch {
|
|
1060
|
+
const actualFocus = await this.page.evaluate(() => {
|
|
1061
|
+
const focused = document.activeElement;
|
|
1062
|
+
return focused ? `${focused.tagName}#${focused.id || "no-id"}.${focused.className || "no-class"}` : "no element focused";
|
|
1063
|
+
});
|
|
1064
|
+
return {
|
|
1065
|
+
success: false,
|
|
1066
|
+
failMessage: `${failureMessage} (actual focus: ${actualFocus})`
|
|
1067
|
+
};
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
/**
|
|
1071
|
+
* Validate role assertion
|
|
1072
|
+
*/
|
|
1073
|
+
async validateRole(target, targetName, expectedRole, failureMessage, testDescription) {
|
|
1074
|
+
const roleValue = await target.getAttribute("role");
|
|
1075
|
+
if (roleValue === expectedRole) {
|
|
1076
|
+
return {
|
|
1077
|
+
success: true,
|
|
1078
|
+
passMessage: `${targetName} has role "${expectedRole}". Test: "${testDescription}".`
|
|
1079
|
+
};
|
|
1080
|
+
} else {
|
|
1081
|
+
return {
|
|
1082
|
+
success: false,
|
|
1083
|
+
failMessage: `${failureMessage} Expected role "${expectedRole}", found "${roleValue}".`
|
|
1084
|
+
};
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
/**
|
|
1088
|
+
* Main validation method - routes to specific validators
|
|
1089
|
+
*/
|
|
1090
|
+
async validate(assertion, testDescription) {
|
|
1091
|
+
if (this.page.isClosed()) {
|
|
1092
|
+
return {
|
|
1093
|
+
success: false,
|
|
1094
|
+
failMessage: `CRITICAL: Browser/page closed before completing all tests. Increase test timeout or reduce test complexity.`
|
|
1095
|
+
};
|
|
1096
|
+
}
|
|
1097
|
+
const { target, error } = await this.resolveTarget(assertion.target, assertion.relativeTarget || assertion.expectedValue);
|
|
1098
|
+
if (error || !target) {
|
|
1099
|
+
return { success: false, failMessage: error || `Target ${assertion.target} not found.`, target: null };
|
|
1100
|
+
}
|
|
1101
|
+
switch (assertion.assertion) {
|
|
1102
|
+
case "toBeVisible":
|
|
1103
|
+
return this.validateVisibility(target, assertion.target, true, assertion.failureMessage || "", testDescription);
|
|
1104
|
+
case "notToBeVisible":
|
|
1105
|
+
return this.validateVisibility(target, assertion.target, false, assertion.failureMessage || "", testDescription);
|
|
1106
|
+
case "toHaveAttribute":
|
|
1107
|
+
if (assertion.attribute && assertion.expectedValue !== void 0) {
|
|
1108
|
+
return this.validateAttribute(
|
|
1109
|
+
target,
|
|
1110
|
+
assertion.target,
|
|
1111
|
+
assertion.attribute,
|
|
1112
|
+
assertion.expectedValue,
|
|
1113
|
+
assertion.failureMessage || "",
|
|
1114
|
+
testDescription
|
|
1115
|
+
);
|
|
1116
|
+
}
|
|
1117
|
+
return { success: false, failMessage: "Missing attribute or expectedValue for toHaveAttribute assertion" };
|
|
1118
|
+
case "toHaveValue":
|
|
1119
|
+
if (assertion.expectedValue !== void 0) {
|
|
1120
|
+
return this.validateValue(target, assertion.target, assertion.expectedValue, assertion.failureMessage || "", testDescription);
|
|
1121
|
+
}
|
|
1122
|
+
return { success: false, failMessage: "Missing expectedValue for toHaveValue assertion" };
|
|
1123
|
+
case "toHaveFocus":
|
|
1124
|
+
return this.validateFocus(target, assertion.target, assertion.failureMessage || "", testDescription);
|
|
1125
|
+
case "toHaveRole":
|
|
1126
|
+
if (assertion.expectedValue !== void 0) {
|
|
1127
|
+
return this.validateRole(target, assertion.target, assertion.expectedValue, assertion.failureMessage || "", testDescription);
|
|
1128
|
+
}
|
|
1129
|
+
return { success: false, failMessage: "Missing expectedValue for toHaveRole assertion" };
|
|
1130
|
+
default:
|
|
1131
|
+
return { success: false, failMessage: `Unknown assertion type: ${assertion.assertion}` };
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
};
|
|
1135
|
+
}
|
|
1136
|
+
});
|
|
1137
|
+
|
|
1138
|
+
// src/utils/test/src/contractTestRunnerPlaywright.ts
|
|
351
1139
|
var contractTestRunnerPlaywright_exports = {};
|
|
352
1140
|
__export(contractTestRunnerPlaywright_exports, {
|
|
353
1141
|
runContractTestsPlaywright: () => runContractTestsPlaywright
|
|
@@ -356,20 +1144,13 @@ async function runContractTestsPlaywright(componentName, url) {
|
|
|
356
1144
|
const reporter = new ContractReporter(true);
|
|
357
1145
|
const actionTimeoutMs = 400;
|
|
358
1146
|
const assertionTimeoutMs = 400;
|
|
359
|
-
function isBrowserClosedError(error) {
|
|
360
|
-
return error instanceof Error && error.message.includes("Target page, context or browser has been closed");
|
|
361
|
-
}
|
|
362
1147
|
const contractTyped = contract_default;
|
|
363
1148
|
const contractPath = contractTyped[componentName]?.path;
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
}
|
|
367
|
-
const resolvedPath = new URL(contractPath, import_meta2.url).pathname;
|
|
368
|
-
const contractData = (0, import_fs.readFileSync)(resolvedPath, "utf-8");
|
|
1149
|
+
const resolvedPath = new URL(contractPath, import_meta3.url).pathname;
|
|
1150
|
+
const contractData = (0, import_fs2.readFileSync)(resolvedPath, "utf-8");
|
|
369
1151
|
const componentContract = JSON.parse(contractData);
|
|
370
1152
|
const totalTests = componentContract.static[0].assertions.length + componentContract.dynamic.length;
|
|
371
1153
|
const apgUrl = componentContract.meta?.source?.apg;
|
|
372
|
-
reporter.start(componentName, totalTests, apgUrl);
|
|
373
1154
|
const failures = [];
|
|
374
1155
|
const passes = [];
|
|
375
1156
|
const skipped = [];
|
|
@@ -389,68 +1170,53 @@ async function runContractTestsPlaywright(componentName, url) {
|
|
|
389
1170
|
}
|
|
390
1171
|
await page.addStyleTag({ content: `* { transition: none !important; animation: none !important; }` });
|
|
391
1172
|
}
|
|
392
|
-
const
|
|
1173
|
+
const strategy = ComponentDetector.detect(componentName, actionTimeoutMs, assertionTimeoutMs);
|
|
1174
|
+
if (!strategy) {
|
|
1175
|
+
throw new Error(`Unsupported component: ${componentName}`);
|
|
1176
|
+
}
|
|
1177
|
+
const mainSelector = strategy.getMainSelector();
|
|
393
1178
|
if (!mainSelector) {
|
|
394
|
-
throw new Error(`CRITICAL: No
|
|
1179
|
+
throw new Error(`CRITICAL: No selector found in contract for ${componentName}`);
|
|
395
1180
|
}
|
|
396
1181
|
try {
|
|
397
1182
|
await page.locator(mainSelector).first().waitFor({ state: "attached", timeout: 3e4 });
|
|
398
1183
|
} catch (error) {
|
|
399
1184
|
throw new Error(
|
|
400
|
-
`
|
|
1185
|
+
`
|
|
1186
|
+
\u274C CRITICAL: Component not found on page!
|
|
1187
|
+
This usually means:
|
|
1188
|
+
- The component didn't render
|
|
1189
|
+
- The URL is incorrect
|
|
1190
|
+
- The component selector '${mainSelector}' in the contract is wrong
|
|
1191
|
+
- Original error: ${error}`
|
|
401
1192
|
);
|
|
402
1193
|
}
|
|
1194
|
+
reporter.start(componentName, totalTests, apgUrl);
|
|
403
1195
|
if (componentName === "menu" && componentContract.selectors.trigger) {
|
|
404
1196
|
await page.locator(componentContract.selectors.trigger).first().waitFor({
|
|
405
|
-
state: "
|
|
1197
|
+
state: "attached",
|
|
406
1198
|
timeout: 5e3
|
|
407
1199
|
}).catch(() => {
|
|
408
|
-
console.warn("Menu trigger not visible, continuing with tests...");
|
|
409
1200
|
});
|
|
410
1201
|
}
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
throw new Error("Page is not initialized");
|
|
414
|
-
}
|
|
415
|
-
const items = await page.locator(selector).all();
|
|
416
|
-
switch (relative) {
|
|
417
|
-
case "first":
|
|
418
|
-
return items[0];
|
|
419
|
-
case "second":
|
|
420
|
-
return items[1];
|
|
421
|
-
case "last":
|
|
422
|
-
return items[items.length - 1];
|
|
423
|
-
case "next": {
|
|
424
|
-
const currentIndex = await page.evaluate(([sel]) => {
|
|
425
|
-
const items2 = Array.from(document.querySelectorAll(sel));
|
|
426
|
-
return items2.indexOf(document.activeElement);
|
|
427
|
-
}, [selector]);
|
|
428
|
-
const nextIndex = (currentIndex + 1) % items.length;
|
|
429
|
-
return items[nextIndex];
|
|
430
|
-
}
|
|
431
|
-
case "previous": {
|
|
432
|
-
const currentIndex = await page.evaluate(([sel]) => {
|
|
433
|
-
const items2 = Array.from(document.querySelectorAll(sel));
|
|
434
|
-
return items2.indexOf(document.activeElement);
|
|
435
|
-
}, [selector]);
|
|
436
|
-
const prevIndex = (currentIndex - 1 + items.length) % items.length;
|
|
437
|
-
return items[prevIndex];
|
|
438
|
-
}
|
|
439
|
-
default:
|
|
440
|
-
return null;
|
|
441
|
-
}
|
|
442
|
-
}
|
|
1202
|
+
const failuresBeforeStatic = failures.length;
|
|
1203
|
+
const staticAssertionRunner = new AssertionRunner(page, componentContract.selectors, assertionTimeoutMs);
|
|
443
1204
|
for (const test of componentContract.static[0]?.assertions || []) {
|
|
444
1205
|
if (test.target === "relative") continue;
|
|
1206
|
+
const staticDescription = `${test.target}${test.attribute ? ` (${test.attribute})` : ""}`;
|
|
445
1207
|
const targetSelector = componentContract.selectors[test.target];
|
|
446
1208
|
if (!targetSelector) {
|
|
447
|
-
|
|
1209
|
+
const failure = `Selector for target ${test.target} not found.`;
|
|
1210
|
+
failures.push(failure);
|
|
1211
|
+
reporter.reportStaticTest(staticDescription, false, failure);
|
|
448
1212
|
continue;
|
|
449
1213
|
}
|
|
450
1214
|
const target = page.locator(targetSelector).first();
|
|
451
1215
|
const exists = await target.count() > 0;
|
|
452
1216
|
if (!exists) {
|
|
453
|
-
|
|
1217
|
+
const failure = `Target ${test.target} not found.`;
|
|
1218
|
+
failures.push(failure);
|
|
1219
|
+
reporter.reportStaticTest(staticDescription, false, failure);
|
|
454
1220
|
continue;
|
|
455
1221
|
}
|
|
456
1222
|
const isRedundantCheck = (selector, attrName, expectedVal) => {
|
|
@@ -484,20 +1250,34 @@ async function runContractTestsPlaywright(componentName, url) {
|
|
|
484
1250
|
}
|
|
485
1251
|
}
|
|
486
1252
|
if (!hasAny && !allRedundant) {
|
|
487
|
-
|
|
1253
|
+
const failure = test.failureMessage + ` None of the attributes "${test.attribute}" are present.`;
|
|
1254
|
+
failures.push(failure);
|
|
1255
|
+
reporter.reportStaticTest(staticDescription, false, failure);
|
|
488
1256
|
} else if (!allRedundant && hasAny) {
|
|
489
1257
|
passes.push(`At least one of the attributes "${test.attribute}" exists on the element.`);
|
|
1258
|
+
reporter.reportStaticTest(staticDescription, true);
|
|
1259
|
+
} else {
|
|
1260
|
+
reporter.reportStaticTest(staticDescription, true);
|
|
490
1261
|
}
|
|
491
1262
|
} else {
|
|
492
1263
|
if (isRedundantCheck(targetSelector, test.attribute, test.expectedValue)) {
|
|
493
1264
|
passes.push(`${test.attribute}="${test.expectedValue}" on ${test.target} verified by selector (already present in: ${targetSelector}).`);
|
|
1265
|
+
reporter.reportStaticTest(staticDescription, true);
|
|
494
1266
|
} else {
|
|
495
|
-
const
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
1267
|
+
const result = await staticAssertionRunner.validateAttribute(
|
|
1268
|
+
target,
|
|
1269
|
+
test.target,
|
|
1270
|
+
test.attribute,
|
|
1271
|
+
test.expectedValue,
|
|
1272
|
+
test.failureMessage,
|
|
1273
|
+
"Static ARIA Test"
|
|
1274
|
+
);
|
|
1275
|
+
if (result.success && result.passMessage) {
|
|
1276
|
+
passes.push(result.passMessage);
|
|
1277
|
+
reporter.reportStaticTest(staticDescription, true);
|
|
1278
|
+
} else if (!result.success && result.failMessage) {
|
|
1279
|
+
failures.push(result.failMessage);
|
|
1280
|
+
reporter.reportStaticTest(staticDescription, false, result.failMessage);
|
|
501
1281
|
}
|
|
502
1282
|
}
|
|
503
1283
|
}
|
|
@@ -512,383 +1292,58 @@ async function runContractTestsPlaywright(componentName, url) {
|
|
|
512
1292
|
}
|
|
513
1293
|
const { action, assertions } = dynamicTest;
|
|
514
1294
|
const failuresBeforeTest = failures.length;
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
const
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
let menuClosed = false;
|
|
522
|
-
let closeSelector = componentContract.selectors.input;
|
|
523
|
-
if (!closeSelector && componentContract.selectors.focusable) {
|
|
524
|
-
closeSelector = componentContract.selectors.focusable;
|
|
525
|
-
} else if (!closeSelector) {
|
|
526
|
-
closeSelector = componentContract.selectors.trigger;
|
|
527
|
-
}
|
|
528
|
-
if (closeSelector) {
|
|
529
|
-
const closeElement = page.locator(closeSelector).first();
|
|
530
|
-
await closeElement.focus();
|
|
531
|
-
await page.keyboard.press("Escape");
|
|
532
|
-
menuClosed = await (0, test_exports.expect)(popupElement).toBeHidden({ timeout: assertionTimeoutMs }).then(() => true).catch(() => false);
|
|
533
|
-
}
|
|
534
|
-
if (!menuClosed && componentContract.selectors.trigger) {
|
|
535
|
-
const triggerElement = page.locator(componentContract.selectors.trigger).first();
|
|
536
|
-
await triggerElement.click({ timeout: actionTimeoutMs });
|
|
537
|
-
menuClosed = await (0, test_exports.expect)(popupElement).toBeHidden({ timeout: assertionTimeoutMs }).then(() => true).catch(() => false);
|
|
538
|
-
}
|
|
539
|
-
if (!menuClosed) {
|
|
540
|
-
await page.mouse.click(10, 10);
|
|
541
|
-
menuClosed = await (0, test_exports.expect)(popupElement).toBeHidden({ timeout: assertionTimeoutMs }).then(() => true).catch(() => false);
|
|
542
|
-
}
|
|
543
|
-
if (!menuClosed) {
|
|
544
|
-
throw new Error(
|
|
545
|
-
`\u274C FATAL: Cannot close menu between tests. Menu remains visible after trying:
|
|
546
|
-
1. Escape key
|
|
547
|
-
2. Clicking trigger
|
|
548
|
-
3. Clicking outside
|
|
549
|
-
This indicates a problem with the menu component's close functionality.`
|
|
550
|
-
);
|
|
551
|
-
}
|
|
552
|
-
if (componentContract.selectors.input) {
|
|
553
|
-
await page.locator(componentContract.selectors.input).first().clear();
|
|
554
|
-
}
|
|
555
|
-
if (componentName === "menu" && componentContract.selectors.trigger) {
|
|
556
|
-
const triggerElement = page.locator(componentContract.selectors.trigger).first();
|
|
557
|
-
await triggerElement.focus();
|
|
558
|
-
}
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
|
-
if (componentContract.selectors.panel && componentContract.selectors.trigger && !componentContract.selectors.popup) {
|
|
562
|
-
const triggerSelector = componentContract.selectors.trigger;
|
|
563
|
-
const panelSelector = componentContract.selectors.panel;
|
|
564
|
-
if (triggerSelector && panelSelector) {
|
|
565
|
-
const allTriggers = await page.locator(triggerSelector).all();
|
|
566
|
-
for (const trigger of allTriggers) {
|
|
567
|
-
const isExpanded = await trigger.getAttribute("aria-expanded") === "true";
|
|
568
|
-
const triggerPanel = await trigger.getAttribute("aria-controls");
|
|
569
|
-
if (isExpanded && triggerPanel) {
|
|
570
|
-
await trigger.click({ timeout: actionTimeoutMs });
|
|
571
|
-
const panel = page.locator(`#${triggerPanel}`);
|
|
572
|
-
await (0, test_exports.expect)(panel).toBeHidden({ timeout: assertionTimeoutMs }).catch(() => {
|
|
573
|
-
});
|
|
574
|
-
}
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
|
-
}
|
|
578
|
-
let shouldSkipTest = false;
|
|
579
|
-
for (const act of action) {
|
|
580
|
-
if (act.type === "keypress" && (act.target === "submenuTrigger" || act.target === "submenu")) {
|
|
581
|
-
const submenuSelector = componentContract.selectors[act.target];
|
|
582
|
-
if (submenuSelector) {
|
|
583
|
-
const submenuCount = await page.locator(submenuSelector).count();
|
|
584
|
-
if (submenuCount === 0) {
|
|
585
|
-
reporter.reportTest(dynamicTest, "skip", `Skipping test - ${act.target} element not found (optional submenu test)`);
|
|
586
|
-
shouldSkipTest = true;
|
|
587
|
-
break;
|
|
588
|
-
}
|
|
589
|
-
}
|
|
590
|
-
}
|
|
591
|
-
}
|
|
592
|
-
if (!shouldSkipTest) {
|
|
593
|
-
for (const assertion of assertions) {
|
|
594
|
-
if (assertion.target === "submenu" || assertion.target === "submenuTrigger") {
|
|
595
|
-
const submenuSelector = componentContract.selectors[assertion.target];
|
|
596
|
-
if (submenuSelector) {
|
|
597
|
-
const submenuCount = await page.locator(submenuSelector).count();
|
|
598
|
-
if (submenuCount === 0) {
|
|
599
|
-
reporter.reportTest(dynamicTest, "skip", `Skipping test - ${assertion.target} element not found (optional submenu test)`);
|
|
600
|
-
shouldSkipTest = true;
|
|
601
|
-
break;
|
|
602
|
-
}
|
|
603
|
-
}
|
|
604
|
-
}
|
|
605
|
-
}
|
|
1295
|
+
try {
|
|
1296
|
+
await strategy.resetState(page);
|
|
1297
|
+
} catch (error) {
|
|
1298
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1299
|
+
reporter.error(errorMessage);
|
|
1300
|
+
throw error;
|
|
606
1301
|
}
|
|
1302
|
+
const shouldSkipTest = await strategy.shouldSkipTest(dynamicTest, page);
|
|
607
1303
|
if (shouldSkipTest) {
|
|
1304
|
+
reporter.reportTest(dynamicTest, "skip", `Skipping test - component-specific conditions not met`);
|
|
608
1305
|
continue;
|
|
609
1306
|
}
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
const tablistSelector = componentContract.selectors.tablist;
|
|
613
|
-
const tablist = page.locator(tablistSelector).first();
|
|
614
|
-
const orientation = await tablist.getAttribute("aria-orientation");
|
|
615
|
-
const isVertical = orientation === "vertical";
|
|
616
|
-
if (dynamicTest.isVertical !== isVertical) {
|
|
617
|
-
const skipReason = dynamicTest.isVertical ? `Skipping vertical tabs test - component has horizontal orientation` : `Skipping horizontal tabs test - component has vertical orientation`;
|
|
618
|
-
reporter.reportTest(dynamicTest, "skip", skipReason);
|
|
619
|
-
continue;
|
|
620
|
-
}
|
|
621
|
-
}
|
|
622
|
-
}
|
|
1307
|
+
const actionExecutor = new ActionExecutor(page, componentContract.selectors, actionTimeoutMs);
|
|
1308
|
+
const assertionRunner = new AssertionRunner(page, componentContract.selectors, assertionTimeoutMs);
|
|
623
1309
|
for (const act of action) {
|
|
624
1310
|
if (!page || page.isClosed()) {
|
|
625
1311
|
failures.push(`CRITICAL: Browser/page closed during test execution. Remaining actions skipped.`);
|
|
626
1312
|
break;
|
|
627
1313
|
}
|
|
1314
|
+
let result;
|
|
628
1315
|
if (act.type === "focus") {
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
}
|
|
641
|
-
failures.push(`Failed to focus ${act.target}: ${error instanceof Error ? error.message : String(error)}`);
|
|
642
|
-
continue;
|
|
643
|
-
}
|
|
644
|
-
}
|
|
645
|
-
if (act.type === "type" && act.value) {
|
|
646
|
-
try {
|
|
647
|
-
const typeSelector = componentContract.selectors[act.target];
|
|
648
|
-
if (!typeSelector) {
|
|
649
|
-
failures.push(`Selector for type target ${act.target} not found.`);
|
|
650
|
-
continue;
|
|
651
|
-
}
|
|
652
|
-
await page.locator(typeSelector).first().fill(act.value, { timeout: actionTimeoutMs });
|
|
653
|
-
} catch (error) {
|
|
654
|
-
if (isBrowserClosedError(error)) {
|
|
655
|
-
failures.push(`CRITICAL: Browser/page closed during test execution. Remaining actions skipped.`);
|
|
656
|
-
break;
|
|
657
|
-
}
|
|
658
|
-
failures.push(`Failed to type into ${act.target}: ${error instanceof Error ? error.message : String(error)}`);
|
|
659
|
-
continue;
|
|
660
|
-
}
|
|
1316
|
+
result = await actionExecutor.focus(act.target);
|
|
1317
|
+
} else if (act.type === "type" && act.value) {
|
|
1318
|
+
result = await actionExecutor.type(act.target, act.value);
|
|
1319
|
+
} else if (act.type === "click") {
|
|
1320
|
+
result = await actionExecutor.click(act.target, act.relativeTarget);
|
|
1321
|
+
} else if (act.type === "keypress" && act.key) {
|
|
1322
|
+
result = await actionExecutor.keypress(act.target, act.key);
|
|
1323
|
+
} else if (act.type === "hover") {
|
|
1324
|
+
result = await actionExecutor.hover(act.target, act.relativeTarget);
|
|
1325
|
+
} else {
|
|
1326
|
+
continue;
|
|
661
1327
|
}
|
|
662
|
-
if (
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
await page.mouse.click(10, 10);
|
|
666
|
-
} else if (act.target === "relative" && act.relativeTarget) {
|
|
667
|
-
const relativeSelector = componentContract.selectors.relative;
|
|
668
|
-
if (!relativeSelector) {
|
|
669
|
-
failures.push(`Relative selector not defined for click action.`);
|
|
670
|
-
continue;
|
|
671
|
-
}
|
|
672
|
-
const relativeElement = await resolveRelativeTarget(relativeSelector, act.relativeTarget);
|
|
673
|
-
if (!relativeElement) {
|
|
674
|
-
failures.push(`Could not resolve relative target ${act.relativeTarget} for click.`);
|
|
675
|
-
continue;
|
|
676
|
-
}
|
|
677
|
-
await relativeElement.click({ timeout: actionTimeoutMs });
|
|
678
|
-
} else {
|
|
679
|
-
const actionSelector = componentContract.selectors[act.target];
|
|
680
|
-
if (!actionSelector) {
|
|
681
|
-
failures.push(`Selector for action target ${act.target} not found.`);
|
|
682
|
-
continue;
|
|
683
|
-
}
|
|
684
|
-
await page.locator(actionSelector).first().click({ timeout: actionTimeoutMs });
|
|
685
|
-
}
|
|
686
|
-
} catch (error) {
|
|
687
|
-
if (isBrowserClosedError(error)) {
|
|
688
|
-
failures.push(`CRITICAL: Browser/page closed during test execution. Remaining actions skipped.`);
|
|
689
|
-
break;
|
|
690
|
-
}
|
|
691
|
-
failures.push(`Failed to click ${act.target}: ${error instanceof Error ? error.message : String(error)}`);
|
|
692
|
-
continue;
|
|
1328
|
+
if (!result.success) {
|
|
1329
|
+
if (result.error) {
|
|
1330
|
+
failures.push(result.error);
|
|
693
1331
|
}
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
const keyMap = {
|
|
698
|
-
"Space": "Space",
|
|
699
|
-
"Enter": "Enter",
|
|
700
|
-
"Escape": "Escape",
|
|
701
|
-
"Arrow Up": "ArrowUp",
|
|
702
|
-
"Arrow Down": "ArrowDown",
|
|
703
|
-
"Arrow Left": "ArrowLeft",
|
|
704
|
-
"Arrow Right": "ArrowRight",
|
|
705
|
-
"Home": "Home",
|
|
706
|
-
"End": "End",
|
|
707
|
-
"Tab": "Tab"
|
|
708
|
-
};
|
|
709
|
-
let keyValue = keyMap[act.key] || act.key;
|
|
710
|
-
if (keyValue === "Space") {
|
|
711
|
-
keyValue = " ";
|
|
712
|
-
} else if (keyValue.includes(" ")) {
|
|
713
|
-
keyValue = keyValue.replace(/ /g, "");
|
|
1332
|
+
if (result.shouldBreak) {
|
|
1333
|
+
if (result.error?.includes("optional submenu test")) {
|
|
1334
|
+
reporter.reportTest(dynamicTest, "skip", result.error);
|
|
714
1335
|
}
|
|
715
|
-
|
|
716
|
-
await page.keyboard.press(keyValue);
|
|
717
|
-
} else {
|
|
718
|
-
const keypressSelector = componentContract.selectors[act.target];
|
|
719
|
-
if (!keypressSelector) {
|
|
720
|
-
failures.push(`Selector for keypress target ${act.target} not found.`);
|
|
721
|
-
continue;
|
|
722
|
-
}
|
|
723
|
-
const target = page.locator(keypressSelector).first();
|
|
724
|
-
const elementCount = await target.count();
|
|
725
|
-
if (elementCount === 0) {
|
|
726
|
-
reporter.reportTest(dynamicTest, "skip", `Skipping test - ${act.target} element not found (optional submenu test)`);
|
|
727
|
-
break;
|
|
728
|
-
}
|
|
729
|
-
await target.press(keyValue, { timeout: actionTimeoutMs });
|
|
730
|
-
}
|
|
731
|
-
} catch (error) {
|
|
732
|
-
if (isBrowserClosedError(error)) {
|
|
733
|
-
failures.push(`CRITICAL: Browser/page closed during test execution. Remaining actions skipped.`);
|
|
734
|
-
break;
|
|
735
|
-
}
|
|
736
|
-
failures.push(`Failed to press ${act.key} on ${act.target}: ${error instanceof Error ? error.message : String(error)}`);
|
|
737
|
-
continue;
|
|
738
|
-
}
|
|
739
|
-
}
|
|
740
|
-
if (act.type === "hover") {
|
|
741
|
-
try {
|
|
742
|
-
if (act.target === "relative" && act.relativeTarget) {
|
|
743
|
-
const relativeSelector = componentContract.selectors.relative;
|
|
744
|
-
if (!relativeSelector) {
|
|
745
|
-
failures.push(`Relative selector not defined for hover action.`);
|
|
746
|
-
continue;
|
|
747
|
-
}
|
|
748
|
-
const relativeElement = await resolveRelativeTarget(relativeSelector, act.relativeTarget);
|
|
749
|
-
if (!relativeElement) {
|
|
750
|
-
failures.push(`Could not resolve relative target ${act.relativeTarget} for hover.`);
|
|
751
|
-
continue;
|
|
752
|
-
}
|
|
753
|
-
await relativeElement.hover({ timeout: actionTimeoutMs });
|
|
754
|
-
} else {
|
|
755
|
-
const hoverSelector = componentContract.selectors[act.target];
|
|
756
|
-
if (!hoverSelector) {
|
|
757
|
-
failures.push(`Selector for hover target ${act.target} not found.`);
|
|
758
|
-
continue;
|
|
759
|
-
}
|
|
760
|
-
await page.locator(hoverSelector).first().hover({ timeout: actionTimeoutMs });
|
|
761
|
-
}
|
|
762
|
-
} catch (error) {
|
|
763
|
-
if (isBrowserClosedError(error)) {
|
|
764
|
-
failures.push(`CRITICAL: Browser/page closed during test execution. Remaining actions skipped.`);
|
|
765
|
-
break;
|
|
766
|
-
}
|
|
767
|
-
failures.push(`Failed to hover ${act.target}: ${error instanceof Error ? error.message : String(error)}`);
|
|
768
|
-
continue;
|
|
1336
|
+
break;
|
|
769
1337
|
}
|
|
1338
|
+
continue;
|
|
770
1339
|
}
|
|
771
1340
|
}
|
|
772
1341
|
for (const assertion of assertions) {
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
}
|
|
777
|
-
|
|
778
|
-
try {
|
|
779
|
-
if (assertion.target === "relative") {
|
|
780
|
-
const relativeSelector = componentContract.selectors.relative;
|
|
781
|
-
if (!relativeSelector) {
|
|
782
|
-
failures.push("Relative selector is not defined in the contract.");
|
|
783
|
-
continue;
|
|
784
|
-
}
|
|
785
|
-
const relativeTargetValue = assertion.relativeTarget || assertion.expectedValue;
|
|
786
|
-
if (!relativeTargetValue) {
|
|
787
|
-
failures.push("Relative target or expected value is not defined.");
|
|
788
|
-
continue;
|
|
789
|
-
}
|
|
790
|
-
target = await resolveRelativeTarget(relativeSelector, relativeTargetValue);
|
|
791
|
-
} else {
|
|
792
|
-
const assertionSelector = componentContract.selectors[assertion.target];
|
|
793
|
-
if (!assertionSelector) {
|
|
794
|
-
failures.push(`Selector for assertion target ${assertion.target} not found.`);
|
|
795
|
-
continue;
|
|
796
|
-
}
|
|
797
|
-
target = page.locator(assertionSelector).first();
|
|
798
|
-
}
|
|
799
|
-
if (!target) {
|
|
800
|
-
failures.push(`Target ${assertion.target} not found.`);
|
|
801
|
-
continue;
|
|
802
|
-
}
|
|
803
|
-
} catch (error) {
|
|
804
|
-
failures.push(`Failed to resolve target ${assertion.target}: ${error instanceof Error ? error.message : String(error)}`);
|
|
805
|
-
continue;
|
|
806
|
-
}
|
|
807
|
-
if (assertion.assertion === "toBeVisible") {
|
|
808
|
-
try {
|
|
809
|
-
await (0, test_exports.expect)(target).toBeVisible({ timeout: assertionTimeoutMs });
|
|
810
|
-
passes.push(`${assertion.target} is visible as expected. Test: "${dynamicTest.description}".`);
|
|
811
|
-
} catch {
|
|
812
|
-
const debugState = await page.evaluate((sel) => {
|
|
813
|
-
const el = sel ? document.querySelector(sel) : null;
|
|
814
|
-
if (!el) return "element not found";
|
|
815
|
-
const styles = window.getComputedStyle(el);
|
|
816
|
-
return `display:${styles.display}, visibility:${styles.visibility}, opacity:${styles.opacity}`;
|
|
817
|
-
}, componentContract.selectors[assertion.target] || "");
|
|
818
|
-
failures.push(`${assertion.failureMessage} (actual: ${debugState})`);
|
|
819
|
-
}
|
|
820
|
-
}
|
|
821
|
-
if (assertion.assertion === "notToBeVisible") {
|
|
822
|
-
try {
|
|
823
|
-
await (0, test_exports.expect)(target).toBeHidden({ timeout: assertionTimeoutMs });
|
|
824
|
-
passes.push(`${assertion.target} is not visible as expected. Test: "${dynamicTest.description}".`);
|
|
825
|
-
} catch {
|
|
826
|
-
const debugState = await page.evaluate((sel) => {
|
|
827
|
-
const el = sel ? document.querySelector(sel) : null;
|
|
828
|
-
if (!el) return "element not found";
|
|
829
|
-
const styles = window.getComputedStyle(el);
|
|
830
|
-
return `display:${styles.display}, visibility:${styles.visibility}, opacity:${styles.opacity}`;
|
|
831
|
-
}, componentContract.selectors[assertion.target] || "");
|
|
832
|
-
failures.push(assertion.failureMessage + ` ${assertion.target} is still visible (actual: ${debugState}).`);
|
|
833
|
-
}
|
|
834
|
-
}
|
|
835
|
-
if (assertion.assertion === "toHaveAttribute" && assertion.attribute && assertion.expectedValue) {
|
|
836
|
-
try {
|
|
837
|
-
if (assertion.expectedValue === "!empty") {
|
|
838
|
-
const attributeValue = await target.getAttribute(assertion.attribute);
|
|
839
|
-
if (attributeValue && attributeValue.trim() !== "") {
|
|
840
|
-
passes.push(`${assertion.target} has non-empty "${assertion.attribute}". Test: "${dynamicTest.description}".`);
|
|
841
|
-
} else {
|
|
842
|
-
failures.push(assertion.failureMessage + ` ${assertion.target} "${assertion.attribute}" should not be empty, found "${attributeValue}".`);
|
|
843
|
-
}
|
|
844
|
-
} else {
|
|
845
|
-
await (0, test_exports.expect)(target).toHaveAttribute(assertion.attribute, assertion.expectedValue, { timeout: assertionTimeoutMs });
|
|
846
|
-
passes.push(`${assertion.target} has expected "${assertion.attribute}". Test: "${dynamicTest.description}".`);
|
|
847
|
-
}
|
|
848
|
-
} catch {
|
|
849
|
-
const attributeValue = await target.getAttribute(assertion.attribute);
|
|
850
|
-
failures.push(assertion.failureMessage + ` ${assertion.target} "${assertion.attribute}" should be "${assertion.expectedValue}", found "${attributeValue}".`);
|
|
851
|
-
}
|
|
852
|
-
}
|
|
853
|
-
if (assertion.assertion === "toHaveValue") {
|
|
854
|
-
const inputValue = await target.inputValue().catch(() => "");
|
|
855
|
-
if (assertion.expectedValue === "!empty") {
|
|
856
|
-
if (inputValue && inputValue.trim() !== "") {
|
|
857
|
-
passes.push(`${assertion.target} has non-empty value. Test: "${dynamicTest.description}".`);
|
|
858
|
-
} else {
|
|
859
|
-
failures.push(assertion.failureMessage + ` ${assertion.target} value should not be empty, found "${inputValue}".`);
|
|
860
|
-
}
|
|
861
|
-
} else if (assertion.expectedValue === "") {
|
|
862
|
-
if (inputValue === "") {
|
|
863
|
-
passes.push(`${assertion.target} has empty value. Test: "${dynamicTest.description}".`);
|
|
864
|
-
} else {
|
|
865
|
-
failures.push(assertion.failureMessage + ` ${assertion.target} value should be empty, found "${inputValue}".`);
|
|
866
|
-
}
|
|
867
|
-
} else if (inputValue === assertion.expectedValue) {
|
|
868
|
-
passes.push(`${assertion.target} has expected value. Test: "${dynamicTest.description}".`);
|
|
869
|
-
} else {
|
|
870
|
-
failures.push(assertion.failureMessage + ` ${assertion.target} value should be "${assertion.expectedValue}", found "${inputValue}".`);
|
|
871
|
-
}
|
|
872
|
-
}
|
|
873
|
-
if (assertion.assertion === "toHaveFocus") {
|
|
874
|
-
try {
|
|
875
|
-
await (0, test_exports.expect)(target).toBeFocused({ timeout: assertionTimeoutMs });
|
|
876
|
-
passes.push(`${assertion.target} has focus as expected. Test: "${dynamicTest.description}".`);
|
|
877
|
-
} catch {
|
|
878
|
-
const actualFocus = await page.evaluate(() => {
|
|
879
|
-
const focused = document.activeElement;
|
|
880
|
-
return focused ? `${focused.tagName}#${focused.id || "no-id"}.${focused.className || "no-class"}` : "no element focused";
|
|
881
|
-
});
|
|
882
|
-
failures.push(`${assertion.failureMessage} (actual focus: ${actualFocus})`);
|
|
883
|
-
}
|
|
884
|
-
}
|
|
885
|
-
if (assertion.assertion === "toHaveRole" && assertion.expectedValue) {
|
|
886
|
-
const roleValue = await target.getAttribute("role");
|
|
887
|
-
if (roleValue === assertion.expectedValue) {
|
|
888
|
-
passes.push(`${assertion.target} has role "${assertion.expectedValue}". Test: "${dynamicTest.description}".`);
|
|
889
|
-
} else {
|
|
890
|
-
failures.push(assertion.failureMessage + ` Expected role "${assertion.expectedValue}", found "${roleValue}".`);
|
|
891
|
-
}
|
|
1342
|
+
const result = await assertionRunner.validate(assertion, dynamicTest.description);
|
|
1343
|
+
if (result.success && result.passMessage) {
|
|
1344
|
+
passes.push(result.passMessage);
|
|
1345
|
+
} else if (!result.success && result.failMessage) {
|
|
1346
|
+
failures.push(result.failMessage);
|
|
892
1347
|
}
|
|
893
1348
|
}
|
|
894
1349
|
const failuresAfterTest = failures.length;
|
|
@@ -901,54 +1356,29 @@ This indicates a problem with the menu component's close functionality.`
|
|
|
901
1356
|
reporter.reportTest(dynamicTest, testPassed ? "pass" : "fail", failureMessage);
|
|
902
1357
|
}
|
|
903
1358
|
}
|
|
904
|
-
const
|
|
905
|
-
const staticFailed =
|
|
1359
|
+
const staticTotal = componentContract.static[0].assertions.length;
|
|
1360
|
+
const staticFailed = failures.length - failuresBeforeStatic;
|
|
1361
|
+
const staticPassed = Math.max(0, staticTotal - staticFailed);
|
|
906
1362
|
reporter.reportStatic(staticPassed, staticFailed);
|
|
907
1363
|
reporter.summary(failures);
|
|
908
1364
|
} catch (error) {
|
|
909
1365
|
if (error instanceof Error) {
|
|
910
1366
|
if (error.message.includes("Executable doesn't exist") || error.message.includes("browserType.launch")) {
|
|
911
|
-
|
|
912
|
-
console.log("\u{1F4E6} Run: npx playwright install chromium\n");
|
|
913
|
-
failures.push("CRITICAL: Playwright browser not installed. Run: npx playwright install chromium");
|
|
1367
|
+
throw new Error("\n\u274C CRITICAL: Playwright browsers not found!\n\u{1F4E6} Run: npx playwright install chromium");
|
|
914
1368
|
} else if (error.message.includes("net::ERR_CONNECTION_REFUSED") || error.message.includes("NS_ERROR_CONNECTION_REFUSED")) {
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
`);
|
|
918
|
-
failures.push(`CRITICAL: Dev server not running at ${url}`);
|
|
1369
|
+
throw new Error(`
|
|
1370
|
+
\u274C CRITICAL: Cannot connect to dev server!
|
|
1371
|
+
Make sure your dev server is running at ${url}`);
|
|
919
1372
|
} else if (error.message.includes("Timeout") && error.message.includes("waitFor")) {
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
console.log(` This usually means:
|
|
924
|
-
`);
|
|
925
|
-
console.log(` - The component didn't render
|
|
926
|
-
`);
|
|
927
|
-
console.log(` - The URL is incorrect
|
|
928
|
-
`);
|
|
929
|
-
console.log(` - The component selector in the contract is wrong
|
|
930
|
-
`);
|
|
931
|
-
failures.push(`CRITICAL: Component element not found on page - ${error.message}`);
|
|
1373
|
+
throw new Error(
|
|
1374
|
+
"\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"
|
|
1375
|
+
);
|
|
932
1376
|
} else if (error.message.includes("Target page, context or browser has been closed")) {
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
console.log(` - The test timeout was too short
|
|
937
|
-
`);
|
|
938
|
-
console.log(` - The browser crashed
|
|
939
|
-
`);
|
|
940
|
-
console.log(` - An external process killed the browser
|
|
941
|
-
`);
|
|
942
|
-
failures.push(`CRITICAL: Browser/page closed unexpectedly - ${error.message}`);
|
|
943
|
-
} else if (error.message.includes("FATAL")) {
|
|
944
|
-
console.error(`
|
|
945
|
-
${error.message}
|
|
946
|
-
`);
|
|
947
|
-
failures.push(error.message);
|
|
1377
|
+
throw new Error(
|
|
1378
|
+
"\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"
|
|
1379
|
+
);
|
|
948
1380
|
} else {
|
|
949
|
-
|
|
950
|
-
console.error("Stack:", error.stack);
|
|
951
|
-
failures.push(`UNEXPECTED ERROR: ${error.message}`);
|
|
1381
|
+
throw error;
|
|
952
1382
|
}
|
|
953
1383
|
}
|
|
954
1384
|
} finally {
|
|
@@ -956,16 +1386,18 @@ ${error.message}
|
|
|
956
1386
|
}
|
|
957
1387
|
return { passes, failures, skipped };
|
|
958
1388
|
}
|
|
959
|
-
var
|
|
1389
|
+
var import_fs2, import_meta3;
|
|
960
1390
|
var init_contractTestRunnerPlaywright = __esm({
|
|
961
|
-
"src/utils/test/
|
|
1391
|
+
"src/utils/test/src/contractTestRunnerPlaywright.ts"() {
|
|
962
1392
|
"use strict";
|
|
963
|
-
|
|
964
|
-
import_fs = require("fs");
|
|
1393
|
+
import_fs2 = require("fs");
|
|
965
1394
|
init_contract();
|
|
966
|
-
init_ContractReporter();
|
|
967
1395
|
init_playwrightTestHarness();
|
|
968
|
-
|
|
1396
|
+
init_ComponentDetector();
|
|
1397
|
+
init_ContractReporter();
|
|
1398
|
+
init_ActionExecutor();
|
|
1399
|
+
init_AssertionRunner();
|
|
1400
|
+
import_meta3 = {};
|
|
969
1401
|
}
|
|
970
1402
|
});
|
|
971
1403
|
|
|
@@ -1633,6 +2065,13 @@ function makeMenuAccessible({ menuId, menuItemsClass, triggerId, callback }) {
|
|
|
1633
2065
|
};
|
|
1634
2066
|
return nodeListLike;
|
|
1635
2067
|
}
|
|
2068
|
+
function intializeMenuItems() {
|
|
2069
|
+
const items = getItems();
|
|
2070
|
+
items.forEach((item) => {
|
|
2071
|
+
item.setAttribute("role", "menuitem");
|
|
2072
|
+
});
|
|
2073
|
+
}
|
|
2074
|
+
intializeMenuItems();
|
|
1636
2075
|
function isItemInNestedSubmenu(item) {
|
|
1637
2076
|
let parent = item.parentElement;
|
|
1638
2077
|
while (parent && parent !== menuDiv) {
|
|
@@ -1745,13 +2184,6 @@ function makeMenuAccessible({ menuId, menuItemsClass, triggerId, callback }) {
|
|
|
1745
2184
|
}
|
|
1746
2185
|
}
|
|
1747
2186
|
}
|
|
1748
|
-
function intializeMenuItems() {
|
|
1749
|
-
const items = getItems();
|
|
1750
|
-
items.forEach((item) => {
|
|
1751
|
-
item.setAttribute("role", "menuitem");
|
|
1752
|
-
});
|
|
1753
|
-
}
|
|
1754
|
-
intializeMenuItems();
|
|
1755
2187
|
function handleTriggerClick() {
|
|
1756
2188
|
const isOpen = triggerButton.getAttribute("aria-expanded") === "true";
|
|
1757
2189
|
if (isOpen) {
|
|
@@ -2527,7 +2959,7 @@ function makeTabsAccessible({ tabListId, tabsClass, tabPanelsClass, orientation
|
|
|
2527
2959
|
// src/utils/test/src/test.ts
|
|
2528
2960
|
var import_jest_axe = require("jest-axe");
|
|
2529
2961
|
|
|
2530
|
-
// src/utils/test/
|
|
2962
|
+
// src/utils/test/src/contractTestRunner.ts
|
|
2531
2963
|
init_contract();
|
|
2532
2964
|
var import_promises = __toESM(require("fs/promises"), 1);
|
|
2533
2965
|
init_ContractReporter();
|
|
@@ -2547,6 +2979,7 @@ async function runContractTests(componentName, component) {
|
|
|
2547
2979
|
const failures = [];
|
|
2548
2980
|
const passes = [];
|
|
2549
2981
|
const skipped = [];
|
|
2982
|
+
const failuresBeforeStatic = failures.length;
|
|
2550
2983
|
for (const test of componentContract.static[0].assertions) {
|
|
2551
2984
|
if (test.target !== "relative") {
|
|
2552
2985
|
const selector = componentContract.selectors[test.target];
|
|
@@ -2585,8 +3018,9 @@ async function runContractTests(componentName, component) {
|
|
|
2585
3018
|
skipped.push(dynamicTest.description);
|
|
2586
3019
|
reporter.reportTest(dynamicTest, "skip");
|
|
2587
3020
|
}
|
|
2588
|
-
const
|
|
2589
|
-
const staticFailed =
|
|
3021
|
+
const staticTotal = componentContract.static[0].assertions.length;
|
|
3022
|
+
const staticFailed = failures.length - failuresBeforeStatic;
|
|
3023
|
+
const staticPassed = Math.max(0, staticTotal - staticFailed);
|
|
2590
3024
|
reporter.reportStatic(staticPassed, staticFailed);
|
|
2591
3025
|
reporter.summary(failures);
|
|
2592
3026
|
return { passes, failures, skipped };
|
|
@@ -2692,6 +3126,11 @@ ${violationDetails}
|
|
|
2692
3126
|
return result;
|
|
2693
3127
|
}
|
|
2694
3128
|
var runTest = async () => {
|
|
3129
|
+
return {
|
|
3130
|
+
passes: [],
|
|
3131
|
+
failures: [],
|
|
3132
|
+
skipped: []
|
|
3133
|
+
};
|
|
2695
3134
|
};
|
|
2696
3135
|
if (typeof window === "undefined") {
|
|
2697
3136
|
runTest = async () => {
|
|
@@ -2699,36 +3138,36 @@ if (typeof window === "undefined") {
|
|
|
2699
3138
|
`);
|
|
2700
3139
|
const { exec } = await import("child_process");
|
|
2701
3140
|
const chalk2 = (await import("chalk")).default;
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
if (stdout) {
|
|
3141
|
+
return new Promise((resolve, reject) => {
|
|
3142
|
+
exec(
|
|
3143
|
+
`npx vitest --run --reporter verbose`,
|
|
3144
|
+
async (error, stdout, stderr) => {
|
|
2707
3145
|
console.log(stdout);
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
3146
|
+
if (stderr) console.error(stderr);
|
|
3147
|
+
const testsPassed = !error || error.code === 0;
|
|
3148
|
+
if (testsPassed) {
|
|
3149
|
+
try {
|
|
3150
|
+
const { displayBadgeInfo: displayBadgeInfo2, promptAddBadge: promptAddBadge2 } = await Promise.resolve().then(() => (init_badgeHelper(), badgeHelper_exports));
|
|
3151
|
+
displayBadgeInfo2("component");
|
|
3152
|
+
await promptAddBadge2("component", process.cwd());
|
|
3153
|
+
console.log(chalk2.dim("\n" + "\u2500".repeat(60)));
|
|
3154
|
+
console.log(chalk2.cyan("\u{1F499} Found aria-ease helpful?"));
|
|
3155
|
+
console.log(chalk2.white(" \u2022 Star us on GitHub: ") + chalk2.blue.underline("https://github.com/aria-ease/aria-ease"));
|
|
3156
|
+
console.log(chalk2.white(" \u2022 Share feedback: ") + chalk2.blue.underline("https://github.com/aria-ease/aria-ease/discussions"));
|
|
3157
|
+
console.log(chalk2.dim("\u2500".repeat(60) + "\n"));
|
|
3158
|
+
} catch (badgeError) {
|
|
3159
|
+
console.error("Warning: Could not display badge prompt:", badgeError);
|
|
3160
|
+
}
|
|
3161
|
+
resolve({ passes: [], failures: [], skipped: [] });
|
|
3162
|
+
process.exit(0);
|
|
3163
|
+
} else {
|
|
3164
|
+
const exitCode = error?.code || 1;
|
|
3165
|
+
reject(new Error(`Tests failed with code ${exitCode}`));
|
|
3166
|
+
process.exit(exitCode);
|
|
2724
3167
|
}
|
|
2725
|
-
process.exit(0);
|
|
2726
|
-
} else {
|
|
2727
|
-
const exitCode = error?.code || 1;
|
|
2728
|
-
process.exit(exitCode);
|
|
2729
3168
|
}
|
|
2730
|
-
|
|
2731
|
-
);
|
|
3169
|
+
);
|
|
3170
|
+
});
|
|
2732
3171
|
};
|
|
2733
3172
|
}
|
|
2734
3173
|
async function cleanupTests() {
|