@testspectra/react 1.2.1 → 1.2.3
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/index.js +81 -39
- package/dist/setup.js +33 -33
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -196,6 +196,13 @@ function mockResponseBodyText(body) {
|
|
|
196
196
|
}
|
|
197
197
|
|
|
198
198
|
// ../../core/worker-runtime/src/dsl/selector_resolver.ts
|
|
199
|
+
function parseElementSentinel(selector) {
|
|
200
|
+
const closest = /^(.*)::closest\((.+)\)$/.exec(selector);
|
|
201
|
+
if (closest) return { kind: "closest", base: closest[1], arg: closest[2] };
|
|
202
|
+
const parent = /^(.*)::parent$/.exec(selector);
|
|
203
|
+
if (parent) return { kind: "parent", base: parent[1] };
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
199
206
|
function parseA11ySelector(selector) {
|
|
200
207
|
const trimmed = selector.trim();
|
|
201
208
|
if (!trimmed.startsWith("~")) return null;
|
|
@@ -391,6 +398,35 @@ function resolveEl(selector, index) {
|
|
|
391
398
|
if (parsed) return resolveLabelledBy(parsed.name, document, index);
|
|
392
399
|
return null;
|
|
393
400
|
}
|
|
401
|
+
function selectorText(target) {
|
|
402
|
+
return typeof target === "string" ? target : target.selector;
|
|
403
|
+
}
|
|
404
|
+
function resolveSentinel(raw, parent) {
|
|
405
|
+
const sentinel = parseElementSentinel(raw);
|
|
406
|
+
if (!sentinel) return void 0;
|
|
407
|
+
const baseTarget = parent ? { selector: sentinel.base, index: null, parent } : sentinel.base;
|
|
408
|
+
const base = resolveScopedEl(baseTarget, null);
|
|
409
|
+
if (!base) return null;
|
|
410
|
+
if (sentinel.kind === "parent") return base.parentElement ?? null;
|
|
411
|
+
return base.closest(sentinel.arg) ?? null;
|
|
412
|
+
}
|
|
413
|
+
function resolveScopedEl(target, fallbackIndex) {
|
|
414
|
+
if (typeof target === "string") {
|
|
415
|
+
const sentinel = resolveSentinel(target, void 0);
|
|
416
|
+
if (sentinel !== void 0) return sentinel;
|
|
417
|
+
return resolveEl(target, fallbackIndex);
|
|
418
|
+
}
|
|
419
|
+
const childSentinel = resolveSentinel(target.selector, target.parent);
|
|
420
|
+
if (childSentinel !== void 0) return childSentinel;
|
|
421
|
+
const parentEl = target.parent ? resolveScopedEl(target.parent, target.parent.index) : document.documentElement;
|
|
422
|
+
if (!parentEl) return null;
|
|
423
|
+
const css = resolveSelector(target.selector);
|
|
424
|
+
const all = Array.from(parentEl.querySelectorAll(css));
|
|
425
|
+
const idx = target.index ?? fallbackIndex;
|
|
426
|
+
if (idx === null || idx === void 0) return all[0] ?? null;
|
|
427
|
+
if (idx >= 0) return all[idx] ?? null;
|
|
428
|
+
return all[all.length + idx] ?? null;
|
|
429
|
+
}
|
|
394
430
|
function isFormEl(el) {
|
|
395
431
|
const tag = el.tagName;
|
|
396
432
|
return tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT";
|
|
@@ -399,17 +435,18 @@ var DomDriverBridge = class {
|
|
|
399
435
|
mockRules = [];
|
|
400
436
|
consoleErrors = [];
|
|
401
437
|
recordedNetwork = [];
|
|
402
|
-
/** Polls until `
|
|
403
|
-
*
|
|
404
|
-
|
|
405
|
-
|
|
438
|
+
/** Polls until `target` resolves to an element, then returns it — shared by every interaction
|
|
439
|
+
* method below (user-event needs a real element handle, not a selector string). Accepts a
|
|
440
|
+
* `ScopedSelector` so chained `.get()`/`.getAll()` lookups resolve within their parent. */
|
|
441
|
+
async waitForScopedEl(target, fallbackIndex) {
|
|
442
|
+
const immediate = resolveScopedEl(target, fallbackIndex);
|
|
406
443
|
if (immediate) return immediate;
|
|
407
444
|
let found = null;
|
|
408
445
|
const ok = await globalThis.__TS_RUNTIME__.pollCondition(async () => {
|
|
409
|
-
found =
|
|
446
|
+
found = resolveScopedEl(target, fallbackIndex);
|
|
410
447
|
return found !== null;
|
|
411
448
|
}, globalThis.__TS_RUNTIME__.getImplicitWaitMs());
|
|
412
|
-
if (!ok || !found) throw new Error("Element not found: " +
|
|
449
|
+
if (!ok || !found) throw new Error("Element not found: " + selectorText(target));
|
|
413
450
|
return found;
|
|
414
451
|
}
|
|
415
452
|
async connect() {
|
|
@@ -449,20 +486,20 @@ var DomDriverBridge = class {
|
|
|
449
486
|
}
|
|
450
487
|
// ─── Element Actions ─────────────────────────────────────────────────────
|
|
451
488
|
async click(selector, index = null) {
|
|
452
|
-
const el = await this.
|
|
489
|
+
const el = await this.waitForScopedEl(selector, index);
|
|
453
490
|
await getUser().click(el);
|
|
454
491
|
}
|
|
455
492
|
async doubleClick(selector, index = null) {
|
|
456
|
-
const el = await this.
|
|
493
|
+
const el = await this.waitForScopedEl(selector, index);
|
|
457
494
|
await getUser().dblClick(el);
|
|
458
495
|
}
|
|
459
496
|
async rightClick(selector, index = null) {
|
|
460
|
-
const el = await this.
|
|
497
|
+
const el = await this.waitForScopedEl(selector, index);
|
|
461
498
|
await getUser().pointer({ keys: "[MouseRight]", target: el });
|
|
462
499
|
}
|
|
463
500
|
async setValue(selector, value, index = null) {
|
|
464
|
-
const el = await this.
|
|
465
|
-
if (!isFormEl(el)) throw new Error("Element is not a form field: " +
|
|
501
|
+
const el = await this.waitForScopedEl(selector, index);
|
|
502
|
+
if (!isFormEl(el)) throw new Error("Element is not a form field: " + selectorText(selector));
|
|
466
503
|
if (el.tagName === "INPUT" && el.type === "file") {
|
|
467
504
|
return this.upload(selector, value, index);
|
|
468
505
|
}
|
|
@@ -473,7 +510,7 @@ var DomDriverBridge = class {
|
|
|
473
510
|
}
|
|
474
511
|
}
|
|
475
512
|
async upload(selector, files, index = null) {
|
|
476
|
-
const el = await this.
|
|
513
|
+
const el = await this.waitForScopedEl(selector, index);
|
|
477
514
|
const fileList = Array.isArray(files) ? files : [files];
|
|
478
515
|
const filesToUpload = [];
|
|
479
516
|
const MIME_MAP = {
|
|
@@ -556,12 +593,12 @@ var DomDriverBridge = class {
|
|
|
556
593
|
}
|
|
557
594
|
}
|
|
558
595
|
async clearValue(selector, index = null) {
|
|
559
|
-
const el = await this.
|
|
596
|
+
const el = await this.waitForScopedEl(selector, index);
|
|
560
597
|
await getUser().clear(el);
|
|
561
598
|
}
|
|
562
599
|
async select(selector, option, index = null) {
|
|
563
600
|
const ok = await globalThis.__TS_RUNTIME__.pollCondition(async () => {
|
|
564
|
-
const el =
|
|
601
|
+
const el = resolveScopedEl(selector, index);
|
|
565
602
|
if (!el || el.tagName !== "SELECT") return false;
|
|
566
603
|
for (let i = 0; i < el.options.length; i++) {
|
|
567
604
|
const opt = el.options[i];
|
|
@@ -576,38 +613,38 @@ var DomDriverBridge = class {
|
|
|
576
613
|
if (!ok) throw new Error("Select option not found: " + JSON.stringify(option));
|
|
577
614
|
}
|
|
578
615
|
async hover(selector, index = null) {
|
|
579
|
-
const el = await this.
|
|
616
|
+
const el = await this.waitForScopedEl(selector, index);
|
|
580
617
|
await getUser().hover(el);
|
|
581
618
|
}
|
|
582
619
|
async focus(selector, index = null) {
|
|
583
620
|
const ok = await globalThis.__TS_RUNTIME__.pollCondition(async () => {
|
|
584
|
-
const el =
|
|
621
|
+
const el = resolveScopedEl(selector, index);
|
|
585
622
|
if (!el) return false;
|
|
586
623
|
el.focus?.();
|
|
587
624
|
return true;
|
|
588
625
|
});
|
|
589
|
-
if (!ok) throw new Error("Element not found: " +
|
|
626
|
+
if (!ok) throw new Error("Element not found: " + selectorText(selector));
|
|
590
627
|
}
|
|
591
628
|
async blur(selector, index = null) {
|
|
592
629
|
const ok = await globalThis.__TS_RUNTIME__.pollCondition(async () => {
|
|
593
|
-
const el =
|
|
630
|
+
const el = resolveScopedEl(selector, index);
|
|
594
631
|
if (!el) return false;
|
|
595
632
|
el.blur?.();
|
|
596
633
|
return true;
|
|
597
634
|
});
|
|
598
|
-
if (!ok) throw new Error("Element not found: " +
|
|
635
|
+
if (!ok) throw new Error("Element not found: " + selectorText(selector));
|
|
599
636
|
}
|
|
600
637
|
async waitForElementRemoved(selector, timeoutMs = 5e3, index = null) {
|
|
601
638
|
const ok = await globalThis.__TS_RUNTIME__.pollCondition(async () => {
|
|
602
639
|
return !await this.isExisting(selector, index);
|
|
603
640
|
}, timeoutMs);
|
|
604
|
-
if (!ok) throw new Error("Element still exists after timeout: " +
|
|
641
|
+
if (!ok) throw new Error("Element still exists after timeout: " + selectorText(selector));
|
|
605
642
|
}
|
|
606
643
|
async waitForUndisplayed(selector, opts, index = null) {
|
|
607
644
|
const ok = await globalThis.__TS_RUNTIME__.pollCondition(async () => {
|
|
608
645
|
return !await this.isDisplayed(selector, index);
|
|
609
646
|
}, opts?.timeout ?? 5e3);
|
|
610
|
-
if (!ok) throw new Error("Element still displayed after timeout: " +
|
|
647
|
+
if (!ok) throw new Error("Element still displayed after timeout: " + selectorText(selector));
|
|
611
648
|
}
|
|
612
649
|
async waitForClickable(selector, opts, index = null) {
|
|
613
650
|
return await globalThis.__TS_RUNTIME__.pollCondition(async () => {
|
|
@@ -615,7 +652,7 @@ var DomDriverBridge = class {
|
|
|
615
652
|
}, opts?.timeout ?? 5e3);
|
|
616
653
|
}
|
|
617
654
|
async getBounds(selector, index = null) {
|
|
618
|
-
const el =
|
|
655
|
+
const el = resolveScopedEl(selector, index);
|
|
619
656
|
if (!el) return { x: 0, y: 0, top: 0, right: 0, bottom: 0, left: 0, width: 0, height: 0 };
|
|
620
657
|
const r = el.getBoundingClientRect();
|
|
621
658
|
return {
|
|
@@ -630,7 +667,7 @@ var DomDriverBridge = class {
|
|
|
630
667
|
};
|
|
631
668
|
}
|
|
632
669
|
async closest(selector, targetSelector, index = null) {
|
|
633
|
-
const el =
|
|
670
|
+
const el = resolveScopedEl(selector, index);
|
|
634
671
|
if (!el) return null;
|
|
635
672
|
const ancestor = el.closest(targetSelector);
|
|
636
673
|
return ancestor ? ancestor.tagName.toLowerCase() : null;
|
|
@@ -640,7 +677,7 @@ var DomDriverBridge = class {
|
|
|
640
677
|
el?.click();
|
|
641
678
|
}
|
|
642
679
|
async dragDrop(sourceSelector, targetSelector, sourceIndex = null) {
|
|
643
|
-
const src =
|
|
680
|
+
const src = resolveScopedEl(sourceSelector, sourceIndex);
|
|
644
681
|
const dest = resolveEl(targetSelector, null);
|
|
645
682
|
if (!src || !dest) throw new Error("Drag source or destination not found");
|
|
646
683
|
const dataTransfer = new DataTransfer();
|
|
@@ -650,30 +687,30 @@ var DomDriverBridge = class {
|
|
|
650
687
|
src.dispatchEvent(new DragEvent("dragend", { bubbles: true, dataTransfer }));
|
|
651
688
|
}
|
|
652
689
|
async longPress(selector, duration, index = null) {
|
|
653
|
-
const el =
|
|
654
|
-
if (!el) throw new Error("Element not found: " +
|
|
690
|
+
const el = resolveScopedEl(selector, index);
|
|
691
|
+
if (!el) throw new Error("Element not found: " + selectorText(selector));
|
|
655
692
|
el.dispatchEvent(new TouchEvent("touchstart", { bubbles: true }));
|
|
656
693
|
await this.pause(duration);
|
|
657
694
|
el.dispatchEvent(new TouchEvent("touchend", { bubbles: true }));
|
|
658
695
|
}
|
|
659
696
|
async scrollIntoView(selector, index = null) {
|
|
660
|
-
const el =
|
|
697
|
+
const el = resolveScopedEl(selector, index);
|
|
661
698
|
el?.scrollIntoView({ block: "center", inline: "center" });
|
|
662
699
|
}
|
|
663
700
|
// ─── Element State Inspection ────────────────────────────────────────────
|
|
664
701
|
async getText(selector, index = null) {
|
|
665
|
-
const el =
|
|
702
|
+
const el = resolveScopedEl(selector, index);
|
|
666
703
|
if (!el) return "";
|
|
667
704
|
return el.innerText !== void 0 ? el.innerText : el.textContent || "";
|
|
668
705
|
}
|
|
669
706
|
async getValue(selector, index = null) {
|
|
670
|
-
const el =
|
|
707
|
+
const el = resolveScopedEl(selector, index);
|
|
671
708
|
if (!el) return "";
|
|
672
709
|
if (isFormEl(el)) return el.value ?? "";
|
|
673
710
|
return el.getAttribute("value") || "";
|
|
674
711
|
}
|
|
675
712
|
async isDisplayed(selector, index = null) {
|
|
676
|
-
const el =
|
|
713
|
+
const el = resolveScopedEl(selector, index);
|
|
677
714
|
if (!el) return false;
|
|
678
715
|
const style = window.getComputedStyle(el);
|
|
679
716
|
if (style.visibility === "hidden" || style.display === "none" || style.opacity === "0") return false;
|
|
@@ -681,25 +718,25 @@ var DomDriverBridge = class {
|
|
|
681
718
|
return rect.width > 0 && rect.height > 0 || el.getClientRects().length > 0 || el.offsetWidth > 0 && el.offsetHeight > 0;
|
|
682
719
|
}
|
|
683
720
|
async isExisting(selector, index = null) {
|
|
684
|
-
return
|
|
721
|
+
return resolveScopedEl(selector, index) !== null;
|
|
685
722
|
}
|
|
686
723
|
async isEnabled(selector, index = null) {
|
|
687
|
-
const el =
|
|
724
|
+
const el = resolveScopedEl(selector, index);
|
|
688
725
|
if (!el) return false;
|
|
689
726
|
return !el.disabled && !el.hasAttribute("disabled") && !el.classList.contains("disabled") && el.getAttribute("aria-disabled") !== "true";
|
|
690
727
|
}
|
|
691
728
|
async isSelected(selector, index = null) {
|
|
692
|
-
const el =
|
|
729
|
+
const el = resolveScopedEl(selector, index);
|
|
693
730
|
if (!el) return false;
|
|
694
731
|
return !!el.checked || el.getAttribute("aria-checked") === "true" || el.getAttribute("data-state") === "checked";
|
|
695
732
|
}
|
|
696
733
|
async isFocused(selector, index = null) {
|
|
697
|
-
const el =
|
|
734
|
+
const el = resolveScopedEl(selector, index);
|
|
698
735
|
if (!el) return false;
|
|
699
736
|
return document.activeElement === el || el.matches(":focus");
|
|
700
737
|
}
|
|
701
738
|
async isClickable(selector, index = null) {
|
|
702
|
-
const el =
|
|
739
|
+
const el = resolveScopedEl(selector, index);
|
|
703
740
|
if (!el) return false;
|
|
704
741
|
const style = window.getComputedStyle(el);
|
|
705
742
|
if (style.visibility === "hidden" || style.display === "none" || style.opacity === "0") return false;
|
|
@@ -708,19 +745,24 @@ var DomDriverBridge = class {
|
|
|
708
745
|
return !el.disabled && !el.hasAttribute("disabled") && !el.classList.contains("disabled") && el.getAttribute("aria-disabled") !== "true";
|
|
709
746
|
}
|
|
710
747
|
async hasClass(selector, className, index = null) {
|
|
711
|
-
const el =
|
|
748
|
+
const el = resolveScopedEl(selector, index);
|
|
712
749
|
return el ? el.classList.contains(className) : false;
|
|
713
750
|
}
|
|
714
751
|
async getAttribute(selector, name, index = null) {
|
|
715
|
-
const el =
|
|
752
|
+
const el = resolveScopedEl(selector, index);
|
|
716
753
|
return el ? el.getAttribute(name) : null;
|
|
717
754
|
}
|
|
718
755
|
async getCSSProperty(selector, name, index = null) {
|
|
719
|
-
const el =
|
|
756
|
+
const el = resolveScopedEl(selector, index);
|
|
720
757
|
return { value: el ? window.getComputedStyle(el).getPropertyValue(name) : "" };
|
|
721
758
|
}
|
|
722
759
|
async count(selector) {
|
|
723
|
-
|
|
760
|
+
if (typeof selector === "string") {
|
|
761
|
+
return document.querySelectorAll(resolveSelector(selector)).length;
|
|
762
|
+
}
|
|
763
|
+
const parentEl = selector.parent ? resolveScopedEl(selector.parent, selector.parent.index) : document.documentElement;
|
|
764
|
+
if (!parentEl) return 0;
|
|
765
|
+
return parentEl.querySelectorAll(resolveSelector(selector.selector)).length;
|
|
724
766
|
}
|
|
725
767
|
// ─── Gestures & Keys ─────────────────────────────────────────────────────
|
|
726
768
|
async pressKey(key) {
|