aria-ease 6.4.7 → 6.4.10

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.
@@ -1,628 +0,0 @@
1
- import {
2
- ContractReporter,
3
- contract_default,
4
- createTestPage
5
- } from "./chunk-TQBS54MM.js";
6
- import {
7
- __export,
8
- __reExport
9
- } from "./chunk-I2KLQ2HA.js";
10
-
11
- // node_modules/@playwright/test/index.mjs
12
- var test_exports = {};
13
- __export(test_exports, {
14
- default: () => default2
15
- });
16
- __reExport(test_exports, test_star);
17
- import * as test_star from "playwright/test";
18
- import { default as default2 } from "playwright/test";
19
-
20
- // src/utils/test/contract/contractTestRunnerPlaywright.ts
21
- import { readFileSync } from "fs";
22
- async function runContractTestsPlaywright(componentName, url) {
23
- const reporter = new ContractReporter(true);
24
- const actionTimeoutMs = 400;
25
- const assertionTimeoutMs = 400;
26
- function isBrowserClosedError(error) {
27
- return error instanceof Error && error.message.includes("Target page, context or browser has been closed");
28
- }
29
- const contractTyped = contract_default;
30
- const contractPath = contractTyped[componentName]?.path;
31
- if (!contractPath) {
32
- throw new Error(`Contract path not found for component: ${componentName}`);
33
- }
34
- const resolvedPath = new URL(contractPath, import.meta.url).pathname;
35
- const contractData = readFileSync(resolvedPath, "utf-8");
36
- const componentContract = JSON.parse(contractData);
37
- const totalTests = componentContract.static[0].assertions.length + componentContract.dynamic.length;
38
- const apgUrl = componentContract.meta?.source?.apg;
39
- reporter.start(componentName, totalTests, apgUrl);
40
- const failures = [];
41
- const passes = [];
42
- const skipped = [];
43
- let page = null;
44
- try {
45
- page = await createTestPage();
46
- if (url) {
47
- try {
48
- await page.goto(url, {
49
- waitUntil: "domcontentloaded",
50
- timeout: 3e4
51
- });
52
- } catch (error) {
53
- throw new Error(
54
- `Failed to navigate to ${url}. Ensure dev server is running and accessible. Original error: ${error instanceof Error ? error.message : String(error)}`
55
- );
56
- }
57
- await page.addStyleTag({ content: `* { transition: none !important; animation: none !important; }` });
58
- }
59
- const mainSelector = componentContract.selectors.trigger || componentContract.selectors.input || componentContract.selectors.container || componentContract.selectors.tablist || componentContract.selectors.tab;
60
- if (!mainSelector) {
61
- throw new Error(`CRITICAL: No main selector (trigger, input, container, tablist, or tab) found in contract for ${componentName}`);
62
- }
63
- try {
64
- await page.locator(mainSelector).first().waitFor({ state: "attached", timeout: 3e4 });
65
- } catch (error) {
66
- throw new Error(
67
- `CRITICAL: Component element '${mainSelector}' not found on page within 30s. This usually means the component didn't render or the contract selector is incorrect. Original error: ${error instanceof Error ? error.message : String(error)}`
68
- );
69
- }
70
- if (componentName === "menu" && componentContract.selectors.trigger) {
71
- await page.locator(componentContract.selectors.trigger).first().waitFor({
72
- state: "visible",
73
- timeout: 5e3
74
- }).catch(() => {
75
- console.warn("Menu trigger not visible, continuing with tests...");
76
- });
77
- }
78
- async function resolveRelativeTarget(selector, relative) {
79
- if (!page) {
80
- throw new Error("Page is not initialized");
81
- }
82
- const items = await page.locator(selector).all();
83
- switch (relative) {
84
- case "first":
85
- return items[0];
86
- case "second":
87
- return items[1];
88
- case "last":
89
- return items[items.length - 1];
90
- case "next": {
91
- const currentIndex = await page.evaluate(([sel]) => {
92
- const items2 = Array.from(document.querySelectorAll(sel));
93
- return items2.indexOf(document.activeElement);
94
- }, [selector]);
95
- const nextIndex = (currentIndex + 1) % items.length;
96
- return items[nextIndex];
97
- }
98
- case "previous": {
99
- const currentIndex = await page.evaluate(([sel]) => {
100
- const items2 = Array.from(document.querySelectorAll(sel));
101
- return items2.indexOf(document.activeElement);
102
- }, [selector]);
103
- const prevIndex = (currentIndex - 1 + items.length) % items.length;
104
- return items[prevIndex];
105
- }
106
- default:
107
- return null;
108
- }
109
- }
110
- for (const test of componentContract.static[0]?.assertions || []) {
111
- if (test.target === "relative") continue;
112
- const targetSelector = componentContract.selectors[test.target];
113
- if (!targetSelector) {
114
- failures.push(`Selector for target ${test.target} not found.`);
115
- continue;
116
- }
117
- const target = page.locator(targetSelector).first();
118
- const exists = await target.count() > 0;
119
- if (!exists) {
120
- failures.push(`Target ${test.target} not found.`);
121
- continue;
122
- }
123
- const isRedundantCheck = (selector, attrName, expectedVal) => {
124
- const attrPattern = new RegExp(`\\[${attrName}(?:=["']?([^\\]"']+)["']?)?\\]`);
125
- const match = selector.match(attrPattern);
126
- if (!match) return false;
127
- if (!expectedVal) return true;
128
- const selectorValue = match[1];
129
- if (selectorValue) {
130
- const expectedValues = expectedVal.split(" | ");
131
- return expectedValues.includes(selectorValue);
132
- }
133
- return false;
134
- };
135
- if (!test.expectedValue) {
136
- const attributes = test.attribute.split(" | ");
137
- let hasAny = false;
138
- let allRedundant = true;
139
- for (const attr of attributes) {
140
- const attrTrimmed = attr.trim();
141
- if (isRedundantCheck(targetSelector, attrTrimmed)) {
142
- passes.push(`${attrTrimmed} on ${test.target} verified by selector (already present in: ${targetSelector}).`);
143
- hasAny = true;
144
- continue;
145
- }
146
- allRedundant = false;
147
- const value = await target.getAttribute(attrTrimmed);
148
- if (value !== null) {
149
- hasAny = true;
150
- break;
151
- }
152
- }
153
- if (!hasAny && !allRedundant) {
154
- failures.push(test.failureMessage + ` None of the attributes "${test.attribute}" are present.`);
155
- } else if (!allRedundant && hasAny) {
156
- passes.push(`At least one of the attributes "${test.attribute}" exists on the element.`);
157
- }
158
- } else {
159
- if (isRedundantCheck(targetSelector, test.attribute, test.expectedValue)) {
160
- passes.push(`${test.attribute}="${test.expectedValue}" on ${test.target} verified by selector (already present in: ${targetSelector}).`);
161
- } else {
162
- const attributeValue = await target.getAttribute(test.attribute);
163
- const expectedValues = test.expectedValue.split(" | ");
164
- if (!attributeValue || !expectedValues.includes(attributeValue)) {
165
- failures.push(test.failureMessage + ` Attribute value does not match expected value. Expected: ${test.expectedValue}, Found: ${attributeValue}`);
166
- } else {
167
- passes.push(`Attribute value matches expected value. Expected: ${test.expectedValue}, Found: ${attributeValue}`);
168
- }
169
- }
170
- }
171
- }
172
- for (const dynamicTest of componentContract.dynamic || []) {
173
- if (!page || page.isClosed()) {
174
- console.warn(`
175
- \u26A0\uFE0F Browser closed - skipping remaining ${componentContract.dynamic.length - componentContract.dynamic.indexOf(dynamicTest)} tests
176
- `);
177
- failures.push(`CRITICAL: Browser/page closed before completing all tests. ${componentContract.dynamic.length - componentContract.dynamic.indexOf(dynamicTest)} tests skipped.`);
178
- break;
179
- }
180
- const { action, assertions } = dynamicTest;
181
- const failuresBeforeTest = failures.length;
182
- if (componentContract.selectors.popup) {
183
- const popupSelector = componentContract.selectors.popup;
184
- if (!popupSelector) continue;
185
- const popupElement = page.locator(popupSelector).first();
186
- const isPopupVisible = await popupElement.isVisible().catch(() => false);
187
- if (isPopupVisible) {
188
- let menuClosed = false;
189
- let closeSelector = componentContract.selectors.input;
190
- if (!closeSelector && componentContract.selectors.focusable) {
191
- closeSelector = componentContract.selectors.focusable;
192
- } else if (!closeSelector) {
193
- closeSelector = componentContract.selectors.trigger;
194
- }
195
- if (closeSelector) {
196
- const closeElement = page.locator(closeSelector).first();
197
- await closeElement.focus();
198
- await page.keyboard.press("Escape");
199
- menuClosed = await (0, test_exports.expect)(popupElement).toBeHidden({ timeout: assertionTimeoutMs }).then(() => true).catch(() => false);
200
- }
201
- if (!menuClosed && componentContract.selectors.trigger) {
202
- const triggerElement = page.locator(componentContract.selectors.trigger).first();
203
- await triggerElement.click({ timeout: actionTimeoutMs });
204
- menuClosed = await (0, test_exports.expect)(popupElement).toBeHidden({ timeout: assertionTimeoutMs }).then(() => true).catch(() => false);
205
- }
206
- if (!menuClosed) {
207
- await page.mouse.click(10, 10);
208
- menuClosed = await (0, test_exports.expect)(popupElement).toBeHidden({ timeout: assertionTimeoutMs }).then(() => true).catch(() => false);
209
- }
210
- if (!menuClosed) {
211
- throw new Error(
212
- `\u274C FATAL: Cannot close menu between tests. Menu remains visible after trying:
213
- 1. Escape key
214
- 2. Clicking trigger
215
- 3. Clicking outside
216
- This indicates a problem with the menu component's close functionality.`
217
- );
218
- }
219
- if (componentContract.selectors.input) {
220
- await page.locator(componentContract.selectors.input).first().clear();
221
- }
222
- if (componentName === "menu" && componentContract.selectors.trigger) {
223
- const triggerElement = page.locator(componentContract.selectors.trigger).first();
224
- await triggerElement.focus();
225
- }
226
- }
227
- }
228
- if (componentContract.selectors.panel && componentContract.selectors.trigger && !componentContract.selectors.popup) {
229
- const triggerSelector = componentContract.selectors.trigger;
230
- const panelSelector = componentContract.selectors.panel;
231
- if (triggerSelector && panelSelector) {
232
- const allTriggers = await page.locator(triggerSelector).all();
233
- for (const trigger of allTriggers) {
234
- const isExpanded = await trigger.getAttribute("aria-expanded") === "true";
235
- const triggerPanel = await trigger.getAttribute("aria-controls");
236
- if (isExpanded && triggerPanel) {
237
- await trigger.click({ timeout: actionTimeoutMs });
238
- const panel = page.locator(`#${triggerPanel}`);
239
- await (0, test_exports.expect)(panel).toBeHidden({ timeout: assertionTimeoutMs }).catch(() => {
240
- });
241
- }
242
- }
243
- }
244
- }
245
- let shouldSkipTest = false;
246
- for (const act of action) {
247
- if (act.type === "keypress" && (act.target === "submenuTrigger" || act.target === "submenu")) {
248
- const submenuSelector = componentContract.selectors[act.target];
249
- if (submenuSelector) {
250
- const submenuCount = await page.locator(submenuSelector).count();
251
- if (submenuCount === 0) {
252
- reporter.reportTest(dynamicTest, "skip", `Skipping test - ${act.target} element not found (optional submenu test)`);
253
- shouldSkipTest = true;
254
- break;
255
- }
256
- }
257
- }
258
- }
259
- if (!shouldSkipTest) {
260
- for (const assertion of assertions) {
261
- if (assertion.target === "submenu" || assertion.target === "submenuTrigger") {
262
- const submenuSelector = componentContract.selectors[assertion.target];
263
- if (submenuSelector) {
264
- const submenuCount = await page.locator(submenuSelector).count();
265
- if (submenuCount === 0) {
266
- reporter.reportTest(dynamicTest, "skip", `Skipping test - ${assertion.target} element not found (optional submenu test)`);
267
- shouldSkipTest = true;
268
- break;
269
- }
270
- }
271
- }
272
- }
273
- }
274
- if (shouldSkipTest) {
275
- continue;
276
- }
277
- if (componentContract.selectors.panel && componentContract.selectors.tab && componentContract.selectors.tablist) {
278
- if (dynamicTest.isVertical !== void 0 && componentContract.selectors.tablist) {
279
- const tablistSelector = componentContract.selectors.tablist;
280
- const tablist = page.locator(tablistSelector).first();
281
- const orientation = await tablist.getAttribute("aria-orientation");
282
- const isVertical = orientation === "vertical";
283
- if (dynamicTest.isVertical !== isVertical) {
284
- const skipReason = dynamicTest.isVertical ? `Skipping vertical tabs test - component has horizontal orientation` : `Skipping horizontal tabs test - component has vertical orientation`;
285
- reporter.reportTest(dynamicTest, "skip", skipReason);
286
- continue;
287
- }
288
- }
289
- }
290
- for (const act of action) {
291
- if (!page || page.isClosed()) {
292
- failures.push(`CRITICAL: Browser/page closed during test execution. Remaining actions skipped.`);
293
- break;
294
- }
295
- if (act.type === "focus") {
296
- try {
297
- const focusSelector = componentContract.selectors[act.target];
298
- if (!focusSelector) {
299
- failures.push(`Selector for focus target ${act.target} not found.`);
300
- continue;
301
- }
302
- await page.locator(focusSelector).first().focus({ timeout: actionTimeoutMs });
303
- } catch (error) {
304
- if (isBrowserClosedError(error)) {
305
- failures.push(`CRITICAL: Browser/page closed during test execution. Remaining actions skipped.`);
306
- break;
307
- }
308
- failures.push(`Failed to focus ${act.target}: ${error instanceof Error ? error.message : String(error)}`);
309
- continue;
310
- }
311
- }
312
- if (act.type === "type" && act.value) {
313
- try {
314
- const typeSelector = componentContract.selectors[act.target];
315
- if (!typeSelector) {
316
- failures.push(`Selector for type target ${act.target} not found.`);
317
- continue;
318
- }
319
- await page.locator(typeSelector).first().fill(act.value, { timeout: actionTimeoutMs });
320
- } catch (error) {
321
- if (isBrowserClosedError(error)) {
322
- failures.push(`CRITICAL: Browser/page closed during test execution. Remaining actions skipped.`);
323
- break;
324
- }
325
- failures.push(`Failed to type into ${act.target}: ${error instanceof Error ? error.message : String(error)}`);
326
- continue;
327
- }
328
- }
329
- if (act.type === "click") {
330
- try {
331
- if (act.target === "document") {
332
- await page.mouse.click(10, 10);
333
- } else if (act.target === "relative" && act.relativeTarget) {
334
- const relativeSelector = componentContract.selectors.relative;
335
- if (!relativeSelector) {
336
- failures.push(`Relative selector not defined for click action.`);
337
- continue;
338
- }
339
- const relativeElement = await resolveRelativeTarget(relativeSelector, act.relativeTarget);
340
- if (!relativeElement) {
341
- failures.push(`Could not resolve relative target ${act.relativeTarget} for click.`);
342
- continue;
343
- }
344
- await relativeElement.click({ timeout: actionTimeoutMs });
345
- } else {
346
- const actionSelector = componentContract.selectors[act.target];
347
- if (!actionSelector) {
348
- failures.push(`Selector for action target ${act.target} not found.`);
349
- continue;
350
- }
351
- await page.locator(actionSelector).first().click({ timeout: actionTimeoutMs });
352
- }
353
- } catch (error) {
354
- if (isBrowserClosedError(error)) {
355
- failures.push(`CRITICAL: Browser/page closed during test execution. Remaining actions skipped.`);
356
- break;
357
- }
358
- failures.push(`Failed to click ${act.target}: ${error instanceof Error ? error.message : String(error)}`);
359
- continue;
360
- }
361
- }
362
- if (act.type === "keypress" && act.key) {
363
- try {
364
- const keyMap = {
365
- "Space": "Space",
366
- "Enter": "Enter",
367
- "Escape": "Escape",
368
- "Arrow Up": "ArrowUp",
369
- "Arrow Down": "ArrowDown",
370
- "Arrow Left": "ArrowLeft",
371
- "Arrow Right": "ArrowRight",
372
- "Home": "Home",
373
- "End": "End",
374
- "Tab": "Tab"
375
- };
376
- let keyValue = keyMap[act.key] || act.key;
377
- if (keyValue === "Space") {
378
- keyValue = " ";
379
- } else if (keyValue.includes(" ")) {
380
- keyValue = keyValue.replace(/ /g, "");
381
- }
382
- if (act.target === "focusable" && ["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight", "Escape"].includes(keyValue)) {
383
- await page.keyboard.press(keyValue);
384
- } else {
385
- const keypressSelector = componentContract.selectors[act.target];
386
- if (!keypressSelector) {
387
- failures.push(`Selector for keypress target ${act.target} not found.`);
388
- continue;
389
- }
390
- const target = page.locator(keypressSelector).first();
391
- const elementCount = await target.count();
392
- if (elementCount === 0) {
393
- reporter.reportTest(dynamicTest, "skip", `Skipping test - ${act.target} element not found (optional submenu test)`);
394
- break;
395
- }
396
- await target.press(keyValue, { timeout: actionTimeoutMs });
397
- }
398
- } catch (error) {
399
- if (isBrowserClosedError(error)) {
400
- failures.push(`CRITICAL: Browser/page closed during test execution. Remaining actions skipped.`);
401
- break;
402
- }
403
- failures.push(`Failed to press ${act.key} on ${act.target}: ${error instanceof Error ? error.message : String(error)}`);
404
- continue;
405
- }
406
- }
407
- if (act.type === "hover") {
408
- try {
409
- if (act.target === "relative" && act.relativeTarget) {
410
- const relativeSelector = componentContract.selectors.relative;
411
- if (!relativeSelector) {
412
- failures.push(`Relative selector not defined for hover action.`);
413
- continue;
414
- }
415
- const relativeElement = await resolveRelativeTarget(relativeSelector, act.relativeTarget);
416
- if (!relativeElement) {
417
- failures.push(`Could not resolve relative target ${act.relativeTarget} for hover.`);
418
- continue;
419
- }
420
- await relativeElement.hover({ timeout: actionTimeoutMs });
421
- } else {
422
- const hoverSelector = componentContract.selectors[act.target];
423
- if (!hoverSelector) {
424
- failures.push(`Selector for hover target ${act.target} not found.`);
425
- continue;
426
- }
427
- await page.locator(hoverSelector).first().hover({ timeout: actionTimeoutMs });
428
- }
429
- } catch (error) {
430
- if (isBrowserClosedError(error)) {
431
- failures.push(`CRITICAL: Browser/page closed during test execution. Remaining actions skipped.`);
432
- break;
433
- }
434
- failures.push(`Failed to hover ${act.target}: ${error instanceof Error ? error.message : String(error)}`);
435
- continue;
436
- }
437
- }
438
- }
439
- for (const assertion of assertions) {
440
- if (!page || page.isClosed()) {
441
- failures.push(`CRITICAL: Browser/page closed before completing all tests. Increase test timeout or reduce test complexity.`);
442
- break;
443
- }
444
- let target;
445
- try {
446
- if (assertion.target === "relative") {
447
- const relativeSelector = componentContract.selectors.relative;
448
- if (!relativeSelector) {
449
- failures.push("Relative selector is not defined in the contract.");
450
- continue;
451
- }
452
- const relativeTargetValue = assertion.relativeTarget || assertion.expectedValue;
453
- if (!relativeTargetValue) {
454
- failures.push("Relative target or expected value is not defined.");
455
- continue;
456
- }
457
- target = await resolveRelativeTarget(relativeSelector, relativeTargetValue);
458
- } else {
459
- const assertionSelector = componentContract.selectors[assertion.target];
460
- if (!assertionSelector) {
461
- failures.push(`Selector for assertion target ${assertion.target} not found.`);
462
- continue;
463
- }
464
- target = page.locator(assertionSelector).first();
465
- }
466
- if (!target) {
467
- failures.push(`Target ${assertion.target} not found.`);
468
- continue;
469
- }
470
- } catch (error) {
471
- failures.push(`Failed to resolve target ${assertion.target}: ${error instanceof Error ? error.message : String(error)}`);
472
- continue;
473
- }
474
- if (assertion.assertion === "toBeVisible") {
475
- try {
476
- await (0, test_exports.expect)(target).toBeVisible({ timeout: assertionTimeoutMs });
477
- passes.push(`${assertion.target} is visible as expected. Test: "${dynamicTest.description}".`);
478
- } catch {
479
- const debugState = await page.evaluate((sel) => {
480
- const el = sel ? document.querySelector(sel) : null;
481
- if (!el) return "element not found";
482
- const styles = window.getComputedStyle(el);
483
- return `display:${styles.display}, visibility:${styles.visibility}, opacity:${styles.opacity}`;
484
- }, componentContract.selectors[assertion.target] || "");
485
- failures.push(`${assertion.failureMessage} (actual: ${debugState})`);
486
- }
487
- }
488
- if (assertion.assertion === "notToBeVisible") {
489
- try {
490
- await (0, test_exports.expect)(target).toBeHidden({ timeout: assertionTimeoutMs });
491
- passes.push(`${assertion.target} is not visible as expected. Test: "${dynamicTest.description}".`);
492
- } catch {
493
- const debugState = await page.evaluate((sel) => {
494
- const el = sel ? document.querySelector(sel) : null;
495
- if (!el) return "element not found";
496
- const styles = window.getComputedStyle(el);
497
- return `display:${styles.display}, visibility:${styles.visibility}, opacity:${styles.opacity}`;
498
- }, componentContract.selectors[assertion.target] || "");
499
- failures.push(assertion.failureMessage + ` ${assertion.target} is still visible (actual: ${debugState}).`);
500
- }
501
- }
502
- if (assertion.assertion === "toHaveAttribute" && assertion.attribute && assertion.expectedValue) {
503
- try {
504
- if (assertion.expectedValue === "!empty") {
505
- const attributeValue = await target.getAttribute(assertion.attribute);
506
- if (attributeValue && attributeValue.trim() !== "") {
507
- passes.push(`${assertion.target} has non-empty "${assertion.attribute}". Test: "${dynamicTest.description}".`);
508
- } else {
509
- failures.push(assertion.failureMessage + ` ${assertion.target} "${assertion.attribute}" should not be empty, found "${attributeValue}".`);
510
- }
511
- } else {
512
- await (0, test_exports.expect)(target).toHaveAttribute(assertion.attribute, assertion.expectedValue, { timeout: assertionTimeoutMs });
513
- passes.push(`${assertion.target} has expected "${assertion.attribute}". Test: "${dynamicTest.description}".`);
514
- }
515
- } catch {
516
- const attributeValue = await target.getAttribute(assertion.attribute);
517
- failures.push(assertion.failureMessage + ` ${assertion.target} "${assertion.attribute}" should be "${assertion.expectedValue}", found "${attributeValue}".`);
518
- }
519
- }
520
- if (assertion.assertion === "toHaveValue") {
521
- const inputValue = await target.inputValue().catch(() => "");
522
- if (assertion.expectedValue === "!empty") {
523
- if (inputValue && inputValue.trim() !== "") {
524
- passes.push(`${assertion.target} has non-empty value. Test: "${dynamicTest.description}".`);
525
- } else {
526
- failures.push(assertion.failureMessage + ` ${assertion.target} value should not be empty, found "${inputValue}".`);
527
- }
528
- } else if (assertion.expectedValue === "") {
529
- if (inputValue === "") {
530
- passes.push(`${assertion.target} has empty value. Test: "${dynamicTest.description}".`);
531
- } else {
532
- failures.push(assertion.failureMessage + ` ${assertion.target} value should be empty, found "${inputValue}".`);
533
- }
534
- } else if (inputValue === assertion.expectedValue) {
535
- passes.push(`${assertion.target} has expected value. Test: "${dynamicTest.description}".`);
536
- } else {
537
- failures.push(assertion.failureMessage + ` ${assertion.target} value should be "${assertion.expectedValue}", found "${inputValue}".`);
538
- }
539
- }
540
- if (assertion.assertion === "toHaveFocus") {
541
- try {
542
- await (0, test_exports.expect)(target).toBeFocused({ timeout: assertionTimeoutMs });
543
- passes.push(`${assertion.target} has focus as expected. Test: "${dynamicTest.description}".`);
544
- } catch {
545
- const actualFocus = await page.evaluate(() => {
546
- const focused = document.activeElement;
547
- return focused ? `${focused.tagName}#${focused.id || "no-id"}.${focused.className || "no-class"}` : "no element focused";
548
- });
549
- failures.push(`${assertion.failureMessage} (actual focus: ${actualFocus})`);
550
- }
551
- }
552
- if (assertion.assertion === "toHaveRole" && assertion.expectedValue) {
553
- const roleValue = await target.getAttribute("role");
554
- if (roleValue === assertion.expectedValue) {
555
- passes.push(`${assertion.target} has role "${assertion.expectedValue}". Test: "${dynamicTest.description}".`);
556
- } else {
557
- failures.push(assertion.failureMessage + ` Expected role "${assertion.expectedValue}", found "${roleValue}".`);
558
- }
559
- }
560
- }
561
- const failuresAfterTest = failures.length;
562
- const testPassed = failuresAfterTest === failuresBeforeTest;
563
- const failureMessage = testPassed ? void 0 : failures[failures.length - 1];
564
- if (dynamicTest.isOptional === true && !testPassed) {
565
- failures.pop();
566
- reporter.reportTest(dynamicTest, "optional-fail", failureMessage);
567
- } else {
568
- reporter.reportTest(dynamicTest, testPassed ? "pass" : "fail", failureMessage);
569
- }
570
- }
571
- const staticPassed = componentContract.static[0].assertions.length;
572
- const staticFailed = 0;
573
- reporter.reportStatic(staticPassed, staticFailed);
574
- reporter.summary(failures);
575
- } catch (error) {
576
- if (error instanceof Error) {
577
- if (error.message.includes("Executable doesn't exist") || error.message.includes("browserType.launch")) {
578
- console.error("\n\u274C CRITICAL: Playwright browsers not found!\n");
579
- console.log("\u{1F4E6} Run: npx playwright install chromium\n");
580
- failures.push("CRITICAL: Playwright browser not installed. Run: npx playwright install chromium");
581
- } else if (error.message.includes("net::ERR_CONNECTION_REFUSED") || error.message.includes("NS_ERROR_CONNECTION_REFUSED")) {
582
- console.error("\n\u274C CRITICAL: Cannot connect to dev server!\n");
583
- console.log(` Make sure your dev server is running at ${url}
584
- `);
585
- failures.push(`CRITICAL: Dev server not running at ${url}`);
586
- } else if (error.message.includes("Timeout") && error.message.includes("waitFor")) {
587
- console.error("\n\u274C CRITICAL: Component not found on page!\n");
588
- console.log(` The component selector could not be found within 30 seconds.
589
- `);
590
- console.log(` This usually means:
591
- `);
592
- console.log(` - The component didn't render
593
- `);
594
- console.log(` - The URL is incorrect
595
- `);
596
- console.log(` - The component selector in the contract is wrong
597
- `);
598
- failures.push(`CRITICAL: Component element not found on page - ${error.message}`);
599
- } else if (error.message.includes("Target page, context or browser has been closed")) {
600
- console.error("\n\u274C CRITICAL: Browser/page was closed unexpectedly!\n");
601
- console.log(` This usually means:
602
- `);
603
- console.log(` - The test timeout was too short
604
- `);
605
- console.log(` - The browser crashed
606
- `);
607
- console.log(` - An external process killed the browser
608
- `);
609
- failures.push(`CRITICAL: Browser/page closed unexpectedly - ${error.message}`);
610
- } else if (error.message.includes("FATAL")) {
611
- console.error(`
612
- ${error.message}
613
- `);
614
- failures.push(error.message);
615
- } else {
616
- console.error("\n\u274C UNEXPECTED ERROR:", error.message);
617
- console.error("Stack:", error.stack);
618
- failures.push(`UNEXPECTED ERROR: ${error.message}`);
619
- }
620
- }
621
- } finally {
622
- if (page) await page.close();
623
- }
624
- return { passes, failures, skipped };
625
- }
626
- export {
627
- runContractTestsPlaywright
628
- };