@testring/plugin-selenium-driver 0.8.3 → 0.8.5

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.
@@ -28,6 +28,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
28
28
  Object.defineProperty(exports, "__esModule", { value: true });
29
29
  exports.SeleniumPlugin = void 0;
30
30
  exports.default = seleniumProxy;
31
+ const types_1 = require("@testring/types");
31
32
  const path = __importStar(require("path"));
32
33
  const fs = __importStar(require("fs"));
33
34
  const webdriverio_1 = require("webdriverio");
@@ -152,6 +153,7 @@ class SeleniumPlugin {
152
153
  this.config = this.createConfig(config);
153
154
  if (this.config.host === undefined) {
154
155
  this.runLocalSelenium();
156
+ this.setupProcessCleanup();
155
157
  }
156
158
  this.initIntervals();
157
159
  }
@@ -191,6 +193,17 @@ class SeleniumPlugin {
191
193
  });
192
194
  }
193
195
  }
196
+ setupProcessCleanup() {
197
+ process.on('SIGINT', () => this.forceKillSelenium());
198
+ process.on('SIGTERM', () => this.forceKillSelenium());
199
+ // Note: SIGKILL cannot be caught or handled - it immediately terminates the process
200
+ }
201
+ forceKillSelenium() {
202
+ if (this.localSelenium && !this.localSelenium.killed) {
203
+ this.logger.debug('Force killing Selenium process due to signal');
204
+ this.localSelenium.kill('SIGKILL');
205
+ }
206
+ }
194
207
  stopAllSessions() {
195
208
  const clientsRequests = [];
196
209
  for (const [applicant] of this.browserClients) {
@@ -235,7 +248,12 @@ class SeleniumPlugin {
235
248
  this.logger.debug(`Starting Selenium with command: java ${seleniumArgs.join(' ')}`);
236
249
  this.localSelenium = (0, child_process_1.spawnWithPipes)('java', seleniumArgs);
237
250
  this.waitForReadyState = new Promise((resolve, reject) => {
238
- setupProcessListeners(this.localSelenium, resolve, reject, version, this.logger);
251
+ if (this.localSelenium) {
252
+ setupProcessListeners(this.localSelenium, resolve, reject, version, this.logger);
253
+ }
254
+ else {
255
+ reject(new Error('Failed to spawn Selenium process'));
256
+ }
239
257
  });
240
258
  // Wait for the server to be ready
241
259
  await this.waitForReadyState;
@@ -376,6 +394,78 @@ class SeleniumPlugin {
376
394
  addCustromMethods(client) {
377
395
  return client;
378
396
  }
397
+ async getElement(applicant, selector) {
398
+ await this.createClient(applicant);
399
+ const client = this.getBrowserClient(applicant);
400
+ if ((0, types_1.isXpathSelector)(selector)) {
401
+ return client.$(selector.xpath);
402
+ }
403
+ else if ((0, types_1.isShadowCssSelector)(selector)) {
404
+ return this.getElementFromShadowCss(client, selector);
405
+ }
406
+ throw new Error('Unknown selector type');
407
+ }
408
+ async getElementFromShadowCss(client, selector) {
409
+ const { css, parentSelectors } = selector;
410
+ // Error first: validate selector structure
411
+ this.validateShadowCssSelector(selector);
412
+ try {
413
+ return await this.traverseShadowDom(client, css, parentSelectors);
414
+ }
415
+ catch (error) {
416
+ // Provide more context in error messages
417
+ if (error instanceof Error) {
418
+ throw new Error(`Shadow DOM traversal failed: ${error.message}. Selector: ${JSON.stringify(selector)}`);
419
+ }
420
+ throw error;
421
+ }
422
+ }
423
+ validateShadowCssSelector(selector) {
424
+ const { css, parentSelectors } = selector;
425
+ if (!css || typeof css !== 'string') {
426
+ throw new Error('Shadow CSS selector must have a valid CSS string');
427
+ }
428
+ if (!Array.isArray(parentSelectors) || parentSelectors.length === 0) {
429
+ throw new Error('Shadow CSS selector must have at least one parent selector');
430
+ }
431
+ // Validate all parent selectors are non-empty strings
432
+ for (const [index, parentSelector] of parentSelectors.entries()) {
433
+ if (!parentSelector || typeof parentSelector !== 'string') {
434
+ throw new Error(`Parent selector at index ${index} must be a non-empty string`);
435
+ }
436
+ }
437
+ }
438
+ async traverseToLastParentSelector(client, parentSelectors) {
439
+ const [firstParentSelector, ...restParentSelectors] = parentSelectors;
440
+ // TypeScript assertion: we know firstParentSelector exists due to validation
441
+ if (!firstParentSelector) {
442
+ throw new Error('First parent selector is required');
443
+ }
444
+ // Get the first parent element
445
+ let currentElement = await client.$(firstParentSelector);
446
+ if (!currentElement) {
447
+ throw new Error(`Failed to find parent element with selector: ${firstParentSelector}`);
448
+ }
449
+ // Traverse through shadow DOM hierarchy
450
+ for (const parentSelector of restParentSelectors) {
451
+ const shadowElement = await currentElement.shadow$(parentSelector);
452
+ if (!shadowElement) {
453
+ throw new Error(`Failed to find shadow element with selector: ${parentSelector}`);
454
+ }
455
+ currentElement = shadowElement;
456
+ }
457
+ return currentElement;
458
+ }
459
+ async traverseShadowDom(client, css, parentSelectors) {
460
+ // Traverse to the last parent selector
461
+ const lastParentElement = await this.traverseToLastParentSelector(client, parentSelectors);
462
+ // Get the final target element within the shadow DOM
463
+ const targetElement = await lastParentElement.shadow$(css);
464
+ if (!targetElement) {
465
+ throw new Error(`Failed to find target element with CSS selector: ${css}`);
466
+ }
467
+ return targetElement;
468
+ }
379
469
  async end(applicant) {
380
470
  await this.waitForReadyState;
381
471
  if (!this.hasBrowserClient(applicant)) {
@@ -483,17 +573,13 @@ class SeleniumPlugin {
483
573
  return client.refresh();
484
574
  }
485
575
  async click(applicant, selector, options) {
486
- await this.createClient(applicant);
487
- const client = this.getBrowserClient(applicant);
488
- const element = await client.$(selector);
576
+ const element = await this.getElement(applicant, selector);
489
577
  return options && Object.keys(options).length > 0
490
578
  ? element.click(options)
491
579
  : element.click();
492
580
  }
493
581
  async getSize(applicant, selector) {
494
- await this.createClient(applicant);
495
- const client = this.getBrowserClient(applicant);
496
- const element = await client.$(selector);
582
+ const element = await this.getElement(applicant, selector);
497
583
  return element.getSize();
498
584
  }
499
585
  async url(applicant, val) {
@@ -518,29 +604,21 @@ class SeleniumPlugin {
518
604
  });
519
605
  return (newWindow === null || newWindow === void 0 ? void 0 : newWindow.handle) || newWindow;
520
606
  }
521
- async waitForExist(applicant, xpath, timeout) {
522
- await this.createClient(applicant);
523
- const client = this.getBrowserClient(applicant);
524
- const selector = await client.$(xpath);
525
- return selector.waitForExist({ timeout });
607
+ async waitForExist(applicant, selector, timeout) {
608
+ const element = await this.getElement(applicant, selector);
609
+ return element.waitForExist({ timeout });
526
610
  }
527
- async waitForVisible(applicant, xpath, timeout) {
528
- await this.createClient(applicant);
529
- const client = this.getBrowserClient(applicant);
530
- const selector = await client.$(xpath);
531
- return selector.waitForDisplayed({ timeout });
611
+ async waitForVisible(applicant, selector, timeout) {
612
+ const element = await this.getElement(applicant, selector);
613
+ return element.waitForDisplayed({ timeout });
532
614
  }
533
- async isVisible(applicant, xpath) {
534
- await this.createClient(applicant);
535
- const client = this.getBrowserClient(applicant);
536
- const selector = await client.$(xpath);
537
- return selector.isDisplayed();
615
+ async isVisible(applicant, selector) {
616
+ const element = await this.getElement(applicant, selector);
617
+ return element.isDisplayed();
538
618
  }
539
- async moveToObject(applicant, xpath, xOffset = 0, yOffset = 0) {
540
- await this.createClient(applicant);
541
- const client = this.getBrowserClient(applicant);
542
- const selector = await client.$(xpath);
543
- return selector.moveTo({ xOffset, yOffset });
619
+ async moveToObject(applicant, selector, xOffset = 0, yOffset = 0) {
620
+ const element = await this.getElement(applicant, selector);
621
+ return element.moveTo({ xOffset, yOffset });
544
622
  }
545
623
  async execute(applicant, fn, args) {
546
624
  await this.createClient(applicant);
@@ -557,11 +635,9 @@ class SeleniumPlugin {
557
635
  const client = this.getBrowserClient(applicant);
558
636
  return client.getTitle();
559
637
  }
560
- async clearValue(applicant, xpath) {
561
- await this.createClient(applicant);
562
- const client = this.getBrowserClient(applicant);
563
- const selector = await client.$(xpath);
564
- return selector.clearValue();
638
+ async clearValue(applicant, selector) {
639
+ const element = await this.getElement(applicant, selector);
640
+ return element.clearValue();
565
641
  }
566
642
  async keys(applicant, value) {
567
643
  await this.createClient(applicant);
@@ -573,18 +649,28 @@ class SeleniumPlugin {
573
649
  const client = this.getBrowserClient(applicant);
574
650
  return client.getElementText(elementId);
575
651
  }
576
- async elements(applicant, xpath) {
652
+ async elements(applicant, selector) {
577
653
  await this.createClient(applicant);
578
654
  const client = this.getBrowserClient(applicant);
579
- const elements = (await client.findElements('xpath', xpath));
580
- return elements.map((o) => {
581
- const keys = Object.keys(o);
582
- const firstKey = keys[0];
583
- if (firstKey === undefined) {
584
- return { ELEMENT: '' };
585
- }
586
- return { ELEMENT: o[firstKey] };
587
- });
655
+ if ((0, types_1.isXpathSelector)(selector)) {
656
+ const elements = (await client.findElements('xpath', selector.xpath));
657
+ return elements.map((o) => {
658
+ const keys = Object.keys(o);
659
+ const firstKey = keys[0];
660
+ if (firstKey === undefined) {
661
+ return { ELEMENT: '' };
662
+ }
663
+ return { ELEMENT: o[firstKey] };
664
+ });
665
+ }
666
+ else if ((0, types_1.isShadowCssSelector)(selector)) {
667
+ const lastParentElement = await this.traverseToLastParentSelector(client, selector.parentSelectors);
668
+ const elements = lastParentElement.shadow$$(selector.css);
669
+ return elements.map((element) => {
670
+ return { ELEMENT: element.elementId };
671
+ });
672
+ }
673
+ throw new Error('Unknown selector type');
588
674
  }
589
675
  async frame(applicant, frameID) {
590
676
  await this.createClient(applicant);
@@ -596,64 +682,46 @@ class SeleniumPlugin {
596
682
  const client = this.getBrowserClient(applicant);
597
683
  return client.switchToParentFrame();
598
684
  }
599
- async getValue(applicant, xpath) {
600
- await this.createClient(applicant);
601
- const client = this.getBrowserClient(applicant);
602
- const selector = await client.$(xpath);
603
- return selector.getValue();
685
+ async getValue(applicant, selector) {
686
+ const element = await this.getElement(applicant, selector);
687
+ return element.getValue();
604
688
  }
605
- async setValue(applicant, xpath, value) {
606
- await this.createClient(applicant);
607
- const client = this.getBrowserClient(applicant);
608
- const selector = await client.$(xpath);
609
- return selector.setValue(value);
689
+ async setValue(applicant, selector, value) {
690
+ const element = await this.getElement(applicant, selector);
691
+ return element.setValue(value);
610
692
  }
611
- async selectByIndex(applicant, xpath, value) {
612
- await this.createClient(applicant);
613
- const client = this.getBrowserClient(applicant);
614
- const selector = await client.$(xpath);
615
- return selector.selectByIndex(value);
693
+ async selectByIndex(applicant, selector, value) {
694
+ const element = await this.getElement(applicant, selector);
695
+ return element.selectByIndex(value);
616
696
  }
617
- async selectByValue(applicant, xpath, value) {
618
- await this.createClient(applicant);
619
- const client = this.getBrowserClient(applicant);
620
- const selector = await client.$(xpath);
621
- return selector.selectByAttribute('value', value);
697
+ async selectByValue(applicant, selector, value) {
698
+ const element = await this.getElement(applicant, selector);
699
+ return element.selectByAttribute('value', value);
622
700
  }
623
- async selectByVisibleText(applicant, xpath, str) {
624
- await this.createClient(applicant);
625
- const client = this.getBrowserClient(applicant);
626
- const selector = await client.$(xpath);
627
- return selector.selectByVisibleText(str);
701
+ async selectByVisibleText(applicant, selector, str) {
702
+ const element = await this.getElement(applicant, selector);
703
+ return element.selectByVisibleText(str);
628
704
  }
629
- async getAttribute(applicant, xpath, attr) {
630
- await this.createClient(applicant);
631
- const client = this.getBrowserClient(applicant);
632
- const selector = await client.$(xpath);
633
- return selector.getAttribute(attr);
705
+ async getAttribute(applicant, selector, attr) {
706
+ const element = await this.getElement(applicant, selector);
707
+ return element.getAttribute(attr);
634
708
  }
635
709
  async windowHandleMaximize(applicant) {
636
710
  await this.createClient(applicant);
637
711
  const client = this.getBrowserClient(applicant);
638
712
  return client.maximizeWindow();
639
713
  }
640
- async isEnabled(applicant, xpath) {
641
- await this.createClient(applicant);
642
- const client = this.getBrowserClient(applicant);
643
- const selector = await client.$(xpath);
644
- return selector.isEnabled();
714
+ async isEnabled(applicant, selector) {
715
+ const element = await this.getElement(applicant, selector);
716
+ return element.isEnabled();
645
717
  }
646
- async scroll(applicant, xpath, xOffset, yOffset) {
647
- await this.createClient(applicant);
648
- const client = this.getBrowserClient(applicant);
649
- const element = await client.$(xpath);
718
+ async scroll(applicant, selector, xOffset, yOffset) {
719
+ const element = await this.getElement(applicant, selector);
650
720
  await element.scrollIntoView();
651
721
  return element.moveTo({ xOffset, yOffset });
652
722
  }
653
- async scrollIntoView(applicant, xpath, scrollIntoViewOptions) {
654
- await this.createClient(applicant);
655
- const client = this.getBrowserClient(applicant);
656
- const element = await client.$(xpath);
723
+ async scrollIntoView(applicant, selector, scrollIntoViewOptions) {
724
+ const element = await this.getElement(applicant, selector);
657
725
  await element.scrollIntoView(scrollIntoViewOptions !== null ? scrollIntoViewOptions : undefined);
658
726
  }
659
727
  async isAlertOpen(applicant) {
@@ -685,11 +753,9 @@ class SeleniumPlugin {
685
753
  }
686
754
  throw Error('There is no open alert');
687
755
  }
688
- async dragAndDrop(applicant, xpathSource, xpathDestination) {
689
- await this.createClient(applicant);
690
- const client = this.getBrowserClient(applicant);
691
- const sourceElement = await client.$(xpathSource);
692
- const destinationElement = await client.$(xpathDestination);
756
+ async dragAndDrop(applicant, sourceSelector, destinationSelector) {
757
+ const sourceElement = await this.getElement(applicant, sourceSelector);
758
+ const destinationElement = await this.getElement(applicant, destinationSelector);
693
759
  return sourceElement.dragAndDrop(destinationElement);
694
760
  }
695
761
  async setCookie(applicant, cookieObj) {
@@ -720,11 +786,9 @@ class SeleniumPlugin {
720
786
  }
721
787
  return client.deleteAllCookies();
722
788
  }
723
- async getHTML(applicant, xpath, b) {
724
- await this.createClient(applicant);
725
- const client = this.getBrowserClient(applicant);
726
- const selector = await client.$(xpath);
727
- return selector.getHTML(b);
789
+ async getHTML(applicant, selector, b) {
790
+ const element = await this.getElement(applicant, selector);
791
+ return element.getHTML(b);
728
792
  }
729
793
  async getCurrentTabId(applicant) {
730
794
  await this.createClient(applicant);
@@ -763,23 +827,17 @@ class SeleniumPlugin {
763
827
  await client.switchToWindow(tabId);
764
828
  return client.closeWindow();
765
829
  }
766
- async getTagName(applicant, xpath) {
767
- await this.createClient(applicant);
768
- const client = this.getBrowserClient(applicant);
769
- const selector = await client.$(xpath);
770
- return selector.getTagName();
830
+ async getTagName(applicant, selector) {
831
+ const element = await this.getElement(applicant, selector);
832
+ return element.getTagName();
771
833
  }
772
- async isSelected(applicant, xpath) {
773
- await this.createClient(applicant);
774
- const client = this.getBrowserClient(applicant);
775
- const selector = await client.$(xpath);
776
- return selector.isSelected();
834
+ async isSelected(applicant, selector) {
835
+ const element = await this.getElement(applicant, selector);
836
+ return element.isSelected();
777
837
  }
778
- async getText(applicant, xpath) {
779
- await this.createClient(applicant);
780
- const client = this.getBrowserClient(applicant);
781
- const selector = await client.$(xpath);
782
- return selector.getText();
838
+ async getText(applicant, selector) {
839
+ const element = await this.getElement(applicant, selector);
840
+ return element.getText();
783
841
  }
784
842
  async elementIdSelected(applicant, id) {
785
843
  await this.createClient(applicant);
@@ -796,10 +854,8 @@ class SeleniumPlugin {
796
854
  const client = this.getBrowserClient(applicant);
797
855
  return client.uploadFile(filePath);
798
856
  }
799
- async getCssProperty(applicant, xpath, cssProperty) {
800
- await this.createClient(applicant);
801
- const client = this.getBrowserClient(applicant);
802
- const element = await client.$(xpath);
857
+ async getCssProperty(applicant, selector, cssProperty) {
858
+ const element = await this.getElement(applicant, selector);
803
859
  const property = await element.getCSSProperty(cssProperty);
804
860
  return property.value;
805
861
  }
@@ -808,25 +864,25 @@ class SeleniumPlugin {
808
864
  const client = this.getBrowserClient(applicant);
809
865
  return client.getPageSource();
810
866
  }
811
- async isExisting(applicant, xpath) {
812
- await this.createClient(applicant);
813
- const client = this.getBrowserClient(applicant);
814
- const selector = await client.$(xpath);
815
- return selector.isExisting();
867
+ async isExisting(applicant, selector) {
868
+ const element = await this.getElement(applicant, selector);
869
+ return element.isExisting();
816
870
  }
817
- async waitForValue(applicant, xpath, timeout, reverse) {
871
+ async waitForValue(applicant, selector, timeout, reverse) {
818
872
  await this.createClient(applicant);
819
873
  const client = this.getBrowserClient(applicant);
820
874
  return client.waitUntil(async () => {
821
- const elemValue = await (await client.$(xpath)).getValue();
875
+ const element = await this.getElement(applicant, selector);
876
+ const elemValue = await element.getValue();
822
877
  return reverse ? !elemValue : !!elemValue;
823
878
  }, { timeout });
824
879
  }
825
- async waitForSelected(applicant, xpath, timeout, reverse) {
880
+ async waitForSelected(applicant, selector, timeout, reverse) {
826
881
  await this.createClient(applicant);
827
882
  const client = this.getBrowserClient(applicant);
828
883
  return client.waitUntil(async () => {
829
- const isSelected = await (await client.$(xpath)).isSelected();
884
+ const element = await this.getElement(applicant, selector);
885
+ const isSelected = await element.isSelected();
830
886
  return reverse ? !isSelected : isSelected;
831
887
  }, { timeout });
832
888
  }
@@ -844,11 +900,9 @@ class SeleniumPlugin {
844
900
  }
845
901
  return client.waitUntil(condition, options);
846
902
  }
847
- async selectByAttribute(applicant, xpath, attribute, value) {
848
- await this.createClient(applicant);
849
- const client = this.getBrowserClient(applicant);
850
- const selector = await client.$(xpath);
851
- return selector.selectByAttribute(attribute, value);
903
+ async selectByAttribute(applicant, selector, attribute, value) {
904
+ const element = await this.getElement(applicant, selector);
905
+ return element.selectByAttribute(attribute, value);
852
906
  }
853
907
  async gridTestSession(applicant) {
854
908
  await this.createClient(applicant);
@@ -921,10 +975,8 @@ class SeleniumPlugin {
921
975
  const client = this.getBrowserClient(applicant);
922
976
  return client.getActiveElement();
923
977
  }
924
- async getLocation(applicant, xpath) {
925
- await this.createClient(applicant);
926
- const client = this.getBrowserClient(applicant);
927
- const element = client.$(xpath);
978
+ async getLocation(applicant, selector) {
979
+ const element = await this.getElement(applicant, selector);
928
980
  return element.getLocation();
929
981
  }
930
982
  async setTimeZone(applicant, timeZone) {
@@ -943,53 +995,37 @@ class SeleniumPlugin {
943
995
  const { filepath, ...restOptions } = options;
944
996
  return client.savePDF(filepath, restOptions);
945
997
  }
946
- async addValue(applicant, xpath, value) {
947
- await this.createClient(applicant);
948
- const client = this.getBrowserClient(applicant);
949
- const selector = await client.$(xpath);
950
- return selector.addValue(value);
998
+ async addValue(applicant, selector, value) {
999
+ const element = await this.getElement(applicant, selector);
1000
+ return element.addValue(value);
951
1001
  }
952
- async doubleClick(applicant, xpath) {
953
- await this.createClient(applicant);
954
- const client = this.getBrowserClient(applicant);
955
- const selector = await client.$(xpath);
956
- return selector.doubleClick();
1002
+ async doubleClick(applicant, selector) {
1003
+ const element = await this.getElement(applicant, selector);
1004
+ return element.doubleClick();
957
1005
  }
958
- async isClickable(applicant, xpath) {
959
- await this.createClient(applicant);
960
- const client = this.getBrowserClient(applicant);
961
- const selector = await client.$(xpath);
962
- return selector.isClickable();
1006
+ async isClickable(applicant, selector) {
1007
+ const element = await this.getElement(applicant, selector);
1008
+ return element.isClickable();
963
1009
  }
964
- async waitForClickable(applicant, xpath, timeout) {
965
- await this.createClient(applicant);
966
- const client = this.getBrowserClient(applicant);
967
- const selector = await client.$(xpath);
968
- return selector.waitForClickable({ timeout });
1010
+ async waitForClickable(applicant, selector, timeout) {
1011
+ const element = await this.getElement(applicant, selector);
1012
+ return element.waitForClickable({ timeout });
969
1013
  }
970
- async isFocused(applicant, xpath) {
971
- await this.createClient(applicant);
972
- const client = this.getBrowserClient(applicant);
973
- const selector = await client.$(xpath);
974
- return selector.isFocused();
1014
+ async isFocused(applicant, selector) {
1015
+ const element = await this.getElement(applicant, selector);
1016
+ return element.isFocused();
975
1017
  }
976
- async isStable(applicant, xpath) {
977
- await this.createClient(applicant);
978
- const client = this.getBrowserClient(applicant);
979
- const selector = await client.$(xpath);
980
- return selector.isStable();
1018
+ async isStable(applicant, selector) {
1019
+ const element = await this.getElement(applicant, selector);
1020
+ return element.isStable();
981
1021
  }
982
- async waitForEnabled(applicant, xpath, timeout) {
983
- await this.createClient(applicant);
984
- const client = this.getBrowserClient(applicant);
985
- const selector = await client.$(xpath);
986
- return selector.waitForEnabled({ timeout });
1022
+ async waitForEnabled(applicant, selector, timeout) {
1023
+ const element = await this.getElement(applicant, selector);
1024
+ return element.waitForEnabled({ timeout });
987
1025
  }
988
- async waitForStable(applicant, xpath, timeout) {
989
- await this.createClient(applicant);
990
- const client = this.getBrowserClient(applicant);
991
- const selector = await client.$(xpath);
992
- return selector.waitForStable({ timeout });
1026
+ async waitForStable(applicant, selector, timeout) {
1027
+ const element = await this.getElement(applicant, selector);
1028
+ return element.waitForStable({ timeout });
993
1029
  }
994
1030
  }
995
1031
  exports.SeleniumPlugin = SeleniumPlugin;