protractor icon indicating copy to clipboard operation
protractor copied to clipboard

ShadowDOM support request

Open firstor opened this issue 7 years ago • 58 comments

Seems that ShadowDOM support is still missing:

Background

I am working with some Polymer 2 components which implements ShadowDOM v1 spec and I need to select elements inside their shadowRoots to run e2e tests. deepCss might be a solution but it doesn't work for me. As far as I can see, by.deepCss is nothing special difference with by.css but appending * /deep/ at the beginning of the given CSS selector, however, /deep/ seems deprecated on the browser.

I am working with the following versions:

  • Node Version: v6.10.3
  • Protractor Version: v5.1.2
  • Angular Version: v4.2.4
  • Browser(s): Chrome
  • Operating System and Version: Ubuntu v16.04.2 AMD64 LTS Xenial

I think I checked all relevant articles including the followings and I couldn't get any satisfied answer:

Workaround

Anyway, I can select inner elements of ShadowDOM elements by adding a custom locator. Here is my workaround:

/**
 * Usage:
 *   O  element(by.css_sr('#parentElement #innerElement'))          <=> $('#parentElement #innerElement')
 *   O  element(by.css_sr('#parentElement::sr #innerElement'))      <=> $('#parentElement').shadowRoot.$('#innerElement')
 *   O  element.all(by.css_sr('#parentElement .inner-element'))     <=> $$('#parentElement .inner-element')
 *   O  element.all(by.css_sr('#parentElement::sr .inner-element')) <=> $$('#parentElement').shadowRoot.$$('.inner-element')
 *   O  parentElement.element(by.css_sr('#innerElement'))           <=> parentElement.$('#innerElement')
 *   O  parentElement.element(by.css_sr('::sr #innerElement'))      <=> parentElement.shadowRoot.$('#innerElement')
 *   O  parentElement.all(by.css_sr('.inner-element'))              <=> parentElement.$$('.inner-element')
 *   O  parentElement.all(by.css_sr('::sr .inner-element'))         <=> parentElement.shadowRoot.$$('.inner-element')
 */
by.addLocator('css_sr', (cssSelector: string, opt_parentElement, opt_rootSelector) => {
    let selectors = cssSelector.split('::sr');
    if (selectors.length === 0) {
        return [];
    }

    let shadowDomInUse = (document.head.createShadowRoot || document.head.attachShadow);
    let getShadowRoot  = (el) => ((el && shadowDomInUse) ? el.shadowRoot : el);
    let findAllMatches = (selector: string, targets: any[], firstTry: boolean) => {
        let using, i, matches = [];
        for (i = 0; i < targets.length; ++i) {
            using = (firstTry) ? targets[i] : getShadowRoot(targets[i]);
            if (using) {
                if (selector === '') {
                    matches.push(using);
                } else {
                    Array.prototype.push.apply(matches, using.querySelectorAll(selector));
                }
            }
        }
        return matches;
    };

    let matches = findAllMatches(selectors.shift().trim(), [opt_parentElement || document], true);
    while (selectors.length > 0 && matches.length > 0) {
        matches = findAllMatches(selectors.shift().trim(), matches, false);
    }
    return matches;
});

Conclusion

Since the workaround works on my side, so I am asking~ is there anything I missed or I misused the background principle of Protractor in my workaround? I am politely saying ... if I don't violate your certain rules too much, is it possible to add ShadowDOM support something like that into the next update of Protractor?

Thank you.

firstor avatar Jul 06 '17 06:07 firstor

@first87

Tnx for sharing this. It looks nice and it's a nice workaround.

If you would like to see this in a newer version of Protractor you could add a PR with a new locator like element(by. shadowRoot('#parentElement #innerElement'))

wswebcreation avatar Jul 17 '17 05:07 wswebcreation

@wswebcreation Ok, good. I will add a PR. But I want to make sure just one thing before go: are you okay with this style of selector #parentElement::sr #innerElement to indicate the element #innerElement inside the shadow tree of the #parentElement?

firstor avatar Jul 17 '17 16:07 firstor

@first87

Looks good to me 👍

I'm only not the (only) one who decides about the PR's. The core members, that will also review your PR, decide in the end.

I think it will be a great addition and a clear logging, so go for it 😉

wswebcreation avatar Jul 17 '17 17:07 wswebcreation

