aria-ease 5.0.3 → 6.0.1
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 +21 -11
- package/{dist/src/utils/test/chunk-TUWQNVQJ.js → bin/chunk-7RMRFSJL.js} +51 -2
- package/bin/cli.cjs +135 -58
- package/bin/cli.js +1 -1
- package/bin/{contractTestRunnerPlaywright-O22AQ4RK.js → contractTestRunnerPlaywright-HL2VPEEV.js} +42 -24
- package/bin/{test-JNQFZBJA.js → test-HH2EW2NM.js} +44 -37
- package/dist/{chunk-KJ33RDSC.js → chunk-PDZQOXUN.js} +48 -2
- package/dist/{contractTestRunnerPlaywright-7ZOM7ZMG.js → contractTestRunnerPlaywright-EXEBWWPC.js} +42 -24
- package/dist/index.cjs +136 -58
- package/dist/index.d.cts +8 -3
- package/dist/index.d.ts +8 -3
- package/dist/index.js +44 -37
- package/{bin/chunk-TUWQNVQJ.js → dist/src/utils/test/chunk-7RMRFSJL.js} +45 -5
- package/dist/src/utils/test/{contractTestRunnerPlaywright-P5QZAIDR.js → contractTestRunnerPlaywright-LJHY3AB4.js} +40 -21
- package/dist/src/utils/test/contracts/ComboboxContract.json +2 -1
- package/dist/src/utils/test/contracts/MenuContract.json +2 -1
- package/dist/src/utils/test/index.cjs +130 -56
- package/dist/src/utils/test/index.d.cts +8 -3
- package/dist/src/utils/test/index.d.ts +8 -3
- package/dist/src/utils/test/index.js +43 -38
- package/package.json +4 -4
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ContractReporter,
|
|
3
|
+
closeSharedBrowser,
|
|
3
4
|
contract_default
|
|
4
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-7RMRFSJL.js";
|
|
5
6
|
import "./chunk-I2KLQ2HA.js";
|
|
6
7
|
|
|
7
8
|
// src/utils/test/src/test.ts
|
|
@@ -74,56 +75,58 @@ async function testUiComponent(componentName, component, url) {
|
|
|
74
75
|
if (!componentName || typeof componentName !== "string") {
|
|
75
76
|
throw new Error("\u274C testUiComponent requires a valid componentName (string)");
|
|
76
77
|
}
|
|
77
|
-
if (!component || !(component instanceof HTMLElement)) {
|
|
78
|
-
throw new Error("\u274C testUiComponent requires a valid component (HTMLElement)");
|
|
78
|
+
if (!url && (!component || !(component instanceof HTMLElement))) {
|
|
79
|
+
throw new Error("\u274C testUiComponent requires either a valid component (HTMLElement) or a URL");
|
|
79
80
|
}
|
|
80
81
|
if (url && typeof url !== "string") {
|
|
81
82
|
throw new Error("\u274C testUiComponent url parameter must be a string");
|
|
82
83
|
}
|
|
83
84
|
let results;
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
85
|
+
if (component) {
|
|
86
|
+
try {
|
|
87
|
+
results = await axe(component);
|
|
88
|
+
} catch (error) {
|
|
89
|
+
throw new Error(
|
|
90
|
+
`\u274C Axe accessibility scan failed
|
|
89
91
|
Error: ${error instanceof Error ? error.message : String(error)}`
|
|
90
|
-
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
} else {
|
|
95
|
+
results = { violations: [] };
|
|
91
96
|
}
|
|
92
|
-
async function checkDevServer(
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
try {
|
|
101
|
-
const response = await fetch(serverUrl, {
|
|
102
|
-
method: "HEAD",
|
|
103
|
-
signal: AbortSignal.timeout(1e3)
|
|
104
|
-
});
|
|
105
|
-
if (response.ok || response.status === 304) {
|
|
106
|
-
return serverUrl;
|
|
107
|
-
}
|
|
108
|
-
} catch {
|
|
109
|
-
return null;
|
|
97
|
+
async function checkDevServer(url2) {
|
|
98
|
+
try {
|
|
99
|
+
const response = await fetch(url2, {
|
|
100
|
+
method: "HEAD",
|
|
101
|
+
signal: AbortSignal.timeout(1e3)
|
|
102
|
+
});
|
|
103
|
+
if (response.ok || response.status === 304) {
|
|
104
|
+
return url2;
|
|
110
105
|
}
|
|
106
|
+
} catch {
|
|
107
|
+
return null;
|
|
111
108
|
}
|
|
112
109
|
return null;
|
|
113
110
|
}
|
|
114
111
|
let contract;
|
|
115
112
|
try {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
113
|
+
if (url) {
|
|
114
|
+
const devServerUrl = await checkDevServer(url);
|
|
115
|
+
if (devServerUrl) {
|
|
116
|
+
console.log(`\u{1F3AD} Running Playwright tests on ${devServerUrl}`);
|
|
117
|
+
const { runContractTestsPlaywright } = await import("./contractTestRunnerPlaywright-HL2VPEEV.js");
|
|
118
|
+
contract = await runContractTestsPlaywright(componentName, devServerUrl);
|
|
119
|
+
} else {
|
|
120
|
+
throw new Error(
|
|
121
|
+
`\u274C Dev server not running at ${url}
|
|
122
|
+
Please start your dev server and try again.`
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
} else if (component) {
|
|
126
|
+
console.log(`\u{1F3AD} Running component contract tests in JSDOM mode`);
|
|
126
127
|
contract = await runContractTests(componentName, component);
|
|
128
|
+
} else {
|
|
129
|
+
throw new Error("\u274C Either component or URL must be provided");
|
|
127
130
|
}
|
|
128
131
|
} catch (error) {
|
|
129
132
|
if (error instanceof Error) {
|
|
@@ -189,7 +192,11 @@ if (typeof window === "undefined") {
|
|
|
189
192
|
);
|
|
190
193
|
};
|
|
191
194
|
}
|
|
195
|
+
async function cleanupTests() {
|
|
196
|
+
await closeSharedBrowser();
|
|
197
|
+
}
|
|
192
198
|
export {
|
|
199
|
+
cleanupTests,
|
|
193
200
|
runTest,
|
|
194
201
|
testUiComponent
|
|
195
202
|
};
|
|
@@ -204,7 +204,7 @@ ${"\u2550".repeat(60)}`);
|
|
|
204
204
|
} else if (totalFailures === 0) {
|
|
205
205
|
this.log(`\u2705 ${totalPasses}/${totalRun} required tests passed`);
|
|
206
206
|
if (this.skipped > 0) {
|
|
207
|
-
this.log(`\u25CB ${this.skipped} tests skipped
|
|
207
|
+
this.log(`\u25CB ${this.skipped} tests skipped`);
|
|
208
208
|
}
|
|
209
209
|
if (this.optionalSuggestions > 0) {
|
|
210
210
|
this.log(`\u{1F4A1} ${this.optionalSuggestions} optional enhancement${this.optionalSuggestions > 1 ? "s" : ""} suggested`);
|
|
@@ -253,9 +253,55 @@ ${"\u2550".repeat(60)}`);
|
|
|
253
253
|
}
|
|
254
254
|
};
|
|
255
255
|
|
|
256
|
+
// src/utils/test/contract/playwrightTestHarness.ts
|
|
257
|
+
import { chromium } from "playwright";
|
|
258
|
+
var sharedBrowser = null;
|
|
259
|
+
var sharedContext = null;
|
|
260
|
+
async function getOrCreateBrowser() {
|
|
261
|
+
if (!sharedBrowser) {
|
|
262
|
+
sharedBrowser = await chromium.launch({
|
|
263
|
+
headless: true,
|
|
264
|
+
// Launch with clean browser profile - no extensions, no user data
|
|
265
|
+
args: [
|
|
266
|
+
"--disable-extensions",
|
|
267
|
+
"--disable-blink-features=AutomationControlled"
|
|
268
|
+
]
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
return sharedBrowser;
|
|
272
|
+
}
|
|
273
|
+
async function getOrCreateContext() {
|
|
274
|
+
if (!sharedContext) {
|
|
275
|
+
const browser = await getOrCreateBrowser();
|
|
276
|
+
sharedContext = await browser.newContext({
|
|
277
|
+
// Isolated context - no permissions, no geolocation, etc.
|
|
278
|
+
permissions: [],
|
|
279
|
+
// Ignore HTTPS errors for local dev servers
|
|
280
|
+
ignoreHTTPSErrors: true
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
return sharedContext;
|
|
284
|
+
}
|
|
285
|
+
async function createTestPage() {
|
|
286
|
+
const context = await getOrCreateContext();
|
|
287
|
+
return await context.newPage();
|
|
288
|
+
}
|
|
289
|
+
async function closeSharedBrowser() {
|
|
290
|
+
if (sharedContext) {
|
|
291
|
+
await sharedContext.close();
|
|
292
|
+
sharedContext = null;
|
|
293
|
+
}
|
|
294
|
+
if (sharedBrowser) {
|
|
295
|
+
await sharedBrowser.close();
|
|
296
|
+
sharedBrowser = null;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
256
300
|
export {
|
|
257
301
|
__export,
|
|
258
302
|
__reExport,
|
|
259
303
|
contract_default,
|
|
260
|
-
ContractReporter
|
|
304
|
+
ContractReporter,
|
|
305
|
+
createTestPage,
|
|
306
|
+
closeSharedBrowser
|
|
261
307
|
};
|
package/dist/{contractTestRunnerPlaywright-7ZOM7ZMG.js → contractTestRunnerPlaywright-EXEBWWPC.js}
RENAMED
|
@@ -2,11 +2,9 @@ import {
|
|
|
2
2
|
ContractReporter,
|
|
3
3
|
__export,
|
|
4
4
|
__reExport,
|
|
5
|
-
contract_default
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
// src/utils/test/contract/contractTestRunnerPlaywright.ts
|
|
9
|
-
import { chromium } from "playwright";
|
|
5
|
+
contract_default,
|
|
6
|
+
createTestPage
|
|
7
|
+
} from "./chunk-PDZQOXUN.js";
|
|
10
8
|
|
|
11
9
|
// node_modules/@playwright/test/index.mjs
|
|
12
10
|
var test_exports = {};
|
|
@@ -35,30 +33,33 @@ async function runContractTestsPlaywright(componentName, url) {
|
|
|
35
33
|
const failures = [];
|
|
36
34
|
const passes = [];
|
|
37
35
|
const skipped = [];
|
|
38
|
-
let
|
|
36
|
+
let page = null;
|
|
39
37
|
try {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
38
|
+
page = await createTestPage();
|
|
39
|
+
if (url) {
|
|
40
|
+
await page.goto(url, {
|
|
41
|
+
waitUntil: "domcontentloaded",
|
|
42
|
+
timeout: 3e4
|
|
43
|
+
});
|
|
44
|
+
await page.addStyleTag({ content: `* { transition: none !important; animation: none !important; }` });
|
|
45
|
+
}
|
|
48
46
|
const mainSelector = componentContract.selectors.trigger || componentContract.selectors.input || componentContract.selectors.container;
|
|
49
47
|
if (!mainSelector) {
|
|
50
48
|
throw new Error(`No main selector (trigger, input, or container) found in contract for ${componentName}`);
|
|
51
49
|
}
|
|
52
|
-
await page.locator(mainSelector).first().waitFor({ state: "attached", timeout:
|
|
50
|
+
await page.locator(mainSelector).first().waitFor({ state: "attached", timeout: 3e4 });
|
|
53
51
|
if (componentName === "menu" && componentContract.selectors.trigger) {
|
|
54
52
|
await page.locator(componentContract.selectors.trigger).first().waitFor({
|
|
55
53
|
state: "visible",
|
|
56
|
-
timeout:
|
|
54
|
+
timeout: 5e3
|
|
57
55
|
}).catch(() => {
|
|
58
56
|
console.warn("Menu trigger not visible, continuing with tests...");
|
|
59
57
|
});
|
|
60
58
|
}
|
|
61
59
|
async function resolveRelativeTarget(selector, relative) {
|
|
60
|
+
if (!page) {
|
|
61
|
+
throw new Error("Page is not initialized");
|
|
62
|
+
}
|
|
62
63
|
const items = await page.locator(selector).all();
|
|
63
64
|
switch (relative) {
|
|
64
65
|
case "first":
|
|
@@ -128,8 +129,8 @@ async function runContractTestsPlaywright(componentName, url) {
|
|
|
128
129
|
for (const dynamicTest of componentContract.dynamic || []) {
|
|
129
130
|
const { action, assertions } = dynamicTest;
|
|
130
131
|
const failuresBeforeTest = failures.length;
|
|
131
|
-
if (componentContract.selectors.
|
|
132
|
-
const popupSelector = componentContract.selectors.
|
|
132
|
+
if (componentContract.selectors.popup) {
|
|
133
|
+
const popupSelector = componentContract.selectors.popup;
|
|
133
134
|
if (!popupSelector) continue;
|
|
134
135
|
const popupElement = page.locator(popupSelector).first();
|
|
135
136
|
const isPopupVisible = await popupElement.isVisible().catch(() => false);
|
|
@@ -145,16 +146,16 @@ async function runContractTestsPlaywright(componentName, url) {
|
|
|
145
146
|
const closeElement = page.locator(closeSelector).first();
|
|
146
147
|
await closeElement.focus();
|
|
147
148
|
await page.keyboard.press("Escape");
|
|
148
|
-
menuClosed = await (0, test_exports.expect)(popupElement).toBeHidden({ timeout:
|
|
149
|
+
menuClosed = await (0, test_exports.expect)(popupElement).toBeHidden({ timeout: 2e3 }).then(() => true).catch(() => false);
|
|
149
150
|
}
|
|
150
151
|
if (!menuClosed && componentContract.selectors.trigger) {
|
|
151
152
|
const triggerElement = page.locator(componentContract.selectors.trigger).first();
|
|
152
153
|
await triggerElement.click();
|
|
153
|
-
menuClosed = await (0, test_exports.expect)(popupElement).toBeHidden({ timeout:
|
|
154
|
+
menuClosed = await (0, test_exports.expect)(popupElement).toBeHidden({ timeout: 2e3 }).then(() => true).catch(() => false);
|
|
154
155
|
}
|
|
155
156
|
if (!menuClosed) {
|
|
156
157
|
await page.mouse.click(10, 10);
|
|
157
|
-
menuClosed = await (0, test_exports.expect)(popupElement).toBeHidden({ timeout:
|
|
158
|
+
menuClosed = await (0, test_exports.expect)(popupElement).toBeHidden({ timeout: 2e3 }).then(() => true).catch(() => false);
|
|
158
159
|
}
|
|
159
160
|
if (!menuClosed) {
|
|
160
161
|
throw new Error(
|
|
@@ -174,6 +175,23 @@ This indicates a problem with the menu component's close functionality.`
|
|
|
174
175
|
}
|
|
175
176
|
}
|
|
176
177
|
}
|
|
178
|
+
if (componentContract.selectors.panel && componentContract.selectors.trigger && !componentContract.selectors.popup) {
|
|
179
|
+
const triggerSelector = componentContract.selectors.trigger;
|
|
180
|
+
const panelSelector = componentContract.selectors.panel;
|
|
181
|
+
if (triggerSelector && panelSelector) {
|
|
182
|
+
const allTriggers = await page.locator(triggerSelector).all();
|
|
183
|
+
for (const trigger of allTriggers) {
|
|
184
|
+
const isExpanded = await trigger.getAttribute("aria-expanded") === "true";
|
|
185
|
+
const triggerPanel = await trigger.getAttribute("aria-controls");
|
|
186
|
+
if (isExpanded && triggerPanel) {
|
|
187
|
+
await trigger.click();
|
|
188
|
+
const panel = page.locator(`#${triggerPanel}`);
|
|
189
|
+
await (0, test_exports.expect)(panel).toBeHidden({ timeout: 1e3 }).catch(() => {
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
177
195
|
let shouldSkipTest = false;
|
|
178
196
|
for (const act of action) {
|
|
179
197
|
if (act.type === "keypress" && (act.target === "submenuTrigger" || act.target === "submenu")) {
|
|
@@ -334,7 +352,7 @@ This indicates a problem with the menu component's close functionality.`
|
|
|
334
352
|
}
|
|
335
353
|
if (assertion.assertion === "toBeVisible") {
|
|
336
354
|
try {
|
|
337
|
-
await (0, test_exports.expect)(target).toBeVisible({ timeout:
|
|
355
|
+
await (0, test_exports.expect)(target).toBeVisible({ timeout: 2e3 });
|
|
338
356
|
passes.push(`${assertion.target} is visible as expected. Test: "${dynamicTest.description}".`);
|
|
339
357
|
} catch {
|
|
340
358
|
const debugState = await page.evaluate((sel) => {
|
|
@@ -348,7 +366,7 @@ This indicates a problem with the menu component's close functionality.`
|
|
|
348
366
|
}
|
|
349
367
|
if (assertion.assertion === "notToBeVisible") {
|
|
350
368
|
try {
|
|
351
|
-
await (0, test_exports.expect)(target).toBeHidden({ timeout:
|
|
369
|
+
await (0, test_exports.expect)(target).toBeHidden({ timeout: 2e3 });
|
|
352
370
|
passes.push(`${assertion.target} is not visible as expected. Test: "${dynamicTest.description}".`);
|
|
353
371
|
} catch {
|
|
354
372
|
const debugState = await page.evaluate((sel) => {
|
|
@@ -450,7 +468,7 @@ This indicates a problem with the menu component's close functionality.`
|
|
|
450
468
|
}
|
|
451
469
|
}
|
|
452
470
|
} finally {
|
|
453
|
-
if (
|
|
471
|
+
if (page) await page.close();
|
|
454
472
|
}
|
|
455
473
|
return { passes, failures, skipped };
|
|
456
474
|
}
|
package/dist/index.cjs
CHANGED
|
@@ -228,7 +228,7 @@ ${"\u2550".repeat(60)}`);
|
|
|
228
228
|
} else if (totalFailures === 0) {
|
|
229
229
|
this.log(`\u2705 ${totalPasses}/${totalRun} required tests passed`);
|
|
230
230
|
if (this.skipped > 0) {
|
|
231
|
-
this.log(`\u25CB ${this.skipped} tests skipped
|
|
231
|
+
this.log(`\u25CB ${this.skipped} tests skipped`);
|
|
232
232
|
}
|
|
233
233
|
if (this.optionalSuggestions > 0) {
|
|
234
234
|
this.log(`\u{1F4A1} ${this.optionalSuggestions} optional enhancement${this.optionalSuggestions > 1 ? "s" : ""} suggested`);
|
|
@@ -279,6 +279,56 @@ ${"\u2550".repeat(60)}`);
|
|
|
279
279
|
}
|
|
280
280
|
});
|
|
281
281
|
|
|
282
|
+
// src/utils/test/contract/playwrightTestHarness.ts
|
|
283
|
+
async function getOrCreateBrowser() {
|
|
284
|
+
if (!sharedBrowser) {
|
|
285
|
+
sharedBrowser = await import_playwright.chromium.launch({
|
|
286
|
+
headless: true,
|
|
287
|
+
// Launch with clean browser profile - no extensions, no user data
|
|
288
|
+
args: [
|
|
289
|
+
"--disable-extensions",
|
|
290
|
+
"--disable-blink-features=AutomationControlled"
|
|
291
|
+
]
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
return sharedBrowser;
|
|
295
|
+
}
|
|
296
|
+
async function getOrCreateContext() {
|
|
297
|
+
if (!sharedContext) {
|
|
298
|
+
const browser = await getOrCreateBrowser();
|
|
299
|
+
sharedContext = await browser.newContext({
|
|
300
|
+
// Isolated context - no permissions, no geolocation, etc.
|
|
301
|
+
permissions: [],
|
|
302
|
+
// Ignore HTTPS errors for local dev servers
|
|
303
|
+
ignoreHTTPSErrors: true
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
return sharedContext;
|
|
307
|
+
}
|
|
308
|
+
async function createTestPage() {
|
|
309
|
+
const context = await getOrCreateContext();
|
|
310
|
+
return await context.newPage();
|
|
311
|
+
}
|
|
312
|
+
async function closeSharedBrowser() {
|
|
313
|
+
if (sharedContext) {
|
|
314
|
+
await sharedContext.close();
|
|
315
|
+
sharedContext = null;
|
|
316
|
+
}
|
|
317
|
+
if (sharedBrowser) {
|
|
318
|
+
await sharedBrowser.close();
|
|
319
|
+
sharedBrowser = null;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
var import_playwright, sharedBrowser, sharedContext;
|
|
323
|
+
var init_playwrightTestHarness = __esm({
|
|
324
|
+
"src/utils/test/contract/playwrightTestHarness.ts"() {
|
|
325
|
+
"use strict";
|
|
326
|
+
import_playwright = require("playwright");
|
|
327
|
+
sharedBrowser = null;
|
|
328
|
+
sharedContext = null;
|
|
329
|
+
}
|
|
330
|
+
});
|
|
331
|
+
|
|
282
332
|
// node_modules/@playwright/test/index.mjs
|
|
283
333
|
var test_exports = {};
|
|
284
334
|
__export(test_exports, {
|
|
@@ -314,30 +364,33 @@ async function runContractTestsPlaywright(componentName, url) {
|
|
|
314
364
|
const failures = [];
|
|
315
365
|
const passes = [];
|
|
316
366
|
const skipped = [];
|
|
317
|
-
let
|
|
367
|
+
let page = null;
|
|
318
368
|
try {
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
369
|
+
page = await createTestPage();
|
|
370
|
+
if (url) {
|
|
371
|
+
await page.goto(url, {
|
|
372
|
+
waitUntil: "domcontentloaded",
|
|
373
|
+
timeout: 3e4
|
|
374
|
+
});
|
|
375
|
+
await page.addStyleTag({ content: `* { transition: none !important; animation: none !important; }` });
|
|
376
|
+
}
|
|
327
377
|
const mainSelector = componentContract.selectors.trigger || componentContract.selectors.input || componentContract.selectors.container;
|
|
328
378
|
if (!mainSelector) {
|
|
329
379
|
throw new Error(`No main selector (trigger, input, or container) found in contract for ${componentName}`);
|
|
330
380
|
}
|
|
331
|
-
await page.locator(mainSelector).first().waitFor({ state: "attached", timeout:
|
|
381
|
+
await page.locator(mainSelector).first().waitFor({ state: "attached", timeout: 3e4 });
|
|
332
382
|
if (componentName === "menu" && componentContract.selectors.trigger) {
|
|
333
383
|
await page.locator(componentContract.selectors.trigger).first().waitFor({
|
|
334
384
|
state: "visible",
|
|
335
|
-
timeout:
|
|
385
|
+
timeout: 5e3
|
|
336
386
|
}).catch(() => {
|
|
337
387
|
console.warn("Menu trigger not visible, continuing with tests...");
|
|
338
388
|
});
|
|
339
389
|
}
|
|
340
390
|
async function resolveRelativeTarget(selector, relative) {
|
|
391
|
+
if (!page) {
|
|
392
|
+
throw new Error("Page is not initialized");
|
|
393
|
+
}
|
|
341
394
|
const items = await page.locator(selector).all();
|
|
342
395
|
switch (relative) {
|
|
343
396
|
case "first":
|
|
@@ -407,8 +460,8 @@ async function runContractTestsPlaywright(componentName, url) {
|
|
|
407
460
|
for (const dynamicTest of componentContract.dynamic || []) {
|
|
408
461
|
const { action, assertions } = dynamicTest;
|
|
409
462
|
const failuresBeforeTest = failures.length;
|
|
410
|
-
if (componentContract.selectors.
|
|
411
|
-
const popupSelector = componentContract.selectors.
|
|
463
|
+
if (componentContract.selectors.popup) {
|
|
464
|
+
const popupSelector = componentContract.selectors.popup;
|
|
412
465
|
if (!popupSelector) continue;
|
|
413
466
|
const popupElement = page.locator(popupSelector).first();
|
|
414
467
|
const isPopupVisible = await popupElement.isVisible().catch(() => false);
|
|
@@ -424,16 +477,16 @@ async function runContractTestsPlaywright(componentName, url) {
|
|
|
424
477
|
const closeElement = page.locator(closeSelector).first();
|
|
425
478
|
await closeElement.focus();
|
|
426
479
|
await page.keyboard.press("Escape");
|
|
427
|
-
menuClosed = await (0, test_exports.expect)(popupElement).toBeHidden({ timeout:
|
|
480
|
+
menuClosed = await (0, test_exports.expect)(popupElement).toBeHidden({ timeout: 2e3 }).then(() => true).catch(() => false);
|
|
428
481
|
}
|
|
429
482
|
if (!menuClosed && componentContract.selectors.trigger) {
|
|
430
483
|
const triggerElement = page.locator(componentContract.selectors.trigger).first();
|
|
431
484
|
await triggerElement.click();
|
|
432
|
-
menuClosed = await (0, test_exports.expect)(popupElement).toBeHidden({ timeout:
|
|
485
|
+
menuClosed = await (0, test_exports.expect)(popupElement).toBeHidden({ timeout: 2e3 }).then(() => true).catch(() => false);
|
|
433
486
|
}
|
|
434
487
|
if (!menuClosed) {
|
|
435
488
|
await page.mouse.click(10, 10);
|
|
436
|
-
menuClosed = await (0, test_exports.expect)(popupElement).toBeHidden({ timeout:
|
|
489
|
+
menuClosed = await (0, test_exports.expect)(popupElement).toBeHidden({ timeout: 2e3 }).then(() => true).catch(() => false);
|
|
437
490
|
}
|
|
438
491
|
if (!menuClosed) {
|
|
439
492
|
throw new Error(
|
|
@@ -453,6 +506,23 @@ This indicates a problem with the menu component's close functionality.`
|
|
|
453
506
|
}
|
|
454
507
|
}
|
|
455
508
|
}
|
|
509
|
+
if (componentContract.selectors.panel && componentContract.selectors.trigger && !componentContract.selectors.popup) {
|
|
510
|
+
const triggerSelector = componentContract.selectors.trigger;
|
|
511
|
+
const panelSelector = componentContract.selectors.panel;
|
|
512
|
+
if (triggerSelector && panelSelector) {
|
|
513
|
+
const allTriggers = await page.locator(triggerSelector).all();
|
|
514
|
+
for (const trigger of allTriggers) {
|
|
515
|
+
const isExpanded = await trigger.getAttribute("aria-expanded") === "true";
|
|
516
|
+
const triggerPanel = await trigger.getAttribute("aria-controls");
|
|
517
|
+
if (isExpanded && triggerPanel) {
|
|
518
|
+
await trigger.click();
|
|
519
|
+
const panel = page.locator(`#${triggerPanel}`);
|
|
520
|
+
await (0, test_exports.expect)(panel).toBeHidden({ timeout: 1e3 }).catch(() => {
|
|
521
|
+
});
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
}
|
|
456
526
|
let shouldSkipTest = false;
|
|
457
527
|
for (const act of action) {
|
|
458
528
|
if (act.type === "keypress" && (act.target === "submenuTrigger" || act.target === "submenu")) {
|
|
@@ -613,7 +683,7 @@ This indicates a problem with the menu component's close functionality.`
|
|
|
613
683
|
}
|
|
614
684
|
if (assertion.assertion === "toBeVisible") {
|
|
615
685
|
try {
|
|
616
|
-
await (0, test_exports.expect)(target).toBeVisible({ timeout:
|
|
686
|
+
await (0, test_exports.expect)(target).toBeVisible({ timeout: 2e3 });
|
|
617
687
|
passes.push(`${assertion.target} is visible as expected. Test: "${dynamicTest.description}".`);
|
|
618
688
|
} catch {
|
|
619
689
|
const debugState = await page.evaluate((sel) => {
|
|
@@ -627,7 +697,7 @@ This indicates a problem with the menu component's close functionality.`
|
|
|
627
697
|
}
|
|
628
698
|
if (assertion.assertion === "notToBeVisible") {
|
|
629
699
|
try {
|
|
630
|
-
await (0, test_exports.expect)(target).toBeHidden({ timeout:
|
|
700
|
+
await (0, test_exports.expect)(target).toBeHidden({ timeout: 2e3 });
|
|
631
701
|
passes.push(`${assertion.target} is not visible as expected. Test: "${dynamicTest.description}".`);
|
|
632
702
|
} catch {
|
|
633
703
|
const debugState = await page.evaluate((sel) => {
|
|
@@ -729,19 +799,19 @@ This indicates a problem with the menu component's close functionality.`
|
|
|
729
799
|
}
|
|
730
800
|
}
|
|
731
801
|
} finally {
|
|
732
|
-
if (
|
|
802
|
+
if (page) await page.close();
|
|
733
803
|
}
|
|
734
804
|
return { passes, failures, skipped };
|
|
735
805
|
}
|
|
736
|
-
var
|
|
806
|
+
var import_fs, import_meta2;
|
|
737
807
|
var init_contractTestRunnerPlaywright = __esm({
|
|
738
808
|
"src/utils/test/contract/contractTestRunnerPlaywright.ts"() {
|
|
739
809
|
"use strict";
|
|
740
|
-
import_playwright = require("playwright");
|
|
741
810
|
init_test();
|
|
742
811
|
import_fs = require("fs");
|
|
743
812
|
init_contract();
|
|
744
813
|
init_ContractReporter();
|
|
814
|
+
init_playwrightTestHarness();
|
|
745
815
|
import_meta2 = {};
|
|
746
816
|
}
|
|
747
817
|
});
|
|
@@ -749,6 +819,7 @@ var init_contractTestRunnerPlaywright = __esm({
|
|
|
749
819
|
// index.ts
|
|
750
820
|
var index_exports = {};
|
|
751
821
|
__export(index_exports, {
|
|
822
|
+
cleanupTests: () => cleanupTests,
|
|
752
823
|
makeAccordionAccessible: () => makeAccordionAccessible,
|
|
753
824
|
makeBlockAccessible: () => makeBlockAccessible,
|
|
754
825
|
makeCheckboxAccessible: () => makeCheckboxAccessible,
|
|
@@ -1994,60 +2065,63 @@ async function runContractTests(componentName, component) {
|
|
|
1994
2065
|
}
|
|
1995
2066
|
|
|
1996
2067
|
// src/utils/test/src/test.ts
|
|
2068
|
+
init_playwrightTestHarness();
|
|
1997
2069
|
async function testUiComponent(componentName, component, url) {
|
|
1998
2070
|
if (!componentName || typeof componentName !== "string") {
|
|
1999
2071
|
throw new Error("\u274C testUiComponent requires a valid componentName (string)");
|
|
2000
2072
|
}
|
|
2001
|
-
if (!component || !(component instanceof HTMLElement)) {
|
|
2002
|
-
throw new Error("\u274C testUiComponent requires a valid component (HTMLElement)");
|
|
2073
|
+
if (!url && (!component || !(component instanceof HTMLElement))) {
|
|
2074
|
+
throw new Error("\u274C testUiComponent requires either a valid component (HTMLElement) or a URL");
|
|
2003
2075
|
}
|
|
2004
2076
|
if (url && typeof url !== "string") {
|
|
2005
2077
|
throw new Error("\u274C testUiComponent url parameter must be a string");
|
|
2006
2078
|
}
|
|
2007
2079
|
let results;
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2080
|
+
if (component) {
|
|
2081
|
+
try {
|
|
2082
|
+
results = await (0, import_jest_axe.axe)(component);
|
|
2083
|
+
} catch (error) {
|
|
2084
|
+
throw new Error(
|
|
2085
|
+
`\u274C Axe accessibility scan failed
|
|
2013
2086
|
Error: ${error instanceof Error ? error.message : String(error)}`
|
|
2014
|
-
|
|
2087
|
+
);
|
|
2088
|
+
}
|
|
2089
|
+
} else {
|
|
2090
|
+
results = { violations: [] };
|
|
2015
2091
|
}
|
|
2016
|
-
async function checkDevServer(
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
try {
|
|
2025
|
-
const response = await fetch(serverUrl, {
|
|
2026
|
-
method: "HEAD",
|
|
2027
|
-
signal: AbortSignal.timeout(1e3)
|
|
2028
|
-
});
|
|
2029
|
-
if (response.ok || response.status === 304) {
|
|
2030
|
-
return serverUrl;
|
|
2031
|
-
}
|
|
2032
|
-
} catch {
|
|
2033
|
-
return null;
|
|
2092
|
+
async function checkDevServer(url2) {
|
|
2093
|
+
try {
|
|
2094
|
+
const response = await fetch(url2, {
|
|
2095
|
+
method: "HEAD",
|
|
2096
|
+
signal: AbortSignal.timeout(1e3)
|
|
2097
|
+
});
|
|
2098
|
+
if (response.ok || response.status === 304) {
|
|
2099
|
+
return url2;
|
|
2034
2100
|
}
|
|
2101
|
+
} catch {
|
|
2102
|
+
return null;
|
|
2035
2103
|
}
|
|
2036
2104
|
return null;
|
|
2037
2105
|
}
|
|
2038
2106
|
let contract;
|
|
2039
2107
|
try {
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2108
|
+
if (url) {
|
|
2109
|
+
const devServerUrl = await checkDevServer(url);
|
|
2110
|
+
if (devServerUrl) {
|
|
2111
|
+
console.log(`\u{1F3AD} Running Playwright tests on ${devServerUrl}`);
|
|
2112
|
+
const { runContractTestsPlaywright: runContractTestsPlaywright2 } = await Promise.resolve().then(() => (init_contractTestRunnerPlaywright(), contractTestRunnerPlaywright_exports));
|
|
2113
|
+
contract = await runContractTestsPlaywright2(componentName, devServerUrl);
|
|
2114
|
+
} else {
|
|
2115
|
+
throw new Error(
|
|
2116
|
+
`\u274C Dev server not running at ${url}
|
|
2117
|
+
Please start your dev server and try again.`
|
|
2118
|
+
);
|
|
2119
|
+
}
|
|
2120
|
+
} else if (component) {
|
|
2121
|
+
console.log(`\u{1F3AD} Running component contract tests in JSDOM mode`);
|
|
2050
2122
|
contract = await runContractTests(componentName, component);
|
|
2123
|
+
} else {
|
|
2124
|
+
throw new Error("\u274C Either component or URL must be provided");
|
|
2051
2125
|
}
|
|
2052
2126
|
} catch (error) {
|
|
2053
2127
|
if (error instanceof Error) {
|
|
@@ -2113,8 +2187,12 @@ if (typeof window === "undefined") {
|
|
|
2113
2187
|
);
|
|
2114
2188
|
};
|
|
2115
2189
|
}
|
|
2190
|
+
async function cleanupTests() {
|
|
2191
|
+
await closeSharedBrowser();
|
|
2192
|
+
}
|
|
2116
2193
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2117
2194
|
0 && (module.exports = {
|
|
2195
|
+
cleanupTests,
|
|
2118
2196
|
makeAccordionAccessible,
|
|
2119
2197
|
makeBlockAccessible,
|
|
2120
2198
|
makeCheckboxAccessible,
|
package/dist/index.d.cts
CHANGED
|
@@ -143,9 +143,14 @@ declare function makeComboboxAccessible({ comboboxInputId, comboboxButtonId, lis
|
|
|
143
143
|
* Runs static and interactions accessibility test on UI components.
|
|
144
144
|
* @param {string} componentName The name of the component contract to test against
|
|
145
145
|
* @param {HTMLElement} component The UI component to be tested
|
|
146
|
-
* @param {string} url Optional URL to run full Playwright E2E tests
|
|
146
|
+
* @param {string} url Optional URL to run full Playwright E2E tests. If omitted, uses isolated component testing with page.setContent()
|
|
147
147
|
*/
|
|
148
148
|
|
|
149
|
-
declare function testUiComponent(componentName: string, component: HTMLElement, url
|
|
149
|
+
declare function testUiComponent(componentName: string, component: HTMLElement | null, url: string | null): Promise<JestAxeResult>;
|
|
150
|
+
/**
|
|
151
|
+
* Cleanup function to close the shared Playwright browser
|
|
152
|
+
* Call this in afterAll() or after all tests complete
|
|
153
|
+
*/
|
|
154
|
+
declare function cleanupTests(): Promise<void>;
|
|
150
155
|
|
|
151
|
-
export { makeAccordionAccessible, makeBlockAccessible, makeCheckboxAccessible, makeComboboxAccessible, makeMenuAccessible, makeRadioAccessible, makeToggleAccessible, testUiComponent };
|
|
156
|
+
export { cleanupTests, makeAccordionAccessible, makeBlockAccessible, makeCheckboxAccessible, makeComboboxAccessible, makeMenuAccessible, makeRadioAccessible, makeToggleAccessible, testUiComponent };
|