@testspectra/react 1.2.1 → 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 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 `selector` resolves to an element, then returns it — shared by every
403
- * interaction method below (user-event needs a real element handle, not a selector string). */
404
- async waitForEl(selector, index) {
405
- const immediate = resolveEl(selector, index);
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 = resolveEl(selector, index);
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: " + JSON.stringify(selector));
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.waitForEl(selector, index);
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.waitForEl(selector, index);
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.waitForEl(selector, index);
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.waitForEl(selector, index);
465
- if (!isFormEl(el)) throw new Error("Element is not a form field: " + JSON.stringify(selector));
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.waitForEl(selector, index);
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.waitForEl(selector, index);
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 = resolveEl(selector, index);
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.waitForEl(selector, index);
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 = resolveEl(selector, index);
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: " + JSON.stringify(selector));
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 = resolveEl(selector, index);
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: " + JSON.stringify(selector));
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: " + JSON.stringify(selector));
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: " + JSON.stringify(selector));
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 = resolveEl(selector, index);
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 = resolveEl(selector, index);
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 = resolveEl(sourceSelector, sourceIndex);
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 = resolveEl(selector, index);
654
- if (!el) throw new Error("Element not found: " + JSON.stringify(selector));
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 = resolveEl(selector, index);
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 = resolveEl(selector, index);
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 = resolveEl(selector, index);
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 = resolveEl(selector, index);
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 resolveEl(selector, index) !== null;
721
+ return resolveScopedEl(selector, index) !== null;
685
722
  }
686
723
  async isEnabled(selector, index = null) {
687
- const el = resolveEl(selector, index);
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 = resolveEl(selector, index);
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 = resolveEl(selector, index);
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 = resolveEl(selector, index);
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 = resolveEl(selector, index);
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 = resolveEl(selector, index);
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 = resolveEl(selector, index);
756
+ const el = resolveScopedEl(selector, index);
720
757
  return { value: el ? window.getComputedStyle(el).getPropertyValue(name) : "" };
721
758
  }
722
759
  async count(selector) {
723
- return document.querySelectorAll(resolveSelector(selector)).length;
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) {