@wswebcreation How can I push my commits? Seems that I have no enough permission to contribute.

firstor avatar Jul 17 '17 21:07 firstor

Hi @first87 ,

Have you seen this document. It holds all the steps you need to take.

wswebcreation avatar Jul 18 '17 03:07 wswebcreation

@wswebcreation Yes, I checked DEVELOPER.md and did not find any guide to push. These are steps I went:

  • git clone [email protected]:angular/protractor.git
  • cd protractor/
  • git remote add upstream https://github.com/angular/protractor.git
  • git checkout -b feature/shadow-root-locator master to create my own branch
  • npm install
  • made several commits
  • gulp lint to check if code formatted correctly
  • npm start, webdriver-manager start, npm test to pass all tests

Now what should I do for next?

firstor avatar Jul 18 '17 10:07 firstor

Hi @first87,

  • You start by forking the project.
  • Then it will be add to your account.
  • Do a clone to your local machine
  • Then push your changes and click on the "Create PR"-button.

That should do the trick

wswebcreation avatar Jul 18 '17 10:07 wswebcreation

@wswebcreation Ok, thank you for guiding.

firstor avatar Jul 18 '17 10:07 firstor

@wswebcreation I just created a PR #4392.

firstor avatar Jul 18 '17 11:07 firstor

@first87 Tnx!!

wswebcreation avatar Jul 18 '17 11:07 wswebcreation

What is the status on this? We would like to be able to use the by.shadowRoot function.

SirJackovich avatar Dec 13 '17 16:12 SirJackovich

Hope someone helps me resolve this: https://github.com/angular/protractor/pull/4392#issuecomment-359814654

firstor avatar Jan 23 '18 20:01 firstor

@first87 I tried using your new locator in my e2e tests. I am unable to get the elements in the shadow dom.

Environment: Angular (v5) Application with Polymer Web Components. Protractor for running e2e tests.

Angular CLI: 1.6.4 Node: 6.10.0 Angular: 5.2.0 @angular/cli: 1.6.4 typescript: 2.5.3 Below given is my polymer web component shadow root expanded in chrome. You could see input type = "text" inside this custom element.

I am unable to access input element inside custom polymer component using protractor by.css_sr.

var polymerFirstName = element(by.className('polyFName')); var inputElement = polymerFirstName.element(by.css_sr('input')); // returns nothing.

It failed with an error "No element found using locator: by.css_sr("input")".

shadowroot

I need to access the inner input element so that I can perform UI Automation tasks like. polymerFirstName.element(by.css_sr('input')).clear(); polymerFirstName.element(by.css_sr('input')).sendKeys('Ritchie');

Anything I am missing?

msbasanth avatar Jan 24 '18 13:01 msbasanth

@msbasanth You missed ::sr since by.css_sr won't start to find elements in a shadow root of the element ~ it will dig into the shadow DOM tree of any element for each ::sr. So you should replace your input element finder with this:

var inputElement = polymerFirstName.element(by.css_sr('::sr input'));

firstor avatar Jan 24 '18 13:01 firstor

Thank you @first87 it worked like a charm. Hope we will get this shadow DOM support in protractor soon.

msbasanth avatar Jan 25 '18 04:01 msbasanth

This works! Thank you! It's going to be so hard locating elements now especially if there are nested shadow doms :( since we can't test the locators in the console. Thanks again!

rominoushana avatar Feb 15 '18 23:02 rominoushana

@firstor Thank you so much for this! Hopefully this can get integrated soon....

I was hoping to get some assistance from you on this though. While I am able to get the correct value using ::sr as needed, when I do title.getText() it seems to get the "incorrect" value. It seems to be coming in as an object. See the following output below:

Expected [ 'Title' ] to be 'Title'.

I have not had success in being able to get the string from that object/array and was wondering if you had an any idea?

Thanks again for the workaround!!

EDIT: Disregard!! Referenced this and it did the trick https://stackoverflow.com/questions/29478905/protractor-element-gettext-returns-an-object-and-not-string. Thanks again!!

oseasmoran73 avatar Jan 04 '19 18:01 oseasmoran73

