@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/dist/spectra.js CHANGED
@@ -1,6 +1,8 @@
1
- import { SingleElementRunner } from "./runner/single.js";
2
- import { MultiElementRunner } from "./runner/collection.js";
3
- import { resolveElement } from "./matchers.js";
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").should("have.length", 5);
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).should("be.visible");
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").should("have.length.greaterThan", 0);
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, textOrOptions) {
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, textOrOptions) {
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 === "number" ? options : options?.duration || 1000;
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 === "function") {
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 === "up" ? -delta : direction === "down" ? delta : 0,
275
- left: direction === "left" ? -delta : direction === "right" ? delta : 0,
276
- behavior: "smooth",
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 || "down", options?.pixels || 500);
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 === "function") {
297
+ if (typeof browser.touchAction === 'function') {
294
298
  const distance = options.distance || 300;
295
299
  await browser.touchAction([
296
- { action: "press", x: 200, y: 500 },
300
+ { action: 'press', x: 200, y: 500 },
297
301
  {
298
- action: "moveTo",
299
- x: options.direction === "right" ? 200 + distance : options.direction === "left" ? 200 - distance : 200,
300
- y: options.direction === "down" ? 500 + distance : options.direction === "up" ? 500 - distance : 500,
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: "release" },
306
+ { action: 'release' },
303
307
  ]);
304
308
  }
305
309
  else {
306
310
  // Fallback to web scroll
307
- await this.scroll({ direction: options.direction, pixels: options.distance });
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
  /**