@testspectra/matchers 1.1.0 → 1.1.8-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/__tests__/matcher-contracts.test.d.ts +1 -0
- package/dist/__tests__/matcher-contracts.test.js +498 -0
- package/dist/contract.d.ts +173 -0
- package/dist/contract.js +10 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/matchers.d.ts +10 -3
- package/dist/matchers.js +576 -135
- package/dist/proto.d.ts +8 -0
- package/dist/reporter.js +13 -1
- package/dist/runner/collection.js +32 -6
- package/dist/runner/single.d.ts +2 -2
- package/dist/runner/single.js +24 -7
- package/dist/semantic.d.ts +11 -0
- package/dist/semantic.js +141 -0
- package/dist/types.d.ts +246 -115
- package/package.json +11 -10
- package/src/contract.ts +223 -0
- package/src/index.ts +1 -0
- package/src/proto.ts +8 -0
- package/src/runtime/assertions.ts +453 -0
- package/src/runtime/element_actions.ts +178 -0
- package/src/runtime/element_proxy.ts +200 -0
- package/src/runtime/element_state.ts +94 -0
- package/src/runtime/spectra.ts +110 -0
- package/src/types.ts +251 -124
- package/tsconfig.json +1 -1
package/src/types.ts
CHANGED
|
@@ -106,6 +106,26 @@ export type Direction = 'up' | 'down' | 'left' | 'right';
|
|
|
106
106
|
*/
|
|
107
107
|
export type ElementTarget = string | SingleElementProxy | Promise<SingleElementProxy>;
|
|
108
108
|
|
|
109
|
+
/**
|
|
110
|
+
* Internal descriptor for a selector resolved relative to a parent element, produced by
|
|
111
|
+
* `element.get()` / `element.getAll()` chaining (see `SingleElementProxy.get`). Recursive so
|
|
112
|
+
* arbitrarily deep chains (e.g. `Spectra.getAll('.card').nth(2).get('.buy-btn')`) carry their
|
|
113
|
+
* full ancestry down to the driver.
|
|
114
|
+
*
|
|
115
|
+
* Resolution differs per platform: web (CDP) nests real DOM `.querySelector()` calls against the
|
|
116
|
+
* resolved parent; Android has no ancestor/descendant API, so the parent's bounds rectangle is
|
|
117
|
+
* used to filter candidates via containment (a child is "within" the parent if its bounds sit
|
|
118
|
+
* inside the parent's).
|
|
119
|
+
*/
|
|
120
|
+
export interface ScopedSelector {
|
|
121
|
+
/** This level's own selector string (`~`/`#` shorthand or raw CSS/xpath), not the full chain. */
|
|
122
|
+
selector: string;
|
|
123
|
+
/** Positional index at this level, or null. */
|
|
124
|
+
index: number | null;
|
|
125
|
+
/** The element this selector is scoped within, or undefined for a root-level selector. */
|
|
126
|
+
parent?: ScopedSelector;
|
|
127
|
+
}
|
|
128
|
+
|
|
109
129
|
/**
|
|
110
130
|
* Configuration options for scroll actions.
|
|
111
131
|
*
|
|
@@ -179,6 +199,26 @@ export interface TypeOptions {
|
|
|
179
199
|
clearFirst?: boolean;
|
|
180
200
|
}
|
|
181
201
|
|
|
202
|
+
/**
|
|
203
|
+
* Per-call override for how long an assertion polls before failing, mirroring Playwright's
|
|
204
|
+
* `{ timeout }` option on web-first assertions (`expect(locator).toHaveText(x, { timeout })`).
|
|
205
|
+
*
|
|
206
|
+
* Defaults to the adaptive assertion timeout (`Spectra`'s fail-fast cap, distinct from
|
|
207
|
+
* `implicitWait`) when omitted — pass this when a specific assertion is known to need longer,
|
|
208
|
+
* e.g. right after a deliberately delayed `Spectra.intercept(..., { delayMs })` mock, without
|
|
209
|
+
* raising the timeout for every other assertion in the test.
|
|
210
|
+
*
|
|
211
|
+
* @example
|
|
212
|
+
* ```ts
|
|
213
|
+
* const mock = await Spectra.intercept('/api/report', { response: { delayMs: 4000, body } });
|
|
214
|
+
* await ReportPage.statusBadge.shouldHaveText('Ready', { timeoutMs: 5000 });
|
|
215
|
+
* ```
|
|
216
|
+
*/
|
|
217
|
+
export interface AssertionOptions {
|
|
218
|
+
/** Maximum time (in milliseconds) to keep polling before the assertion fails. */
|
|
219
|
+
timeoutMs?: number;
|
|
220
|
+
}
|
|
221
|
+
|
|
182
222
|
/**
|
|
183
223
|
* Canonical assertion matcher keys for single element verification.
|
|
184
224
|
* Dispatched internally by semantic receiver methods (`.shouldBeVisible()`, `.shouldHaveText()`, etc.).
|
|
@@ -244,7 +284,7 @@ export interface ElementReceiverAssertions<TReturn = Promise<void>> {
|
|
|
244
284
|
* await LoginPage.submitButton.shouldBeVisible();
|
|
245
285
|
* ```
|
|
246
286
|
*/
|
|
247
|
-
shouldBeVisible(): TReturn;
|
|
287
|
+
shouldBeVisible(options?: AssertionOptions): TReturn;
|
|
248
288
|
|
|
249
289
|
/**
|
|
250
290
|
* Asserts that the target element is hidden, detached, or not displayed on the page.
|
|
@@ -254,7 +294,7 @@ export interface ElementReceiverAssertions<TReturn = Promise<void>> {
|
|
|
254
294
|
* await Spectra.get('.loading-spinner').shouldNotBeVisible();
|
|
255
295
|
* ```
|
|
256
296
|
*/
|
|
257
|
-
shouldNotBeVisible(): TReturn;
|
|
297
|
+
shouldNotBeVisible(options?: AssertionOptions): TReturn;
|
|
258
298
|
|
|
259
299
|
/**
|
|
260
300
|
* Asserts that the target element exists in the DOM.
|
|
@@ -264,7 +304,7 @@ export interface ElementReceiverAssertions<TReturn = Promise<void>> {
|
|
|
264
304
|
* await Spectra.get('#cookie-consent-modal').shouldExist();
|
|
265
305
|
* ```
|
|
266
306
|
*/
|
|
267
|
-
shouldExist(): TReturn;
|
|
307
|
+
shouldExist(options?: AssertionOptions): TReturn;
|
|
268
308
|
|
|
269
309
|
/**
|
|
270
310
|
* Asserts that the target element does not exist in the DOM.
|
|
@@ -274,7 +314,7 @@ export interface ElementReceiverAssertions<TReturn = Promise<void>> {
|
|
|
274
314
|
* await Spectra.get('#deleted-record-row').shouldNotExist();
|
|
275
315
|
* ```
|
|
276
316
|
*/
|
|
277
|
-
shouldNotExist(): TReturn;
|
|
317
|
+
shouldNotExist(options?: AssertionOptions): TReturn;
|
|
278
318
|
|
|
279
319
|
/**
|
|
280
320
|
* Asserts that the target element is visible, enabled, and clickable.
|
|
@@ -284,7 +324,7 @@ export interface ElementReceiverAssertions<TReturn = Promise<void>> {
|
|
|
284
324
|
* await Spectra.get('button[type="submit"]').shouldBeClickable();
|
|
285
325
|
* ```
|
|
286
326
|
*/
|
|
287
|
-
shouldBeClickable(): TReturn;
|
|
327
|
+
shouldBeClickable(options?: AssertionOptions): TReturn;
|
|
288
328
|
|
|
289
329
|
/**
|
|
290
330
|
* Asserts that the target element is disabled, covered, or not clickable.
|
|
@@ -294,7 +334,7 @@ export interface ElementReceiverAssertions<TReturn = Promise<void>> {
|
|
|
294
334
|
* await Spectra.get('button.disabled-action').shouldNotBeClickable();
|
|
295
335
|
* ```
|
|
296
336
|
*/
|
|
297
|
-
shouldNotBeClickable(): TReturn;
|
|
337
|
+
shouldNotBeClickable(options?: AssertionOptions): TReturn;
|
|
298
338
|
|
|
299
339
|
/**
|
|
300
340
|
* Asserts that the target form input/button is enabled (not disabled).
|
|
@@ -304,7 +344,7 @@ export interface ElementReceiverAssertions<TReturn = Promise<void>> {
|
|
|
304
344
|
* await Spectra.get('#username-field').shouldBeEnabled();
|
|
305
345
|
* ```
|
|
306
346
|
*/
|
|
307
|
-
shouldBeEnabled(): TReturn;
|
|
347
|
+
shouldBeEnabled(options?: AssertionOptions): TReturn;
|
|
308
348
|
|
|
309
349
|
/**
|
|
310
350
|
* Asserts that the target form input/button has the disabled state/attribute.
|
|
@@ -314,7 +354,7 @@ export interface ElementReceiverAssertions<TReturn = Promise<void>> {
|
|
|
314
354
|
* await Spectra.get('#submit-order-btn').shouldBeDisabled();
|
|
315
355
|
* ```
|
|
316
356
|
*/
|
|
317
|
-
shouldBeDisabled(): TReturn;
|
|
357
|
+
shouldBeDisabled(options?: AssertionOptions): TReturn;
|
|
318
358
|
|
|
319
359
|
/**
|
|
320
360
|
* Asserts that the target checkbox or radio input is checked/selected.
|
|
@@ -324,7 +364,7 @@ export interface ElementReceiverAssertions<TReturn = Promise<void>> {
|
|
|
324
364
|
* await Spectra.get('#terms-checkbox').shouldBeChecked();
|
|
325
365
|
* ```
|
|
326
366
|
*/
|
|
327
|
-
shouldBeChecked(): TReturn;
|
|
367
|
+
shouldBeChecked(options?: AssertionOptions): TReturn;
|
|
328
368
|
|
|
329
369
|
/**
|
|
330
370
|
* Asserts that the target checkbox or radio input is unchecked/deselected.
|
|
@@ -334,7 +374,7 @@ export interface ElementReceiverAssertions<TReturn = Promise<void>> {
|
|
|
334
374
|
* await Spectra.get('#subscribe-newsletter').shouldNotBeChecked();
|
|
335
375
|
* ```
|
|
336
376
|
*/
|
|
337
|
-
shouldNotBeChecked(): TReturn;
|
|
377
|
+
shouldNotBeChecked(options?: AssertionOptions): TReturn;
|
|
338
378
|
|
|
339
379
|
/**
|
|
340
380
|
* Asserts that the target element currently holds active document focus.
|
|
@@ -344,7 +384,7 @@ export interface ElementReceiverAssertions<TReturn = Promise<void>> {
|
|
|
344
384
|
* await Spectra.get('#search-input').shouldBeFocused();
|
|
345
385
|
* ```
|
|
346
386
|
*/
|
|
347
|
-
shouldBeFocused(): TReturn;
|
|
387
|
+
shouldBeFocused(options?: AssertionOptions): TReturn;
|
|
348
388
|
|
|
349
389
|
/**
|
|
350
390
|
* Asserts that the target element does not hold active document focus.
|
|
@@ -354,7 +394,7 @@ export interface ElementReceiverAssertions<TReturn = Promise<void>> {
|
|
|
354
394
|
* await Spectra.get('#blur-input').shouldNotBeFocused();
|
|
355
395
|
* ```
|
|
356
396
|
*/
|
|
357
|
-
shouldNotBeFocused(): TReturn;
|
|
397
|
+
shouldNotBeFocused(options?: AssertionOptions): TReturn;
|
|
358
398
|
|
|
359
399
|
/**
|
|
360
400
|
* Asserts that the target element's text content matches the expected string or regular expression.
|
|
@@ -366,7 +406,7 @@ export interface ElementReceiverAssertions<TReturn = Promise<void>> {
|
|
|
366
406
|
* await Spectra.get('.badge').shouldHaveText(/Active|Pending/);
|
|
367
407
|
* ```
|
|
368
408
|
*/
|
|
369
|
-
shouldHaveText(expected: string | RegExp): TReturn;
|
|
409
|
+
shouldHaveText(expected: string | RegExp, options?: AssertionOptions): TReturn;
|
|
370
410
|
|
|
371
411
|
/**
|
|
372
412
|
* Asserts that the target element's text content does not match the expected string or regular expression.
|
|
@@ -377,7 +417,7 @@ export interface ElementReceiverAssertions<TReturn = Promise<void>> {
|
|
|
377
417
|
* await Spectra.get('.status-label').shouldNotHaveText('Error');
|
|
378
418
|
* ```
|
|
379
419
|
*/
|
|
380
|
-
shouldNotHaveText(expected: string | RegExp): TReturn;
|
|
420
|
+
shouldNotHaveText(expected: string | RegExp, options?: AssertionOptions): TReturn;
|
|
381
421
|
|
|
382
422
|
/**
|
|
383
423
|
* Asserts that the target element's text content contains the specified substring.
|
|
@@ -388,7 +428,7 @@ export interface ElementReceiverAssertions<TReturn = Promise<void>> {
|
|
|
388
428
|
* await Spectra.get('.toast-message').shouldContainText('Successfully saved');
|
|
389
429
|
* ```
|
|
390
430
|
*/
|
|
391
|
-
shouldContainText(substring: string): TReturn;
|
|
431
|
+
shouldContainText(substring: string, options?: AssertionOptions): TReturn;
|
|
392
432
|
|
|
393
433
|
/**
|
|
394
434
|
* Asserts that the target element's text content does not contain the specified substring.
|
|
@@ -399,7 +439,7 @@ export interface ElementReceiverAssertions<TReturn = Promise<void>> {
|
|
|
399
439
|
* await Spectra.get('.log-output').shouldNotContainText('Fatal Exception');
|
|
400
440
|
* ```
|
|
401
441
|
*/
|
|
402
|
-
shouldNotContainText(substring: string): TReturn;
|
|
442
|
+
shouldNotContainText(substring: string, options?: AssertionOptions): TReturn;
|
|
403
443
|
|
|
404
444
|
/**
|
|
405
445
|
* Asserts that the form input or textarea element's value exactly equals the specified string.
|
|
@@ -410,7 +450,7 @@ export interface ElementReceiverAssertions<TReturn = Promise<void>> {
|
|
|
410
450
|
* await Spectra.get('input[name="email"]').shouldHaveValue('admin@testspectra.dev');
|
|
411
451
|
* ```
|
|
412
452
|
*/
|
|
413
|
-
shouldHaveValue(value: string): TReturn;
|
|
453
|
+
shouldHaveValue(value: string, options?: AssertionOptions): TReturn;
|
|
414
454
|
|
|
415
455
|
/**
|
|
416
456
|
* Asserts that the form input or textarea element's value does not equal the specified string.
|
|
@@ -421,7 +461,7 @@ export interface ElementReceiverAssertions<TReturn = Promise<void>> {
|
|
|
421
461
|
* await Spectra.get('input[name="role"]').shouldNotHaveValue('guest');
|
|
422
462
|
* ```
|
|
423
463
|
*/
|
|
424
|
-
shouldNotHaveValue(value: string): TReturn;
|
|
464
|
+
shouldNotHaveValue(value: string, options?: AssertionOptions): TReturn;
|
|
425
465
|
|
|
426
466
|
/**
|
|
427
467
|
* Asserts that the form input or textarea element's value contains the specified substring.
|
|
@@ -432,7 +472,7 @@ export interface ElementReceiverAssertions<TReturn = Promise<void>> {
|
|
|
432
472
|
* await Spectra.get('input[name="email"]').shouldContainValue('@testspectra.dev');
|
|
433
473
|
* ```
|
|
434
474
|
*/
|
|
435
|
-
shouldContainValue(substring: string): TReturn;
|
|
475
|
+
shouldContainValue(substring: string, options?: AssertionOptions): TReturn;
|
|
436
476
|
|
|
437
477
|
/**
|
|
438
478
|
* Asserts that the form input or textarea element's value does not contain the specified substring.
|
|
@@ -443,7 +483,7 @@ export interface ElementReceiverAssertions<TReturn = Promise<void>> {
|
|
|
443
483
|
* await Spectra.get('input[name="url"]').shouldNotContainValue('http://');
|
|
444
484
|
* ```
|
|
445
485
|
*/
|
|
446
|
-
shouldNotContainValue(substring: string): TReturn;
|
|
486
|
+
shouldNotContainValue(substring: string, options?: AssertionOptions): TReturn;
|
|
447
487
|
|
|
448
488
|
/**
|
|
449
489
|
* Asserts that the element has the specified attribute, and optionally that its value matches.
|
|
@@ -456,7 +496,7 @@ export interface ElementReceiverAssertions<TReturn = Promise<void>> {
|
|
|
456
496
|
* await Spectra.get('input.required-field').shouldHaveAttribute('required');
|
|
457
497
|
* ```
|
|
458
498
|
*/
|
|
459
|
-
shouldHaveAttribute(name: string, value?: string): TReturn;
|
|
499
|
+
shouldHaveAttribute(name: string, value?: string, options?: AssertionOptions): TReturn;
|
|
460
500
|
|
|
461
501
|
/**
|
|
462
502
|
* Asserts that the element does not have the specified attribute.
|
|
@@ -467,7 +507,7 @@ export interface ElementReceiverAssertions<TReturn = Promise<void>> {
|
|
|
467
507
|
* await Spectra.get('button#action-btn').shouldNotHaveAttribute('disabled');
|
|
468
508
|
* ```
|
|
469
509
|
*/
|
|
470
|
-
shouldNotHaveAttribute(name: string): TReturn;
|
|
510
|
+
shouldNotHaveAttribute(name: string, options?: AssertionOptions): TReturn;
|
|
471
511
|
|
|
472
512
|
/**
|
|
473
513
|
* Asserts that the element contains the specified CSS class name.
|
|
@@ -478,7 +518,7 @@ export interface ElementReceiverAssertions<TReturn = Promise<void>> {
|
|
|
478
518
|
* await Spectra.get('.nav-tab').shouldHaveClass('active');
|
|
479
519
|
* ```
|
|
480
520
|
*/
|
|
481
|
-
shouldHaveClass(className: string): TReturn;
|
|
521
|
+
shouldHaveClass(className: string, options?: AssertionOptions): TReturn;
|
|
482
522
|
|
|
483
523
|
/**
|
|
484
524
|
* Asserts that the element does not contain the specified CSS class name.
|
|
@@ -489,7 +529,7 @@ export interface ElementReceiverAssertions<TReturn = Promise<void>> {
|
|
|
489
529
|
* await Spectra.get('.modal-backdrop').shouldNotHaveClass('hidden');
|
|
490
530
|
* ```
|
|
491
531
|
*/
|
|
492
|
-
shouldNotHaveClass(className: string): TReturn;
|
|
532
|
+
shouldNotHaveClass(className: string, options?: AssertionOptions): TReturn;
|
|
493
533
|
|
|
494
534
|
/**
|
|
495
535
|
* Asserts that the computed CSS style property of the element equals the specified value.
|
|
@@ -501,7 +541,7 @@ export interface ElementReceiverAssertions<TReturn = Promise<void>> {
|
|
|
501
541
|
* await Spectra.get('.badge-success').shouldHaveCss('color', 'rgb(0, 128, 0)');
|
|
502
542
|
* ```
|
|
503
543
|
*/
|
|
504
|
-
shouldHaveCss(property: string, value: string): TReturn;
|
|
544
|
+
shouldHaveCss(property: string, value: string, options?: AssertionOptions): TReturn;
|
|
505
545
|
|
|
506
546
|
/**
|
|
507
547
|
* Asserts that the computed CSS style property of the element does not equal the specified value.
|
|
@@ -513,7 +553,7 @@ export interface ElementReceiverAssertions<TReturn = Promise<void>> {
|
|
|
513
553
|
* await Spectra.get('.main-content').shouldNotHaveCss('display', 'none');
|
|
514
554
|
* ```
|
|
515
555
|
*/
|
|
516
|
-
shouldNotHaveCss(property: string, value: string): TReturn;
|
|
556
|
+
shouldNotHaveCss(property: string, value: string, options?: AssertionOptions): TReturn;
|
|
517
557
|
}
|
|
518
558
|
|
|
519
559
|
/**
|
|
@@ -530,7 +570,7 @@ export interface CollectionReceiverAssertions<TReturn = Promise<void>> {
|
|
|
530
570
|
* await Spectra.getAll('.user-table-row').shouldHaveLength(10);
|
|
531
571
|
* ```
|
|
532
572
|
*/
|
|
533
|
-
shouldHaveLength(count: number): TReturn;
|
|
573
|
+
shouldHaveLength(count: number, options?: AssertionOptions): TReturn;
|
|
534
574
|
|
|
535
575
|
/**
|
|
536
576
|
* Asserts that the collection does not contain the specified number of matching elements.
|
|
@@ -541,7 +581,7 @@ export interface CollectionReceiverAssertions<TReturn = Promise<void>> {
|
|
|
541
581
|
* await Spectra.getAll('.error-item').shouldNotHaveLength(0);
|
|
542
582
|
* ```
|
|
543
583
|
*/
|
|
544
|
-
shouldNotHaveLength(count: number): TReturn;
|
|
584
|
+
shouldNotHaveLength(count: number, options?: AssertionOptions): TReturn;
|
|
545
585
|
|
|
546
586
|
/**
|
|
547
587
|
* Asserts that the collection contains strictly more than `min` matching elements.
|
|
@@ -552,7 +592,7 @@ export interface CollectionReceiverAssertions<TReturn = Promise<void>> {
|
|
|
552
592
|
* await Spectra.getAll('.search-result-card').shouldHaveLengthGreaterThan(0);
|
|
553
593
|
* ```
|
|
554
594
|
*/
|
|
555
|
-
shouldHaveLengthGreaterThan(min: number): TReturn;
|
|
595
|
+
shouldHaveLengthGreaterThan(min: number, options?: AssertionOptions): TReturn;
|
|
556
596
|
|
|
557
597
|
/**
|
|
558
598
|
* Asserts that the collection contains strictly fewer than `max` matching elements.
|
|
@@ -563,7 +603,7 @@ export interface CollectionReceiverAssertions<TReturn = Promise<void>> {
|
|
|
563
603
|
* await Spectra.getAll('.warning-banner').shouldHaveLengthLessThan(5);
|
|
564
604
|
* ```
|
|
565
605
|
*/
|
|
566
|
-
shouldHaveLengthLessThan(max: number): TReturn;
|
|
606
|
+
shouldHaveLengthLessThan(max: number, options?: AssertionOptions): TReturn;
|
|
567
607
|
|
|
568
608
|
/**
|
|
569
609
|
* Asserts that the collection contains zero matching elements.
|
|
@@ -573,7 +613,7 @@ export interface CollectionReceiverAssertions<TReturn = Promise<void>> {
|
|
|
573
613
|
* await Spectra.getAll('.unread-notification-badge').shouldBeEmpty();
|
|
574
614
|
* ```
|
|
575
615
|
*/
|
|
576
|
-
shouldBeEmpty(): TReturn;
|
|
616
|
+
shouldBeEmpty(options?: AssertionOptions): TReturn;
|
|
577
617
|
|
|
578
618
|
/**
|
|
579
619
|
* Asserts that the collection contains at least one matching element.
|
|
@@ -583,7 +623,7 @@ export interface CollectionReceiverAssertions<TReturn = Promise<void>> {
|
|
|
583
623
|
* await Spectra.getAll('.product-card').shouldNotBeEmpty();
|
|
584
624
|
* ```
|
|
585
625
|
*/
|
|
586
|
-
shouldNotBeEmpty(): TReturn;
|
|
626
|
+
shouldNotBeEmpty(options?: AssertionOptions): TReturn;
|
|
587
627
|
}
|
|
588
628
|
|
|
589
629
|
/**
|
|
@@ -600,7 +640,7 @@ export interface BrowserReceiverAssertions {
|
|
|
600
640
|
* await Spectra.browser.shouldHaveUrl('https://app.testspectra.dev/dashboard');
|
|
601
641
|
* ```
|
|
602
642
|
*/
|
|
603
|
-
shouldHaveUrl(expectedUrl: string): Promise<void>;
|
|
643
|
+
shouldHaveUrl(expectedUrl: string, options?: AssertionOptions): Promise<void>;
|
|
604
644
|
|
|
605
645
|
/**
|
|
606
646
|
* Asserts that current browser URL contains the specified substring.
|
|
@@ -611,7 +651,7 @@ export interface BrowserReceiverAssertions {
|
|
|
611
651
|
* await Spectra.browser.shouldContainUrl('/dashboard');
|
|
612
652
|
* ```
|
|
613
653
|
*/
|
|
614
|
-
shouldContainUrl(expectedSubstr: string): Promise<void>;
|
|
654
|
+
shouldContainUrl(expectedSubstr: string, options?: AssertionOptions): Promise<void>;
|
|
615
655
|
|
|
616
656
|
/**
|
|
617
657
|
* Asserts that current page title exactly matches the expected title.
|
|
@@ -622,7 +662,7 @@ export interface BrowserReceiverAssertions {
|
|
|
622
662
|
* await Spectra.browser.shouldHaveTitle('Dashboard - TestSpectra');
|
|
623
663
|
* ```
|
|
624
664
|
*/
|
|
625
|
-
shouldHaveTitle(expectedTitle: string): Promise<void>;
|
|
665
|
+
shouldHaveTitle(expectedTitle: string, options?: AssertionOptions): Promise<void>;
|
|
626
666
|
|
|
627
667
|
/**
|
|
628
668
|
* Asserts that current page title contains the specified substring.
|
|
@@ -633,7 +673,7 @@ export interface BrowserReceiverAssertions {
|
|
|
633
673
|
* await Spectra.browser.shouldContainTitle('Dashboard');
|
|
634
674
|
* ```
|
|
635
675
|
*/
|
|
636
|
-
shouldContainTitle(expectedSubstr: string): Promise<void>;
|
|
676
|
+
shouldContainTitle(expectedSubstr: string, options?: AssertionOptions): Promise<void>;
|
|
637
677
|
|
|
638
678
|
/**
|
|
639
679
|
* Asserts that the browser document `readyState` is 'complete' or 'interactive'.
|
|
@@ -643,7 +683,7 @@ export interface BrowserReceiverAssertions {
|
|
|
643
683
|
* await Spectra.browser.shouldBeLoaded();
|
|
644
684
|
* ```
|
|
645
685
|
*/
|
|
646
|
-
shouldBeLoaded(): Promise<void>;
|
|
686
|
+
shouldBeLoaded(options?: AssertionOptions): Promise<void>;
|
|
647
687
|
|
|
648
688
|
/**
|
|
649
689
|
* Asserts that no severe or unhandled JavaScript errors occurred in the browser console.
|
|
@@ -653,7 +693,7 @@ export interface BrowserReceiverAssertions {
|
|
|
653
693
|
* await Spectra.browser.shouldHaveNoConsoleErrors();
|
|
654
694
|
* ```
|
|
655
695
|
*/
|
|
656
|
-
shouldHaveNoConsoleErrors(): Promise<void>;
|
|
696
|
+
shouldHaveNoConsoleErrors(options?: AssertionOptions): Promise<void>;
|
|
657
697
|
|
|
658
698
|
/**
|
|
659
699
|
* Clears all browser cookies for the active domain.
|
|
@@ -665,6 +705,29 @@ export interface BrowserReceiverAssertions {
|
|
|
665
705
|
*/
|
|
666
706
|
clearCookies(): Promise<void>;
|
|
667
707
|
|
|
708
|
+
/**
|
|
709
|
+
* Applies one or more raw `Set-Cookie` header values — exactly as received from a `fetch()`
|
|
710
|
+
* response, e.g. `response.headers.getSetCookie()` — to the browser's cookie jar. Operates at
|
|
711
|
+
* the CDP `Network` domain level rather than through `document.cookie`, so `HttpOnly` cookies
|
|
712
|
+
* are fully supported (set, not just read-blocked). Useful for seeding an authenticated session
|
|
713
|
+
* by logging in via a direct API call instead of driving the real login UI — see
|
|
714
|
+
* `docs/v2/features/authentication-and-session-seeding.md`.
|
|
715
|
+
*
|
|
716
|
+
* `url` is required to resolve `Domain`/`Path`/`Secure` defaults for any `Set-Cookie` value that
|
|
717
|
+
* doesn't specify them explicitly (an omitted `Domain` defaults to the issuing request's own
|
|
718
|
+
* host, per RFC 6265) — pass the URL the response actually came from.
|
|
719
|
+
*
|
|
720
|
+
* Web only — Android has no browser/cookie-jar concept; see the docs above for the mobile
|
|
721
|
+
* equivalent (deep-link-triggered, Keystore-backed session seeding).
|
|
722
|
+
*
|
|
723
|
+
* @example
|
|
724
|
+
* ```ts
|
|
725
|
+
* const res = await fetch('https://api.example.com/auth/login', { method: 'POST', body: ... });
|
|
726
|
+
* await Spectra.browser.setCookies(res.headers.getSetCookie(), res.url);
|
|
727
|
+
* ```
|
|
728
|
+
*/
|
|
729
|
+
setCookies(setCookieHeaders: string | string[], url: string): Promise<void>;
|
|
730
|
+
|
|
668
731
|
/**
|
|
669
732
|
* Clears all key-value entries in browser `localStorage`.
|
|
670
733
|
*
|
|
@@ -686,6 +749,31 @@ export interface SingleElementProxy extends ElementReceiverAssertions<Promise<vo
|
|
|
686
749
|
/** Positional index when matched from a collection (or null for standalone selectors). */
|
|
687
750
|
index: number | null;
|
|
688
751
|
|
|
752
|
+
/**
|
|
753
|
+
* Finds `childSelector` scoped to this element's subtree, mirroring Playwright's locator
|
|
754
|
+
* chaining (`parent.locator(child)`) instead of a separate `within()`/`findWithin()` verb.
|
|
755
|
+
* Resolution is a real DOM descendant query on web; on Android (no ancestor API) it's a
|
|
756
|
+
* bounds-containment heuristic over the flat accessibility-tree dump.
|
|
757
|
+
*
|
|
758
|
+
* @example
|
|
759
|
+
* ```ts
|
|
760
|
+
* const modal = Spectra.get('#modal');
|
|
761
|
+
* await modal.get('#save-btn').click();
|
|
762
|
+
* ```
|
|
763
|
+
*/
|
|
764
|
+
get(childSelector: string, index?: number | null): SingleElementProxy;
|
|
765
|
+
|
|
766
|
+
/**
|
|
767
|
+
* Finds all elements matching `childSelector` scoped to this element's subtree — the
|
|
768
|
+
* collection equivalent of `get()`.
|
|
769
|
+
*
|
|
770
|
+
* @example
|
|
771
|
+
* ```ts
|
|
772
|
+
* await Spectra.get('#modal').getAll('.list-item').shouldHaveLength(3);
|
|
773
|
+
* ```
|
|
774
|
+
*/
|
|
775
|
+
getAll(childSelector: string): CollectionProxy;
|
|
776
|
+
|
|
689
777
|
/**
|
|
690
778
|
* Waits until the element exists in the DOM within the specified timeout.
|
|
691
779
|
*
|
|
@@ -982,10 +1070,15 @@ export interface CollectionProxy extends CollectionReceiverAssertions<Promise<vo
|
|
|
982
1070
|
/**
|
|
983
1071
|
* Returns a `SingleElementProxy` pointing to the matching element at the specified zero-based index.
|
|
984
1072
|
*
|
|
1073
|
+
* When this collection itself came from a `.getAll()` chain, `first()`/`last()`/`nth()` carry
|
|
1074
|
+
* that scope forward instead of reverting to an unscoped lookup — e.g. the button below is
|
|
1075
|
+
* resolved within the 3rd `.card`, not just anywhere on the page:
|
|
1076
|
+
*
|
|
985
1077
|
* @param index Zero-based index of the target element.
|
|
986
1078
|
* @example
|
|
987
1079
|
* ```ts
|
|
988
1080
|
* await Spectra.getAll('.list-item').nth(2).click();
|
|
1081
|
+
* await Spectra.getAll('.card').nth(2).get('.buy-btn').click();
|
|
989
1082
|
* ```
|
|
990
1083
|
*/
|
|
991
1084
|
nth(index: number): SingleElementProxy;
|
|
@@ -1001,15 +1094,20 @@ export type SpectraCollection = CollectionProxy;
|
|
|
1001
1094
|
*/
|
|
1002
1095
|
export interface SpectraBrowserBridge extends BrowserReceiverAssertions {
|
|
1003
1096
|
/**
|
|
1004
|
-
*
|
|
1097
|
+
* All network requests recorded so far this session — both genuine (non-intercepted) traffic
|
|
1098
|
+
* and requests an active `intercept()` mock fulfilled. Recording is always on; nothing needs
|
|
1099
|
+
* to be explicitly enabled first. On Android, only requests routed through the worker's local
|
|
1100
|
+
* mock proxy are recorded (see `Spectra.intercept`'s docs on why mobile needs an absolute URL
|
|
1101
|
+
* rather than a relative path) — a request the app makes that never reaches the proxy at all
|
|
1102
|
+
* won't appear here.
|
|
1005
1103
|
*
|
|
1006
|
-
* @param targetUrl Target destination URL.
|
|
1007
1104
|
* @example
|
|
1008
1105
|
* ```ts
|
|
1009
|
-
* await Spectra.
|
|
1106
|
+
* await Spectra.get('~fetch-api-btn').click();
|
|
1107
|
+
* const entry = Spectra.browser.recordedNetwork.find((e) => e.url.includes('/posts'));
|
|
1010
1108
|
* ```
|
|
1011
1109
|
*/
|
|
1012
|
-
|
|
1110
|
+
recordedNetwork: CDPNetworkEntry[];
|
|
1013
1111
|
|
|
1014
1112
|
/**
|
|
1015
1113
|
* Retrieves the current browser URL.
|
|
@@ -1031,59 +1129,6 @@ export interface SpectraBrowserBridge extends BrowserReceiverAssertions {
|
|
|
1031
1129
|
*/
|
|
1032
1130
|
getTitle(): Promise<string>;
|
|
1033
1131
|
|
|
1034
|
-
/**
|
|
1035
|
-
* Navigates one step back in the browser history.
|
|
1036
|
-
*
|
|
1037
|
-
* @example
|
|
1038
|
-
* ```ts
|
|
1039
|
-
* await Spectra.browser.back();
|
|
1040
|
-
* ```
|
|
1041
|
-
*/
|
|
1042
|
-
back(): Promise<void>;
|
|
1043
|
-
|
|
1044
|
-
/**
|
|
1045
|
-
* Navigates one step forward in the browser history.
|
|
1046
|
-
*
|
|
1047
|
-
* @example
|
|
1048
|
-
* ```ts
|
|
1049
|
-
* await Spectra.browser.forward();
|
|
1050
|
-
* ```
|
|
1051
|
-
*/
|
|
1052
|
-
forward(): Promise<void>;
|
|
1053
|
-
|
|
1054
|
-
/**
|
|
1055
|
-
* Reloads / refreshes the current active page.
|
|
1056
|
-
*
|
|
1057
|
-
* @example
|
|
1058
|
-
* ```ts
|
|
1059
|
-
* await Spectra.browser.refresh();
|
|
1060
|
-
* ```
|
|
1061
|
-
*/
|
|
1062
|
-
refresh(): Promise<void>;
|
|
1063
|
-
|
|
1064
|
-
/**
|
|
1065
|
-
* Pauses test execution for the specified number of milliseconds.
|
|
1066
|
-
*
|
|
1067
|
-
* @param ms Duration in milliseconds to pause.
|
|
1068
|
-
* @example
|
|
1069
|
-
* ```ts
|
|
1070
|
-
* await Spectra.browser.pause(1000);
|
|
1071
|
-
* ```
|
|
1072
|
-
*/
|
|
1073
|
-
pause(ms: number): Promise<void>;
|
|
1074
|
-
|
|
1075
|
-
/**
|
|
1076
|
-
* Sets the browser viewport dimensions (width and height in pixels).
|
|
1077
|
-
*
|
|
1078
|
-
* @param width Viewport width in pixels.
|
|
1079
|
-
* @param height Viewport height in pixels.
|
|
1080
|
-
* @example
|
|
1081
|
-
* ```ts
|
|
1082
|
-
* await Spectra.browser.setViewport(1920, 1080);
|
|
1083
|
-
* ```
|
|
1084
|
-
*/
|
|
1085
|
-
setViewport(width: number, height: number): Promise<void>;
|
|
1086
|
-
|
|
1087
1132
|
/**
|
|
1088
1133
|
* Executes a JavaScript function or script snippet in the browser context and returns the result.
|
|
1089
1134
|
*
|
|
@@ -1128,36 +1173,44 @@ export interface SpectraBrowserBridge extends BrowserReceiverAssertions {
|
|
|
1128
1173
|
* @param type Optional log type filter (e.g. 'browser', 'error').
|
|
1129
1174
|
*/
|
|
1130
1175
|
getLogs(type?: string): Promise<Array<{ level: string; message: string }>>;
|
|
1176
|
+
}
|
|
1131
1177
|
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1178
|
+
/**
|
|
1179
|
+
* Intercepted HTTP request metadata captured during test execution.
|
|
1180
|
+
*/
|
|
1181
|
+
export interface InterceptedRequest {
|
|
1182
|
+
/** Optional unique identifier for the request */
|
|
1183
|
+
id?: string;
|
|
1184
|
+
/** Target request URL */
|
|
1185
|
+
url: string;
|
|
1186
|
+
/** HTTP method */
|
|
1187
|
+
method: string;
|
|
1188
|
+
/** Request headers */
|
|
1189
|
+
headers?: Record<string, string>;
|
|
1190
|
+
/** Request body / payload if available */
|
|
1191
|
+
postData?: string;
|
|
1192
|
+
/** Timestamp when intercepted */
|
|
1193
|
+
timestamp: number;
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
/**
|
|
1197
|
+
* Queued one-time mock response item for FIFO polling and retry flows.
|
|
1198
|
+
*/
|
|
1199
|
+
export interface QueuedMockResponse {
|
|
1200
|
+
response: unknown;
|
|
1201
|
+
statusCode: number;
|
|
1202
|
+
headers?: Record<string, string>;
|
|
1203
|
+
/** Milliseconds to wait before fulfilling this response — simulates a late/slow network reply. */
|
|
1204
|
+
delayMs?: number;
|
|
1152
1205
|
}
|
|
1153
1206
|
|
|
1154
1207
|
/**
|
|
1155
|
-
* Mock rule configuration for CDP network request interception.
|
|
1208
|
+
* Mock rule configuration for CDP & Mobile network request interception.
|
|
1156
1209
|
*/
|
|
1157
1210
|
export interface MockRule {
|
|
1158
1211
|
/** URL pattern or substring to match. */
|
|
1159
1212
|
pattern: string;
|
|
1160
|
-
/** HTTP method (e.g. GET, POST). */
|
|
1213
|
+
/** HTTP method (e.g. GET, POST, ALL). */
|
|
1161
1214
|
method: string;
|
|
1162
1215
|
/** Mock response payload. */
|
|
1163
1216
|
response: unknown;
|
|
@@ -1165,8 +1218,20 @@ export interface MockRule {
|
|
|
1165
1218
|
statusCode: number;
|
|
1166
1219
|
/** Custom HTTP response headers. */
|
|
1167
1220
|
headers?: Record<string, string>;
|
|
1221
|
+
/** Milliseconds to wait before fulfilling the default response — simulates a late/slow reply. */
|
|
1222
|
+
delayMs?: number;
|
|
1168
1223
|
/** Total times this mock rule matched and intercepted requests. */
|
|
1169
1224
|
callCount: number;
|
|
1225
|
+
/** FIFO queue of one-time responses (respondOnce) */
|
|
1226
|
+
respondOnceQueue: QueuedMockResponse[];
|
|
1227
|
+
/** Whether requests matching this rule should be aborted / failed */
|
|
1228
|
+
aborted?: boolean;
|
|
1229
|
+
/** Specific error code for network abort simulation */
|
|
1230
|
+
abortReason?: 'Failed' | 'Aborted' | 'TimedOut' | 'ConnectionReset';
|
|
1231
|
+
/** Recorded list of intercepted requests matching this rule */
|
|
1232
|
+
calls: InterceptedRequest[];
|
|
1233
|
+
/** Internal pending waitForCall resolvers (keyed by expected call count). */
|
|
1234
|
+
waitResolvers: Array<{ count: number; resolve: (req: InterceptedRequest) => void }>;
|
|
1170
1235
|
}
|
|
1171
1236
|
|
|
1172
1237
|
/**
|
|
@@ -1174,20 +1239,50 @@ export interface MockRule {
|
|
|
1174
1239
|
*/
|
|
1175
1240
|
export interface MockInterceptHandle {
|
|
1176
1241
|
/**
|
|
1177
|
-
* Dynamically updates the response payload for this active mock rule.
|
|
1242
|
+
* Dynamically updates the default response payload for this active mock rule.
|
|
1178
1243
|
*
|
|
1179
1244
|
* @param newFixture New response payload object or string.
|
|
1180
1245
|
* @param newOptions Optional status code and header overrides.
|
|
1181
1246
|
*/
|
|
1182
1247
|
respondWith: (
|
|
1183
1248
|
newFixture: unknown,
|
|
1184
|
-
newOptions?: { statusCode?: number; headers?: Record<string, string
|
|
1249
|
+
newOptions?: { statusCode?: number; headers?: Record<string, string>; delayMs?: number },
|
|
1250
|
+
) => Promise<void>;
|
|
1251
|
+
|
|
1252
|
+
/**
|
|
1253
|
+
* Queues a one-time mock response for the next matching request (FIFO queue for polling/retries).
|
|
1254
|
+
*
|
|
1255
|
+
* @param newFixture One-time response payload object or string.
|
|
1256
|
+
* @param newOptions Optional status code and header overrides.
|
|
1257
|
+
*/
|
|
1258
|
+
respondOnce: (
|
|
1259
|
+
newFixture: unknown,
|
|
1260
|
+
newOptions?: { statusCode?: number; headers?: Record<string, string>; delayMs?: number },
|
|
1185
1261
|
) => Promise<void>;
|
|
1186
1262
|
|
|
1263
|
+
/**
|
|
1264
|
+
* Simulates a network failure or connection abort for matching requests.
|
|
1265
|
+
*
|
|
1266
|
+
* @param errorCode Network failure reason (default: 'Failed').
|
|
1267
|
+
*/
|
|
1268
|
+
abort: (errorCode?: 'Failed' | 'Aborted' | 'TimedOut' | 'ConnectionReset') => Promise<void>;
|
|
1269
|
+
|
|
1270
|
+
/**
|
|
1271
|
+
* Awaits until the mock rule has intercepted at least `count` matching requests.
|
|
1272
|
+
*
|
|
1273
|
+
* @param options Timeout and expected request count.
|
|
1274
|
+
*/
|
|
1275
|
+
waitForCall: (options?: { timeout?: number; count?: number }) => Promise<InterceptedRequest>;
|
|
1276
|
+
|
|
1187
1277
|
/**
|
|
1188
1278
|
* Returns the number of times this mock intercepted network requests.
|
|
1189
1279
|
*/
|
|
1190
|
-
callCount: () => number;
|
|
1280
|
+
callCount: (() => number) & number;
|
|
1281
|
+
|
|
1282
|
+
/**
|
|
1283
|
+
* Historical array of all intercepted requests matching this rule.
|
|
1284
|
+
*/
|
|
1285
|
+
calls: InterceptedRequest[];
|
|
1191
1286
|
}
|
|
1192
1287
|
|
|
1193
1288
|
/**
|
|
@@ -1249,12 +1344,16 @@ export interface SpectraStatic {
|
|
|
1249
1344
|
getAll(selector: string): CollectionProxy;
|
|
1250
1345
|
|
|
1251
1346
|
/**
|
|
1252
|
-
* Navigates the active browser window
|
|
1347
|
+
* Navigates the active browser window to the specified URL. On Android, deep-links directly
|
|
1348
|
+
* into the app via `adb shell am start` instead — pass a full URI matching a scheme the app
|
|
1349
|
+
* registers (e.g. `expo-router`'s `scheme` in `app.json`), not a bare path, since there's no
|
|
1350
|
+
* configured base scheme to combine one against.
|
|
1253
1351
|
*
|
|
1254
|
-
* @param url Absolute or relative URL
|
|
1352
|
+
* @param url Absolute or relative URL on web; a full deep-link URI on Android.
|
|
1255
1353
|
* @example
|
|
1256
1354
|
* ```ts
|
|
1257
|
-
* await Spectra.navigate('/dashboard');
|
|
1355
|
+
* await Spectra.navigate('/dashboard'); // web
|
|
1356
|
+
* await Spectra.navigate('testspectra-demo://permission-rationale'); // Android
|
|
1258
1357
|
* ```
|
|
1259
1358
|
*/
|
|
1260
1359
|
navigate(url: string): Promise<void>;
|
|
@@ -1462,6 +1561,21 @@ export interface SpectraStatic {
|
|
|
1462
1561
|
*/
|
|
1463
1562
|
pressKey(key: KeyOption | string): Promise<void>;
|
|
1464
1563
|
|
|
1564
|
+
/**
|
|
1565
|
+
* Grants an Android runtime permission on demand — typically called right after confirming an
|
|
1566
|
+
* in-app rationale dialog, so a test can exercise its own permission-request UX instead of
|
|
1567
|
+
* having every permission pre-granted before the app even launches. No-op on platforms without
|
|
1568
|
+
* an OS-level runtime permission model (e.g. web).
|
|
1569
|
+
*
|
|
1570
|
+
* @param name Fully-qualified Android permission name (e.g. 'android.permission.CAMERA').
|
|
1571
|
+
* @example
|
|
1572
|
+
* ```ts
|
|
1573
|
+
* await Spectra.get('~rationale-allow-btn').click();
|
|
1574
|
+
* await Spectra.grantPermission('android.permission.CAMERA');
|
|
1575
|
+
* ```
|
|
1576
|
+
*/
|
|
1577
|
+
grantPermission(name: string): Promise<void>;
|
|
1578
|
+
|
|
1465
1579
|
/**
|
|
1466
1580
|
* Pauses test execution for the specified number of milliseconds.
|
|
1467
1581
|
*
|
|
@@ -1490,6 +1604,19 @@ export interface SpectraStatic {
|
|
|
1490
1604
|
*/
|
|
1491
1605
|
browser: SpectraBrowserBridge;
|
|
1492
1606
|
|
|
1607
|
+
/**
|
|
1608
|
+
* Typed environment variables from `spectra.config.ts`'s `executionConfig.environmentVariables`.
|
|
1609
|
+
* Each configured key is generated into `.testspectra/types/env.d.ts` as a `SpectraEnv` member
|
|
1610
|
+
* (TypeScript interface merging), so `Spectra.env.MY_KEY` resolves to `string` — never
|
|
1611
|
+
* `string | undefined` like raw `process.env.MY_KEY` would.
|
|
1612
|
+
*
|
|
1613
|
+
* @example
|
|
1614
|
+
* ```ts
|
|
1615
|
+
* const mode = Spectra.env.API_MODE;
|
|
1616
|
+
* ```
|
|
1617
|
+
*/
|
|
1618
|
+
env: SpectraEnv;
|
|
1619
|
+
|
|
1493
1620
|
/**
|
|
1494
1621
|
* Intercepts and mocks HTTP network requests matching the specified pattern or options.
|
|
1495
1622
|
*
|
|
@@ -1506,7 +1633,7 @@ export interface SpectraStatic {
|
|
|
1506
1633
|
patternOrOptions: string | { url: string; method?: string; response?: unknown },
|
|
1507
1634
|
method?: string,
|
|
1508
1635
|
fixture?: unknown,
|
|
1509
|
-
options?: { statusCode?: number; headers?: Record<string, string
|
|
1636
|
+
options?: { statusCode?: number; headers?: Record<string, string>; delayMs?: number },
|
|
1510
1637
|
): Promise<MockInterceptHandle>;
|
|
1511
1638
|
|
|
1512
1639
|
/**
|