Hi @firstor / @msbasanth , I am unable to get an element present in shadow-root DOM. I copied your above code in a .ts file but getting a compilation error at line no. 6: Code: let shadowDomInUse = (document.head.createShadowRoot ....... Error: Property 'createShadowRoot' does not exist on type 'HTMLHeadElement'.

Application: Angular v7 NodeJS: 6.9.0 typescript: 2.3.3 protractor: 5.3.2

Can you please elaborate on how to use your method to enter some text in the input box inside shadow-root? Thanks!

image

ssudhanshu avatar Mar 07 '19 09:03 ssudhanshu

image

@firstor Thanks for this. Am trying to use it like this. var searchBar = element(by.css_sr('::sr input')); var searchButton = element(by.css_sr('::sr i'));

Am getting the below error [11:36:06] E/launcher - TypeError: Invalid locator

What am i doing wrong? Any help is highly appreciated. Thanks in advance.

ansreenath avatar May 24 '19 10:05 ansreenath

@firstor hey, thanks for the workaround) however, we can't fully use it in our team unless it's merged into Protractor master branch (we're using Protractor on remote nodes so the libs are always pulled when the tests are being run).

Any progress on merging the https://github.com/angular/protractor/pull/4786 ?

alexzavg avatar Sep 04 '19 10:09 alexzavg

Any update on this?

sudeeppaudyal avatar Sep 16 '19 20:09 sudeeppaudyal

any update? this is a blocker for many specs as of late(

alexzavg avatar Nov 29 '19 13:11 alexzavg

Hello @firstor ,

Application : Angular 8 NodeJS : 12.13.1 typescript: 3.5.3 Protractor : 5.4.0

Thank you for your workaround.

I try to find element into ShadowRoot. In our frontend architecture, we Have 3 depth levels

  • First level : Micro frontend application
  • Second level : Micro frontend component
  • Third level : Angular material Component

Protractor_ShadowRoot-issue

First and second shadowRoot level are accessible but not the last one.

Extract of the Code source :

    await expect(element(by.tagName('page-affaire')).isDisplayed());
    await browser.driver.sleep(3000);
    console.log('Page affaire chargée');
    var el1 = element(by.tagName('page-affaire'));
    
    var el2 = el1.element(by.css_sr('::sr rac-card'));
    console.log('rac-card found');

    var el3 = el1.element(by.css_sr('::sr rac-option'));
    console.log('rac-option found');

    var el4 = el1.element(by.css_sr('::sr mat-select')); 
    console.log('rac-card select');

Result logs :

Jasmine` started
Page affaire chargée
rac-card found
rac-option found
rac-card select
Sélection du type de demande

  créer Affaire
    × Select type demande
      - NoSuchElementError: No element found using locator: by.css_sr("::sr mat-select")

We try with other solution (https://stackoverflow.com/questions/57979981/how-to-check-in-protractor-javescript-in-shadow-dom-if-the-button-is-enabled-o). We have the same issue :

await browser.executeScript("return document.querySelector(\"page-affaire\");").then(function () {
      console.log('page affaire found');
      // browser.sleep(5000);
    });
    await browser.executeScript("return document.querySelector(\"page-affaire\").shadowRoot.querySelector(\"rac-card\");").then(function () {
      console.log('rac card found');
      // browser.sleep(5000);
    });
    await browser.executeScript("return document.querySelector(\"page-affaire\").shadowRoot.querySelector(\"rac-card\").shadowRoot.querySelector(\"rac-option\");").then(function () {
      console.log('rac option found');
      // browser.sleep(5000);
    });
    await browser.executeScript("return document.querySelector(\"page-affaire\").shadowRoot.querySelector(\"rac-card\").shadowRoot.querySelector(\"rac-option\").shadowRoot.querySelector(\"mat-select\").click();").then(function () {
      console.log('Type de Demande cliqué');
      browser.sleep(5000);
    });

Result :

Jasmine started
Page affaire chargée
rac-card found
rac-option found
rac-card select
Sélection du type de demande

  créer Affaire
    × Select type demande
      - NoSuchElementError: No element found using locator: by.css_sr("::sr mat-select")

Is there a limit regarding shadowRoot depth limit access ?

Thanks you for your support.

V1nc3kr0

v1nc3kr0 avatar Dec 12 '19 09:12 v1nc3kr0

Hello @firstor ,

Application : Angular 8 NodeJS : 12.13.1 typescript: 3.5.3 Protractor : 5.4.0

Thank you for your workaround.

I try to find element into ShadowRoot. In our frontend architecture, we Have 3 depth levels

  • First level : Micro frontend application
  • Second level : Micro frontend component
  • Third level : Angular material Component

Protractor_ShadowRoot-issue

First and second shadowRoot level are accessible but not the last one.

Extract of the Code source :

    await expect(element(by.tagName('page-affaire')).isDisplayed());
    await browser.driver.sleep(3000);
    console.log('Page affaire chargée');
    var el1 = element(by.tagName('page-affaire'));
    
    var el2 = el1.element(by.css_sr('::sr rac-card'));
    console.log('rac-card found');

    var el3 = el1.element(by.css_sr('::sr rac-option'));
    console.log('rac-option found');

    var el4 = el1.element(by.css_sr('::sr mat-select')); 
    console.log('rac-card select');

Result logs :

Jasmine` started
Page affaire chargée
rac-card found
rac-option found
rac-card select
Sélection du type de demande

  créer Affaire
    × Select type demande
      - NoSuchElementError: No element found using locator: by.css_sr("::sr mat-select")

We try with other solution (https://stackoverflow.com/questions/57979981/how-to-check-in-protractor-javescript-in-shadow-dom-if-the-button-is-enabled-o). We have the same issue :

await browser.executeScript("return document.querySelector(\"page-affaire\");").then(function () {
      console.log('page affaire found');
      // browser.sleep(5000);
    });
    await browser.executeScript("return document.querySelector(\"page-affaire\").shadowRoot.querySelector(\"rac-card\");").then(function () {
      console.log('rac card found');
      // browser.sleep(5000);
    });
    await browser.executeScript("return document.querySelector(\"page-affaire\").shadowRoot.querySelector(\"rac-card\").shadowRoot.querySelector(\"rac-option\");").then(function () {
      console.log('rac option found');
      // browser.sleep(5000);
    });
    await browser.executeScript("return document.querySelector(\"page-affaire\").shadowRoot.querySelector(\"rac-card\").shadowRoot.querySelector(\"rac-option\").shadowRoot.querySelector(\"mat-select\").click();").then(function () {
      console.log('Type de Demande cliqué');
      browser.sleep(5000);
    });

Result :

Jasmine started
Page affaire chargée
rac-card found
rac-option found
rac-card select
Sélection du type de demande

  créer Affaire
    × Select type demande
      - NoSuchElementError: No element found using locator: by.css_sr("::sr mat-select")

Is there a limit regarding shadowRoot depth limit access ?

Thanks you for your support.

V1nc3kr0

Give a try like var el4 = el3.element(by.css_sr('::sr mat-select'));

alagappan-qa avatar Jan 10 '20 10:01 alagappan-qa

@firstor My Web Application is using Polymer.js with 10 levels of Shadow roots. RJvlf Is it possible to call the last Shadow root element from the top node instead of traversing the individual Shadow roots. Thanks for your support.

alagappan-qa avatar Jan 11 '20 14:01 alagappan-qa

Hello guys , I am new to protractor and i wish i can get a help on how to include this locator script (shadowDOM) in protractor .
i am doing some UI testing on polymer webcomponent , the protractor version : 5.4.2 and node v12.13 .

could any one tell me where to include that script ? @firstor , @alagappan-qa

thanks in advance.

rugaba avatar Jan 15 '20 17:01 rugaba

@rugaba Include the script in the Configuration.js file under onPrepare() method.

alagappan-qa avatar Jan 20 '20 06:01 alagappan-qa

thx @alagappan-qa , it worked !!

rugaba avatar Jan 22 '20 11:01 rugaba

Hi,

Is it possible to include this script in a separate js file and import it in the protractor.conf file and include in onPrepare() method. If so, how do we do it ?

Thanks, Lakshmi

Lakhs02 avatar Mar 14 '20 08:03 Lakhs02

If anyone needs this now, see https://github.com/angular/protractor/pull/4786#issuecomment-607204672 and https://github.com/angular/protractor/pull/4786#issuecomment-607224145

You can also use https://github.com/angular/protractor/pull/4786.patch or https://github.com/angular/protractor/pull/4786.diff

DanielRuf avatar Apr 01 '20 12:04 DanielRuf