Is there a way to use Cypress to test Polymer (that uses shadow DOM) applications? Any suggestions or best practices? Especially if shadow roots are deeply nested inside each other? Because I don't think querying in each shadow root deep into a page is a solution...
Thanks!
Shadow DOM is a particular problem the same way that iframes are.
Can you reference any API's from selenium based frameworks that you've used before and that you like?
We could take some inspiration from other players to then create out own.
Would be good if cy.get() just worked even when there is a shadow dom. Someone else's solution here: https://gist.github.com/ChadKillingsworth/d4cb3d30b9d7fbc3fd0af93c2a133a53
deepCss in Protractor docs: http://www.protractortest.org/#/api?view=ProtractorBy.prototype.deepCss
That's my gist and has become the authoritative source for the subject.
Traversing into a shadowRoot should never be accidental - it should be an explicit operation.
There are two basic primitives that are needed:
Both of these operations should be available in selenium directly, but until that time testing frameworks should offer helpers to assist with this.
I believe the design of these API's are similar to iframes in that there is an "enter" and "exit" mode. I agree, should be explicit.
Selenium is irrelevant to us because Cypress has native access to the DOM and can query and drill into things at will without serialization.
Thinking of shadowDom similar to iFrame is the wrong mental model - "enter" and "exit" modes are just going to create pain for devs.
You really need something more like:
cy.get('my-element').shadowRoot.get('button').click()
There's not much more to it.
I see. That may work well.
Likely would be .shadowRoot()
I just started to use Cypress a couple days ago, so I might not completely get all concepts. So far I added a function:
Cypress.Commands.add("shadowDomElement", {prevSubject: true},
function(subject, selectors) {
var currentElement = subject[0];
for (var i = 0; i < selectors.length; i++) {
currentElement = currentElement.shadowRoot;
currentElement = currentElement.querySelector(selectors[i]);
if (!currentElement) {
break;
}
}
return currentElement;
});
which basically just takes an array of selectors and returns the last element in that array, but I am not sure if that's a good approach..
The way I use it is:
cy.get('app-shell').shadowDomElement(['page-app', 'page-cluster-reporting', 'page-cluster-reporting-select-plan', 'h2'])
.then((res) => { expect(Cypress.$(res).text()).to.contain('Select Response Plan') })
which seems to be working but I don't think I can perform actions like click() the same way:
cy.get('app-shell').shadowDomElement(['page-app', 'page-cluster-reporting', 'page-cluster-reporting-select-plan', 'paper-button[id=confirm]'])
.then((res) => { Cypress.$(res).click() })
which doesn't seem to be doing anything. Anything I am missing?
When you use Cypress.$(...)
you're simply wrapping the DOM element into a jquery object. Calling .click()
on that is using jquery's click, and not the Cypress click.
You could simply change all of the Cypress.$(...)
into cy.wrap
which then assumes the element and enables you to continue to chain off of it and call cy commands...
Cypress.Commands.add("shadowDomElement", {prevSubject: true},
function(subject, selectors) {
var currentElement = subject[0];
for (var i = 0; i < selectors.length; i++) {
currentElement = currentElement.shadowRoot;
currentElement = currentElement.querySelector(selectors[i]);
if (!currentElement) {
break;
}
}
// wrap this guy back into cypress so you can continue chaining off of it
return cy.wrap(currentElement);
});
cy.get('app-shell').shadowDomElement(['page-app', 'page-cluster-reporting', 'page-cluster-reporting-select-plan', 'h2']).should('contain', 'Select Response Plan')
cy.get('app-shell').shadowDomElement(['page-app', 'page-cluster-reporting', 'page-cluster-reporting-select-plan', 'paper-button[id=confirm]']).click()
I tried that and I am getting RangeError: Maximum call stack size exceeded
on the wrap command.
I think the problem is that many of our internal methods that calculate things like visibility are failing on shadow root elements. So probably just need to take this into account and add a bunch of test cases.
I'm 100% certain all this is solvable, but unfortunately I wouldn't say its a priority for our internal team. We are focusing on higher value features that are applicable to a bigger audience. Would be happy to direct as needed / provide feedback / accept PR's.
If this is something that your company is interested in getting in sooner rather than later than we can talk about prioritizing this work.
@brian-mann thanks. We got shady dom working in our code. And can test for assertions etc. But cannot seem to now click, hover etc. even when shadow dom is not in play (using 'shady' dom).
I got the same "stack overflow" result on the second shadowRoot. (So the first worked.)
Too bad this isn't working, it might be a minor problem. It prevents Cypress from being used with apps build in/with Polymer and web components in general.
Is there any way to speed this up? Donations?
Firefox is finally shipping shadow DOM in March, Edge excluded we can expect for shadow DOM to become mainstream by 2019 for frameworks using or supporting webcomponents (pretty much every framework has PRs to support webcomponents aside from framework specific components)
We are testing TodoMVC made with Polymer here http://todomvc.com/examples/polymer/ just fine. In my humble opinion, shadow DOM might be available in the browsers, but does not mean frameworks would be using it widely.
As far as speeding it up - Cypress is OSS and every contribution is welcome. For us as a company we unfortunately have to prioritize based on what we think has the largest impact. So shadow dom did not make to our https://docs.cypress.io/guides/references/roadmap.html yet
I looked at it and you are testing with the older version version of Polymer (1.x) and WebComponentsjs 0.7.18 not 1.x. This is not using shadowDom nor the newer polyfill of webcomponentjs. In this version you access everthing without using shadowRoot.
Any updates on this? How can we get this moving forward?
+1 To move this higher in priority. @brian-mann I would like to introduce Cypress at the company where I work right now where Polymer is used. However, this is not possible until this feature is added.
I'm looking to test my web components which are ostensibly based off of SkateJS so they have a lot of shadowDOM within them. Are there any updates on what a desired API might look like? I'd be open to contributing if there was more information about what a generally desired solution to this looks like.
It sounds like people generally like the idea of an array of selectors where each item in the array represents a different document, breaking on shadowRoot
s. It seems reasonable to me to extend this into the multiple querying commands, get
, children
, find
and if passed an array they traverse down into shadowRoots
.
Then there's the problems mentioned above with stuff like isVisible
and isAttached
/Detached
. Those don't seem like they need API discussion if I'm not mistake, more just that they need work and test cases.
Just a friendly reminder for everyone watching to give their thumbs up on the first post so it's easier for the team to gauge interest for a roadmap :).
This issue should be renamed to e2e application with shadowDom. It pertains to any app using shadowDom, not just apps that specifically use the polymer library.
Examples:
• Apps that use lit-element, or other shadowDom-based custom elements libraries
• Apps that define shadomDom boundaries using plain old vanilla JS
• Future apps that affect built in element shadowDom
@jennifer-shehane @brian-mann @bahmutov
any suggestions for someone looking to embark on finding a solution to this problem? we would like to square away testing for webcomponents ideally using Cypress, but may have to look at other options unless something can be figured out within Cypress. As @trescenzi mentioned we were looking for guidance as to a possible API which we could implement and raise a PR for so looking for some guidance from the Cypress team before starting.
@egucciar @trescenzi
Thank you so much for your interest. Cypress is OSS and every contribution is welcome.
All the information you need to get started should be right here:
https://github.com/cypress-io/cypress/blob/develop/CONTRIBUTING.md
It is difficult to speculate on what kind of guidance you are seeking, please come forward with specific questions if they arise.
If you are concerned about receiving feedback, you will most certainly receive some after submitting a PR.
Thanks so much @ValerieThoma . So far i have given a try to implement some Cypress custom commands but will need to iterate more before thinking about touching the core. Currently I've implemented shadowGet, shadowFind, and shadowClick. Will let you know what else i discover along the way and wont hesitate to continue to provide feedback so that the eventual tools can be incorporated back to Cypress.
@brian-mann can you please advise if it would be easier for us to change the isAttached
behaviour to account for shadowDom than it would be to re-implement all the cypress commands ourselves?
I'm very new to Cypress and i'm been experimenting with E2E testing with Shadow DOM.
Was testing out an alternative approach to @ChadKillingsworth gist, which uses a library i put together that i've been experimenting with for automation https://www.npmjs.com/package/query-selector-shadow-dom that lets you query without needing to specify the full shadow root path.
I wrote some code to load my lib and find my element in the dom, which worked but cypress throws an error claiming that the element is no longer in the dom, i suspect this is because it is in the shadow dom.
Test code
describe('navigation link', function() {
it('can visit home', function() {
cy.visit('./test.html');
cy.shadowGet('a.go-to').click();
})
})
Hacky hour 1 or trying to work out how Cypress works and how can inject scripts that work with the DOM that led to the screenshot
Cypress.Commands.add("shadowGet", (selector) => {
return cy.window().then((win) => {
return new Promise((resolve) => {
if (win.querySelectorShadowDom) {
const element = win.querySelectorShadowDom.querySelectorAllDeep(selector);
return resolve(element);
} else {
const script = document.createElement('script');
script.onload = () => {
const element = win.querySelectorShadowDom.querySelectorDeep(selector);
return resolve(element);
}
script.src = "node_modules/query-selector-shadow-dom/dist/querySelectorShadowDom.js";
win.document.head.appendChild(script);
}
});
});
});
Hi all. I started working on this feature --> https://github.com/egucciar/cypress/pull/1
Lots left to do, so just posting here to let people know.
Hi All,
I have also the same issue.
Do we have any update on that?
+1
Web components are becoming the standard for building micro frontends. This topic should get a higher priority.
@snapu not to disagree but as a devil's advocate approach one could be using webcomponents without using shadowDom.
But as someone whose written over 100 tests against shadowDom apps using entirely custom commands (and therefore spending lots of time trying to figure out how to make those commands behave similar to cypress ones, which was a very difficult process since custom commands don't come with a lot of documentation explaining the cypress conventions for retryability and such), native support for cypress and shadowDom would be beyond awesome
I even had started implementing this in a feature branch and it wasn't really even that difficult. I was able to get a large amount of native coverage in a few short hours. I began to get stuck because I wanted to be able to discuss with Cypress team what would be the best path to proceed for acceptance as I didn't want an all or nothing approach and be able to release working code more quickly. But the cypress team is inaccessible from that perspective unfortunately I was never able to get feedback or guidance when asking questions.
It would be awesome if cypress team could implement shadowDom support because it's really not much work and only requires a few small shifts in perspective of what they do today. But I could see if no one has shadowDom experience right now it could be difficult as shadowDom does have a lot of differences from the actual Dom.
It's possible to create userland code that queries Shadow Dom using standard query selector syntax, I've done it, have a library on npm for it and integrated with puppeteer. The issues I had with cypress were even though my library could find the Shadow Dom nodes in cypress it does some checks on the visibility of the JS node that I provided (which was in the shadow dom) and errors out
That's correct, it's possible and achievable to make custom commands. As I said I have over 100 tests therefore have a fleet of custom commands for querying and actionability (clicking, selecting from a select box, chai jQuery should, event trigger, input) it's not open source, because I did not want the burden of supporting the library for a wide audience but I can gladly share code snippets.
@egucciar In the end I did the same. Instead of wrapping the shadow dom I have a set of custom commands that operate inside the shadow dom and return back the original element so I can chain these custom commands like shadowContains, shadowType, shadowClick, etc.
That's correct, it's possible and achievable to make custom commands. As I said I have over 100 tests therefore have a fleet of custom commands for querying and actionability (clicking, selecting from a select box, chai jQuery should, event trigger, input) it's not open source, because I did not want the burden of supporting the library for a wide audience but I can gladly share code snippets.
@egucciar Some teams in our organization want to go from Selenium to Cypress but they are doubting to make the move as the applications are running with Polymer. Could you send/post the code snippets that you use for the most used shadowDom commands, so I can do some pilots?
@Ycaglayan here's a gist with the full current source code as of today + Readme + spec file (just needs an index.html with a <cypress-spec>
element to work, or you can modify the spec....). This works with cypress v 3.1.3, but be aware theres a bug in cypress 3.1.2 that affects our commands so ensure youre higher or below :)
just ask me if any questions. start with shadowCommands.js
as the main file. Good luck!
https://gist.github.com/egucciar/5f31c84f19190b5e64737a9d80eb7a9d
@egucciar Thanks a million and the best wishes for 2019!!
ps.:Sorry for the late reply, was offline for the last couple of days. :)
@egucciar That works great! Thank you.
No problem, I forgot to mention for cypress contains
selector functionalities I use the jquery contains
selector (e.g. shadowGet('*:contains("some text")')
. Since this feature is not documented but diverges from the regular cypress way of doing things I figured it is worth mentioning. It was nice to be able to use jQuery for this and why jQuery is used and not native Dom querying.
@egucciar Can you tell me how to use your work? Or sources on how to make that a reality? I don't really get it from your tl;dr discussion.
Thanks for the community work btw.
@codepleb I have the Gist up which you can use as a reference on how to design your own custom shadowDom commands.
@egucciar Thx for the response, but I don't get it.
import { registerShadowCommands } from '@updater/cypress-helpers';
What is this line? Where do I get this dependency from? It doesn't seem to exist on npm.
Or do I need to put the bare files you provided into my support folder and import it from there?
You are free to choose how you want to use the commands. The implementation for registerShadowCommands is provided so if you want to use that you would just move the implementation into your support folder or whatever you choose to do and import. Or you could refactor the code to fit your style. It's really up to you.
I would suggest taking this at face value. There is no dependency on our private packages in this gist, but I lifted all the docs as is.
We're going to close this issue in favor of https://github.com/cypress-io/cypress/issues/144 - which is Shadow Dom support. So please follow that issue.
No longer need to keep up with 2 large issues for the same feature.
We've added experimental shadow DOM support through the experimentalShadowDomSupport
configuration option.
To use it, update to Cypress 4.8.0 and pass the following to your configuration file or however you pass in configuration.
{
"experimentalShadowDomSupport": true
}
See the Experiments for more information including how to use the new .shadow()
command and includeShadowDom
option.
This is still experimental, so we'd like your help working through any bugs or unexpected behavior before releasing into the main product. If you encounter any issues using the new experimental shadow DOM feature, please open a new issue, filling in the issue template and providing a reproducible example.
just tested shadow DOM support. i'm coming from here
i'm wondering if there is an easy to nested query shadow DOM.
for a simple login action. i have to
cy
.get('amplify-authenticator')
.shadow()
.find('amplify-sign-in')
.shadow()
.find('amplify-form-section')
.find('amplify-auth-fields')
.shadow()
// above are common
.find('amplify-username-field')
.shadow()
.find('amplify-form-field')
.shadow()
.find(selectors.usernameInput)
.type(dummy.email);
cy
.get('amplify-authenticator')
.shadow()
.find('amplify-sign-in')
.shadow()
.find('amplify-form-section')
.find('amplify-auth-fields')
.shadow()
// above are common
.find('amplify-password-field')
.shadow()
.find('amplify-form-field')
.shadow()
.find(selectors.signInPasswordInput)
.type(dummy.password);
cy
.get('amplify-authenticator')
.shadow()
.find('amplify-sign-in')
.shadow()
.find('amplify-form-section')
.find(selectors.signInSignInButton)
// .contains('Sign In')
.click();
btw, shadowDOM support is experimental Feature in cypress 5.0, you've turn it on
Most helpful comment
+1 To move this higher in priority. @brian-mann I would like to introduce Cypress at the company where I work right now where Polymer is used. However, this is not possible until this feature is added.