From 24607db882d3f3df0498792a79a2a66b42f587ed Mon Sep 17 00:00:00 2001 From: first87 Date: Tue, 18 Jul 2017 04:06:09 -0700 Subject: [PATCH 1/5] Add new Protractor locator - shadowRoot to find an element throughout the Shadow DOM --- lib/clientsidescripts.js | 57 +++++++++++++++++++++++++++++++++++++--- lib/locators.ts | 35 ++++++++++++++++++++++++ 2 files changed, 89 insertions(+), 3 deletions(-) diff --git a/lib/clientsidescripts.js b/lib/clientsidescripts.js index c741c8d7e..5887577fd 100644 --- a/lib/clientsidescripts.js +++ b/lib/clientsidescripts.js @@ -86,7 +86,7 @@ function getNg1Hooks(selector, injectorPlease) { return {$injector: $injector}; } } - } catch(err) {} + } catch(err) {} } function trySelector(selector) { var els = document.querySelectorAll(selector); @@ -277,7 +277,7 @@ function findRepeaterRows(repeater, exact, index, using) { var row = rows[index] || [], multiRow = multiRows[index] || []; return [].concat(row, multiRow); } -functions.findRepeaterRows = wrapWithHelpers(findRepeaterRows, repeaterMatch); +functions.findRepeaterRows = wrapWithHelpers(findRepeaterRows, repeaterMatch); /** * Find all rows of an ng-repeat. @@ -654,6 +654,57 @@ functions.findByCssContainingText = function(cssSelector, searchText, using) { return matches; }; +/** + * Find an element throughout the Shadow DOM by extended css selector. + * In order to go into the shadow tree of the element, a special selector `::sr` is used. + * + * Usage: + * element(by.shadowRoot('#outer #inner')) <=> $('#outer #inner') + * element(by.shadowRoot('#outer::sr #inner')) <=> $('#outer').shadowRoot.$('#inner') + * element.all(by.shadowRoot('#outer .inner')) <=> $$('#outer .inner') + * element.all(by.shadowRoot('#outer::sr .inner')) <=> $$('#outer').shadowRoot.$$('.inner') + * outer.element(by.shadowRoot('#inner')) <=> outer.$('#inner') + * outer.element(by.shadowRoot('::sr #inner')) <=> outer.shadowRoot.$('#inner') + * outer.all(by.shadowRoot('.inner')) <=> outer.$$('.inner') + * outer.all(by.shadowRoot('::sr .inner')) <=> outer.shadowRoot.$$('.inner') + * + * @param {string} selector An extended css selector, each `::sr` in which will go into the shadow root of the element. + * @param {Element} using The scope of the search. + * + * @return {Array.} The matching elements. + */ +functions.findByShadowRoot = function(selector, using) { + var selectors = cssSelector.split('::sr'); + if (selectors.length === 0) { + return []; + } + + var shadowDomInUse = (document.head.createShadowRoot || document.head.attachShadow); + var getShadowRoot = function (el) { + return ((el && shadowDomInUse) ? el.shadowRoot : el); + }; + var findAllMatches = function (selector /*string*/, targets /*array*/, firstTry /*boolean*/) { + var scope, i, matches = []; + for (i = 0; i < targets.length; ++i) { + scope = (firstTry) ? targets[i] : getShadowRoot(targets[i]); + if (scope) { + if (selector === '') { + matches.push(scope); + } else { + Array.prototype.push.apply(matches, scope.querySelectorAll(selector)); + } + } + } + return matches; + }; + + var matches = findAllMatches(selectors.shift().trim(), [using || document], true); + while (selectors.length > 0 && matches.length > 0) { + matches = findAllMatches(selectors.shift().trim(), matches, false); + } + return matches; +}; + /** * Tests whether the angular global variable is present on a page. Retries * in case the page is just loading slowly. @@ -697,7 +748,7 @@ functions.testForAngular = function(attempts, ng12Hybrid, asyncCallback) { if (n < 1) { if (definitelyNg1 && window.angular) { callback({message: 'angular never provided resumeBootstrap'}); - } else if (ng12Hybrid && !window.angular) { + } else if (ng12Hybrid && !window.angular) { callback({message: 'angular 1 never loaded' + window.getAllAngularTestabilities ? ' (are you sure this app ' + 'uses ngUpgrade? Try un-setting ng12Hybrid)' : ''}); diff --git a/lib/locators.ts b/lib/locators.ts index 5cf944a30..e67d792fd 100644 --- a/lib/locators.ts +++ b/lib/locators.ts @@ -489,4 +489,39 @@ export class ProtractorBy extends WebdriverBy { // When that is supported, switch it here. return By.css('* /deep/ ' + selector); }; + + /** + * Find an element throughout the Shadow DOM by extended css selector. + * In order to go into the shadow tree of the element, use a special selector `::sr`. + * + * @alias by.shadowRoot(selector) + * @view + *
+ * + * <"shadow tree"> + * + * <"shadow tree"> + * + * + * + *
+ * @example + * var span1 = element(by.shadowRoot('#outerspan::sr #span1')); + * var span2 = element(by.shadowRoot('#outerspan::sr #span1::sr #span2')); + * + * @param {string} selector an extended css selector, each `::sr` in which will go into the shadow root of the element + * @returns {Locator} location strategy + */ + shadowRoot(selector: string): Locator { + return { + findElementsOverride: (driver: WebDriver, using: WebElement, rootSelector: string): + wdpromise.Promise => { + return driver.findElements( + By.js(clientSideScripts.findByShadowRoot, selector, using, rootSelector)); + }, + toString: (): string => { + return 'by.shadowRoot("' + selector + '")'; + } + }; + }; } From c17c5ec7f4c42dd79e9036d0eefd358c10948573 Mon Sep 17 00:00:00 2001 From: first87 Date: Tue, 18 Jul 2017 04:07:33 -0700 Subject: [PATCH 2/5] Add additional comments related with Shadow DOM compatiblity into the shadowRoot locator --- lib/clientsidescripts.js | 2 ++ lib/locators.ts | 2 ++ 2 files changed, 4 insertions(+) diff --git a/lib/clientsidescripts.js b/lib/clientsidescripts.js index 5887577fd..1c8ff15d7 100644 --- a/lib/clientsidescripts.js +++ b/lib/clientsidescripts.js @@ -657,6 +657,8 @@ functions.findByCssContainingText = function(cssSelector, searchText, using) { /** * Find an element throughout the Shadow DOM by extended css selector. * In order to go into the shadow tree of the element, a special selector `::sr` is used. + * As compatible with both Shadow DOM v1 and prior specification, `::sr` will be ignored + * for the browser with no support of Shadow DOM, so the result will be same. * * Usage: * element(by.shadowRoot('#outer #inner')) <=> $('#outer #inner') diff --git a/lib/locators.ts b/lib/locators.ts index e67d792fd..c9487b9bb 100644 --- a/lib/locators.ts +++ b/lib/locators.ts @@ -493,6 +493,8 @@ export class ProtractorBy extends WebdriverBy { /** * Find an element throughout the Shadow DOM by extended css selector. * In order to go into the shadow tree of the element, use a special selector `::sr`. + * As compatible with both Shadow DOM v1 and prior specification, `::sr` will be ignored + * for the browser with no support of Shadow DOM, so the result will be same. * * @alias by.shadowRoot(selector) * @view From e6381fd294e4a7b0690b905f0fb90f9e1d4f1ddc Mon Sep 17 00:00:00 2001 From: first87 Date: Tue, 18 Jul 2017 04:08:50 -0700 Subject: [PATCH 3/5] Fix lint errors --- lib/clientsidescripts.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/clientsidescripts.js b/lib/clientsidescripts.js index 1c8ff15d7..f2dd4be51 100644 --- a/lib/clientsidescripts.js +++ b/lib/clientsidescripts.js @@ -670,13 +670,14 @@ functions.findByCssContainingText = function(cssSelector, searchText, using) { * outer.all(by.shadowRoot('.inner')) <=> outer.$$('.inner') * outer.all(by.shadowRoot('::sr .inner')) <=> outer.shadowRoot.$$('.inner') * - * @param {string} selector An extended css selector, each `::sr` in which will go into the shadow root of the element. + * @param {string} selector An extended css selector, each `::sr` in which + * will go into the shadow root of the element. * @param {Element} using The scope of the search. * * @return {Array.} The matching elements. */ functions.findByShadowRoot = function(selector, using) { - var selectors = cssSelector.split('::sr'); + var selectors = selector.split('::sr'); if (selectors.length === 0) { return []; } From 2b6e91d1865b1a242970432126eb0462d31c4bb2 Mon Sep 17 00:00:00 2001 From: first87 Date: Tue, 18 Jul 2017 04:09:36 -0700 Subject: [PATCH 4/5] Add test spec for shadowRoot locator --- spec/basic/locators_spec.js | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/spec/basic/locators_spec.js b/spec/basic/locators_spec.js index 6d64bb727..34b1c5574 100644 --- a/spec/basic/locators_spec.js +++ b/spec/basic/locators_spec.js @@ -394,6 +394,27 @@ describe('locators', function() { }); }); + describe('by shadowRoot', function() { + beforeEach(function() { + browser.get('index.html#/shadow'); + }); + + it('should find items inside the shadow DOM', function() { + var parent = element(by.shadowRoot('#innerDiv::sr #parentDiv')); + var parentHeading = parent.element(by.shadowRoot('.parentshadowheading')); + var youngerChildHeading = parent.element(by.shadowRoot('::sr .youngershadowheading')); + + expect(parentHeading.isPresent()).toBe(true); + expect(youngerChildHeading.isPresent()).toBe(true); + + expect(parentHeading.getText()).toEqual('Parent'); + expect(youngerChildHeading.getText()).toEqual('Younger Child'); + + expect(element(by.shadowRoot('.originalcontent')).getText()) + .toEqual('original content'); + }); + }); + it('should determine if an element is present', function() { expect(browser.isElementPresent(by.binding('greet'))).toBe(true); expect(browser.isElementPresent(by.binding('nopenopenope'))).toBe(false); From 3228b335756a3475256c08168f28fa2217e8e643 Mon Sep 17 00:00:00 2001 From: first87 Date: Tue, 23 Jan 2018 05:35:09 -0800 Subject: [PATCH 5/5] Removed shadow root selector --- lib/clientsidescripts.js | 54 ------------------------------------- lib/locators.ts | 37 ------------------------- spec/basic/locators_spec.js | 21 --------------- 3 files changed, 112 deletions(-) diff --git a/lib/clientsidescripts.js b/lib/clientsidescripts.js index f2dd4be51..b7206a312 100644 --- a/lib/clientsidescripts.js +++ b/lib/clientsidescripts.js @@ -654,60 +654,6 @@ functions.findByCssContainingText = function(cssSelector, searchText, using) { return matches; }; -/** - * Find an element throughout the Shadow DOM by extended css selector. - * In order to go into the shadow tree of the element, a special selector `::sr` is used. - * As compatible with both Shadow DOM v1 and prior specification, `::sr` will be ignored - * for the browser with no support of Shadow DOM, so the result will be same. - * - * Usage: - * element(by.shadowRoot('#outer #inner')) <=> $('#outer #inner') - * element(by.shadowRoot('#outer::sr #inner')) <=> $('#outer').shadowRoot.$('#inner') - * element.all(by.shadowRoot('#outer .inner')) <=> $$('#outer .inner') - * element.all(by.shadowRoot('#outer::sr .inner')) <=> $$('#outer').shadowRoot.$$('.inner') - * outer.element(by.shadowRoot('#inner')) <=> outer.$('#inner') - * outer.element(by.shadowRoot('::sr #inner')) <=> outer.shadowRoot.$('#inner') - * outer.all(by.shadowRoot('.inner')) <=> outer.$$('.inner') - * outer.all(by.shadowRoot('::sr .inner')) <=> outer.shadowRoot.$$('.inner') - * - * @param {string} selector An extended css selector, each `::sr` in which - * will go into the shadow root of the element. - * @param {Element} using The scope of the search. - * - * @return {Array.} The matching elements. - */ -functions.findByShadowRoot = function(selector, using) { - var selectors = selector.split('::sr'); - if (selectors.length === 0) { - return []; - } - - var shadowDomInUse = (document.head.createShadowRoot || document.head.attachShadow); - var getShadowRoot = function (el) { - return ((el && shadowDomInUse) ? el.shadowRoot : el); - }; - var findAllMatches = function (selector /*string*/, targets /*array*/, firstTry /*boolean*/) { - var scope, i, matches = []; - for (i = 0; i < targets.length; ++i) { - scope = (firstTry) ? targets[i] : getShadowRoot(targets[i]); - if (scope) { - if (selector === '') { - matches.push(scope); - } else { - Array.prototype.push.apply(matches, scope.querySelectorAll(selector)); - } - } - } - return matches; - }; - - var matches = findAllMatches(selectors.shift().trim(), [using || document], true); - while (selectors.length > 0 && matches.length > 0) { - matches = findAllMatches(selectors.shift().trim(), matches, false); - } - return matches; -}; - /** * Tests whether the angular global variable is present on a page. Retries * in case the page is just loading slowly. diff --git a/lib/locators.ts b/lib/locators.ts index c9487b9bb..5cf944a30 100644 --- a/lib/locators.ts +++ b/lib/locators.ts @@ -489,41 +489,4 @@ export class ProtractorBy extends WebdriverBy { // When that is supported, switch it here. return By.css('* /deep/ ' + selector); }; - - /** - * Find an element throughout the Shadow DOM by extended css selector. - * In order to go into the shadow tree of the element, use a special selector `::sr`. - * As compatible with both Shadow DOM v1 and prior specification, `::sr` will be ignored - * for the browser with no support of Shadow DOM, so the result will be same. - * - * @alias by.shadowRoot(selector) - * @view - *
- * - * <"shadow tree"> - * - * <"shadow tree"> - * - * - * - *
- * @example - * var span1 = element(by.shadowRoot('#outerspan::sr #span1')); - * var span2 = element(by.shadowRoot('#outerspan::sr #span1::sr #span2')); - * - * @param {string} selector an extended css selector, each `::sr` in which will go into the shadow root of the element - * @returns {Locator} location strategy - */ - shadowRoot(selector: string): Locator { - return { - findElementsOverride: (driver: WebDriver, using: WebElement, rootSelector: string): - wdpromise.Promise => { - return driver.findElements( - By.js(clientSideScripts.findByShadowRoot, selector, using, rootSelector)); - }, - toString: (): string => { - return 'by.shadowRoot("' + selector + '")'; - } - }; - }; } diff --git a/spec/basic/locators_spec.js b/spec/basic/locators_spec.js index 34b1c5574..6d64bb727 100644 --- a/spec/basic/locators_spec.js +++ b/spec/basic/locators_spec.js @@ -394,27 +394,6 @@ describe('locators', function() { }); }); - describe('by shadowRoot', function() { - beforeEach(function() { - browser.get('index.html#/shadow'); - }); - - it('should find items inside the shadow DOM', function() { - var parent = element(by.shadowRoot('#innerDiv::sr #parentDiv')); - var parentHeading = parent.element(by.shadowRoot('.parentshadowheading')); - var youngerChildHeading = parent.element(by.shadowRoot('::sr .youngershadowheading')); - - expect(parentHeading.isPresent()).toBe(true); - expect(youngerChildHeading.isPresent()).toBe(true); - - expect(parentHeading.getText()).toEqual('Parent'); - expect(youngerChildHeading.getText()).toEqual('Younger Child'); - - expect(element(by.shadowRoot('.originalcontent')).getText()) - .toEqual('original content'); - }); - }); - it('should determine if an element is present', function() { expect(browser.isElementPresent(by.binding('greet'))).toBe(true); expect(browser.isElementPresent(by.binding('nopenopenope'))).toBe(false);