@testspectra/react 1.1.8-rc.22 → 1.1.8-rc.23
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/dist/cli.js +131 -15
- package/dist/index.js +138 -17
- package/dist/setup.js +1415 -0
- package/package.json +4 -5
- package/vitest.config.ts +0 -117
package/dist/setup.js
ADDED
|
@@ -0,0 +1,1415 @@
|
|
|
1
|
+
// src/prelude.ts
|
|
2
|
+
var DEFAULT_IMPLICIT_WAIT_MS = 5e3;
|
|
3
|
+
function emitEvent(tag, payload) {
|
|
4
|
+
if (!__TESTSPECTRA_SEMANTIC_REPORTER__) return;
|
|
5
|
+
console.log(`[${tag}] ${payload}`);
|
|
6
|
+
}
|
|
7
|
+
function getImplicitWaitMs() {
|
|
8
|
+
return DEFAULT_IMPLICIT_WAIT_MS;
|
|
9
|
+
}
|
|
10
|
+
function getAssertionTimeoutMs() {
|
|
11
|
+
return Math.min(getImplicitWaitMs(), 2e3);
|
|
12
|
+
}
|
|
13
|
+
async function trackStep(_category, payloadObj, fn) {
|
|
14
|
+
const currentTest = globalThis.__CURRENT_TEST_TITLE__;
|
|
15
|
+
if (currentTest && !payloadObj.test) payloadObj.test = currentTest;
|
|
16
|
+
const payload = JSON.stringify(payloadObj);
|
|
17
|
+
emitEvent("TESTSPECTRA_STEP_START", payload);
|
|
18
|
+
const start = Date.now();
|
|
19
|
+
try {
|
|
20
|
+
const res = await fn();
|
|
21
|
+
const duration = Date.now() - start;
|
|
22
|
+
emitEvent("TESTSPECTRA_STEP_PASS", `${payload} (${duration}ms)`);
|
|
23
|
+
const delay = __TESTSPECTRA_STEP_DELAY_MS__ || 0;
|
|
24
|
+
if (delay > 0) {
|
|
25
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
26
|
+
}
|
|
27
|
+
return res;
|
|
28
|
+
} catch (err) {
|
|
29
|
+
const duration = Date.now() - start;
|
|
30
|
+
emitEvent("TESTSPECTRA_STEP_FAIL", `${payload} (${duration}ms)`);
|
|
31
|
+
throw err;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
async function pollCondition(fn, timeoutMs, intervalMs = 15) {
|
|
35
|
+
const effectiveTimeout = timeoutMs ?? getImplicitWaitMs();
|
|
36
|
+
const start = Date.now();
|
|
37
|
+
while (Date.now() - start < effectiveTimeout) {
|
|
38
|
+
try {
|
|
39
|
+
if (await fn()) return true;
|
|
40
|
+
} catch {
|
|
41
|
+
}
|
|
42
|
+
await new Promise((r) => setTimeout(r, intervalMs));
|
|
43
|
+
}
|
|
44
|
+
try {
|
|
45
|
+
return await fn();
|
|
46
|
+
} catch {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function matchesUrlPattern(pattern, url) {
|
|
51
|
+
if (!pattern || pattern === "*" || pattern === "**" || pattern === "ALL") return true;
|
|
52
|
+
if (pattern.startsWith("/") && pattern.endsWith("/")) {
|
|
53
|
+
try {
|
|
54
|
+
return new RegExp(pattern.slice(1, -1)).test(url);
|
|
55
|
+
} catch {
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (pattern.includes("*")) {
|
|
59
|
+
const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
|
|
60
|
+
try {
|
|
61
|
+
return new RegExp(escaped, "i").test(url);
|
|
62
|
+
} catch {
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return url.toLowerCase().includes(pattern.toLowerCase());
|
|
66
|
+
}
|
|
67
|
+
function createMockHandle(rule) {
|
|
68
|
+
const handle = {
|
|
69
|
+
respondWith: async () => {
|
|
70
|
+
throw new Error("Spectra.intercept() is not supported in component testing mode.");
|
|
71
|
+
},
|
|
72
|
+
respondOnce: async () => {
|
|
73
|
+
throw new Error("Spectra.intercept() is not supported in component testing mode.");
|
|
74
|
+
},
|
|
75
|
+
abort: async () => {
|
|
76
|
+
throw new Error("Spectra.intercept() is not supported in component testing mode.");
|
|
77
|
+
},
|
|
78
|
+
waitForCall: async () => {
|
|
79
|
+
throw new Error("Spectra.intercept() is not supported in component testing mode.");
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
handle.callCount = Object.assign(
|
|
83
|
+
() => rule.callCount,
|
|
84
|
+
{ valueOf: () => rule.callCount, [Symbol.toPrimitive]: () => rule.callCount }
|
|
85
|
+
);
|
|
86
|
+
Object.defineProperty(handle, "calls", { get: () => rule.calls, enumerable: true });
|
|
87
|
+
return handle;
|
|
88
|
+
}
|
|
89
|
+
function parseMockRule() {
|
|
90
|
+
return {
|
|
91
|
+
pattern: "*",
|
|
92
|
+
method: "GET",
|
|
93
|
+
response: void 0,
|
|
94
|
+
statusCode: 200,
|
|
95
|
+
callCount: 0,
|
|
96
|
+
respondOnceQueue: [],
|
|
97
|
+
calls: [],
|
|
98
|
+
waitResolvers: []
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
globalThis.__TS_RUNTIME__ = {
|
|
102
|
+
config: {
|
|
103
|
+
rootDir: "",
|
|
104
|
+
activePlatform: "web",
|
|
105
|
+
configuredBaseUrl: "",
|
|
106
|
+
cdpWsUrl: "",
|
|
107
|
+
driverServerUrl: ""
|
|
108
|
+
},
|
|
109
|
+
activeDriver: null,
|
|
110
|
+
mobileMockRules: [],
|
|
111
|
+
lastFocusedSelector: null,
|
|
112
|
+
emitEvent,
|
|
113
|
+
trackStep,
|
|
114
|
+
pollCondition,
|
|
115
|
+
getImplicitWaitMs,
|
|
116
|
+
getAssertionTimeoutMs,
|
|
117
|
+
matchesUrlPattern,
|
|
118
|
+
createMockHandle,
|
|
119
|
+
parseMockRule,
|
|
120
|
+
beforeHook: null,
|
|
121
|
+
afterHook: null,
|
|
122
|
+
beforeEachHook: null,
|
|
123
|
+
afterEachHook: null,
|
|
124
|
+
suiteBeforeEachHooks: {},
|
|
125
|
+
suiteAfterEachHooks: {}
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
// ../matchers/src/runtime/element_actions.ts
|
|
129
|
+
function createElementActions(selector, index) {
|
|
130
|
+
const driver = () => globalThis.__TS_RUNTIME__.activeDriver;
|
|
131
|
+
async function click(_options) {
|
|
132
|
+
return globalThis.__TS_RUNTIME__.trackStep(
|
|
133
|
+
"action",
|
|
134
|
+
{ type: "action", key: "click", target: selector, args: [] },
|
|
135
|
+
async () => {
|
|
136
|
+
await driver().click(selector, index);
|
|
137
|
+
}
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
async function doubleClick() {
|
|
141
|
+
return globalThis.__TS_RUNTIME__.trackStep(
|
|
142
|
+
"action",
|
|
143
|
+
{ type: "action", key: "doubleClick", target: selector, args: [] },
|
|
144
|
+
async () => {
|
|
145
|
+
await driver().doubleClick(selector, index);
|
|
146
|
+
}
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
async function rightClick() {
|
|
150
|
+
return globalThis.__TS_RUNTIME__.trackStep(
|
|
151
|
+
"action",
|
|
152
|
+
{ type: "action", key: "rightClick", target: selector, args: [] },
|
|
153
|
+
async () => {
|
|
154
|
+
await driver().rightClick(selector, index);
|
|
155
|
+
}
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
async function setValue(value) {
|
|
159
|
+
return globalThis.__TS_RUNTIME__.trackStep(
|
|
160
|
+
"action",
|
|
161
|
+
{ type: "action", key: "type", target: selector, args: [value] },
|
|
162
|
+
async () => {
|
|
163
|
+
await driver().setValue(selector, value, index);
|
|
164
|
+
}
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
async function type(value, options) {
|
|
168
|
+
if (options?.clearFirst) {
|
|
169
|
+
await clearValue();
|
|
170
|
+
}
|
|
171
|
+
await setValue(value);
|
|
172
|
+
}
|
|
173
|
+
async function clearValue() {
|
|
174
|
+
return globalThis.__TS_RUNTIME__.trackStep(
|
|
175
|
+
"action",
|
|
176
|
+
{ type: "action", key: "clear", target: selector, args: [] },
|
|
177
|
+
async () => {
|
|
178
|
+
await driver().clearValue(selector, index);
|
|
179
|
+
}
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
async function clear() {
|
|
183
|
+
await clearValue();
|
|
184
|
+
}
|
|
185
|
+
async function select(option) {
|
|
186
|
+
return globalThis.__TS_RUNTIME__.trackStep(
|
|
187
|
+
"action",
|
|
188
|
+
{ type: "action", key: "select", target: selector, args: [option] },
|
|
189
|
+
async () => {
|
|
190
|
+
await driver().select(selector, option, index);
|
|
191
|
+
}
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
async function selectByVisibleText(text) {
|
|
195
|
+
await select(text);
|
|
196
|
+
}
|
|
197
|
+
async function hover() {
|
|
198
|
+
return globalThis.__TS_RUNTIME__.trackStep(
|
|
199
|
+
"action",
|
|
200
|
+
{ type: "action", key: "hover", target: selector, args: [] },
|
|
201
|
+
async () => {
|
|
202
|
+
await driver().hover(selector, index);
|
|
203
|
+
}
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
async function focus() {
|
|
207
|
+
return globalThis.__TS_RUNTIME__.trackStep(
|
|
208
|
+
"action",
|
|
209
|
+
{ type: "action", key: "focus", target: selector, args: [] },
|
|
210
|
+
async () => {
|
|
211
|
+
await driver().focus(selector, index);
|
|
212
|
+
}
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
async function moveTo() {
|
|
216
|
+
await hover();
|
|
217
|
+
}
|
|
218
|
+
async function dragDrop(target) {
|
|
219
|
+
const destSelector = typeof target === "string" ? target : target?.selector || String(target);
|
|
220
|
+
return globalThis.__TS_RUNTIME__.trackStep(
|
|
221
|
+
"action",
|
|
222
|
+
{ type: "action", key: "dragDrop", target: selector, args: [destSelector] },
|
|
223
|
+
async () => {
|
|
224
|
+
await driver().dragDrop(selector, destSelector, index);
|
|
225
|
+
}
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
async function dragAndDrop(target) {
|
|
229
|
+
await dragDrop(target);
|
|
230
|
+
}
|
|
231
|
+
async function longPress(options) {
|
|
232
|
+
const duration = typeof options === "number" ? options : options?.duration || 1e3;
|
|
233
|
+
return globalThis.__TS_RUNTIME__.trackStep(
|
|
234
|
+
"action",
|
|
235
|
+
{ type: "action", key: "longPress", target: selector, args: [duration] },
|
|
236
|
+
async () => {
|
|
237
|
+
await driver().longPress(selector, duration, index);
|
|
238
|
+
}
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
async function scrollIntoView() {
|
|
242
|
+
return globalThis.__TS_RUNTIME__.trackStep(
|
|
243
|
+
"action",
|
|
244
|
+
{ type: "action", key: "scrollIntoView", target: selector, args: [] },
|
|
245
|
+
async () => {
|
|
246
|
+
await driver().scrollIntoView(selector, index);
|
|
247
|
+
}
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
return {
|
|
251
|
+
click,
|
|
252
|
+
doubleClick,
|
|
253
|
+
rightClick,
|
|
254
|
+
setValue,
|
|
255
|
+
type,
|
|
256
|
+
clearValue,
|
|
257
|
+
clear,
|
|
258
|
+
select,
|
|
259
|
+
selectByVisibleText,
|
|
260
|
+
hover,
|
|
261
|
+
focus,
|
|
262
|
+
moveTo,
|
|
263
|
+
dragDrop,
|
|
264
|
+
dragAndDrop,
|
|
265
|
+
longPress,
|
|
266
|
+
scrollIntoView
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
globalThis.__TS_RUNTIME__.createElementActions = createElementActions;
|
|
270
|
+
|
|
271
|
+
// ../matchers/src/runtime/element_state.ts
|
|
272
|
+
function createElementState(selector, index) {
|
|
273
|
+
const driver = () => globalThis.__TS_RUNTIME__.activeDriver;
|
|
274
|
+
const selectorText2 = typeof selector === "string" ? selector : selector.selector;
|
|
275
|
+
async function getText() {
|
|
276
|
+
return await driver().getText(selector, index);
|
|
277
|
+
}
|
|
278
|
+
async function getValue() {
|
|
279
|
+
return await driver().getValue(selector, index);
|
|
280
|
+
}
|
|
281
|
+
async function isDisplayed() {
|
|
282
|
+
return await driver().isDisplayed(selector, index);
|
|
283
|
+
}
|
|
284
|
+
async function isVisible() {
|
|
285
|
+
return await isDisplayed();
|
|
286
|
+
}
|
|
287
|
+
async function isExisting() {
|
|
288
|
+
return await driver().isExisting(selector, index);
|
|
289
|
+
}
|
|
290
|
+
async function isEnabled() {
|
|
291
|
+
return await driver().isEnabled(selector, index);
|
|
292
|
+
}
|
|
293
|
+
async function isSelected() {
|
|
294
|
+
return await driver().isSelected(selector, index);
|
|
295
|
+
}
|
|
296
|
+
async function isChecked() {
|
|
297
|
+
return await isSelected();
|
|
298
|
+
}
|
|
299
|
+
async function isFocused() {
|
|
300
|
+
return await driver().isFocused(selector, index);
|
|
301
|
+
}
|
|
302
|
+
async function getAttribute(name) {
|
|
303
|
+
return await driver().getAttribute(selector, name, index);
|
|
304
|
+
}
|
|
305
|
+
async function getCSSProperty(name) {
|
|
306
|
+
return await driver().getCSSProperty(selector, name, index);
|
|
307
|
+
}
|
|
308
|
+
async function waitForElement(timeoutMs) {
|
|
309
|
+
const waitTimeout = timeoutMs ?? globalThis.__TS_RUNTIME__.getImplicitWaitMs();
|
|
310
|
+
const ok = await globalThis.__TS_RUNTIME__.pollCondition(
|
|
311
|
+
async () => driver().isExisting(selector, index),
|
|
312
|
+
waitTimeout
|
|
313
|
+
);
|
|
314
|
+
if (ok) return true;
|
|
315
|
+
throw new Error("Element not found: " + JSON.stringify(selectorText2));
|
|
316
|
+
}
|
|
317
|
+
async function waitForDisplayed(opts = {}) {
|
|
318
|
+
const timeout = opts.timeout || 5e3;
|
|
319
|
+
const start = Date.now();
|
|
320
|
+
while (Date.now() - start < timeout) {
|
|
321
|
+
if (await driver().isDisplayed(selector, index)) return true;
|
|
322
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
323
|
+
}
|
|
324
|
+
throw new Error(`Element ${selectorText2} not displayed after ${timeout}ms`);
|
|
325
|
+
}
|
|
326
|
+
return {
|
|
327
|
+
getText,
|
|
328
|
+
getValue,
|
|
329
|
+
isDisplayed,
|
|
330
|
+
isVisible,
|
|
331
|
+
isExisting,
|
|
332
|
+
isEnabled,
|
|
333
|
+
isSelected,
|
|
334
|
+
isChecked,
|
|
335
|
+
isFocused,
|
|
336
|
+
getAttribute,
|
|
337
|
+
getCSSProperty,
|
|
338
|
+
waitForElement,
|
|
339
|
+
waitForDisplayed
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
globalThis.__TS_RUNTIME__.createElementState = createElementState;
|
|
343
|
+
|
|
344
|
+
// ../matchers/src/runtime/assertions.ts
|
|
345
|
+
function createElementAssertions(selector, index, state) {
|
|
346
|
+
const driver = () => globalThis.__TS_RUNTIME__.activeDriver;
|
|
347
|
+
const selectorText2 = typeof selector === "string" ? selector : selector.selector;
|
|
348
|
+
async function shouldBeVisible(options) {
|
|
349
|
+
return globalThis.__TS_RUNTIME__.trackStep(
|
|
350
|
+
"assertion",
|
|
351
|
+
{ type: "assertion", key: "be.visible", target: selector, args: [] },
|
|
352
|
+
async () => {
|
|
353
|
+
const ok = await globalThis.__TS_RUNTIME__.pollCondition(
|
|
354
|
+
() => state.isVisible(),
|
|
355
|
+
options?.timeoutMs ?? globalThis.__TS_RUNTIME__.getAssertionTimeoutMs()
|
|
356
|
+
);
|
|
357
|
+
if (!ok) throw new Error(`Expected element "${selectorText2}" to be visible`);
|
|
358
|
+
}
|
|
359
|
+
);
|
|
360
|
+
}
|
|
361
|
+
async function shouldNotBeVisible(options) {
|
|
362
|
+
return globalThis.__TS_RUNTIME__.trackStep(
|
|
363
|
+
"assertion",
|
|
364
|
+
{ type: "assertion", key: "not.be.visible", target: selector, args: [] },
|
|
365
|
+
async () => {
|
|
366
|
+
const ok = await globalThis.__TS_RUNTIME__.pollCondition(
|
|
367
|
+
async () => !await state.isVisible(),
|
|
368
|
+
options?.timeoutMs ?? globalThis.__TS_RUNTIME__.getAssertionTimeoutMs()
|
|
369
|
+
);
|
|
370
|
+
if (!ok) throw new Error(`Expected element "${selectorText2}" not to be visible`);
|
|
371
|
+
}
|
|
372
|
+
);
|
|
373
|
+
}
|
|
374
|
+
async function shouldExist(options) {
|
|
375
|
+
return globalThis.__TS_RUNTIME__.trackStep(
|
|
376
|
+
"assertion",
|
|
377
|
+
{ type: "assertion", key: "exist", target: selector, args: [] },
|
|
378
|
+
async () => {
|
|
379
|
+
const ok = await globalThis.__TS_RUNTIME__.pollCondition(
|
|
380
|
+
() => state.isExisting(),
|
|
381
|
+
options?.timeoutMs ?? globalThis.__TS_RUNTIME__.getAssertionTimeoutMs()
|
|
382
|
+
);
|
|
383
|
+
if (!ok) throw new Error(`Expected element "${selectorText2}" to exist in DOM`);
|
|
384
|
+
}
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
async function shouldNotExist(options) {
|
|
388
|
+
return globalThis.__TS_RUNTIME__.trackStep(
|
|
389
|
+
"assertion",
|
|
390
|
+
{ type: "assertion", key: "not.exist", target: selector, args: [] },
|
|
391
|
+
async () => {
|
|
392
|
+
const ok = await globalThis.__TS_RUNTIME__.pollCondition(
|
|
393
|
+
async () => !await state.isExisting(),
|
|
394
|
+
options?.timeoutMs ?? globalThis.__TS_RUNTIME__.getAssertionTimeoutMs()
|
|
395
|
+
);
|
|
396
|
+
if (!ok) throw new Error(`Expected element "${selectorText2}" not to exist in DOM`);
|
|
397
|
+
}
|
|
398
|
+
);
|
|
399
|
+
}
|
|
400
|
+
async function shouldBeEnabled(options) {
|
|
401
|
+
return globalThis.__TS_RUNTIME__.trackStep(
|
|
402
|
+
"assertion",
|
|
403
|
+
{ type: "assertion", key: "be.enabled", target: selector, args: [] },
|
|
404
|
+
async () => {
|
|
405
|
+
const ok = await globalThis.__TS_RUNTIME__.pollCondition(
|
|
406
|
+
() => state.isEnabled(),
|
|
407
|
+
options?.timeoutMs ?? globalThis.__TS_RUNTIME__.getAssertionTimeoutMs()
|
|
408
|
+
);
|
|
409
|
+
if (!ok) throw new Error(`Expected element "${selectorText2}" to be enabled`);
|
|
410
|
+
}
|
|
411
|
+
);
|
|
412
|
+
}
|
|
413
|
+
async function shouldBeDisabled(options) {
|
|
414
|
+
return globalThis.__TS_RUNTIME__.trackStep(
|
|
415
|
+
"assertion",
|
|
416
|
+
{ type: "assertion", key: "be.disabled", target: selector, args: [] },
|
|
417
|
+
async () => {
|
|
418
|
+
const ok = await globalThis.__TS_RUNTIME__.pollCondition(
|
|
419
|
+
async () => !await state.isEnabled(),
|
|
420
|
+
options?.timeoutMs ?? globalThis.__TS_RUNTIME__.getAssertionTimeoutMs()
|
|
421
|
+
);
|
|
422
|
+
if (!ok) throw new Error(`Expected element "${selectorText2}" to be disabled`);
|
|
423
|
+
}
|
|
424
|
+
);
|
|
425
|
+
}
|
|
426
|
+
async function shouldBeClickable(options) {
|
|
427
|
+
return globalThis.__TS_RUNTIME__.trackStep(
|
|
428
|
+
"assertion",
|
|
429
|
+
{ type: "assertion", key: "be.clickable", target: selector, args: [] },
|
|
430
|
+
async () => {
|
|
431
|
+
const ok = await globalThis.__TS_RUNTIME__.pollCondition(
|
|
432
|
+
() => driver().isClickable(selector, index),
|
|
433
|
+
options?.timeoutMs ?? globalThis.__TS_RUNTIME__.getAssertionTimeoutMs()
|
|
434
|
+
);
|
|
435
|
+
if (!ok) throw new Error(`Expected element "${selectorText2}" to be clickable`);
|
|
436
|
+
}
|
|
437
|
+
);
|
|
438
|
+
}
|
|
439
|
+
async function shouldNotBeClickable(options) {
|
|
440
|
+
return globalThis.__TS_RUNTIME__.trackStep(
|
|
441
|
+
"assertion",
|
|
442
|
+
{ type: "assertion", key: "not.be.clickable", target: selector, args: [] },
|
|
443
|
+
async () => {
|
|
444
|
+
const ok = await globalThis.__TS_RUNTIME__.pollCondition(
|
|
445
|
+
async () => !await driver().isClickable(selector, index),
|
|
446
|
+
options?.timeoutMs ?? globalThis.__TS_RUNTIME__.getAssertionTimeoutMs()
|
|
447
|
+
);
|
|
448
|
+
if (!ok) throw new Error(`Expected element "${selectorText2}" not to be clickable`);
|
|
449
|
+
}
|
|
450
|
+
);
|
|
451
|
+
}
|
|
452
|
+
async function shouldBeChecked(options) {
|
|
453
|
+
return globalThis.__TS_RUNTIME__.trackStep(
|
|
454
|
+
"assertion",
|
|
455
|
+
{ type: "assertion", key: "be.checked", target: selector, args: [] },
|
|
456
|
+
async () => {
|
|
457
|
+
const ok = await globalThis.__TS_RUNTIME__.pollCondition(
|
|
458
|
+
() => state.isChecked(),
|
|
459
|
+
options?.timeoutMs ?? globalThis.__TS_RUNTIME__.getAssertionTimeoutMs()
|
|
460
|
+
);
|
|
461
|
+
if (!ok) throw new Error(`Expected element "${selectorText2}" to be checked`);
|
|
462
|
+
}
|
|
463
|
+
);
|
|
464
|
+
}
|
|
465
|
+
async function shouldNotBeChecked(options) {
|
|
466
|
+
return globalThis.__TS_RUNTIME__.trackStep(
|
|
467
|
+
"assertion",
|
|
468
|
+
{ type: "assertion", key: "not.be.checked", target: selector, args: [] },
|
|
469
|
+
async () => {
|
|
470
|
+
const ok = await globalThis.__TS_RUNTIME__.pollCondition(
|
|
471
|
+
async () => !await state.isChecked(),
|
|
472
|
+
options?.timeoutMs ?? globalThis.__TS_RUNTIME__.getAssertionTimeoutMs()
|
|
473
|
+
);
|
|
474
|
+
if (!ok) throw new Error(`Expected element "${selectorText2}" not to be checked`);
|
|
475
|
+
}
|
|
476
|
+
);
|
|
477
|
+
}
|
|
478
|
+
async function shouldBeFocused(options) {
|
|
479
|
+
return globalThis.__TS_RUNTIME__.trackStep(
|
|
480
|
+
"assertion",
|
|
481
|
+
{ type: "assertion", key: "be.focused", target: selector, args: [] },
|
|
482
|
+
async () => {
|
|
483
|
+
const ok = await globalThis.__TS_RUNTIME__.pollCondition(
|
|
484
|
+
() => state.isFocused(),
|
|
485
|
+
options?.timeoutMs ?? globalThis.__TS_RUNTIME__.getAssertionTimeoutMs()
|
|
486
|
+
);
|
|
487
|
+
if (!ok) throw new Error(`Expected element "${selectorText2}" to be focused`);
|
|
488
|
+
}
|
|
489
|
+
);
|
|
490
|
+
}
|
|
491
|
+
async function shouldNotBeFocused(options) {
|
|
492
|
+
return globalThis.__TS_RUNTIME__.trackStep(
|
|
493
|
+
"assertion",
|
|
494
|
+
{ type: "assertion", key: "not.be.focused", target: selector, args: [] },
|
|
495
|
+
async () => {
|
|
496
|
+
const ok = await globalThis.__TS_RUNTIME__.pollCondition(
|
|
497
|
+
async () => !await state.isFocused(),
|
|
498
|
+
options?.timeoutMs ?? globalThis.__TS_RUNTIME__.getAssertionTimeoutMs()
|
|
499
|
+
);
|
|
500
|
+
if (!ok) throw new Error(`Expected element "${selectorText2}" not to be focused`);
|
|
501
|
+
}
|
|
502
|
+
);
|
|
503
|
+
}
|
|
504
|
+
async function shouldHaveValue(value, options) {
|
|
505
|
+
return globalThis.__TS_RUNTIME__.trackStep(
|
|
506
|
+
"assertion",
|
|
507
|
+
{ type: "assertion", key: "have.value", target: selector, args: [value] },
|
|
508
|
+
async () => {
|
|
509
|
+
let actual = "";
|
|
510
|
+
const ok = await globalThis.__TS_RUNTIME__.pollCondition(async () => {
|
|
511
|
+
actual = await state.getValue();
|
|
512
|
+
return actual === String(value);
|
|
513
|
+
}, options?.timeoutMs ?? globalThis.__TS_RUNTIME__.getAssertionTimeoutMs());
|
|
514
|
+
if (!ok) throw new Error(`Expected element "${selectorText2}" to have value "${value}", but got "${actual}"`);
|
|
515
|
+
}
|
|
516
|
+
);
|
|
517
|
+
}
|
|
518
|
+
async function shouldNotHaveValue(value, options) {
|
|
519
|
+
return globalThis.__TS_RUNTIME__.trackStep(
|
|
520
|
+
"assertion",
|
|
521
|
+
{ type: "assertion", key: "not.have.value", target: selector, args: [value] },
|
|
522
|
+
async () => {
|
|
523
|
+
const ok = await globalThis.__TS_RUNTIME__.pollCondition(async () => {
|
|
524
|
+
const actual = await state.getValue();
|
|
525
|
+
return actual !== String(value);
|
|
526
|
+
}, options?.timeoutMs ?? globalThis.__TS_RUNTIME__.getAssertionTimeoutMs());
|
|
527
|
+
if (!ok) throw new Error(`Expected element "${selectorText2}" not to have value "${value}"`);
|
|
528
|
+
}
|
|
529
|
+
);
|
|
530
|
+
}
|
|
531
|
+
async function shouldContainValue(value, options) {
|
|
532
|
+
return globalThis.__TS_RUNTIME__.trackStep(
|
|
533
|
+
"assertion",
|
|
534
|
+
{ type: "assertion", key: "contain.value", target: selector, args: [value] },
|
|
535
|
+
async () => {
|
|
536
|
+
let actual = "";
|
|
537
|
+
const ok = await globalThis.__TS_RUNTIME__.pollCondition(async () => {
|
|
538
|
+
actual = await state.getValue();
|
|
539
|
+
return actual.includes(String(value));
|
|
540
|
+
}, options?.timeoutMs ?? globalThis.__TS_RUNTIME__.getAssertionTimeoutMs());
|
|
541
|
+
if (!ok) throw new Error(`Expected element "${selectorText2}" to contain value "${value}", but got "${actual}"`);
|
|
542
|
+
}
|
|
543
|
+
);
|
|
544
|
+
}
|
|
545
|
+
async function shouldNotContainValue(value, options) {
|
|
546
|
+
return globalThis.__TS_RUNTIME__.trackStep(
|
|
547
|
+
"assertion",
|
|
548
|
+
{ type: "assertion", key: "not.contain.value", target: selector, args: [value] },
|
|
549
|
+
async () => {
|
|
550
|
+
const ok = await globalThis.__TS_RUNTIME__.pollCondition(async () => {
|
|
551
|
+
const actual = await state.getValue();
|
|
552
|
+
return !actual.includes(String(value));
|
|
553
|
+
}, options?.timeoutMs ?? globalThis.__TS_RUNTIME__.getAssertionTimeoutMs());
|
|
554
|
+
if (!ok) throw new Error(`Expected element "${selectorText2}" not to contain value "${value}"`);
|
|
555
|
+
}
|
|
556
|
+
);
|
|
557
|
+
}
|
|
558
|
+
async function shouldHaveText(expected, options) {
|
|
559
|
+
return globalThis.__TS_RUNTIME__.trackStep(
|
|
560
|
+
"assertion",
|
|
561
|
+
{ type: "assertion", key: "have.text", target: selector, args: [expected] },
|
|
562
|
+
async () => {
|
|
563
|
+
let actual = "";
|
|
564
|
+
const ok = await globalThis.__TS_RUNTIME__.pollCondition(async () => {
|
|
565
|
+
actual = await state.getText();
|
|
566
|
+
if (expected instanceof RegExp) return expected.test(actual);
|
|
567
|
+
return actual === String(expected) || actual.trim() === String(expected).trim();
|
|
568
|
+
}, options?.timeoutMs ?? globalThis.__TS_RUNTIME__.getAssertionTimeoutMs());
|
|
569
|
+
if (!ok) {
|
|
570
|
+
if (expected instanceof RegExp)
|
|
571
|
+
throw new Error(`Expected element "${selectorText2}" text to match ${expected}, but got "${actual}"`);
|
|
572
|
+
throw new Error(`Expected element "${selectorText2}" to have text "${expected}", but got "${actual}"`);
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
);
|
|
576
|
+
}
|
|
577
|
+
async function shouldNotHaveText(expected, options) {
|
|
578
|
+
return globalThis.__TS_RUNTIME__.trackStep(
|
|
579
|
+
"assertion",
|
|
580
|
+
{ type: "assertion", key: "not.have.text", target: selector, args: [expected] },
|
|
581
|
+
async () => {
|
|
582
|
+
let actual = "";
|
|
583
|
+
const ok = await globalThis.__TS_RUNTIME__.pollCondition(async () => {
|
|
584
|
+
if (!await state.isExisting()) return false;
|
|
585
|
+
actual = await state.getText();
|
|
586
|
+
if (expected instanceof RegExp) return !expected.test(actual);
|
|
587
|
+
return actual !== String(expected) && actual.trim() !== String(expected).trim();
|
|
588
|
+
}, options?.timeoutMs ?? globalThis.__TS_RUNTIME__.getAssertionTimeoutMs());
|
|
589
|
+
if (!ok) {
|
|
590
|
+
if (expected instanceof RegExp)
|
|
591
|
+
throw new Error(`Expected element "${selectorText2}" text not to match ${expected}`);
|
|
592
|
+
throw new Error(`Expected element "${selectorText2}" not to have text "${expected}"`);
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
);
|
|
596
|
+
}
|
|
597
|
+
async function shouldContainText(substring, options) {
|
|
598
|
+
return globalThis.__TS_RUNTIME__.trackStep(
|
|
599
|
+
"assertion",
|
|
600
|
+
{ type: "assertion", key: "contain.text", target: selector, args: [substring] },
|
|
601
|
+
async () => {
|
|
602
|
+
let actual = "";
|
|
603
|
+
const ok = await globalThis.__TS_RUNTIME__.pollCondition(async () => {
|
|
604
|
+
actual = await state.getText();
|
|
605
|
+
return actual.includes(String(substring));
|
|
606
|
+
}, options?.timeoutMs ?? globalThis.__TS_RUNTIME__.getAssertionTimeoutMs());
|
|
607
|
+
if (!ok)
|
|
608
|
+
throw new Error(`Expected element "${selectorText2}" to contain text "${substring}", but got "${actual}"`);
|
|
609
|
+
}
|
|
610
|
+
);
|
|
611
|
+
}
|
|
612
|
+
async function shouldNotContainText(substring, options) {
|
|
613
|
+
return globalThis.__TS_RUNTIME__.trackStep(
|
|
614
|
+
"assertion",
|
|
615
|
+
{ type: "assertion", key: "not.contain.text", target: selector, args: [substring] },
|
|
616
|
+
async () => {
|
|
617
|
+
const ok = await globalThis.__TS_RUNTIME__.pollCondition(async () => {
|
|
618
|
+
const actual = await state.getText();
|
|
619
|
+
return !actual.includes(String(substring));
|
|
620
|
+
}, options?.timeoutMs ?? globalThis.__TS_RUNTIME__.getAssertionTimeoutMs());
|
|
621
|
+
if (!ok) throw new Error(`Expected element "${selectorText2}" not to contain text "${substring}"`);
|
|
622
|
+
}
|
|
623
|
+
);
|
|
624
|
+
}
|
|
625
|
+
async function shouldHaveClass(className, options) {
|
|
626
|
+
return globalThis.__TS_RUNTIME__.trackStep(
|
|
627
|
+
"assertion",
|
|
628
|
+
{ type: "assertion", key: "have.class", target: selector, args: [className] },
|
|
629
|
+
async () => {
|
|
630
|
+
const ok = await globalThis.__TS_RUNTIME__.pollCondition(
|
|
631
|
+
() => driver().hasClass(selector, className, index),
|
|
632
|
+
options?.timeoutMs ?? globalThis.__TS_RUNTIME__.getAssertionTimeoutMs()
|
|
633
|
+
);
|
|
634
|
+
if (!ok) throw new Error(`Expected element "${selectorText2}" to have class "${className}"`);
|
|
635
|
+
}
|
|
636
|
+
);
|
|
637
|
+
}
|
|
638
|
+
async function shouldNotHaveClass(className, options) {
|
|
639
|
+
return globalThis.__TS_RUNTIME__.trackStep(
|
|
640
|
+
"assertion",
|
|
641
|
+
{ type: "assertion", key: "not.have.class", target: selector, args: [className] },
|
|
642
|
+
async () => {
|
|
643
|
+
const ok = await globalThis.__TS_RUNTIME__.pollCondition(
|
|
644
|
+
async () => !await driver().hasClass(selector, className, index),
|
|
645
|
+
options?.timeoutMs ?? globalThis.__TS_RUNTIME__.getAssertionTimeoutMs()
|
|
646
|
+
);
|
|
647
|
+
if (!ok) throw new Error(`Expected element "${selectorText2}" not to have class "${className}"`);
|
|
648
|
+
}
|
|
649
|
+
);
|
|
650
|
+
}
|
|
651
|
+
async function shouldHaveAttribute(name, value, options) {
|
|
652
|
+
return globalThis.__TS_RUNTIME__.trackStep(
|
|
653
|
+
"assertion",
|
|
654
|
+
{ type: "assertion", key: "have.attr", target: selector, args: value !== void 0 ? [name, value] : [name] },
|
|
655
|
+
async () => {
|
|
656
|
+
let actual = null;
|
|
657
|
+
const ok = await globalThis.__TS_RUNTIME__.pollCondition(async () => {
|
|
658
|
+
actual = await state.getAttribute(name);
|
|
659
|
+
if (value !== void 0) return actual === String(value);
|
|
660
|
+
return actual !== null;
|
|
661
|
+
}, options?.timeoutMs ?? globalThis.__TS_RUNTIME__.getAssertionTimeoutMs());
|
|
662
|
+
if (!ok) {
|
|
663
|
+
if (value !== void 0)
|
|
664
|
+
throw new Error(
|
|
665
|
+
`Expected element "${selectorText2}" to have attribute "${name}" = "${value}", but got "${actual}"`
|
|
666
|
+
);
|
|
667
|
+
throw new Error(`Expected element "${selectorText2}" to have attribute "${name}"`);
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
);
|
|
671
|
+
}
|
|
672
|
+
async function shouldNotHaveAttribute(name, options) {
|
|
673
|
+
return globalThis.__TS_RUNTIME__.trackStep(
|
|
674
|
+
"assertion",
|
|
675
|
+
{ type: "assertion", key: "not.have.attr", target: selector, args: [name] },
|
|
676
|
+
async () => {
|
|
677
|
+
const ok = await globalThis.__TS_RUNTIME__.pollCondition(async () => {
|
|
678
|
+
const actual = await state.getAttribute(name);
|
|
679
|
+
return actual === null;
|
|
680
|
+
}, options?.timeoutMs ?? globalThis.__TS_RUNTIME__.getAssertionTimeoutMs());
|
|
681
|
+
if (!ok) throw new Error(`Expected element "${selectorText2}" not to have attribute "${name}"`);
|
|
682
|
+
}
|
|
683
|
+
);
|
|
684
|
+
}
|
|
685
|
+
async function shouldHaveCss(property, value, options) {
|
|
686
|
+
return globalThis.__TS_RUNTIME__.trackStep(
|
|
687
|
+
"assertion",
|
|
688
|
+
{ type: "assertion", key: "have.css", target: selector, args: [property, value] },
|
|
689
|
+
async () => {
|
|
690
|
+
const norm = (v) => String(v ?? "").replace(/\s+/g, "").replace(/rgba\((.+?),1\)/, "rgb($1)").toLowerCase();
|
|
691
|
+
let actualCss = "";
|
|
692
|
+
const ok = await globalThis.__TS_RUNTIME__.pollCondition(async () => {
|
|
693
|
+
const css = await state.getCSSProperty(property);
|
|
694
|
+
actualCss = css.value;
|
|
695
|
+
return norm(actualCss) === norm(value);
|
|
696
|
+
}, options?.timeoutMs ?? globalThis.__TS_RUNTIME__.getAssertionTimeoutMs());
|
|
697
|
+
if (!ok)
|
|
698
|
+
throw new Error(
|
|
699
|
+
`Expected element "${selectorText2}" to have CSS "${property}" = "${value}", but got "${actualCss}"`
|
|
700
|
+
);
|
|
701
|
+
}
|
|
702
|
+
);
|
|
703
|
+
}
|
|
704
|
+
async function shouldNotHaveCss(property, value, options) {
|
|
705
|
+
return globalThis.__TS_RUNTIME__.trackStep(
|
|
706
|
+
"assertion",
|
|
707
|
+
{ type: "assertion", key: "not.have.css", target: selector, args: [property, value] },
|
|
708
|
+
async () => {
|
|
709
|
+
const norm = (v) => String(v ?? "").replace(/\s+/g, "").replace(/rgba\((.+?),1\)/, "rgb($1)").toLowerCase();
|
|
710
|
+
const ok = await globalThis.__TS_RUNTIME__.pollCondition(async () => {
|
|
711
|
+
const css = await state.getCSSProperty(property);
|
|
712
|
+
return norm(css.value) !== norm(value);
|
|
713
|
+
}, options?.timeoutMs ?? globalThis.__TS_RUNTIME__.getAssertionTimeoutMs());
|
|
714
|
+
if (!ok) throw new Error(`Expected element "${selectorText2}" not to have CSS "${property}" = "${value}"`);
|
|
715
|
+
}
|
|
716
|
+
);
|
|
717
|
+
}
|
|
718
|
+
return {
|
|
719
|
+
shouldBeVisible,
|
|
720
|
+
shouldNotBeVisible,
|
|
721
|
+
shouldExist,
|
|
722
|
+
shouldNotExist,
|
|
723
|
+
shouldBeEnabled,
|
|
724
|
+
shouldBeDisabled,
|
|
725
|
+
shouldBeClickable,
|
|
726
|
+
shouldNotBeClickable,
|
|
727
|
+
shouldBeChecked,
|
|
728
|
+
shouldNotBeChecked,
|
|
729
|
+
shouldBeFocused,
|
|
730
|
+
shouldNotBeFocused,
|
|
731
|
+
shouldHaveValue,
|
|
732
|
+
shouldNotHaveValue,
|
|
733
|
+
shouldContainValue,
|
|
734
|
+
shouldNotContainValue,
|
|
735
|
+
shouldHaveText,
|
|
736
|
+
shouldNotHaveText,
|
|
737
|
+
shouldContainText,
|
|
738
|
+
shouldNotContainText,
|
|
739
|
+
shouldHaveClass,
|
|
740
|
+
shouldNotHaveClass,
|
|
741
|
+
shouldHaveAttribute,
|
|
742
|
+
shouldNotHaveAttribute,
|
|
743
|
+
shouldHaveCss,
|
|
744
|
+
shouldNotHaveCss
|
|
745
|
+
};
|
|
746
|
+
}
|
|
747
|
+
globalThis.__TS_RUNTIME__.createElementAssertions = createElementAssertions;
|
|
748
|
+
|
|
749
|
+
// ../matchers/src/runtime/element_proxy.ts
|
|
750
|
+
function selectorText(s) {
|
|
751
|
+
return typeof s === "string" ? s : s.selector;
|
|
752
|
+
}
|
|
753
|
+
function toScopedSelector(selector, index) {
|
|
754
|
+
return typeof selector === "string" ? { selector, index, parent: void 0 } : selector;
|
|
755
|
+
}
|
|
756
|
+
function createElementProxy(selector, index = null) {
|
|
757
|
+
const selectorTextValue = selectorText(selector);
|
|
758
|
+
const resolvedIndex = typeof selector === "string" ? index : selector.index;
|
|
759
|
+
const actions = globalThis.__TS_RUNTIME__.createElementActions(selector, index);
|
|
760
|
+
const state = globalThis.__TS_RUNTIME__.createElementState(selector, index);
|
|
761
|
+
const assertions = globalThis.__TS_RUNTIME__.createElementAssertions(selector, index, state);
|
|
762
|
+
return {
|
|
763
|
+
selector: selectorTextValue,
|
|
764
|
+
index: resolvedIndex,
|
|
765
|
+
...actions,
|
|
766
|
+
...state,
|
|
767
|
+
...assertions,
|
|
768
|
+
get(childSelector, childIndex = null) {
|
|
769
|
+
return globalThis.__TS_RUNTIME__.createElementProxy(
|
|
770
|
+
{ selector: childSelector, index: childIndex, parent: toScopedSelector(selector, index) },
|
|
771
|
+
null
|
|
772
|
+
);
|
|
773
|
+
},
|
|
774
|
+
getAll(childSelector) {
|
|
775
|
+
return globalThis.__TS_RUNTIME__.createCollectionProxy({
|
|
776
|
+
selector: childSelector,
|
|
777
|
+
index: null,
|
|
778
|
+
parent: toScopedSelector(selector, index)
|
|
779
|
+
});
|
|
780
|
+
}
|
|
781
|
+
};
|
|
782
|
+
}
|
|
783
|
+
function createCollectionProxy(selector) {
|
|
784
|
+
const driver = () => globalThis.__TS_RUNTIME__.activeDriver;
|
|
785
|
+
const selectorTextValue = selectorText(selector);
|
|
786
|
+
const withIndex = (idx) => typeof selector === "string" ? [selector, idx] : [{ ...selector, index: idx }, null];
|
|
787
|
+
const collectionProxy = {
|
|
788
|
+
selector: selectorTextValue,
|
|
789
|
+
async count() {
|
|
790
|
+
return await driver().count(selector);
|
|
791
|
+
},
|
|
792
|
+
get length() {
|
|
793
|
+
return this.count();
|
|
794
|
+
},
|
|
795
|
+
first() {
|
|
796
|
+
return globalThis.__TS_RUNTIME__.createElementProxy(...withIndex(0));
|
|
797
|
+
},
|
|
798
|
+
last() {
|
|
799
|
+
return globalThis.__TS_RUNTIME__.createElementProxy(...withIndex(-1));
|
|
800
|
+
},
|
|
801
|
+
nth(index) {
|
|
802
|
+
return globalThis.__TS_RUNTIME__.createElementProxy(...withIndex(index));
|
|
803
|
+
},
|
|
804
|
+
async shouldHaveLength(expected, options) {
|
|
805
|
+
return globalThis.__TS_RUNTIME__.trackStep(
|
|
806
|
+
"assertion",
|
|
807
|
+
{ type: "assertion", key: "have.length", target: selector, args: [expected] },
|
|
808
|
+
async () => {
|
|
809
|
+
let actual = 0;
|
|
810
|
+
const ok = await globalThis.__TS_RUNTIME__.pollCondition(async () => {
|
|
811
|
+
actual = await this.count();
|
|
812
|
+
return actual === expected;
|
|
813
|
+
}, options?.timeoutMs ?? globalThis.__TS_RUNTIME__.getAssertionTimeoutMs());
|
|
814
|
+
if (!ok) {
|
|
815
|
+
throw new Error(`Expected collection "${selectorTextValue}" count to equal ${expected}, but got ${actual}`);
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
);
|
|
819
|
+
},
|
|
820
|
+
async shouldNotHaveLength(expected, options) {
|
|
821
|
+
return globalThis.__TS_RUNTIME__.trackStep(
|
|
822
|
+
"assertion",
|
|
823
|
+
{ type: "assertion", key: "not.have.length", target: selector, args: [expected] },
|
|
824
|
+
async () => {
|
|
825
|
+
let actual = 0;
|
|
826
|
+
const ok = await globalThis.__TS_RUNTIME__.pollCondition(async () => {
|
|
827
|
+
actual = await this.count();
|
|
828
|
+
return actual !== expected;
|
|
829
|
+
}, options?.timeoutMs ?? globalThis.__TS_RUNTIME__.getAssertionTimeoutMs());
|
|
830
|
+
if (!ok) {
|
|
831
|
+
throw new Error(`Expected collection "${selectorTextValue}" count not to equal ${expected}`);
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
);
|
|
835
|
+
},
|
|
836
|
+
async shouldHaveLengthGreaterThan(expected, options) {
|
|
837
|
+
return globalThis.__TS_RUNTIME__.trackStep(
|
|
838
|
+
"assertion",
|
|
839
|
+
{ type: "assertion", key: "have.length.greaterThan", target: selector, args: [expected] },
|
|
840
|
+
async () => {
|
|
841
|
+
let actual = 0;
|
|
842
|
+
const ok = await globalThis.__TS_RUNTIME__.pollCondition(async () => {
|
|
843
|
+
actual = await this.count();
|
|
844
|
+
return actual > expected;
|
|
845
|
+
}, options?.timeoutMs ?? globalThis.__TS_RUNTIME__.getAssertionTimeoutMs());
|
|
846
|
+
if (!ok) {
|
|
847
|
+
throw new Error(
|
|
848
|
+
`Expected collection "${selectorTextValue}" count to be greater than ${expected}, but got ${actual}`
|
|
849
|
+
);
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
);
|
|
853
|
+
},
|
|
854
|
+
async shouldHaveLengthLessThan(expected, options) {
|
|
855
|
+
return globalThis.__TS_RUNTIME__.trackStep(
|
|
856
|
+
"assertion",
|
|
857
|
+
{ type: "assertion", key: "have.length.lessThan", target: selector, args: [expected] },
|
|
858
|
+
async () => {
|
|
859
|
+
let actual = 0;
|
|
860
|
+
const ok = await globalThis.__TS_RUNTIME__.pollCondition(async () => {
|
|
861
|
+
actual = await this.count();
|
|
862
|
+
return actual < expected;
|
|
863
|
+
}, options?.timeoutMs ?? globalThis.__TS_RUNTIME__.getAssertionTimeoutMs());
|
|
864
|
+
if (!ok) {
|
|
865
|
+
throw new Error(`Expected collection "${selectorTextValue}" count to be less than ${expected}, but got ${actual}`);
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
);
|
|
869
|
+
},
|
|
870
|
+
async shouldBeEmpty(options) {
|
|
871
|
+
return globalThis.__TS_RUNTIME__.trackStep(
|
|
872
|
+
"assertion",
|
|
873
|
+
{ type: "assertion", key: "be.empty", target: selector, args: [] },
|
|
874
|
+
async () => {
|
|
875
|
+
let actual = 0;
|
|
876
|
+
const ok = await globalThis.__TS_RUNTIME__.pollCondition(async () => {
|
|
877
|
+
actual = await this.count();
|
|
878
|
+
return actual === 0;
|
|
879
|
+
}, options?.timeoutMs ?? globalThis.__TS_RUNTIME__.getAssertionTimeoutMs());
|
|
880
|
+
if (!ok) {
|
|
881
|
+
throw new Error(`Expected collection "${selectorTextValue}" to be empty, but got ${actual}`);
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
);
|
|
885
|
+
},
|
|
886
|
+
async shouldNotBeEmpty(options) {
|
|
887
|
+
return globalThis.__TS_RUNTIME__.trackStep(
|
|
888
|
+
"assertion",
|
|
889
|
+
{ type: "assertion", key: "not.be.empty", target: selector, args: [] },
|
|
890
|
+
async () => {
|
|
891
|
+
let actual = 0;
|
|
892
|
+
const ok = await globalThis.__TS_RUNTIME__.pollCondition(async () => {
|
|
893
|
+
actual = await this.count();
|
|
894
|
+
return actual > 0;
|
|
895
|
+
}, options?.timeoutMs ?? globalThis.__TS_RUNTIME__.getAssertionTimeoutMs());
|
|
896
|
+
if (!ok) {
|
|
897
|
+
throw new Error(`Expected collection "${selectorTextValue}" not to be empty`);
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
);
|
|
901
|
+
}
|
|
902
|
+
};
|
|
903
|
+
return collectionProxy;
|
|
904
|
+
}
|
|
905
|
+
function resolveTargetProxy(target) {
|
|
906
|
+
if (typeof target === "string") {
|
|
907
|
+
return globalThis.$(target);
|
|
908
|
+
}
|
|
909
|
+
if (target && typeof target === "object" && "selector" in target) {
|
|
910
|
+
return target;
|
|
911
|
+
}
|
|
912
|
+
throw new Error(`Invalid element target: ${String(target)}`);
|
|
913
|
+
}
|
|
914
|
+
globalThis.__TS_RUNTIME__.createElementProxy = createElementProxy;
|
|
915
|
+
globalThis.__TS_RUNTIME__.createCollectionProxy = createCollectionProxy;
|
|
916
|
+
globalThis.__TS_RUNTIME__.resolveTargetProxy = resolveTargetProxy;
|
|
917
|
+
|
|
918
|
+
// ../matchers/src/runtime/spectra.ts
|
|
919
|
+
function buildSpectra() {
|
|
920
|
+
const driver = () => globalThis.__TS_RUNTIME__.activeDriver;
|
|
921
|
+
return {
|
|
922
|
+
get: (target) => globalThis.__TS_RUNTIME__.resolveTargetProxy(target),
|
|
923
|
+
getAll: (selector) => globalThis.__TS_RUNTIME__.createCollectionProxy(selector),
|
|
924
|
+
navigate: (url) => driver().navigate(url),
|
|
925
|
+
back: () => driver().back(),
|
|
926
|
+
forward: () => driver().forward(),
|
|
927
|
+
refresh: () => driver().refresh(),
|
|
928
|
+
setViewport: (width, height) => driver().setViewport(width, height),
|
|
929
|
+
wait: (ms) => driver().pause(ms),
|
|
930
|
+
click: async (target, _textOrOptions) => {
|
|
931
|
+
const el = globalThis.__TS_RUNTIME__.resolveTargetProxy(target);
|
|
932
|
+
await el.click();
|
|
933
|
+
},
|
|
934
|
+
doubleClick: async (target) => {
|
|
935
|
+
const el = globalThis.__TS_RUNTIME__.resolveTargetProxy(target);
|
|
936
|
+
await el.doubleClick();
|
|
937
|
+
},
|
|
938
|
+
rightClick: async (target) => {
|
|
939
|
+
const el = globalThis.__TS_RUNTIME__.resolveTargetProxy(target);
|
|
940
|
+
await el.rightClick();
|
|
941
|
+
},
|
|
942
|
+
type: async (target, text, options) => {
|
|
943
|
+
const el = globalThis.__TS_RUNTIME__.resolveTargetProxy(target);
|
|
944
|
+
await el.type(text, options);
|
|
945
|
+
},
|
|
946
|
+
clear: async (target) => {
|
|
947
|
+
const el = globalThis.__TS_RUNTIME__.resolveTargetProxy(target);
|
|
948
|
+
await el.clear();
|
|
949
|
+
},
|
|
950
|
+
select: async (target, option) => {
|
|
951
|
+
const el = globalThis.__TS_RUNTIME__.resolveTargetProxy(target);
|
|
952
|
+
await el.select(option);
|
|
953
|
+
},
|
|
954
|
+
hover: async (target) => {
|
|
955
|
+
const el = globalThis.__TS_RUNTIME__.resolveTargetProxy(target);
|
|
956
|
+
await el.hover();
|
|
957
|
+
},
|
|
958
|
+
focus: async (target) => {
|
|
959
|
+
const el = globalThis.__TS_RUNTIME__.resolveTargetProxy(target);
|
|
960
|
+
await el.focus();
|
|
961
|
+
},
|
|
962
|
+
dragDrop: async (source, destination) => {
|
|
963
|
+
const srcEl = globalThis.__TS_RUNTIME__.resolveTargetProxy(source);
|
|
964
|
+
await srcEl.dragDrop(destination);
|
|
965
|
+
},
|
|
966
|
+
scrollIntoView: async (target) => {
|
|
967
|
+
const el = globalThis.__TS_RUNTIME__.resolveTargetProxy(target);
|
|
968
|
+
await el.scrollIntoView();
|
|
969
|
+
},
|
|
970
|
+
scroll: async (options) => {
|
|
971
|
+
await driver().scroll(options);
|
|
972
|
+
},
|
|
973
|
+
swipe: async (options) => {
|
|
974
|
+
await driver().swipe(options);
|
|
975
|
+
},
|
|
976
|
+
longPress: async (target, options) => {
|
|
977
|
+
const el = globalThis.__TS_RUNTIME__.resolveTargetProxy(target);
|
|
978
|
+
await el.longPress(options);
|
|
979
|
+
},
|
|
980
|
+
pressKey: async (key) => {
|
|
981
|
+
await driver().pressKey(key);
|
|
982
|
+
},
|
|
983
|
+
grantPermission: async (name) => {
|
|
984
|
+
await driver().grantPermission(name);
|
|
985
|
+
},
|
|
986
|
+
waitForElement: async (target, timeoutMs) => {
|
|
987
|
+
const el = globalThis.__TS_RUNTIME__.resolveTargetProxy(target);
|
|
988
|
+
await el.waitForElement(timeoutMs);
|
|
989
|
+
},
|
|
990
|
+
intercept: async (patternOrOptions, maybeMethodOrHandler, maybeFixture, maybeOptions) => {
|
|
991
|
+
return await driver().intercept(patternOrOptions, maybeMethodOrHandler, maybeFixture, maybeOptions);
|
|
992
|
+
},
|
|
993
|
+
clearMocks: () => {
|
|
994
|
+
driver().clearMocks();
|
|
995
|
+
},
|
|
996
|
+
get browser() {
|
|
997
|
+
return driver();
|
|
998
|
+
},
|
|
999
|
+
env: globalThis.__TS_RUNTIME__.config.environmentVariables || {}
|
|
1000
|
+
};
|
|
1001
|
+
}
|
|
1002
|
+
globalThis.__TS_RUNTIME__.buildSpectra = buildSpectra;
|
|
1003
|
+
|
|
1004
|
+
// src/auto_import.ts
|
|
1005
|
+
async function dynamicImport(path) {
|
|
1006
|
+
const vitePath = `/@fs/${path.replace(/\\/g, "/")}`.replace(/\/@fs\/\//g, "/@fs/");
|
|
1007
|
+
return import(
|
|
1008
|
+
/* @vite-ignore */
|
|
1009
|
+
vitePath
|
|
1010
|
+
);
|
|
1011
|
+
}
|
|
1012
|
+
async function bootstrapAutoImports() {
|
|
1013
|
+
const manifest = __TESTSPECTRA_AUTO_IMPORT_MANIFEST__;
|
|
1014
|
+
for (const { name, path } of manifest.pageObjects) {
|
|
1015
|
+
const mod = await dynamicImport(path);
|
|
1016
|
+
globalThis[name] = mod.default ?? mod;
|
|
1017
|
+
}
|
|
1018
|
+
const fixtures = {};
|
|
1019
|
+
for (const { name, path } of manifest.fixtures) {
|
|
1020
|
+
const mod = await dynamicImport(path);
|
|
1021
|
+
fixtures[name] = mod.default ?? mod;
|
|
1022
|
+
}
|
|
1023
|
+
globalThis.Fixture = fixtures;
|
|
1024
|
+
const steps = {};
|
|
1025
|
+
for (const { name, path } of manifest.steps) {
|
|
1026
|
+
const mod = await dynamicImport(path);
|
|
1027
|
+
steps[name] = mod.default ?? mod;
|
|
1028
|
+
}
|
|
1029
|
+
globalThis.Step = steps;
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
// src/dom_driver_bridge.ts
|
|
1033
|
+
var cachedUser = null;
|
|
1034
|
+
async function getUser() {
|
|
1035
|
+
if (!cachedUser) {
|
|
1036
|
+
const { userEvent } = await import("@testing-library/user-event");
|
|
1037
|
+
cachedUser = userEvent.setup();
|
|
1038
|
+
}
|
|
1039
|
+
return cachedUser;
|
|
1040
|
+
}
|
|
1041
|
+
function resolveSelector(selector) {
|
|
1042
|
+
const trimmed = selector.trim();
|
|
1043
|
+
if (trimmed.startsWith("~")) {
|
|
1044
|
+
return `[aria-label=${JSON.stringify(trimmed.slice(1))}]`;
|
|
1045
|
+
}
|
|
1046
|
+
return trimmed;
|
|
1047
|
+
}
|
|
1048
|
+
function resolveEl(selector, index) {
|
|
1049
|
+
const css = resolveSelector(selector);
|
|
1050
|
+
if (index === null) {
|
|
1051
|
+
return document.querySelector(css);
|
|
1052
|
+
}
|
|
1053
|
+
const all = Array.from(document.querySelectorAll(css));
|
|
1054
|
+
if (index >= 0) return all[index] ?? null;
|
|
1055
|
+
return all[all.length + index] ?? null;
|
|
1056
|
+
}
|
|
1057
|
+
function isFormEl(el) {
|
|
1058
|
+
const tag = el.tagName;
|
|
1059
|
+
return tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT";
|
|
1060
|
+
}
|
|
1061
|
+
var DomDriverBridge = class {
|
|
1062
|
+
mockRules = [];
|
|
1063
|
+
consoleErrors = [];
|
|
1064
|
+
recordedNetwork = [];
|
|
1065
|
+
/** Polls until `selector` resolves to an element, then returns it — shared by every
|
|
1066
|
+
* interaction method below (user-event needs a real element handle, not a selector string). */
|
|
1067
|
+
async waitForEl(selector, index) {
|
|
1068
|
+
let found = null;
|
|
1069
|
+
const ok = await globalThis.__TS_RUNTIME__.pollCondition(async () => {
|
|
1070
|
+
found = resolveEl(selector, index);
|
|
1071
|
+
return found !== null;
|
|
1072
|
+
}, globalThis.__TS_RUNTIME__.getImplicitWaitMs());
|
|
1073
|
+
if (!ok || !found) throw new Error("Element not found: " + JSON.stringify(selector));
|
|
1074
|
+
return found;
|
|
1075
|
+
}
|
|
1076
|
+
async connect() {
|
|
1077
|
+
}
|
|
1078
|
+
async close() {
|
|
1079
|
+
}
|
|
1080
|
+
async navigate(_targetUrl) {
|
|
1081
|
+
throw new Error(
|
|
1082
|
+
"Spectra.navigate() is not supported in component testing mode. Use Spectra.mount(<Component />) to render a component tree instead of navigating a URL."
|
|
1083
|
+
);
|
|
1084
|
+
}
|
|
1085
|
+
async setCookies(setCookieHeaders, _url) {
|
|
1086
|
+
const headers = Array.isArray(setCookieHeaders) ? setCookieHeaders : [setCookieHeaders];
|
|
1087
|
+
for (const header of headers) {
|
|
1088
|
+
if (typeof document !== "undefined") {
|
|
1089
|
+
document.cookie = header;
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
async back() {
|
|
1094
|
+
throw new Error("Spectra.back() is not supported in component testing mode.");
|
|
1095
|
+
}
|
|
1096
|
+
async forward() {
|
|
1097
|
+
throw new Error("Spectra.forward() is not supported in component testing mode.");
|
|
1098
|
+
}
|
|
1099
|
+
async refresh() {
|
|
1100
|
+
throw new Error("Spectra.refresh() is not supported in component testing mode.");
|
|
1101
|
+
}
|
|
1102
|
+
async setViewport(width, height) {
|
|
1103
|
+
try {
|
|
1104
|
+
window.resizeTo(width, height);
|
|
1105
|
+
} catch {
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
async pause(ms) {
|
|
1109
|
+
await new Promise((r) => setTimeout(r, ms));
|
|
1110
|
+
}
|
|
1111
|
+
// ─── Element Actions ─────────────────────────────────────────────────────
|
|
1112
|
+
async click(selector, index = null) {
|
|
1113
|
+
const el = await this.waitForEl(selector, index);
|
|
1114
|
+
await (await getUser()).click(el);
|
|
1115
|
+
}
|
|
1116
|
+
async doubleClick(selector, index = null) {
|
|
1117
|
+
const el = await this.waitForEl(selector, index);
|
|
1118
|
+
await (await getUser()).dblClick(el);
|
|
1119
|
+
}
|
|
1120
|
+
async rightClick(selector, index = null) {
|
|
1121
|
+
const el = await this.waitForEl(selector, index);
|
|
1122
|
+
await (await getUser()).pointer({ keys: "[MouseRight]", target: el });
|
|
1123
|
+
}
|
|
1124
|
+
async setValue(selector, value, index = null) {
|
|
1125
|
+
const el = await this.waitForEl(selector, index);
|
|
1126
|
+
if (!isFormEl(el)) throw new Error("Element is not a form field: " + JSON.stringify(selector));
|
|
1127
|
+
const u = await getUser();
|
|
1128
|
+
await u.clear(el);
|
|
1129
|
+
if (String(value).length > 0) {
|
|
1130
|
+
await u.type(el, String(value));
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
async clearValue(selector, index = null) {
|
|
1134
|
+
const el = await this.waitForEl(selector, index);
|
|
1135
|
+
await (await getUser()).clear(el);
|
|
1136
|
+
}
|
|
1137
|
+
async select(selector, option, index = null) {
|
|
1138
|
+
const ok = await globalThis.__TS_RUNTIME__.pollCondition(async () => {
|
|
1139
|
+
const el = resolveEl(selector, index);
|
|
1140
|
+
if (!el || el.tagName !== "SELECT") return false;
|
|
1141
|
+
for (let i = 0; i < el.options.length; i++) {
|
|
1142
|
+
const opt = el.options[i];
|
|
1143
|
+
if (opt.text === option || opt.value === option) {
|
|
1144
|
+
el.selectedIndex = i;
|
|
1145
|
+
el.dispatchEvent(new Event("change", { bubbles: true }));
|
|
1146
|
+
return true;
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
return false;
|
|
1150
|
+
});
|
|
1151
|
+
if (!ok) throw new Error("Select option not found: " + JSON.stringify(option));
|
|
1152
|
+
}
|
|
1153
|
+
async hover(selector, index = null) {
|
|
1154
|
+
const el = await this.waitForEl(selector, index);
|
|
1155
|
+
await (await getUser()).hover(el);
|
|
1156
|
+
}
|
|
1157
|
+
async focus(selector, index = null) {
|
|
1158
|
+
const ok = await globalThis.__TS_RUNTIME__.pollCondition(async () => {
|
|
1159
|
+
const el = resolveEl(selector, index);
|
|
1160
|
+
if (!el) return false;
|
|
1161
|
+
el.focus?.();
|
|
1162
|
+
return true;
|
|
1163
|
+
});
|
|
1164
|
+
if (!ok) throw new Error("Element not found: " + JSON.stringify(selector));
|
|
1165
|
+
}
|
|
1166
|
+
async dragDrop(sourceSelector, targetSelector, sourceIndex = null) {
|
|
1167
|
+
const src = resolveEl(sourceSelector, sourceIndex);
|
|
1168
|
+
const dest = resolveEl(targetSelector, null);
|
|
1169
|
+
if (!src || !dest) throw new Error("Drag source or destination not found");
|
|
1170
|
+
const dataTransfer = new DataTransfer();
|
|
1171
|
+
src.dispatchEvent(new DragEvent("dragstart", { bubbles: true, dataTransfer }));
|
|
1172
|
+
dest.dispatchEvent(new DragEvent("dragover", { bubbles: true, dataTransfer }));
|
|
1173
|
+
dest.dispatchEvent(new DragEvent("drop", { bubbles: true, dataTransfer }));
|
|
1174
|
+
src.dispatchEvent(new DragEvent("dragend", { bubbles: true, dataTransfer }));
|
|
1175
|
+
}
|
|
1176
|
+
async longPress(selector, duration, index = null) {
|
|
1177
|
+
const el = resolveEl(selector, index);
|
|
1178
|
+
if (!el) throw new Error("Element not found: " + JSON.stringify(selector));
|
|
1179
|
+
el.dispatchEvent(new TouchEvent("touchstart", { bubbles: true }));
|
|
1180
|
+
await this.pause(duration);
|
|
1181
|
+
el.dispatchEvent(new TouchEvent("touchend", { bubbles: true }));
|
|
1182
|
+
}
|
|
1183
|
+
async scrollIntoView(selector, index = null) {
|
|
1184
|
+
const el = resolveEl(selector, index);
|
|
1185
|
+
el?.scrollIntoView({ block: "center", inline: "center" });
|
|
1186
|
+
}
|
|
1187
|
+
// ─── Element State Inspection ────────────────────────────────────────────
|
|
1188
|
+
async getText(selector, index = null) {
|
|
1189
|
+
const el = resolveEl(selector, index);
|
|
1190
|
+
if (!el) return "";
|
|
1191
|
+
return el.innerText !== void 0 ? el.innerText : el.textContent || "";
|
|
1192
|
+
}
|
|
1193
|
+
async getValue(selector, index = null) {
|
|
1194
|
+
const el = resolveEl(selector, index);
|
|
1195
|
+
if (!el) return "";
|
|
1196
|
+
if (isFormEl(el)) return el.value ?? "";
|
|
1197
|
+
return el.getAttribute("value") || "";
|
|
1198
|
+
}
|
|
1199
|
+
async isDisplayed(selector, index = null) {
|
|
1200
|
+
const el = resolveEl(selector, index);
|
|
1201
|
+
if (!el) return false;
|
|
1202
|
+
const style = window.getComputedStyle(el);
|
|
1203
|
+
if (style.visibility === "hidden" || style.display === "none" || style.opacity === "0") return false;
|
|
1204
|
+
const rect = el.getBoundingClientRect();
|
|
1205
|
+
return rect.width > 0 && rect.height > 0 || el.getClientRects().length > 0 || el.offsetWidth > 0 && el.offsetHeight > 0;
|
|
1206
|
+
}
|
|
1207
|
+
async isExisting(selector, index = null) {
|
|
1208
|
+
return resolveEl(selector, index) !== null;
|
|
1209
|
+
}
|
|
1210
|
+
async isEnabled(selector, index = null) {
|
|
1211
|
+
const el = resolveEl(selector, index);
|
|
1212
|
+
if (!el) return false;
|
|
1213
|
+
return !el.disabled && !el.hasAttribute("disabled") && !el.classList.contains("disabled") && el.getAttribute("aria-disabled") !== "true";
|
|
1214
|
+
}
|
|
1215
|
+
async isSelected(selector, index = null) {
|
|
1216
|
+
const el = resolveEl(selector, index);
|
|
1217
|
+
if (!el) return false;
|
|
1218
|
+
return !!el.checked || el.getAttribute("aria-checked") === "true" || el.getAttribute("data-state") === "checked";
|
|
1219
|
+
}
|
|
1220
|
+
async isFocused(selector, index = null) {
|
|
1221
|
+
const el = resolveEl(selector, index);
|
|
1222
|
+
if (!el) return false;
|
|
1223
|
+
return document.activeElement === el || el.matches(":focus");
|
|
1224
|
+
}
|
|
1225
|
+
async isClickable(selector, index = null) {
|
|
1226
|
+
const el = resolveEl(selector, index);
|
|
1227
|
+
if (!el) return false;
|
|
1228
|
+
const style = window.getComputedStyle(el);
|
|
1229
|
+
if (style.visibility === "hidden" || style.display === "none" || style.opacity === "0") return false;
|
|
1230
|
+
const rect = el.getBoundingClientRect();
|
|
1231
|
+
if (rect.width === 0 && rect.height === 0) return false;
|
|
1232
|
+
return !el.disabled && !el.hasAttribute("disabled") && !el.classList.contains("disabled") && el.getAttribute("aria-disabled") !== "true";
|
|
1233
|
+
}
|
|
1234
|
+
async hasClass(selector, className, index = null) {
|
|
1235
|
+
const el = resolveEl(selector, index);
|
|
1236
|
+
return el ? el.classList.contains(className) : false;
|
|
1237
|
+
}
|
|
1238
|
+
async getAttribute(selector, name, index = null) {
|
|
1239
|
+
const el = resolveEl(selector, index);
|
|
1240
|
+
return el ? el.getAttribute(name) : null;
|
|
1241
|
+
}
|
|
1242
|
+
async getCSSProperty(selector, name, index = null) {
|
|
1243
|
+
const el = resolveEl(selector, index);
|
|
1244
|
+
return { value: el ? window.getComputedStyle(el).getPropertyValue(name) : "" };
|
|
1245
|
+
}
|
|
1246
|
+
async count(selector) {
|
|
1247
|
+
return document.querySelectorAll(resolveSelector(selector)).length;
|
|
1248
|
+
}
|
|
1249
|
+
// ─── Gestures & Keys ─────────────────────────────────────────────────────
|
|
1250
|
+
async pressKey(key) {
|
|
1251
|
+
const active = document.activeElement || document.body;
|
|
1252
|
+
const keyName = String(key);
|
|
1253
|
+
active.dispatchEvent(new KeyboardEvent("keydown", { key: keyName, bubbles: true }));
|
|
1254
|
+
active.dispatchEvent(new KeyboardEvent("keyup", { key: keyName, bubbles: true }));
|
|
1255
|
+
}
|
|
1256
|
+
async scroll(options = {}) {
|
|
1257
|
+
if (options.selector) {
|
|
1258
|
+
await this.scrollIntoView(String(options.selector), null);
|
|
1259
|
+
return;
|
|
1260
|
+
}
|
|
1261
|
+
const dir = options.direction || "down";
|
|
1262
|
+
const px = options.pixels || 500;
|
|
1263
|
+
const deltaY = dir === "up" ? -px : dir === "down" ? px : 0;
|
|
1264
|
+
const deltaX = dir === "left" ? -px : dir === "right" ? px : 0;
|
|
1265
|
+
window.scrollBy(deltaX, deltaY);
|
|
1266
|
+
}
|
|
1267
|
+
async swipe(options) {
|
|
1268
|
+
const dist = options.distance || 300;
|
|
1269
|
+
const deltaY = options.direction === "up" ? dist : options.direction === "down" ? -dist : 0;
|
|
1270
|
+
const deltaX = options.direction === "left" ? dist : options.direction === "right" ? -dist : 0;
|
|
1271
|
+
window.scrollBy(deltaX, deltaY);
|
|
1272
|
+
}
|
|
1273
|
+
async grantPermission(_name) {
|
|
1274
|
+
}
|
|
1275
|
+
// ─── Browser-Level Bridge (SpectraBrowserBridge) ────────────────────────
|
|
1276
|
+
async getUrl() {
|
|
1277
|
+
return window.location.href;
|
|
1278
|
+
}
|
|
1279
|
+
async getTitle() {
|
|
1280
|
+
return document.title;
|
|
1281
|
+
}
|
|
1282
|
+
async execute(fn, ...args) {
|
|
1283
|
+
if (typeof fn === "function") {
|
|
1284
|
+
return fn(...args);
|
|
1285
|
+
}
|
|
1286
|
+
return new Function(`return (${fn});`)();
|
|
1287
|
+
}
|
|
1288
|
+
async evaluate(expr) {
|
|
1289
|
+
return new Function(`return (${expr});`)();
|
|
1290
|
+
}
|
|
1291
|
+
async waitUntil(fn, opts) {
|
|
1292
|
+
const ok = await globalThis.__TS_RUNTIME__.pollCondition(fn, opts?.timeout);
|
|
1293
|
+
if (!ok) throw new Error(opts?.timeoutMsg || "waitUntil() condition was not met in time");
|
|
1294
|
+
return ok;
|
|
1295
|
+
}
|
|
1296
|
+
async getLogs(_type) {
|
|
1297
|
+
return [];
|
|
1298
|
+
}
|
|
1299
|
+
async shouldHaveUrl(expectedUrl) {
|
|
1300
|
+
const actual = await this.getUrl();
|
|
1301
|
+
if (actual !== expectedUrl) throw new Error(`Expected browser URL to be "${expectedUrl}", but got "${actual}"`);
|
|
1302
|
+
}
|
|
1303
|
+
async shouldContainUrl(expectedSubstr) {
|
|
1304
|
+
const actual = await this.getUrl();
|
|
1305
|
+
if (!actual.includes(expectedSubstr)) {
|
|
1306
|
+
throw new Error(`Expected browser URL to contain "${expectedSubstr}", but got "${actual}"`);
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
async shouldHaveTitle(expectedTitle) {
|
|
1310
|
+
const actual = await this.getTitle();
|
|
1311
|
+
if (actual !== expectedTitle) throw new Error(`Expected page title to be "${expectedTitle}", but got "${actual}"`);
|
|
1312
|
+
}
|
|
1313
|
+
async shouldContainTitle(expectedSubstr) {
|
|
1314
|
+
const actual = await this.getTitle();
|
|
1315
|
+
if (!actual.includes(expectedSubstr)) {
|
|
1316
|
+
throw new Error(`Expected page title to contain "${expectedSubstr}", but got "${actual}"`);
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
async shouldBeLoaded() {
|
|
1320
|
+
if (document.readyState !== "complete" && document.readyState !== "interactive") {
|
|
1321
|
+
throw new Error(`Expected document to be loaded, but readyState is "${document.readyState}"`);
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
async shouldHaveNoConsoleErrors() {
|
|
1325
|
+
if (this.consoleErrors.length > 0) {
|
|
1326
|
+
throw new Error(
|
|
1327
|
+
`Expected no console errors, but found ${this.consoleErrors.length}: ${this.consoleErrors.join("; ")}`
|
|
1328
|
+
);
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
async clearCookies() {
|
|
1332
|
+
document.cookie.split(";").forEach((c) => {
|
|
1333
|
+
const name = c.split("=")[0]?.trim();
|
|
1334
|
+
if (name) document.cookie = `${name}=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/`;
|
|
1335
|
+
});
|
|
1336
|
+
}
|
|
1337
|
+
async clearLocalStorage() {
|
|
1338
|
+
window.localStorage.clear();
|
|
1339
|
+
}
|
|
1340
|
+
// ─── Network Interception (Out of Scope for Component Testing MVP) ─────
|
|
1341
|
+
async intercept(_patternOrOptions, _method, _fixture, _options) {
|
|
1342
|
+
throw new Error(
|
|
1343
|
+
'Spectra.intercept() is not supported in component testing mode \u2014 there is no network boundary between the test and the mounted component to intercept. Mock fetch/HTTP clients directly instead (e.g. `vi.spyOn(global, "fetch")` or MSW).'
|
|
1344
|
+
);
|
|
1345
|
+
}
|
|
1346
|
+
clearMocks() {
|
|
1347
|
+
}
|
|
1348
|
+
};
|
|
1349
|
+
|
|
1350
|
+
// src/mount.ts
|
|
1351
|
+
import * as ReactDOMClient from "react-dom/client";
|
|
1352
|
+
var ROOT_ID = "spectra-root";
|
|
1353
|
+
var cachedRoot = null;
|
|
1354
|
+
function getCreateRoot() {
|
|
1355
|
+
const client = ReactDOMClient;
|
|
1356
|
+
return client.createRoot || client.default?.createRoot || client.default;
|
|
1357
|
+
}
|
|
1358
|
+
function ensureContainer() {
|
|
1359
|
+
let container = document.getElementById(ROOT_ID);
|
|
1360
|
+
if (!container) {
|
|
1361
|
+
container = document.createElement("div");
|
|
1362
|
+
container.id = ROOT_ID;
|
|
1363
|
+
document.body.appendChild(container);
|
|
1364
|
+
}
|
|
1365
|
+
return container;
|
|
1366
|
+
}
|
|
1367
|
+
async function mount(element) {
|
|
1368
|
+
const container = ensureContainer();
|
|
1369
|
+
if (!cachedRoot) {
|
|
1370
|
+
const createRootFn = getCreateRoot();
|
|
1371
|
+
cachedRoot = createRootFn(container);
|
|
1372
|
+
}
|
|
1373
|
+
const componentName = (typeof element.type === "string" ? element.type : element.type?.displayName || element.type?.name) || "component";
|
|
1374
|
+
return globalThis.__TS_RUNTIME__.trackStep(
|
|
1375
|
+
"action",
|
|
1376
|
+
{ type: "action", key: "mount", target: componentName, args: [] },
|
|
1377
|
+
async () => {
|
|
1378
|
+
cachedRoot.render(element);
|
|
1379
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
1380
|
+
return globalThis.__TS_RUNTIME__.createElementProxy(`#${ROOT_ID}`);
|
|
1381
|
+
}
|
|
1382
|
+
);
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
// src/setup.ts
|
|
1386
|
+
var activeDriver = new DomDriverBridge();
|
|
1387
|
+
globalThis.__TS_RUNTIME__.activeDriver = activeDriver;
|
|
1388
|
+
globalThis.browser = activeDriver;
|
|
1389
|
+
globalThis.$ = function(selector) {
|
|
1390
|
+
return globalThis.__TS_RUNTIME__.createElementProxy(selector);
|
|
1391
|
+
};
|
|
1392
|
+
globalThis.$$ = function(selector) {
|
|
1393
|
+
return globalThis.__TS_RUNTIME__.createCollectionProxy(selector);
|
|
1394
|
+
};
|
|
1395
|
+
var spectra = globalThis.__TS_RUNTIME__.buildSpectra();
|
|
1396
|
+
spectra.mount = mount;
|
|
1397
|
+
globalThis.Spectra = spectra;
|
|
1398
|
+
await bootstrapAutoImports();
|
|
1399
|
+
var testStartedAt = 0;
|
|
1400
|
+
beforeEach((ctx) => {
|
|
1401
|
+
testStartedAt = Date.now();
|
|
1402
|
+
globalThis.__CURRENT_TEST_TITLE__ = ctx.task.name;
|
|
1403
|
+
globalThis.__TS_RUNTIME__.emitEvent("TESTSPECTRA_TEST_START", JSON.stringify({ title: ctx.task.name }));
|
|
1404
|
+
});
|
|
1405
|
+
afterEach((ctx) => {
|
|
1406
|
+
const duration = Date.now() - testStartedAt;
|
|
1407
|
+
const failed = ctx.task.result?.state === "fail";
|
|
1408
|
+
if (failed) {
|
|
1409
|
+
const message = ctx.task.result?.errors?.[0]?.message;
|
|
1410
|
+
if (message) globalThis.__TS_RUNTIME__.emitEvent("TESTSPECTRA_TEST_ERROR", message);
|
|
1411
|
+
globalThis.__TS_RUNTIME__.emitEvent("TESTSPECTRA_TEST_FAIL", `${ctx.task.name} (${duration}ms)`);
|
|
1412
|
+
} else {
|
|
1413
|
+
globalThis.__TS_RUNTIME__.emitEvent("TESTSPECTRA_TEST_PASS", `${ctx.task.name} (${duration}ms)`);
|
|
1414
|
+
}
|
|
1415
|
+
});
|