protractor
protractor copied to clipboard
ShadowDOM support request
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 shadowRoot
s 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.
@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 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
?
@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 How can I push my commits? Seems that I have no enough permission to contribute.
@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?
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 Ok, thank you for guiding.
@wswebcreation I just created a PR #4392.
@first87 Tnx!!
What is the status on this? We would like to be able to use the by.shadowRoot function.
Hope someone helps me resolve this: https://github.com/angular/protractor/pull/4392#issuecomment-359814654
@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")".
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 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'));
Thank you @first87 it worked like a charm. Hope we will get this shadow DOM support in protractor soon.
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!
@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!!
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!
@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.
@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 ?
Any update on this?
any update? this is a blocker for many specs as of late(
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
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
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
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'));
@firstor
My Web Application is using Polymer.js with 10 levels of Shadow roots.
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.
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 Include the script in the Configuration.js file under onPrepare() method.
thx @alagappan-qa , it worked !!
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
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