@testspectra/react 1.2.0 → 1.2.2
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 +119 -35
- 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,29 +613,71 @@ 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));
|
|
636
|
+
}
|
|
637
|
+
async waitForElementRemoved(selector, timeoutMs = 5e3, index = null) {
|
|
638
|
+
const ok = await globalThis.__TS_RUNTIME__.pollCondition(async () => {
|
|
639
|
+
return !await this.isExisting(selector, index);
|
|
640
|
+
}, timeoutMs);
|
|
641
|
+
if (!ok) throw new Error("Element still exists after timeout: " + selectorText(selector));
|
|
642
|
+
}
|
|
643
|
+
async waitForUndisplayed(selector, opts, index = null) {
|
|
644
|
+
const ok = await globalThis.__TS_RUNTIME__.pollCondition(async () => {
|
|
645
|
+
return !await this.isDisplayed(selector, index);
|
|
646
|
+
}, opts?.timeout ?? 5e3);
|
|
647
|
+
if (!ok) throw new Error("Element still displayed after timeout: " + selectorText(selector));
|
|
648
|
+
}
|
|
649
|
+
async waitForClickable(selector, opts, index = null) {
|
|
650
|
+
return await globalThis.__TS_RUNTIME__.pollCondition(async () => {
|
|
651
|
+
return this.isClickable(selector, index);
|
|
652
|
+
}, opts?.timeout ?? 5e3);
|
|
653
|
+
}
|
|
654
|
+
async getBounds(selector, index = null) {
|
|
655
|
+
const el = resolveScopedEl(selector, index);
|
|
656
|
+
if (!el) return { x: 0, y: 0, top: 0, right: 0, bottom: 0, left: 0, width: 0, height: 0 };
|
|
657
|
+
const r = el.getBoundingClientRect();
|
|
658
|
+
return {
|
|
659
|
+
x: r.x,
|
|
660
|
+
y: r.y,
|
|
661
|
+
top: r.top,
|
|
662
|
+
right: r.right,
|
|
663
|
+
bottom: r.bottom,
|
|
664
|
+
left: r.left,
|
|
665
|
+
width: r.width,
|
|
666
|
+
height: r.height
|
|
667
|
+
};
|
|
668
|
+
}
|
|
669
|
+
async closest(selector, targetSelector, index = null) {
|
|
670
|
+
const el = resolveScopedEl(selector, index);
|
|
671
|
+
if (!el) return null;
|
|
672
|
+
const ancestor = el.closest(targetSelector);
|
|
673
|
+
return ancestor ? ancestor.tagName.toLowerCase() : null;
|
|
674
|
+
}
|
|
675
|
+
async clickAt(x, y) {
|
|
676
|
+
const el = document.elementFromPoint(x, y);
|
|
677
|
+
el?.click();
|
|
599
678
|
}
|
|
600
679
|
async dragDrop(sourceSelector, targetSelector, sourceIndex = null) {
|
|
601
|
-
const src =
|
|
680
|
+
const src = resolveScopedEl(sourceSelector, sourceIndex);
|
|
602
681
|
const dest = resolveEl(targetSelector, null);
|
|
603
682
|
if (!src || !dest) throw new Error("Drag source or destination not found");
|
|
604
683
|
const dataTransfer = new DataTransfer();
|
|
@@ -608,30 +687,30 @@ var DomDriverBridge = class {
|
|
|
608
687
|
src.dispatchEvent(new DragEvent("dragend", { bubbles: true, dataTransfer }));
|
|
609
688
|
}
|
|
610
689
|
async longPress(selector, duration, index = null) {
|
|
611
|
-
const el =
|
|
612
|
-
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));
|
|
613
692
|
el.dispatchEvent(new TouchEvent("touchstart", { bubbles: true }));
|
|
614
693
|
await this.pause(duration);
|
|
615
694
|
el.dispatchEvent(new TouchEvent("touchend", { bubbles: true }));
|
|
616
695
|
}
|
|
617
696
|
async scrollIntoView(selector, index = null) {
|
|
618
|
-
const el =
|
|
697
|
+
const el = resolveScopedEl(selector, index);
|
|
619
698
|
el?.scrollIntoView({ block: "center", inline: "center" });
|
|
620
699
|
}
|
|
621
700
|
// ─── Element State Inspection ────────────────────────────────────────────
|
|
622
701
|
async getText(selector, index = null) {
|
|
623
|
-
const el =
|
|
702
|
+
const el = resolveScopedEl(selector, index);
|
|
624
703
|
if (!el) return "";
|
|
625
704
|
return el.innerText !== void 0 ? el.innerText : el.textContent || "";
|
|
626
705
|
}
|
|
627
706
|
async getValue(selector, index = null) {
|
|
628
|
-
const el =
|
|
707
|
+
const el = resolveScopedEl(selector, index);
|
|
629
708
|
if (!el) return "";
|
|
630
709
|
if (isFormEl(el)) return el.value ?? "";
|
|
631
710
|
return el.getAttribute("value") || "";
|
|
632
711
|
}
|
|
633
712
|
async isDisplayed(selector, index = null) {
|
|
634
|
-
const el =
|
|
713
|
+
const el = resolveScopedEl(selector, index);
|
|
635
714
|
if (!el) return false;
|
|
636
715
|
const style = window.getComputedStyle(el);
|
|
637
716
|
if (style.visibility === "hidden" || style.display === "none" || style.opacity === "0") return false;
|
|
@@ -639,25 +718,25 @@ var DomDriverBridge = class {
|
|
|
639
718
|
return rect.width > 0 && rect.height > 0 || el.getClientRects().length > 0 || el.offsetWidth > 0 && el.offsetHeight > 0;
|
|
640
719
|
}
|
|
641
720
|
async isExisting(selector, index = null) {
|
|
642
|
-
return
|
|
721
|
+
return resolveScopedEl(selector, index) !== null;
|
|
643
722
|
}
|
|
644
723
|
async isEnabled(selector, index = null) {
|
|
645
|
-
const el =
|
|
724
|
+
const el = resolveScopedEl(selector, index);
|
|
646
725
|
if (!el) return false;
|
|
647
726
|
return !el.disabled && !el.hasAttribute("disabled") && !el.classList.contains("disabled") && el.getAttribute("aria-disabled") !== "true";
|
|
648
727
|
}
|
|
649
728
|
async isSelected(selector, index = null) {
|
|
650
|
-
const el =
|
|
729
|
+
const el = resolveScopedEl(selector, index);
|
|
651
730
|
if (!el) return false;
|
|
652
731
|
return !!el.checked || el.getAttribute("aria-checked") === "true" || el.getAttribute("data-state") === "checked";
|
|
653
732
|
}
|
|
654
733
|
async isFocused(selector, index = null) {
|
|
655
|
-
const el =
|
|
734
|
+
const el = resolveScopedEl(selector, index);
|
|
656
735
|
if (!el) return false;
|
|
657
736
|
return document.activeElement === el || el.matches(":focus");
|
|
658
737
|
}
|
|
659
738
|
async isClickable(selector, index = null) {
|
|
660
|
-
const el =
|
|
739
|
+
const el = resolveScopedEl(selector, index);
|
|
661
740
|
if (!el) return false;
|
|
662
741
|
const style = window.getComputedStyle(el);
|
|
663
742
|
if (style.visibility === "hidden" || style.display === "none" || style.opacity === "0") return false;
|
|
@@ -666,19 +745,24 @@ var DomDriverBridge = class {
|
|
|
666
745
|
return !el.disabled && !el.hasAttribute("disabled") && !el.classList.contains("disabled") && el.getAttribute("aria-disabled") !== "true";
|
|
667
746
|
}
|
|
668
747
|
async hasClass(selector, className, index = null) {
|
|
669
|
-
const el =
|
|
748
|
+
const el = resolveScopedEl(selector, index);
|
|
670
749
|
return el ? el.classList.contains(className) : false;
|
|
671
750
|
}
|
|
672
751
|
async getAttribute(selector, name, index = null) {
|
|
673
|
-
const el =
|
|
752
|
+
const el = resolveScopedEl(selector, index);
|
|
674
753
|
return el ? el.getAttribute(name) : null;
|
|
675
754
|
}
|
|
676
755
|
async getCSSProperty(selector, name, index = null) {
|
|
677
|
-
const el =
|
|
756
|
+
const el = resolveScopedEl(selector, index);
|
|
678
757
|
return { value: el ? window.getComputedStyle(el).getPropertyValue(name) : "" };
|
|
679
758
|
}
|
|
680
759
|
async count(selector) {
|
|
681
|
-
|
|
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;
|
|
682
766
|
}
|
|
683
767
|
// ─── Gestures & Keys ─────────────────────────────────────────────────────
|
|
684
768
|
async pressKey(key) {
|