@empiricalrun/test-gen 0.38.6 → 0.38.7

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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @empiricalrun/test-gen
2
2
 
3
+ ## 0.38.7
4
+
5
+ ### Patch Changes
6
+
7
+ - b13b57d: fix: remove duplicate code
8
+
3
9
  ## 0.38.6
4
10
 
5
11
  ### Patch Changes
@@ -410,460 +410,6 @@ function annotateClickableElements(options = {}) {
410
410
  }
411
411
 
412
412
 
413
- // Initialize and enable annotations
414
- function enable() {
415
- clearAnnotations();
416
- if (!annotationsContainer) {
417
- annotationsContainer = document.createElement("div");
418
- annotationsContainer.className = "annotations";
419
- Object.assign(annotationsContainer.style, {
420
- position: "absolute",
421
- top: "0",
422
- left: "0",
423
- width: "100%",
424
- height: "100%",
425
- pointerEvents: "none",
426
- zIndex: "9999",
427
- });
428
- document.body.appendChild(annotationsContainer);
429
- initializeAnnotations(window, null, []);
430
- } else {
431
- annotationsContainer.style.display = "block";
432
- }
433
- }
434
-
435
- // Destroy annotations
436
- function destroy() {
437
- if (annotationsContainer) {
438
- clearAnnotations();
439
- Object.values(annotationsMap).forEach((annotation) => {
440
- annotation.node.style.boxShadow = "none";
441
- });
442
-
443
- annotationsContainer.parentNode.removeChild(annotationsContainer);
444
- annotationsContainer = null;
445
- }
446
- }
447
-
448
- enable();
449
-
450
- return {
451
- annotations: annotationsMap,
452
- destroy,
453
- };
454
- }
455
- /* eslint-disable no-undef */
456
- /**
457
- * Annotates all clickable elements on the page with unique hint markers.
458
- * Returns an object containing annotations and methods to enable/disable them.
459
- *
460
- * @param {Object} options - Configuration options for hint markers.
461
- * @param {string} options.hintCharacterSet - Characters to use for generating hint identifiers.
462
- * @param {number} options.maxHints - Maximum number of hints to generate.
463
- * @param {string} options.markerClass - CSS class to apply to hint markers.
464
- * @returns {Object} An object containing annotations map and enable/disable methods.
465
- */
466
- function annotateClickableElements(options = {}) {
467
- const {
468
- hintCharacterSet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ", // Default set of characters for hints
469
- maxHints = 1000, // Maximum number of hints to generate
470
- markerClass = "hint-marker", // CSS class for markers
471
- } = options;
472
-
473
- const document = window.document;
474
- const annotationsMap = {};
475
- const usedHints = new Set();
476
- let annotationsContainer = null;
477
-
478
- // Check if the element is not blocked and visible for clicking
479
- function isElementClickNotBlocked(element, windowToAnnotate) {
480
- const rect = element.getBoundingClientRect();
481
-
482
- // Calculate the center point of the element
483
- const centerX = rect.left + rect.width / 2;
484
- const centerY = rect.top + rect.height / 2;
485
-
486
- // check if element is within the viewport
487
- if (
488
- centerX < 0 ||
489
- centerY < 0 ||
490
- centerX > (windowToAnnotate.innerWidth || document.documentElement.clientWidth) ||
491
- centerY > (windowToAnnotate.innerHeight || document.documentElement.clientHeight)
492
- ) {
493
- // Determine the viewport dimensions
494
- const viewportWidth =
495
- windowToAnnotate.innerWidth || document.documentElement.clientWidth;
496
- const viewportHeight =
497
- windowToAnnotate.innerHeight || document.documentElement.clientHeight;
498
-
499
- // Calculate the new scroll positions to bring the element into the center of the viewport
500
- const newScrollX = centerX - viewportWidth / 2;
501
- const newScrollY = centerY - viewportHeight / 2;
502
-
503
- // Scroll the window to the new positions
504
- windowToAnnotate.scrollTo({
505
- top: newScrollY,
506
- left: newScrollX,
507
- });
508
-
509
- const newRect = element.getBoundingClientRect();
510
- const newCenterX = newRect.left + newRect.width / 2;
511
- const newCenterY = newRect.top + newRect.height / 2;
512
- const topElement = document.elementFromPoint(newCenterX, newCenterY);
513
- return element.contains(topElement);
514
- }
515
-
516
- const topElement = windowToAnnotate.document.elementFromPoint(centerX, centerY);
517
-
518
- // Check if the topmost element is the target element or one of its descendants
519
- return element.contains(topElement);
520
- }
521
-
522
- function generateHintStrings(charset, max) {
523
- const hints = [];
524
- let length = 1;
525
-
526
- while (hints.length < max) {
527
- const combos = cartesianProduct(Array(length).fill(charset.split("")));
528
- for (const combo of combos) {
529
- const hint = combo.join("");
530
- if (!usedHints.has(hint)) {
531
- hints.push(hint);
532
- usedHints.add(hint);
533
- }
534
- if (hints.length >= max) break;
535
- }
536
- length++;
537
- }
538
-
539
- return hints;
540
- }
541
-
542
- function cartesianProduct(arrays) {
543
- return arrays.reduce(
544
- (acc, curr) =>
545
- acc
546
- .map((a) => curr.map((b) => a.concat([b])))
547
- .reduce((a, b) => a.concat(b), []),
548
- [[]]
549
- );
550
- }
551
-
552
- // Check if an element is clickable
553
- function isElementClickable(element) {
554
- // if (!(element instanceof Element)) return false;
555
-
556
- const tagName = element.tagName.toLowerCase();
557
- let isClickable = false;
558
-
559
- // Check for aria-disabled
560
- const ariaDisabled = element.getAttribute("aria-disabled");
561
- if (ariaDisabled && ["", "true"].includes(ariaDisabled.toLowerCase())) {
562
- return false; // Element should not be clickable if aria-disabled is true
563
- }
564
-
565
- // Check for visibility
566
- const style = window.getComputedStyle(element);
567
- if (
568
- style.display === "none" ||
569
- style.visibility === "hidden" ||
570
- // This is done for cases where opacity is undefined
571
- // parseFloat(style.opacity) === 0
572
- style.pointerEvents === "none"
573
- ) {
574
- return false;
575
- }
576
-
577
- // Check if element is disabled (for applicable elements)
578
- if (element.disabled) return false;
579
-
580
- // Check for AngularJS click handlers
581
- if (!isElementClickable._checkForAngularJs) {
582
- isElementClickable._checkForAngularJs = (function () {
583
- const angularElements = document.getElementsByClassName("ng-scope");
584
- if (angularElements.length === 0) {
585
- return () => false;
586
- } else {
587
- const ngAttributes = [];
588
- for (const prefix of ["", "data-", "x-"]) {
589
- for (const separator of ["-", ":", "_"]) {
590
- ngAttributes.push(`${prefix}ng${separator}click`);
591
- }
592
- }
593
- return function (el) {
594
- for (const attribute of ngAttributes) {
595
- if (el.hasAttribute(attribute)) return true;
596
- }
597
- return false;
598
- };
599
- }
600
- })();
601
- }
602
-
603
- if (!isClickable && isElementClickable._checkForAngularJs(element)) {
604
- isClickable = true;
605
- }
606
-
607
- // Check for onclick attribute or listener
608
- if (
609
- element.hasAttribute("onclick") ||
610
- typeof element.onclick === "function"
611
- ) {
612
- isClickable = true;
613
- }
614
-
615
- // Check for jsaction attribute (commonly used in frameworks like Google's)
616
- if (!isClickable && element.hasAttribute("jsaction")) {
617
- const jsactionRules = element.getAttribute("jsaction").split(";");
618
- for (const jsactionRule of jsactionRules) {
619
- const ruleSplit = jsactionRule.trim().split(":");
620
- if (ruleSplit.length >= 1 && ruleSplit.length <= 2) {
621
- const [eventType] = ruleSplit[0].trim().split(".");
622
- if (eventType === "click") {
623
- isClickable = true;
624
- break;
625
- }
626
- }
627
- }
628
- }
629
-
630
- // Check for role attributes that imply clickability
631
- if (!isClickable) {
632
- const role = element.getAttribute("role");
633
- const clickableRoles = [
634
- "button",
635
- "tab",
636
- "link",
637
- "checkbox",
638
- "menuitem",
639
- "menuitemcheckbox",
640
- "menuitemradio",
641
- "radio",
642
- "switch",
643
- ];
644
- if (role && clickableRoles.includes(role.toLowerCase())) {
645
- isClickable = true;
646
- }
647
- }
648
-
649
- // Check for contentEditable
650
- if (!isClickable) {
651
- const contentEditable = element.getAttribute("contentEditable");
652
- if (
653
- contentEditable != null &&
654
- ["", "contenteditable", "true"].includes(contentEditable.toLowerCase())
655
- ) {
656
- isClickable = true;
657
- }
658
- }
659
-
660
- // Tag-specific clickability
661
- const focusableTags = [
662
- "a",
663
- "button",
664
- "input",
665
- "select",
666
- "textarea",
667
- "object",
668
- "embed",
669
- "label",
670
- "details",
671
- ];
672
- if (focusableTags.includes(tagName)) {
673
- switch (tagName) {
674
- case "a":
675
- // Ensure it's not just a named anchor without href
676
- if (element.hasAttribute("href")) {
677
- isClickable = true;
678
- }
679
- break;
680
- case "input": {
681
- const type = (element.getAttribute("type") || "").toLowerCase();
682
- if (
683
- type !== "hidden" &&
684
- !element.disabled &&
685
- !(element.readOnly && isInputSelectable(element))
686
- ) {
687
- isClickable = true;
688
- }
689
- break;
690
- }
691
- case "textarea":
692
- if (!element.disabled && !element.readOnly) {
693
- isClickable = true;
694
- }
695
- break;
696
- case "button":
697
- case "select":
698
- if (!element.disabled) {
699
- isClickable = true;
700
- }
701
- break;
702
- case "object":
703
- case "embed":
704
- isClickable = true;
705
- break;
706
- case "label":
707
- if (element.control && !element.control.disabled) {
708
- isClickable = true;
709
- }
710
- break;
711
- case "details":
712
- isClickable = true;
713
- break;
714
- default:
715
- break;
716
- }
717
- }
718
-
719
- // Check for class names containing 'button' as a possible click target
720
- if (!isClickable) {
721
- const className = element.getAttribute("class");
722
- if (className && className.toLowerCase().includes("button")) {
723
- isClickable = true;
724
- } else if (element.classList.contains("cursor-pointer")) {
725
- isClickable = true;
726
- } else if (element.classList.contains("v-list-item--link")) {
727
- // vue specific click handling
728
- isClickable = true;
729
- } else if (element.style.cursor === "pointer") {
730
- isClickable = true;
731
- }
732
- }
733
-
734
- // Check for tabindex
735
- if (!isClickable) {
736
- const tabIndexValue = element.getAttribute("tabindex");
737
- const tabIndex = tabIndexValue ? parseInt(tabIndexValue) : -1;
738
- if (tabIndex >= 0 && !isNaN(tabIndex)) {
739
- isClickable = true;
740
- }
741
- }
742
-
743
- return isClickable;
744
- }
745
-
746
- function isInputSelectable(input) {
747
- const selectableTypes = [
748
- "text",
749
- "search",
750
- "password",
751
- "url",
752
- "email",
753
- "number",
754
- "tel",
755
- ];
756
- const type = (input.getAttribute("type") || "").toLowerCase();
757
- return selectableTypes.includes(type);
758
- }
759
-
760
- var parentElements = [];
761
-
762
- // Create a hint marker
763
- function createHintMarker(el, hint, parentElement) {
764
- const rect = el.getBoundingClientRect();
765
- const parentRect = parentElement.getBoundingClientRect();
766
-
767
- // Create the marker element
768
- const marker = document.createElement("div");
769
- marker.textContent = hint;
770
- marker.className = markerClass;
771
-
772
- // Style the marker
773
- Object.assign(marker.style, {
774
- position: "absolute",
775
- top: `${rect.top + parentRect.top}px`,
776
- left: `${rect.left + parentRect.left}px`,
777
- background:
778
- "-webkit-gradient(linear, 0% 0%, 0% 100%, from(rgb(255, 247, 133)), to(rgb(255, 197, 66)))",
779
- padding: "1px 3px 0px",
780
- borderRadius: "3px",
781
- border: "1px solid rgb(227, 190, 35)",
782
- fontSize: "11px",
783
- pointerEvents: "none",
784
- zIndex: "10000",
785
- whiteSpace: "nowrap",
786
- overflow: "hidden",
787
- boxShadow: "rgba(0, 0, 0, 0.3) 0px 3px 7px 0px",
788
- letterSpacing: 0,
789
- minHeight: 0,
790
- lineHeight: "100%",
791
- color: "rgb(48, 37, 5)",
792
- fontFamily: "Helvetica, Arial, sans-serif",
793
- fontWeight: "bold",
794
- textShadow: "rgba(255, 255, 255, 0.6) 0px 1px 0px",
795
- });
796
-
797
- // Attach the marker to the specified parent element
798
- parentElement.appendChild(marker);
799
- parentElements.push(parentElement);
800
- return marker;
801
- }
802
-
803
-
804
- // Clear existing annotations
805
- //TODO: Handle clearing annotations
806
- function clearAnnotations() {
807
- parentElements.forEach((parentElement) => {
808
- const markers = parentElement.querySelectorAll(`.${markerClass}`);
809
- markers.forEach((marker) => marker.remove());
810
- });
811
- parentElements = [];
812
- }
813
-
814
- // Initialize annotations for a given window (including iframes)
815
- function initializeAnnotations(windowToAnnotate, parentHints, depth) {
816
- const container =
817
- parentHints?.nodeName === "IFRAME"
818
- ? parentHints.contentWindow.document.body
819
- : annotationsContainer;
820
-
821
- // Ensure the container exists
822
- if (!container) return;
823
-
824
- // Filter for clickable elements
825
- const clickableElements = Array.from(windowToAnnotate.document.querySelectorAll("*")).filter((el) => {
826
- return isElementClickable(el) && isElementClickNotBlocked(el, windowToAnnotate);
827
- });
828
- // Generate hint strings for the clickable elements
829
- const hints = generateHintStrings(hintCharacterSet, Math.min(maxHints, clickableElements.length));
830
-
831
- // Create markers for the elements
832
- clickableElements.slice(0, maxHints).forEach((el, index) => {
833
- const hint = hints[index];
834
- const rect = el.getBoundingClientRect();
835
-
836
- // Use createHintMarker with the specified container
837
- createHintMarker(el, hint, container);
838
- el.style.boxShadow = `inset 0 0 0px 2px red`;
839
-
840
- // Add element details to the annotations map
841
- annotationsMap[hint] = {
842
- node: el,
843
- rect: {
844
- top: rect.top + windowToAnnotate.scrollY,
845
- left: rect.left + windowToAnnotate.scrollX,
846
- width: rect.width,
847
- height: rect.height,
848
- },
849
- depth: [...depth ]
850
- };
851
- });
852
-
853
- // Process iframes recursively
854
- Array.from(windowToAnnotate.document.querySelectorAll("iframe")).forEach((iframe) => {
855
- try {
856
- const frameWindow = iframe.contentWindow;
857
- if (frameWindow && iframe.offsetWidth > 0 && iframe.offsetHeight > 0) {
858
- initializeAnnotations(frameWindow, iframe, [...depth, iframe]);
859
- }
860
- } catch (e) {
861
- console.warn("Cannot access iframe:", e);
862
- }
863
- });
864
- }
865
-
866
-
867
413
  // Initialize and enable annotations
868
414
  function enable() {
869
415
  clearAnnotations();
@@ -410,460 +410,6 @@ function annotateClickableElements(options = {}) {
410
410
  }
411
411
 
412
412
 
413
- // Initialize and enable annotations
414
- function enable() {
415
- clearAnnotations();
416
- if (!annotationsContainer) {
417
- annotationsContainer = document.createElement("div");
418
- annotationsContainer.className = "annotations";
419
- Object.assign(annotationsContainer.style, {
420
- position: "absolute",
421
- top: "0",
422
- left: "0",
423
- width: "100%",
424
- height: "100%",
425
- pointerEvents: "none",
426
- zIndex: "9999",
427
- });
428
- document.body.appendChild(annotationsContainer);
429
- initializeAnnotations(window, null, []);
430
- } else {
431
- annotationsContainer.style.display = "block";
432
- }
433
- }
434
-
435
- // Destroy annotations
436
- function destroy() {
437
- if (annotationsContainer) {
438
- clearAnnotations();
439
- Object.values(annotationsMap).forEach((annotation) => {
440
- annotation.node.style.boxShadow = "none";
441
- });
442
-
443
- annotationsContainer.parentNode.removeChild(annotationsContainer);
444
- annotationsContainer = null;
445
- }
446
- }
447
-
448
- enable();
449
-
450
- return {
451
- annotations: annotationsMap,
452
- destroy,
453
- };
454
- }
455
- /* eslint-disable no-undef */
456
- /**
457
- * Annotates all clickable elements on the page with unique hint markers.
458
- * Returns an object containing annotations and methods to enable/disable them.
459
- *
460
- * @param {Object} options - Configuration options for hint markers.
461
- * @param {string} options.hintCharacterSet - Characters to use for generating hint identifiers.
462
- * @param {number} options.maxHints - Maximum number of hints to generate.
463
- * @param {string} options.markerClass - CSS class to apply to hint markers.
464
- * @returns {Object} An object containing annotations map and enable/disable methods.
465
- */
466
- function annotateClickableElements(options = {}) {
467
- const {
468
- hintCharacterSet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ", // Default set of characters for hints
469
- maxHints = 1000, // Maximum number of hints to generate
470
- markerClass = "hint-marker", // CSS class for markers
471
- } = options;
472
-
473
- const document = window.document;
474
- const annotationsMap = {};
475
- const usedHints = new Set();
476
- let annotationsContainer = null;
477
-
478
- // Check if the element is not blocked and visible for clicking
479
- function isElementClickNotBlocked(element, windowToAnnotate) {
480
- const rect = element.getBoundingClientRect();
481
-
482
- // Calculate the center point of the element
483
- const centerX = rect.left + rect.width / 2;
484
- const centerY = rect.top + rect.height / 2;
485
-
486
- // check if element is within the viewport
487
- if (
488
- centerX < 0 ||
489
- centerY < 0 ||
490
- centerX > (windowToAnnotate.innerWidth || document.documentElement.clientWidth) ||
491
- centerY > (windowToAnnotate.innerHeight || document.documentElement.clientHeight)
492
- ) {
493
- // Determine the viewport dimensions
494
- const viewportWidth =
495
- windowToAnnotate.innerWidth || document.documentElement.clientWidth;
496
- const viewportHeight =
497
- windowToAnnotate.innerHeight || document.documentElement.clientHeight;
498
-
499
- // Calculate the new scroll positions to bring the element into the center of the viewport
500
- const newScrollX = centerX - viewportWidth / 2;
501
- const newScrollY = centerY - viewportHeight / 2;
502
-
503
- // Scroll the window to the new positions
504
- windowToAnnotate.scrollTo({
505
- top: newScrollY,
506
- left: newScrollX,
507
- });
508
-
509
- const newRect = element.getBoundingClientRect();
510
- const newCenterX = newRect.left + newRect.width / 2;
511
- const newCenterY = newRect.top + newRect.height / 2;
512
- const topElement = document.elementFromPoint(newCenterX, newCenterY);
513
- return element.contains(topElement);
514
- }
515
-
516
- const topElement = windowToAnnotate.document.elementFromPoint(centerX, centerY);
517
-
518
- // Check if the topmost element is the target element or one of its descendants
519
- return element.contains(topElement);
520
- }
521
-
522
- function generateHintStrings(charset, max) {
523
- const hints = [];
524
- let length = 1;
525
-
526
- while (hints.length < max) {
527
- const combos = cartesianProduct(Array(length).fill(charset.split("")));
528
- for (const combo of combos) {
529
- const hint = combo.join("");
530
- if (!usedHints.has(hint)) {
531
- hints.push(hint);
532
- usedHints.add(hint);
533
- }
534
- if (hints.length >= max) break;
535
- }
536
- length++;
537
- }
538
-
539
- return hints;
540
- }
541
-
542
- function cartesianProduct(arrays) {
543
- return arrays.reduce(
544
- (acc, curr) =>
545
- acc
546
- .map((a) => curr.map((b) => a.concat([b])))
547
- .reduce((a, b) => a.concat(b), []),
548
- [[]]
549
- );
550
- }
551
-
552
- // Check if an element is clickable
553
- function isElementClickable(element) {
554
- // if (!(element instanceof Element)) return false;
555
-
556
- const tagName = element.tagName.toLowerCase();
557
- let isClickable = false;
558
-
559
- // Check for aria-disabled
560
- const ariaDisabled = element.getAttribute("aria-disabled");
561
- if (ariaDisabled && ["", "true"].includes(ariaDisabled.toLowerCase())) {
562
- return false; // Element should not be clickable if aria-disabled is true
563
- }
564
-
565
- // Check for visibility
566
- const style = window.getComputedStyle(element);
567
- if (
568
- style.display === "none" ||
569
- style.visibility === "hidden" ||
570
- // This is done for cases where opacity is undefined
571
- // parseFloat(style.opacity) === 0
572
- style.pointerEvents === "none"
573
- ) {
574
- return false;
575
- }
576
-
577
- // Check if element is disabled (for applicable elements)
578
- if (element.disabled) return false;
579
-
580
- // Check for AngularJS click handlers
581
- if (!isElementClickable._checkForAngularJs) {
582
- isElementClickable._checkForAngularJs = (function () {
583
- const angularElements = document.getElementsByClassName("ng-scope");
584
- if (angularElements.length === 0) {
585
- return () => false;
586
- } else {
587
- const ngAttributes = [];
588
- for (const prefix of ["", "data-", "x-"]) {
589
- for (const separator of ["-", ":", "_"]) {
590
- ngAttributes.push(`${prefix}ng${separator}click`);
591
- }
592
- }
593
- return function (el) {
594
- for (const attribute of ngAttributes) {
595
- if (el.hasAttribute(attribute)) return true;
596
- }
597
- return false;
598
- };
599
- }
600
- })();
601
- }
602
-
603
- if (!isClickable && isElementClickable._checkForAngularJs(element)) {
604
- isClickable = true;
605
- }
606
-
607
- // Check for onclick attribute or listener
608
- if (
609
- element.hasAttribute("onclick") ||
610
- typeof element.onclick === "function"
611
- ) {
612
- isClickable = true;
613
- }
614
-
615
- // Check for jsaction attribute (commonly used in frameworks like Google's)
616
- if (!isClickable && element.hasAttribute("jsaction")) {
617
- const jsactionRules = element.getAttribute("jsaction").split(";");
618
- for (const jsactionRule of jsactionRules) {
619
- const ruleSplit = jsactionRule.trim().split(":");
620
- if (ruleSplit.length >= 1 && ruleSplit.length <= 2) {
621
- const [eventType] = ruleSplit[0].trim().split(".");
622
- if (eventType === "click") {
623
- isClickable = true;
624
- break;
625
- }
626
- }
627
- }
628
- }
629
-
630
- // Check for role attributes that imply clickability
631
- if (!isClickable) {
632
- const role = element.getAttribute("role");
633
- const clickableRoles = [
634
- "button",
635
- "tab",
636
- "link",
637
- "checkbox",
638
- "menuitem",
639
- "menuitemcheckbox",
640
- "menuitemradio",
641
- "radio",
642
- "switch",
643
- ];
644
- if (role && clickableRoles.includes(role.toLowerCase())) {
645
- isClickable = true;
646
- }
647
- }
648
-
649
- // Check for contentEditable
650
- if (!isClickable) {
651
- const contentEditable = element.getAttribute("contentEditable");
652
- if (
653
- contentEditable != null &&
654
- ["", "contenteditable", "true"].includes(contentEditable.toLowerCase())
655
- ) {
656
- isClickable = true;
657
- }
658
- }
659
-
660
- // Tag-specific clickability
661
- const focusableTags = [
662
- "a",
663
- "button",
664
- "input",
665
- "select",
666
- "textarea",
667
- "object",
668
- "embed",
669
- "label",
670
- "details",
671
- ];
672
- if (focusableTags.includes(tagName)) {
673
- switch (tagName) {
674
- case "a":
675
- // Ensure it's not just a named anchor without href
676
- if (element.hasAttribute("href")) {
677
- isClickable = true;
678
- }
679
- break;
680
- case "input": {
681
- const type = (element.getAttribute("type") || "").toLowerCase();
682
- if (
683
- type !== "hidden" &&
684
- !element.disabled &&
685
- !(element.readOnly && isInputSelectable(element))
686
- ) {
687
- isClickable = true;
688
- }
689
- break;
690
- }
691
- case "textarea":
692
- if (!element.disabled && !element.readOnly) {
693
- isClickable = true;
694
- }
695
- break;
696
- case "button":
697
- case "select":
698
- if (!element.disabled) {
699
- isClickable = true;
700
- }
701
- break;
702
- case "object":
703
- case "embed":
704
- isClickable = true;
705
- break;
706
- case "label":
707
- if (element.control && !element.control.disabled) {
708
- isClickable = true;
709
- }
710
- break;
711
- case "details":
712
- isClickable = true;
713
- break;
714
- default:
715
- break;
716
- }
717
- }
718
-
719
- // Check for class names containing 'button' as a possible click target
720
- if (!isClickable) {
721
- const className = element.getAttribute("class");
722
- if (className && className.toLowerCase().includes("button")) {
723
- isClickable = true;
724
- } else if (element.classList.contains("cursor-pointer")) {
725
- isClickable = true;
726
- } else if (element.classList.contains("v-list-item--link")) {
727
- // vue specific click handling
728
- isClickable = true;
729
- } else if (element.style.cursor === "pointer") {
730
- isClickable = true;
731
- }
732
- }
733
-
734
- // Check for tabindex
735
- if (!isClickable) {
736
- const tabIndexValue = element.getAttribute("tabindex");
737
- const tabIndex = tabIndexValue ? parseInt(tabIndexValue) : -1;
738
- if (tabIndex >= 0 && !isNaN(tabIndex)) {
739
- isClickable = true;
740
- }
741
- }
742
-
743
- return isClickable;
744
- }
745
-
746
- function isInputSelectable(input) {
747
- const selectableTypes = [
748
- "text",
749
- "search",
750
- "password",
751
- "url",
752
- "email",
753
- "number",
754
- "tel",
755
- ];
756
- const type = (input.getAttribute("type") || "").toLowerCase();
757
- return selectableTypes.includes(type);
758
- }
759
-
760
- var parentElements = [];
761
-
762
- // Create a hint marker
763
- function createHintMarker(el, hint, parentElement) {
764
- const rect = el.getBoundingClientRect();
765
- const parentRect = parentElement.getBoundingClientRect();
766
-
767
- // Create the marker element
768
- const marker = document.createElement("div");
769
- marker.textContent = hint;
770
- marker.className = markerClass;
771
-
772
- // Style the marker
773
- Object.assign(marker.style, {
774
- position: "absolute",
775
- top: `${rect.top + parentRect.top}px`,
776
- left: `${rect.left + parentRect.left}px`,
777
- background:
778
- "-webkit-gradient(linear, 0% 0%, 0% 100%, from(rgb(255, 247, 133)), to(rgb(255, 197, 66)))",
779
- padding: "1px 3px 0px",
780
- borderRadius: "3px",
781
- border: "1px solid rgb(227, 190, 35)",
782
- fontSize: "11px",
783
- pointerEvents: "none",
784
- zIndex: "10000",
785
- whiteSpace: "nowrap",
786
- overflow: "hidden",
787
- boxShadow: "rgba(0, 0, 0, 0.3) 0px 3px 7px 0px",
788
- letterSpacing: 0,
789
- minHeight: 0,
790
- lineHeight: "100%",
791
- color: "rgb(48, 37, 5)",
792
- fontFamily: "Helvetica, Arial, sans-serif",
793
- fontWeight: "bold",
794
- textShadow: "rgba(255, 255, 255, 0.6) 0px 1px 0px",
795
- });
796
-
797
- // Attach the marker to the specified parent element
798
- parentElement.appendChild(marker);
799
- parentElements.push(parentElement);
800
- return marker;
801
- }
802
-
803
-
804
- // Clear existing annotations
805
- //TODO: Handle clearing annotations
806
- function clearAnnotations() {
807
- parentElements.forEach((parentElement) => {
808
- const markers = parentElement.querySelectorAll(`.${markerClass}`);
809
- markers.forEach((marker) => marker.remove());
810
- });
811
- parentElements = [];
812
- }
813
-
814
- // Initialize annotations for a given window (including iframes)
815
- function initializeAnnotations(windowToAnnotate, parentHints, depth) {
816
- const container =
817
- parentHints?.nodeName === "IFRAME"
818
- ? parentHints.contentWindow.document.body
819
- : annotationsContainer;
820
-
821
- // Ensure the container exists
822
- if (!container) return;
823
-
824
- // Filter for clickable elements
825
- const clickableElements = Array.from(windowToAnnotate.document.querySelectorAll("*")).filter((el) => {
826
- return isElementClickable(el) && isElementClickNotBlocked(el, windowToAnnotate);
827
- });
828
- // Generate hint strings for the clickable elements
829
- const hints = generateHintStrings(hintCharacterSet, Math.min(maxHints, clickableElements.length));
830
-
831
- // Create markers for the elements
832
- clickableElements.slice(0, maxHints).forEach((el, index) => {
833
- const hint = hints[index];
834
- const rect = el.getBoundingClientRect();
835
-
836
- // Use createHintMarker with the specified container
837
- createHintMarker(el, hint, container);
838
- el.style.boxShadow = `inset 0 0 0px 2px red`;
839
-
840
- // Add element details to the annotations map
841
- annotationsMap[hint] = {
842
- node: el,
843
- rect: {
844
- top: rect.top + windowToAnnotate.scrollY,
845
- left: rect.left + windowToAnnotate.scrollX,
846
- width: rect.width,
847
- height: rect.height,
848
- },
849
- depth: [...depth ]
850
- };
851
- });
852
-
853
- // Process iframes recursively
854
- Array.from(windowToAnnotate.document.querySelectorAll("iframe")).forEach((iframe) => {
855
- try {
856
- const frameWindow = iframe.contentWindow;
857
- if (frameWindow && iframe.offsetWidth > 0 && iframe.offsetHeight > 0) {
858
- initializeAnnotations(frameWindow, iframe, [...depth, iframe]);
859
- }
860
- } catch (e) {
861
- console.warn("Cannot access iframe:", e);
862
- }
863
- });
864
- }
865
-
866
-
867
413
  // Initialize and enable annotations
868
414
  function enable() {
869
415
  clearAnnotations();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@empiricalrun/test-gen",
3
- "version": "0.38.6",
3
+ "version": "0.38.7",
4
4
  "publishConfig": {
5
5
  "registry": "https://registry.npmjs.org/",
6
6
  "access": "public"