@testspectra/matchers 1.0.70 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +48 -0
- package/dist/__tests__/intercept.test.d.ts +1 -0
- package/dist/__tests__/intercept.test.js +183 -0
- package/dist/__tests__/matchers.test.d.ts +1 -0
- package/dist/__tests__/matchers.test.js +161 -0
- package/dist/index.d.ts +3 -21
- package/dist/index.js +3 -21
- package/dist/intercept/cdp-handler.d.ts +22 -0
- package/dist/intercept/cdp-handler.js +123 -0
- package/dist/intercept/index.d.ts +4 -0
- package/dist/intercept/index.js +4 -0
- package/dist/intercept/mock-handle.d.ts +49 -0
- package/dist/intercept/mock-handle.js +124 -0
- package/dist/intercept/mock-registry.d.ts +18 -0
- package/dist/intercept/mock-registry.js +97 -0
- package/dist/intercept/types.d.ts +43 -0
- package/dist/intercept/types.js +1 -0
- package/dist/matchers.d.ts +5 -1
- package/dist/matchers.js +116 -29
- package/dist/proto.d.ts +18 -283
- package/dist/proto.js +1 -114
- package/dist/reporter.d.ts +30 -0
- package/dist/reporter.js +85 -0
- package/dist/runner/collection.d.ts +33 -20
- package/dist/runner/collection.js +78 -26
- package/dist/runner/single.d.ts +123 -32
- package/dist/runner/single.js +266 -48
- package/dist/spectra.d.ts +32 -8
- package/dist/spectra.js +157 -40
- package/dist/types.d.ts +1195 -28
- package/package.json +16 -12
- package/src/index.ts +3 -22
- package/src/proto.ts +22 -433
- package/src/types.ts +1403 -96
- package/tsconfig.json +2 -2
- package/src/matchers.ts +0 -146
- package/src/runner/collection.ts +0 -153
- package/src/runner/single.ts +0 -301
- package/src/spectra.ts +0 -376
package/dist/spectra.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import { SingleElementRunner } from
|
|
2
|
-
import { MultiElementRunner } from
|
|
3
|
-
import { resolveElement } from
|
|
1
|
+
import { SingleElementRunner } from './runner/single.js';
|
|
2
|
+
import { MultiElementRunner } from './runner/collection.js';
|
|
3
|
+
import { executeBrowserAssertion, resolveElement } from './matchers.js';
|
|
4
|
+
import { CdpHandler, MockRegistry } from './intercept/index.js';
|
|
5
|
+
import { trackCommand } from './reporter.js';
|
|
4
6
|
/**
|
|
5
7
|
* Main TestSpectra cross-platform automation and assertion orchestrator.
|
|
6
8
|
*
|
|
@@ -14,7 +16,7 @@ import { resolveElement } from "./matchers.js";
|
|
|
14
16
|
* await Spectra.get("#username").type("admin");
|
|
15
17
|
*
|
|
16
18
|
* // Collection assertion
|
|
17
|
-
* await Spectra.getAll(".item").
|
|
19
|
+
* await Spectra.getAll(".item").shouldHaveLength(5);
|
|
18
20
|
*
|
|
19
21
|
* // Mobile touch swipe
|
|
20
22
|
* await Spectra.swipe({ direction: "left", distance: 350 });
|
|
@@ -28,7 +30,7 @@ export class SpectraStatic {
|
|
|
28
30
|
* @example
|
|
29
31
|
* ```ts
|
|
30
32
|
* await Spectra.get("#login-btn").click();
|
|
31
|
-
* await Spectra.get(LoginPage.submitButton).
|
|
33
|
+
* await Spectra.get(LoginPage.submitButton).shouldBeVisible();
|
|
32
34
|
* ```
|
|
33
35
|
* @returns `SingleElementRunner` for chaining actions and assertions.
|
|
34
36
|
*/
|
|
@@ -41,7 +43,7 @@ export class SpectraStatic {
|
|
|
41
43
|
* @param selector - Selector string matching multiple elements.
|
|
42
44
|
* @example
|
|
43
45
|
* ```ts
|
|
44
|
-
* await Spectra.getAll(".product-card").
|
|
46
|
+
* await Spectra.getAll(".product-card").shouldHaveLengthGreaterThan(0);
|
|
45
47
|
* await Spectra.getAll("li.tab").first().click();
|
|
46
48
|
* ```
|
|
47
49
|
* @returns `MultiElementRunner` for collection assertions and indexing.
|
|
@@ -63,7 +65,7 @@ export class SpectraStatic {
|
|
|
63
65
|
navigate(url) {
|
|
64
66
|
return new SingleElementRunner(undefined, async () => {
|
|
65
67
|
await browser.url(url);
|
|
66
|
-
});
|
|
68
|
+
}, { type: 'action', key: 'navigate', target: undefined, args: [url] });
|
|
67
69
|
}
|
|
68
70
|
/**
|
|
69
71
|
* Navigates back one step in the browser / app history.
|
|
@@ -76,7 +78,7 @@ export class SpectraStatic {
|
|
|
76
78
|
back() {
|
|
77
79
|
return new SingleElementRunner(undefined, async () => {
|
|
78
80
|
await browser.back();
|
|
79
|
-
});
|
|
81
|
+
}, { type: 'action', key: 'back', target: undefined, args: [] });
|
|
80
82
|
}
|
|
81
83
|
/**
|
|
82
84
|
* Refreshes the current browser page.
|
|
@@ -89,7 +91,7 @@ export class SpectraStatic {
|
|
|
89
91
|
refresh() {
|
|
90
92
|
return new SingleElementRunner(undefined, async () => {
|
|
91
93
|
await browser.refresh();
|
|
92
|
-
});
|
|
94
|
+
}, { type: 'action', key: 'refresh', target: undefined, args: [] });
|
|
93
95
|
}
|
|
94
96
|
// --- Direct Interaction Actions ---
|
|
95
97
|
/**
|
|
@@ -103,11 +105,11 @@ export class SpectraStatic {
|
|
|
103
105
|
* await Spectra.click(LoginPage.loginBtn);
|
|
104
106
|
* ```
|
|
105
107
|
*/
|
|
106
|
-
click(target,
|
|
108
|
+
click(target, _textOrOptions) {
|
|
107
109
|
return new SingleElementRunner(target, async () => {
|
|
108
110
|
const el = await resolveElement(target);
|
|
109
111
|
await el.click();
|
|
110
|
-
});
|
|
112
|
+
}, { type: 'action', key: 'click', target: target, args: [] });
|
|
111
113
|
}
|
|
112
114
|
/**
|
|
113
115
|
* Performs a double-click on the specified element.
|
|
@@ -119,11 +121,11 @@ export class SpectraStatic {
|
|
|
119
121
|
* await Spectra.doubleClick(".grid-row");
|
|
120
122
|
* ```
|
|
121
123
|
*/
|
|
122
|
-
doubleClick(target,
|
|
124
|
+
doubleClick(target, _textOrOptions) {
|
|
123
125
|
return new SingleElementRunner(target, async () => {
|
|
124
126
|
const el = await resolveElement(target);
|
|
125
127
|
await el.doubleClick();
|
|
126
|
-
});
|
|
128
|
+
}, { type: 'action', key: 'doubleClick', target: target, args: [] });
|
|
127
129
|
}
|
|
128
130
|
/**
|
|
129
131
|
* Performs a long-press touch or hold gesture on the specified element.
|
|
@@ -137,19 +139,16 @@ export class SpectraStatic {
|
|
|
137
139
|
* ```
|
|
138
140
|
*/
|
|
139
141
|
longPress(target, options) {
|
|
140
|
-
const duration = typeof options ===
|
|
142
|
+
const duration = typeof options === 'number' ? options : options?.duration || 1000;
|
|
141
143
|
return new SingleElementRunner(target, async () => {
|
|
142
144
|
const el = await resolveElement(target);
|
|
143
|
-
if (typeof el.touchAction ===
|
|
144
|
-
await el.touchAction([
|
|
145
|
-
{ action: "longPress", ms: duration },
|
|
146
|
-
{ action: "release" },
|
|
147
|
-
]);
|
|
145
|
+
if (typeof el.touchAction === 'function') {
|
|
146
|
+
await el.touchAction([{ action: 'longPress', ms: duration }, { action: 'release' }]);
|
|
148
147
|
}
|
|
149
148
|
else {
|
|
150
149
|
await el.click();
|
|
151
150
|
}
|
|
152
|
-
});
|
|
151
|
+
}, { type: 'action', key: 'longPress', target: target, args: [duration] });
|
|
153
152
|
}
|
|
154
153
|
/**
|
|
155
154
|
* Types text into the specified element.
|
|
@@ -170,7 +169,7 @@ export class SpectraStatic {
|
|
|
170
169
|
await el.clearValue();
|
|
171
170
|
}
|
|
172
171
|
await el.setValue(value);
|
|
173
|
-
});
|
|
172
|
+
}, { type: 'action', key: 'type', target: target, args: [value] });
|
|
174
173
|
}
|
|
175
174
|
/**
|
|
176
175
|
* Clears the input content from the specified element.
|
|
@@ -185,7 +184,7 @@ export class SpectraStatic {
|
|
|
185
184
|
return new SingleElementRunner(target, async () => {
|
|
186
185
|
const el = await resolveElement(target);
|
|
187
186
|
await el.clearValue();
|
|
188
|
-
});
|
|
187
|
+
}, { type: 'action', key: 'clear', target: target, args: [] });
|
|
189
188
|
}
|
|
190
189
|
/**
|
|
191
190
|
* Selects an option from a `<select>` dropdown by its visible text.
|
|
@@ -201,7 +200,7 @@ export class SpectraStatic {
|
|
|
201
200
|
return new SingleElementRunner(target, async () => {
|
|
202
201
|
const el = await resolveElement(target);
|
|
203
202
|
await el.selectByVisibleText(value);
|
|
204
|
-
});
|
|
203
|
+
}, { type: 'action', key: 'select', target: target, args: [value] });
|
|
205
204
|
}
|
|
206
205
|
/**
|
|
207
206
|
* Hovers the mouse pointer over the specified element.
|
|
@@ -216,7 +215,7 @@ export class SpectraStatic {
|
|
|
216
215
|
return new SingleElementRunner(target, async () => {
|
|
217
216
|
const el = await resolveElement(target);
|
|
218
217
|
await el.moveTo();
|
|
219
|
-
});
|
|
218
|
+
}, { type: 'action', key: 'hover', target: target, args: [] });
|
|
220
219
|
}
|
|
221
220
|
/**
|
|
222
221
|
* Simulates pressing a specific keyboard key.
|
|
@@ -231,7 +230,7 @@ export class SpectraStatic {
|
|
|
231
230
|
pressKey(key) {
|
|
232
231
|
return new SingleElementRunner(undefined, async () => {
|
|
233
232
|
await browser.keys(key);
|
|
234
|
-
});
|
|
233
|
+
}, { type: 'action', key: 'pressKey', target: undefined, args: [key] });
|
|
235
234
|
}
|
|
236
235
|
/**
|
|
237
236
|
* Drags a source element and drops it onto a destination target.
|
|
@@ -248,7 +247,7 @@ export class SpectraStatic {
|
|
|
248
247
|
const source = await resolveElement(sourceTarget);
|
|
249
248
|
const target = await resolveElement(destTarget);
|
|
250
249
|
await source.dragAndDrop(target);
|
|
251
|
-
});
|
|
250
|
+
}, { type: 'action', key: 'dragDrop', target: sourceTarget, args: [destTarget] });
|
|
252
251
|
}
|
|
253
252
|
// --- Gesture Actions ---
|
|
254
253
|
/**
|
|
@@ -271,12 +270,17 @@ export class SpectraStatic {
|
|
|
271
270
|
await browser.execute((direction, pixels) => {
|
|
272
271
|
const delta = pixels || 500;
|
|
273
272
|
window.scrollBy({
|
|
274
|
-
top: direction ===
|
|
275
|
-
left: direction ===
|
|
276
|
-
behavior:
|
|
273
|
+
top: direction === 'up' ? -delta : direction === 'down' ? delta : 0,
|
|
274
|
+
left: direction === 'left' ? -delta : direction === 'right' ? delta : 0,
|
|
275
|
+
behavior: 'smooth',
|
|
277
276
|
});
|
|
278
|
-
}, options?.direction ||
|
|
277
|
+
}, options?.direction || 'down', options?.pixels || 500);
|
|
279
278
|
}
|
|
279
|
+
}, {
|
|
280
|
+
type: 'action',
|
|
281
|
+
key: 'scroll',
|
|
282
|
+
target: options?.selector,
|
|
283
|
+
args: [options?.direction || 'down', options?.pixels || 500],
|
|
280
284
|
});
|
|
281
285
|
}
|
|
282
286
|
/**
|
|
@@ -290,23 +294,26 @@ export class SpectraStatic {
|
|
|
290
294
|
*/
|
|
291
295
|
swipe(options) {
|
|
292
296
|
return new SingleElementRunner(options.selector, async () => {
|
|
293
|
-
if (typeof browser.touchAction ===
|
|
297
|
+
if (typeof browser.touchAction === 'function') {
|
|
294
298
|
const distance = options.distance || 300;
|
|
295
299
|
await browser.touchAction([
|
|
296
|
-
{ action:
|
|
300
|
+
{ action: 'press', x: 200, y: 500 },
|
|
297
301
|
{
|
|
298
|
-
action:
|
|
299
|
-
x: options.direction ===
|
|
300
|
-
y: options.direction ===
|
|
302
|
+
action: 'moveTo',
|
|
303
|
+
x: options.direction === 'right' ? 200 + distance : options.direction === 'left' ? 200 - distance : 200,
|
|
304
|
+
y: options.direction === 'down' ? 500 + distance : options.direction === 'up' ? 500 - distance : 500,
|
|
301
305
|
},
|
|
302
|
-
{ action:
|
|
306
|
+
{ action: 'release' },
|
|
303
307
|
]);
|
|
304
308
|
}
|
|
305
309
|
else {
|
|
306
310
|
// Fallback to web scroll
|
|
307
|
-
await this.scroll({
|
|
311
|
+
await this.scroll({
|
|
312
|
+
direction: options.direction,
|
|
313
|
+
pixels: options.distance,
|
|
314
|
+
});
|
|
308
315
|
}
|
|
309
|
-
});
|
|
316
|
+
}, { type: 'action', key: 'swipe', target: options.selector, args: [options.direction, options.distance || 300] });
|
|
310
317
|
}
|
|
311
318
|
// --- Timing Actions ---
|
|
312
319
|
/**
|
|
@@ -321,7 +328,7 @@ export class SpectraStatic {
|
|
|
321
328
|
wait(durationMs) {
|
|
322
329
|
return new SingleElementRunner(undefined, async () => {
|
|
323
330
|
await browser.pause(durationMs);
|
|
324
|
-
});
|
|
331
|
+
}, { type: 'action', key: 'wait', target: undefined, args: [durationMs] });
|
|
325
332
|
}
|
|
326
333
|
/**
|
|
327
334
|
* Waits until the specified element is displayed on the screen.
|
|
@@ -337,7 +344,117 @@ export class SpectraStatic {
|
|
|
337
344
|
return new SingleElementRunner(target, async () => {
|
|
338
345
|
const el = await resolveElement(target);
|
|
339
346
|
await el.waitForDisplayed({ timeout: timeoutMs });
|
|
340
|
-
});
|
|
347
|
+
}, { type: 'action', key: 'waitForElement', target: target, args: [] });
|
|
348
|
+
}
|
|
349
|
+
// --- Network Interception & Mocking Actions ---
|
|
350
|
+
/**
|
|
351
|
+
* Intercepts HTTP/HTTPS network traffic matching the given URL rule.
|
|
352
|
+
*
|
|
353
|
+
* @param rule - URL pattern string or InterceptRule configuration.
|
|
354
|
+
* @example
|
|
355
|
+
* ```ts
|
|
356
|
+
* const mock = await Spectra.intercept({
|
|
357
|
+
* url: "/api/v1/users",
|
|
358
|
+
* method: "GET",
|
|
359
|
+
* response: { status: 200, body: [{ id: 1, name: "Admin" }] }
|
|
360
|
+
* });
|
|
361
|
+
* ```
|
|
362
|
+
* @returns `MockHandle` for sequential queue responses, abort, and assertions.
|
|
363
|
+
*/
|
|
364
|
+
async intercept(rule) {
|
|
365
|
+
const normalizedRule = typeof rule === 'string' ? { url: rule } : rule;
|
|
366
|
+
return CdpHandler.registerRule(normalizedRule);
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Clears all active mock rules and resets the mock registry for worker isolation.
|
|
370
|
+
*/
|
|
371
|
+
clearMocks() {
|
|
372
|
+
MockRegistry.getInstance().clear();
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* Browser-level assertions and utilities (cookies, localStorage, url, title).
|
|
376
|
+
*/
|
|
377
|
+
get browser() {
|
|
378
|
+
return {
|
|
379
|
+
shouldHaveUrl: async (expectedUrl) => {
|
|
380
|
+
const payload = {
|
|
381
|
+
type: 'assertion',
|
|
382
|
+
key: 'shouldHaveUrl',
|
|
383
|
+
args: [expectedUrl],
|
|
384
|
+
};
|
|
385
|
+
await trackCommand('browser', payload, async () => {
|
|
386
|
+
await executeBrowserAssertion('shouldHaveUrl', [expectedUrl]);
|
|
387
|
+
});
|
|
388
|
+
},
|
|
389
|
+
shouldContainUrl: async (expectedSubstr) => {
|
|
390
|
+
const payload = {
|
|
391
|
+
type: 'assertion',
|
|
392
|
+
key: 'shouldContainUrl',
|
|
393
|
+
args: [expectedSubstr],
|
|
394
|
+
};
|
|
395
|
+
await trackCommand('browser', payload, async () => {
|
|
396
|
+
await executeBrowserAssertion('shouldContainUrl', [expectedSubstr]);
|
|
397
|
+
});
|
|
398
|
+
},
|
|
399
|
+
shouldHaveTitle: async (expectedTitle) => {
|
|
400
|
+
const payload = {
|
|
401
|
+
type: 'assertion',
|
|
402
|
+
key: 'shouldHaveTitle',
|
|
403
|
+
args: [expectedTitle],
|
|
404
|
+
};
|
|
405
|
+
await trackCommand('browser', payload, async () => {
|
|
406
|
+
await executeBrowserAssertion('shouldHaveTitle', [expectedTitle]);
|
|
407
|
+
});
|
|
408
|
+
},
|
|
409
|
+
shouldContainTitle: async (expectedSubstr) => {
|
|
410
|
+
const payload = {
|
|
411
|
+
type: 'assertion',
|
|
412
|
+
key: 'shouldContainTitle',
|
|
413
|
+
args: [expectedSubstr],
|
|
414
|
+
};
|
|
415
|
+
await trackCommand('browser', payload, async () => {
|
|
416
|
+
await executeBrowserAssertion('shouldContainTitle', [expectedSubstr]);
|
|
417
|
+
});
|
|
418
|
+
},
|
|
419
|
+
shouldBePageLoaded: async () => {
|
|
420
|
+
const payload = {
|
|
421
|
+
type: 'assertion',
|
|
422
|
+
key: 'shouldBePageLoaded',
|
|
423
|
+
args: [],
|
|
424
|
+
};
|
|
425
|
+
await trackCommand('browser', payload, async () => {
|
|
426
|
+
await executeBrowserAssertion('shouldBePageLoaded');
|
|
427
|
+
});
|
|
428
|
+
},
|
|
429
|
+
shouldHaveNoConsoleErrors: async () => {
|
|
430
|
+
const payload = {
|
|
431
|
+
type: 'assertion',
|
|
432
|
+
key: 'shouldHaveNoConsoleErrors',
|
|
433
|
+
args: [],
|
|
434
|
+
};
|
|
435
|
+
await trackCommand('browser', payload, async () => {
|
|
436
|
+
await executeBrowserAssertion('shouldHaveNoConsoleErrors');
|
|
437
|
+
});
|
|
438
|
+
},
|
|
439
|
+
clearCookies: async () => {
|
|
440
|
+
const payload = { type: 'action', key: 'clearCookies', args: [] };
|
|
441
|
+
await trackCommand('browser', payload, async () => {
|
|
442
|
+
const b = typeof browser !== 'undefined' ? browser : globalThis.browser;
|
|
443
|
+
if (b && typeof b.deleteAllCookies === 'function') {
|
|
444
|
+
await b.deleteAllCookies();
|
|
445
|
+
}
|
|
446
|
+
});
|
|
447
|
+
},
|
|
448
|
+
clearLocalStorage: async () => {
|
|
449
|
+
const payload = { type: 'action', key: 'clearLocalStorage', args: [] };
|
|
450
|
+
await trackCommand('browser', payload, async () => {
|
|
451
|
+
const b = typeof browser !== 'undefined' ? browser : globalThis.browser;
|
|
452
|
+
if (b && typeof b.execute === 'function') {
|
|
453
|
+
await b.execute(() => localStorage.clear());
|
|
454
|
+
}
|
|
455
|
+
});
|
|
456
|
+
},
|
|
457
|
+
};
|
|
341
458
|
}
|
|
342
459
|
}
|
|
343
460
|
/**
|