Currently, Cypress fails for various reasons when dealing with the shadow dom. So far, I've seen three issues.
1) Cypress considers the shadow root to be a non-DOM subject.
cy.get("#root")
.then ($root) ->
return Cypress.$($root[0].shadowRoot)
.find('.child-2')
.should('have.text', 'I am child 2')
Moving the .find
into the .then
gets past this issue, but is not ideal:
cy.get("#root")
.then ($root) ->
return Cypress.$($root[0].shadowRoot).find('.child-2')
.should('have.text', 'I am child 2')
It might be nice and maybe even necessary to have a convenience method like cy.getShadowRoot()
that could be chained off other dom commands.
2) When you try to assert something on an element inside a shadow root, Cypress reports that the element is detached from the dom. This stems from the fact that $.contains(el1, el2)
returns false when el1 is outside the shadow root and el2 is inside it. Some discussion here.
cy.get("#root")
.then ($root) ->
return Cypress.$($root[0].shadowRoot).find('.child-1')
.should("have.text", "I am child 1")
3) Commands that cause Cypress to check if the subject is hidden by ancestors trigger infinite recursion and blow the call stack because it never hits the <body>
or <html>
elements while walking up the chain of ancestors.
cy.get("#root")
.then ($root) ->
return Cypress.$($root[0].shadowRoot).find('.child-2')
.contains('button', 'Go')
For anyone finding this issue on Google when searching for how to access the shadow Dom:
I just this workaround until this feature is implemented:
it('.should - search by email', () => {
cy.get('profile-search').then(search=>{
const email = search[0].shadowRoot.querySelector('#email');
email.value='[email protected]';
email.dispatchEvent(new CustomEvent('change'));
});
cy.wait(200);
cy.get('profile-search').then(search => {
return search[0].shadowRoot.querySelector('profile-preview').getAttribute('uid');
}).should('eq', 1);
});
Any update on what might be best way to use Cypress on say a Polymer project nicely?
@chachra https://github.com/cypress-io/cypress/issues/830#issuecomment-339816720
Is there any update on this? The temporary solution proposed by @brian-mann in #830 doesn't seem to work in the newest versions of Cypress anymore. It throws a Maximum call stack size exceeded
error when cy.wrap
is called on an element that is in a shadow root. Might be related to issue 3 that @chrisbreiding mentioned above. We'd love to use Cypress for a large Polymer 2 app we're developing, but this seems to rule Cypress out as an option for now.
I would love to get some update on this too :)
Same here, we're heavily relying on shadow-DOM and use Cypress for our functional testing.
Thanks to @chrisbreiding we've figured a few ways to get around and select elements, but not having a proper support of shadow DOM makes it impossible to use .get()
, and therefore to do anything related to forms.
Updates? :sweat_smile:
Would love to see this working! :+1:
Commented on a similar issue here with my findings, although am able to find shadow Dom elements with JS, cypress thinks the element is not attached to the DOM anymore when I work with it
https://github.com/cypress-io/cypress/issues/830
This feature is still in the 'proposal' stage. No work has been done on this feature as of yet. We're a small team and as much as we'd love to work on everything, we have to choose what to work on based on a multitude of things.
Just plus oneing this issue. I hope you prioritize this, because I'd like to be able to use cypress without having to petition every company I work for to rewrite their front end, since shadow dom is super common. I've been using it all of two weeks and I'm impassibly stuck because shadow dom can't be seen.
Another +1 here!
Just plus oneing this issue. I hope you prioritize this, because I'd like to be able to use cypress without having to petition every company I work for to rewrite their front end, since shadow dom is super common. I've been using it all of two weeks and I'm impassibly stuck because shadow dom can't be seen.
I agree with you. Nowadays it's a common scenario big companies bet for WebComponents for build their front-end, and it's a shame we can't use cypress for testing :(
Hope it get's prioritized.
+1
May I ask is there any update or workarounds? I'm testing ionic4 based apps and there are thousands of shadow doms in ionic4 components. :-(
I'm also on boat for cypress supporting shadow-dom for us stenciljs users. Any updates?
No work has been done on this issue.
What's the status quo here? Is it somehow possible to test WebComponents and/or ShadowDom?
+1
We are using Angular WebComponents and need this Feature.
Hi, I just started writing a plugin to support web components. It's still very raw but it works for me and my apps pretty well.
Check it out here https://github.com/JaySunSyn/cypress-daywalker
If I get enough GitHub stars, I'll continue 馃檲
Tested within a very big Polymer & lit app ... Performs well also with very deeply nested components. Creates an index of all registered custom elements. Not all CSS selectors are supported yet.
Adds some custom commands as well:
cy.get('my-el').call('close')
cy.get('my-el').call('search', ['jimmy'])
cy.get('my-el').dispatch('click')
cy.get('paper-input').setProp('Hello how are you?')
cy.get('paper-input').setProp('Question', 'label')
@jaysunsyn it looks like your code might do similar things to this package I wrote for discovering deeply nested shadow roots with normal query selector syntax as if the shadow roots don't exist at all, might be worth checking out https://www.npmjs.com/package/query-selector-shadow-dom
I鈥檓 not really querying the Dom to find an element but creating an index on registration time. But your lib looks interesting @Georgegriff
:+1: This issue is really starting to drive me up the wall
I do realize Cypress' team is small and there's loads work to do, but still - I'm surprised this isn't a higher priority issue, considering how much of a blocker it is.
It pretty much forces us to pick either Cypress, or Shadow DOM, in any given project. Can't use both. (Unless you're willing to spend significant amount of time working on your own commands etc.)
And that's a Sophie's Choice - I want both, and both for good reasons [1] [2]. (IMO, Shadow DOM's benefits are more important)
Even worse - it doesn't seem like this situation is going to change any time soon :/
@JaySunSyn Could you open a pull request to add this plugin to our documentation? Thanks! Instructions here.
Thanks @jennifer-shehane - I'll do that once I implemented some breaking changes and added better documentation for the plugin. Should be soon.
On developers.google.com where they discuss Web Components, they mention the idea of overriding attachShadow()
:
There's nothing stopping an attacker from hijacking
Element.prototype.attachShadow
.
A gist by praveenpuglia mentions the same thing:
Also, closed mode isn't a security mechanism. It just gives a fake sense of security. Nobody can stop someone from modifying how
Element.prototype.attachShadow()
works.
I believe this is a strategy that test frameworks can take advantage of as well. Simply create a div
instead of a shadow root and testing can resume as always. Or create an open shadow root instead of closed. Or register that a shadow root is created there so you don't have to crawl through the entire DOM.
@jennifer-shehane: I understand that you guys are a small team and I don't want to be harsh here, but it's been three years. Your current mission statement clearly states:
The web has evolved. Finally, testing has too.
Fast, easy and reliable testing for anything that runs in a browser.
Do Web Components not fit into that picture?
Going to create a set of custom commands to bring support shadow DOM. More to come at https://github.com/abramenal/cypress-shadow-dom
Is this one not working for you guys? https://github.com/JaySunSyn/cypress-daywalker
@JaySunSyn: I'm gonna test it upcoming monday. @jennifer-shehane has indicated she's interested in your solution, so for the moment that's where my interest will be too. My comment from a couple of days ago was more out of amazement that it had been three years while I believe that the implementation code isn't that difficult. I've checked out your code just now, you're overriding customElements.define()
, a very similar approach to overriding attachShadow()
that I described.
@JaySunSyn: Ok, I've tested it! I couldn't get it to work with a simple page I'd setup myself, so I tried your example. Sadly, I get inconsistent results when running your example tests. At first, about half the tests failed. I commented one test out and after the second run, I noticed one of the tests that suddenly failed where it previously succeeded. Simply pressing F5 yielded different results again: Tests that passed the previous run suddenly failed and vice versa.
The failing tests all produce similar error messages:
TypeError: Cannot read property ... of undefined
I started commenting out more tests and noticed different results almost every run. And I've noticed, running only 1 or 2 tests, the success rate increases greatly. But as soon as I start to add more tests, even simple ones like nth
and class
, the failure rate rises to about 50%.
Has this example worked for you locally? Because one test seems to have an assertion error. nested custom element
checks whether there are two paper-buttons
on the page, but my-element
really only uses one in its template. I've corrected this assertion locally during testing, but I can't imagine that this example testsuite has not resulted in errors at your end.
I'm running the example with polymer serve
and .\node_modules\.bin\cypress open
with the latest Polymer-cli and the latest Cypress installed.
I'm really concerned about tweaking Cypress' existing commands, it feels same way as modifying host/native objects in JS. If I'm a newcomer to a certain object I would expect commands working in the way as official docs say, not really digging into all the packages finding what's actually affecting them.
@JPtenBerge I guess this is not the place to discuss the issues you have with the plugin but to answer your question, yes, the tests do work for me.
git clone [email protected]:JaySunSyn/cypress-daywalker.git
cd example
npm i
npm run test
If you need help or if you found bugs, pls create issues.
I wanted to share some thoughts on that. Initially, I presumed I needed to reach into the shadow DOM of custom element to assert on its contents and/or interact with it. I think some folks over here are trying to do the same thing.
However, since then I realized that might not be necessary, and in fact, might be counterproductive. Custom elements are all about encapsulation. The idiomatic way of using them in the web app is by setting properties and/or attributes, and listening for events. If the web app doesn't care about implementation details of a custom element, neither should that web app's tests. So instead of trying to mess with shadow DOM's contents, tests should also use custom element's properties and attributes.
Of course, custom element itself needs to be tested too. But this should be done separately, not in Cypress - because Cypress is an integration testing tool, not a tool for testing isolated elements.
For instance, consider a hypothetical custom element that implements a toggle button. It might be used like that:
let toggle = document.createElement('my-toggle')
toggle.onLabel = 'Switch On'
toggle.offLabel = 'Switch Off'
toggle.toggled = false
toggle.addEventListener('toggled', () => {
toggle.toggled = !toggle.toggled
})
and its shadow DOM might look like:
<my-toggle>
<!-- Shadow DOM begins -->
<div class="some-fancy-wrapper">
<button>Switch On</button>
<button>Switch Off</button>
</div>
<!-- Shadow DOM ends -->
</my-toggle>
Testing a web app with this component in it might look like:
cy.visit(...)
cy.get('my-toggle').invoke('toggled', true)
cy.get('my-toggle').invoke('toggled', false)
...
No need to even attempt to .get()
<button>
's inside of the shadow DOM.
Of course, the assumption here is that this toggle component is tested properly elsewhere, so we know that setting a .toggled
property actually works. This can be done in a number of ways of course. I'm using Karma for now.
I think advantages of the above approach is kinda obvious, but just to reiterate:
Edit: forgot to mention, if your app relies on the custom element dispatching an event after a change, you might need to test it in a slightly different way:
cy.get('my-toggle').then($el => {
$el[0].toggled = true
$el[0].dispatchEvent(new Event('toggled'))
})
@kamituel That's a good suggestion, but what do you do if the button component you describe is itself part of another web component, let's say a form? You can't test inside the form (and according to your opinion we shouldn't). So you would need something else besides Cypress to test the form's behaviour. The form itself is part of a page, which also might be a web component and have shadow DOM. If we can only assert on the properties and events of the page component, testing the form's behaviour is again impossible. This chain goes up all the way to the root application component.
In the end, Cypress would be useless because we can only test the properties and event handlers of the main application component, which often doesn't have any inputs nor event handlers.
@leonrodenburg yup, pretty much. So to be clear, an approach I described works in some cases, but certainly not in every case.
For us it seems to be a viable approach because (a) our web app won't be defining its own Custom Elements w/ Shadow DOM (b) a set of UI elements that will utilize Shadow DOM is limited to "widgets" (buttons, form elements, headings, etc - an implementation of a design system).
In our case web app will be tested with Cypress, and a library of Custom Elements won't (we're using Karma instead for now).
If your architecture is solely based on Custom Elements w/ Shadow DOM up to the level of the whole web app, then yeah, you'll need another solution.
@kamituel
Of course, custom element itself needs to be tested too. But this should be done separately, not in Cypress - because Cypress is an integration testing tool, not a tool for testing isolated elements.
I respectfully disagree with this.
Firstly, we're integrating web components with each other. That integration is interesting to test.
Secondly, Cypress describes itself as:
Fast, easy and reliable testing for anything that runs in a browser.
To me, that includes isolated elements. I'm not sure on this though, I've asked @jennifer-shehane last week, but haven't heard back yet. But I don't see why Cypress would limit itself this way.
Lastly, testing a custom element should indeed be tested in other ways, like with unit tests. But unit tests and end-to-end tests often have a bit of overlap. In general, it's not one or the other, people use both. Most functional logic will be covered with unit tests, but a simple check whether everything is rendered properly in browsers is very important as well.
If your architecture is solely based on Custom Elements w/ Shadow DOM up to the level of the whole web app, then yeah, you'll need another solution.
Which is the case for Polymer/LitElement projects.
@kamituel That's a good suggestion, but what do you do if the button component you describe is itself part of another web component, let's say a form? You can't test inside the form (and according to your opinion we shouldn't). So you would need something else besides Cypress to test the form's behaviour. The form itself is part of a page, which also might be a web component and have shadow DOM. If we can only assert on the properties and events of the page component, testing the form's behaviour is again impossible. This chain goes up all the way to the root application component.
In the end, Cypress would be useless because we can only test the properties and event handlers of the main application component, which often doesn't have any inputs nor event handlers.
This is exactly what would happen in a micro frontends architecture where whole pages can be a web component. My team is in a the process of choosing a testing tool and we are about to commit to choose TestCafe instead of Cypress mostly because of better shadow dom support.
I'm sure you've heard this hundreds of times: many of us are looking for a Selenium alternative and as a result are predisposed to use Cypress, but this issue (and to a lesser extent multi-browser and tab support) is a deal-breaker. Any transparency on this would be helpful.
@grenma-ld It's something we want to add, but nobody from the Cypress team is currently working on this. There's no PR open for it from the community either.
For working with the shadow DOM today, you can try this user-created plugin that adds support: https://github.com/JaySunSyn/cypress-daywalker
Haven't been able to get daywalker to work yet - will keep trying and perhaps reach out, but unless it works well we'll have to fall back to Protractor/Selenium
Such a shame this great tool won't be made to work with the ShadowDom, lots and lots of apps are starting to use this great feature and cypress supporting it would be a great feature :)
@grenma-ld @GlenHughes and anyone who are interested
I think until there is no official support for shadow DOM we can make a set of custom commands to make our stuff work.
I've created an initial set of shadow'ish commands for querying, asserting elements and working with events. It'll be great if we can review that together, create bugs & decide what are must-have commands that majority of the users need. I'm fully okay to support the development of anything related to that.
https://github.com/abramenal/cypress-shadow-dom
Note: I saw daywalker package, but don't like both the implementation approach used and the exposed commands structure (especially since it's a try to override cypress' ones).
Looking forward your feedback!
Just remind that's not really a ready package but a place to start with something 馃槇
Hi @abramenal,
I've actually started to use TestCafe for the moment but what you've come up with looks brilliant. I will give that a go and see how it works. I'm happy to chip in when I can :)
any of the above solutions works for lightning components rendered by salesforce, if someone has faced this issue before and have a workaround the guidance should be more than welcome!!
So from a brief look into it, it seems like jQuery may need to be modified as the methods there are not 'ShadowDOM aware', there are various places where cypress uses such methods to check for visibility and if the element is on the page (eg. it uses parent
and contains
method for these).
Patching these should be quite simple just making sure it's traversing up through shadow boundaries.
I think a real solution here would also need to not use any special notation for traversing through shadow boundaries, the get method imo should be extended to perform something similar to @Georgegriff's https://www.npmjs.com/package/query-selector-shadow-dom, perhaps a deep: true
option could be added to the options parameter.
Will take a look further at some point in the future to figure out exactly what needs to be fixed to make this work as expected. Would you be accepting PR's for this feature?
Note: As a work around, providing you are using the ShadyDOM polyfill, you can actually use window.ShadyDOM = { force: true }
to force the polyfill to load. This means that your site is no longer using real shadow dom and you can do your tests as normal.
Of course though this means you're testing against something your users won't actually see and you risk having to rewrite a lot of tests if the Cypress devs decide on a different syntax for piercing shadow dom in the 'get' method, but it's something.
So after revisiting this it seems like there may be quite a bit of work here, I was working with just trying to get Cypress to accept elements in the shadow dom without crashing with Maximum callstack
errors etc.
First up jQuery needs to be updated as there have been some fixes in regards to working with Shadow DOM.
Everywhere that jQuery.parent
is used it needs to be changed as by default as soon as jQuery encounters a Document Fragment (nodeType 11) it will just return null as the parent as it expects it's not connected to the DOM. What we should do in this scenario is traverse the DOM ourselves and check if node type is fragment node and then check if there is a host
property on this to continue traversing up.
isChild (maybe - not certain yet) and other similar methods need to actually do the reverse and check if any of the children have a shadow root, this is slightly more tricky and would become quite resource intensive I imagine.
Mouse events & Keyboard events & Other UI events should set the composed
flag to true
as these by default can pass through the ShadowDOM.
getDefaultButtons in type.js
needs to find them inside the shadow dom.
getElementAtPointFromViewport
needs to jump into shadow root and traverse down until it finds the first element without a shadowRoot. This gets tricky as you need to account for slotted text nodes that will be the element 'on top' but actually is inside the child component meaning you also need to check if the element thats supposed to be covering the child is actually a parent node.
type()
needs to support custom input web components. Thought this would be as simple as just adding configurable additional selectors to isTextLike
however it gets complicated with the getNativeProp
method being called.
Similar to 7, with web components that extend button elements, cypress needs to check the 'disabled' attribute/property to determine if it's clickable yet.
traversals.coffee
needs to be completely re-written to support shadow dom or majority of the jquery methods there need to be modified.
I started work on these here: https://github.com/cypress-io/cypress/compare/develop...TomCaserta:issue-144?expand=1
Not much there at the moment but it now doesn't throw errors when picking up elements in the shadow dom but I imagine there are tons of other issues around. It would be great to get some input from some Cypress devs on how this issue could be tackled, right now it seems like actually implementing this could introduce some breaking changes which is not ideal.
@abramenal I've installed the cypress-shadow-dom but getting an issue with locating the elements, I keep getting 'shadowRoot' of undefined
that's how I'm calling it
cy.visit('/url')
cy.shadowGet('css locator - root element')
.shadowFind('css locator')
.shadowTrigger('click')
@mohammedalnuaimi
have you seen the example app covered with test cases?
https://github.com/abramenal/cypress-shadow-dom/tree/master/example
If you still seeing the issue, please feel free to submit the issue to the plugin's repo so we can take a look together on your specific setup, tests and issue you're getting. Current place is not the best one to discuss my plugin 馃槇
thanks @abramenal for sending the example over, will have a look and raise an issue if required
So, I have faced with the Shadow Dom issue as well :((
After reading all this thread and bunch of information for years I have tried the NPM package by @abramenal and after a few attempts I was able to figure out how to use it and it works for me. Thank you! But unfortunately in its't current state it s not really a way to deal with shadow dom if you have a lot of those in the app. If just few and need a workaround - yes, it works. But build all tests using it - I don't think so.
The issues I have faced with:
But anyway, @abramenal did a great job. Why Cypress team @bahmutov @jennifer-shehane can't pick this module up, made some improvements and add to the cypress?
This is highly needed feature!
While the cypress-shadow-dom plugin helps with querying shadow DOM, DOM snapshots and video/screenshots are broken. I don't think DOM snapshotting works with Shadow DOM? Is there any workaround for that yet or any related tickets open?
Hmm I'm getting correct recordings with https://github.com/JaySunSyn/cypress-daywalker
+1 following. I have a component library heavily leaning on shadow dom and I've just realized Cypress does not work out of the box... very disappointing
Anyone stumbling on this thread and missed the previous, I've got a working setup using the gist provided here, original post: https://github.com/cypress-io/cypress/issues/830#issuecomment-449411701
hello 馃憢
just catching up with all of this, looks like @TomCaserta is or was working on a branch?
so re that:
keep in mind i've done _very_ similar work over on selenium/webdriver and the discussion around changing the specs over there, to achieve the same things.
find('.foo').enterShadowRoot().find('.bar')
(im bad with names, im sure a better one could be bikeshedded, but you get the idea)isChild
) should probably do some traversal and Node.contains()
(see below)is
) spec got thrown out so you can't "extend button" anymore. i may be wrong but i'd double check before thinking its something that needs supportingGiven container
and child
(in that we want to know if container
contains child
):
container.contains(child)
child.getRootNode()
is a shadow rootcontainer.contains(child.getRootNode().host)
HTMLDocument
rather than a shadow root when retrieving the root node, or until a contains call was truehello
just catching up with all of this, looks like @TomCaserta is or was working on a branch?
so re that:
keep in mind i've done _very_ similar work over on selenium/webdriver and the discussion around changing the specs over there, to achieve the same things.
- not specific to your branch, ive seen people mention a "deep selector". please don't do this, it isn't part of the CSS selector spec and seems like a pretty bad idea to diverge from using standard CSS selectors. if anything, it should be a chainable switch just like in the native dom:
find('.foo').enterShadowRoot().find('.bar')
(im bad with names, im sure a better one could be bikeshedded, but you get the idea)- anything which asserts whether a node exists inside another node (i.e.
isChild
) should probably do some traversal andNode.contains()
(see below)- afaik the whole custom element extension (
is
) spec got thrown out so you can't "extend button" anymore. i may be wrong but i'd double check before thinking its something that needs supporting- maybe drop jquery? speak_no_evil all selections and events are pretty simple using native APIs now
isChild like traversal
Given
container
andchild
(in that we want to know ifcontainer
containschild
):
- Check if
container.contains(child)
- If not, check if
child.getRootNode()
is a shadow root- If it is, check that
container.contains(child.getRootNode().host)
- Repeat until you hit a
HTMLDocument
rather than a shadow root when retrieving the root node, or until a contains call was true
What you talk about is similar to the logic I have in https://github.com/Georgegriff/query-selector-shadow-dom the library takes a standard CSS selector and will traverse shadow roots and parents til elements matching the CSS selector are found. Basically let's you do querySelector
as if shadow roots doesn't even exist
To be honest I'd very much stay away from covering up the fact the shadow roots exist.
I'd make it an explicit call to something in order to access an element's shadow root. I think having things like "deep" and convenience helpers which pass through shadow boundaries make things "easy" but cover up too much.
Personally much rather select an element, then explicitly access it's shadow root, then select another element. This is also roughly how selenium is likely to do it. Close to how the browser it's self does it.
(Though not to say your code isn't useful to people, I'm sure it is)
The downside to doing that is building a library of reusable test steps for interacting with your common web components becomes cumbersome because you always need to know where to find that component in a shadow root
Just like you have to find any element you want to assert against.
I can understand people enjoying being able to pretend shadow roots don't exist and querying across boundaries, but it comes at a cost. The selectors no longer match up in behaviour with CSS selectors, and it becomes very easy to have flaky tests which should be more explicit about where they find the elements they assert against.
Personal preference too but I would hope the implementation here in cypress it's self matches the browser as closely as it can: by explicitly having to enter a shadow root.
Just like you have to find any element you want to assert against.
While true usually you can have a global test ID on any element and find it without having to know the exact path through to that element. I think there needs to be some sort of middle ground here as it gets way too verbose to be forced to write out pretty much the entire path to the exact element you want.
I've written tests both ways (explicitly going through shadow roots) and by basically ignoring the shadow root exists IMO the latter is much more maintainable. The former breaks every time you modify the DOM structure.
@TomCaserta that's precisely my findings too, and why I wrote the above library, at my old job we used selenium heavily and I wrote a custom CSS locator using the above library because every time a team wanted to interact with shadow Dom in tests there was issues forced testers to understand deeply how the app is structured, and as you say, tests broke as soon as something minor changed
Its an area you need to take care around, though.
I understand why you want a shortcut, but it makes things different/off-spec to CSS and the DOM its self. as soon as you introduce custom selector syntax, you're no longer using CSS selectors and thats not a good thing.
again, i see why you want a shortcut but tbh i'd still argue for being explicit as it'll help keep tests very clear too.
one compromise could be to make something like a "findById" which goes through shadow roots, maybe...
@43081j Why is complying with CSS selector semantics necessarily a good thing? The presence or absence of a shadow root is an implementation detail - or should at least be easy to treat that way. It is not a "shortcut", it is testing the right thing, instead of being unnecessarily brittle. "Shallow" and "deep" selector variants would be fine, but id is not the only thing we need to select by.
There's also this spec: https://www.w3.org/TR/css-shadow-parts-1/
Maybe it's useful somehow :)
@raihle because we currently support CSS selector syntax and, if we did this change, would not (i.e. we would have an extension of it). then you have the possibility of the CSS spec some day conflicting with your newly created syntax, or invalid selectors being more difficult to find.
i call it a shortcut because its a way to get around having to explicitly select each individual root. so it pretty much is one, which is fine for some.
i only disagree with it because i'd rather be closer to the browser, instead of abstracting things away from it (like shadow root traversal).
lets say we have:
#shadow-root
el-two
#shadow-root
button
it seems like a compromise and a flaky approach to select button
to press it for a test. rather than being explicit about _which_ button.
anywho if it turns out everyone wants to instead extend the CSS syntax for convenience, fine but it would be nice to have a way of also being explicit. no reason both can't be accounted for.
To be fair you wouldn't really write a test that looks for a button
on the page without checking which button you're clicking. You would still have a potentially flaky test if you don't narrow down what/where you're looking for a particular element even without ShadowDOM.
It does get a little tricky I agree as there is the argument that whatever is in a shadow root of a web component shouldn't be the concern of you and simply including a web component on your page shouldn't have the ability of breaking your tests.
IMO a best of both worlds approach would be what I suggested above to have an option stating you would like to deeply select. Eg. { deep: true }
as the second parameter of find
/get
etc.
My main concern and what I would like to avoid is a long chain such as the following:
find('.page').find('el-one').enterShadowRoot().find('el-two').enterShadowRoot().find('button')
Because at this point I'm already assuming to know that actually this el-one
web component internally uses el-two
which has a button inside of it so all I am doing is increasing the verbosity of my test code.
From a testing perspective should I be concerned with the implementation?
In my opinion it shouldn't matter if the component is or isn't located in shadow dom. I need my test to interact with an element, regardless of it's technical implementation. Being very explicit in selecting shadow roots seems to me as the basis of hard to/unmaintainable tests. Staying close to the css specs is very nice from a learning perspective but if it results in hard to write, read and maintain tests isn't this moving away from what Cypress is trying to accomplish?
@TomCaserta an option like your parameter makes good sense compared to altering the selector syntax its self.
in an ideal world, shadow roots shouldn't need to be "looked into" because they are meant to contain their internal implementation, hidden away from you. but in reality there's been heavy use over the years of top level shadow roots (like an app shell to contain all of your components) so we have to account for that and being able to select into them.
@michaelw85 you need your test to "interact with an element". which element? it seems like bad practice to have an over-matching selector which seems to work right now but may not in future.
shadow roots (as mentioned above) are not always "implementation details" due to how they've ended up being used in the wild. but even so, its often far better to be explicit about which of an element you are selecting rather than relying on it currently being the only one.
a good compromise is leaving the syntax untouched and having a param like tom said. { deep: true }
makes sense, but at the same time we must expose shadow roots as their own entity so we're able to explicitly enter one, too.
@43081j I did not say that you should select using something generic as say button
... there are multiple ways to identify a specific element, I don't think a shadow dom boundary is a good one.
its often far better to be explicit about which of an element you are selecting
I agree but this does not equal requiring a user to chain Cypress commands. Your selector should produce a "unique" element. I even think that by introducing the hard boundary you might be tempted to be less specific for selectors inside shadow dom.
What would be the added benefit of a cy.get('parent').shadowRoot().find('child')
vs parent > child
selector, the goal of the code is still the same with or without shadow dom, get the child to perform an action. I don't see how the added specificity of the shadow root improves this scenario.
It seems to be a user preference looking at the discussion here.
Maybe the solution is to introduce a flag at global level that determines the behavior. This would either require the use of { deep: true }
or would set this by default.
parent > child
is already a valid CSS selector, i dont think it'd be a good idea to start overriding that behaviour with your own shadow-penetrating one.
i really don't see this as a "preference" but more of a way to avoid introducing our own language (which we would do if we start extending CSS syntax in possibly conflicting ways in future).
{ deep: true }
on things like find
seems fine.
the problem too is if we try to implement even the ability to do a descendant selector through roots, we have to implement a css selector parser. even if its a dumb one, its our own parser we have to maintain.
this is better than implementing your own CSS syntax though, definitely.
even then, we need a way to explicitly access a shadow root because thats how browsers work and we're testing in browsers. there are many situations where we want to access the shadow root, not necessarily to traverse it, so we still need cypress to understand it as a concept.
a shadow root and a document should be treated pretty much the same.
Here's a project that supports a "piercing" selector parent >>> child
. Not sure if it's common or not, but imagine eventually something like this will be on the standards track. https://stenciljs.com/docs/end-to-end-testing#find-an-element-in-the-shadow-dom
had a chat with a friend about this and actually it does seem a bit ambiguous and unsafe to even have {deep: true}
.
going all the way back to some of the early suggestions, i think its probably best we explicitly have a find
and a findThroughShadowRoots
method to make it very clear we're breaking the encapsulation and passing through boundaries.
this, combined with a way to explicitly access a shadow root, seems to cover both use cases. Those who want to be explicit can find(...).shadowRoot().find(...)
, and those who don't can findThroughShadowRoots(...)
.
again im awful with names so i expect it'd be called something else, though avoiding 'deep' might be sensible to make it clear we're doing this to break through the encapsulation.
it still means we'd have to implement a very simple parser to basically brute force selection through roots (do a chunk at a time, testing for a root at each level) but i guess thats inevitable.
what we shouldn't do is implement our own syntax or deprecated syntax into the selectors at least (i.e. >>>
, /deep/
and all that jazz).
thoughts?
Any update on this ?Can we automate Shadow DOM?
Its sad that still there is no update on this. Its been more than 3 years.
There is an excellent plug-in for inspecting shadow-dom elements on page by @abramenal
https://github.com/abramenal/cypress-shadow-dom
However when I run cypress, I cannot see the shadow-dom component on the electron/chrome browser page to allow it be inspected.
I've raised an issue here - https://github.com/cypress-io/cypress/issues/5798
Is it something to do with this - https://docs.cypress.io/guides/guides/web-security.html#Cross-origin-iframes ?
Any help would be great. Thanks
Hi, I have questions about Shadow DOM Polyfills, and particularly how Salesforce lightning web components (LWC) uses them. These two articles outline how SF uses a Polyfill:
https://developer.salesforce.com/docs/component-library/documentation/lwc/lwc.create_dom
https://developer.salesforce.com/docs/component-library/documentation/lwc/lwc.testing_dom_api
And from what I understand this really throws a wrench in how cypress tries to identifies elements. But does it also undermine how the various libraries being talked about here attempt to work with Shadow DOM? I think this comment in 2nd article seems to get more directly at the root cause:
"The article has a screenshot that shows DOM elements in Chrome Developer Tools. The screenshot shows a #shadow-root document fragment, which is the top node of a component鈥檚 shadow tree. If you look at a Lightning web component in Chrome Developer Tools, you don鈥檛 see the #shadow-root because LWC uses a shadow DOM polyfill. Salesforce supports some browsers that don鈥檛 implement the Shadow DOM web standard. The polyfill provides a shadow DOM in these browsers. To find Lightning web components on the page, look for element names that contain a hyphen. Select the element and run $0.shadowRoot in the Console. A Lightning web component returns #document-fragment."
If the DOM has no "#shardow-root" will any of the libraries from @abramenal, @JaySunSyn, @Georgegriff or @egucciar actually work? Or any other attempt at a solution I may have missed from the threads? I've only tried the one from abramenal so far, though only briefly, but I can't seem to get it to work.
I also see now that @TomCaserta says this:
Note: As a work around, providing you are using the ShadyDOM polyfill, you can actually use
window.ShadyDOM = { force: true }
to force the polyfill to load. This means that your site is no longer using real shadow dom and you can do your tests as normal.Of course though this means you're testing against something your users won't actually see and you risk having to rewrite a lot of tests if the Cypress devs decide on a different syntax for piercing shadow dom in the 'get' method, but it's something.
Is it really that simple or does it depend entirely on if LWC uses a very specific "ShadyDOM"? I have a sinking feeling it isn't that simple. But even if its possible, should I do something like this to do sets of e2e tests on sites built using LWC? I wonder if Salesforce would throw fits about it. I would really like to know if any SF developers had tried something like this. Because I wonder if there are deeper implications for doing something like this than those that Tom talks a bit about. Where would I go about adding window.ShadyDOM = { force: true } to cypress to see if it makes any difference?
@JasonFairchild I'll preface this response with a note that I have not used LWC just used pure web components myself. I don't think this really entirely relates to Cypress tbh as it's more about Salesforce implementation details.
If LWC is forcing the polyfill to be used - even on browsers that does support web components natively - then you likely wont be encountering this issue. You should already be able to use Cypress as is as it purely emulates the shadow dom. That being said, if LWC was to suddenly enable the real shadow dom, then your tests will fail and updating them to work would really depend on the outcome of this ticket.
The comment I made about window.ShadyDOM = { force: true }
is purely relating to the ShadyDOM polyfill found here:
https://github.com/webcomponents/polyfills/
This line of code is to be placed before any script loading these polyfill scripts. Typically this polyfill library won't load if your browser supports WCs natively but setting the force true flag forces it to download. If your first quote author is to be trusted then this will not make a difference for LWCs.
In my experience the issue with forcing the polyfills is that they actually do emulate shadowDom, meaning they remove the parentNode
. Cypress relies on parentNode
to walk up the chain and perform its checks. The code I've used can work in "fake" shadowDOM, but there could be some caveats or changes (I cannot recall specifically, just know that I've tried it on a few specs and my commands ran as usual). The code itself is very bare, it just walks through shadowRoot
properties on the DOM, which should be present even with polyfills because polyfills emulate the JavaScript aspects of the Spec while not truly being ShadowDOM and not truly having ShadowRoots in the real live DOM. Actions and checks are just paired down versions of Cypress. Hope this helps.
cc @JasonFairchild @TomCaserta
EDIT: the best approach i think is solving this in Cypress itself, which i Did try to do, but I no longer have the time to complete that effort. I think this would be a great addition to Cypress api.
@TomCaserta and @egucciar Thank you for the replies. Since I last posted my question, I've learned a good deal more about how Salesforce implements their polyfilled "fake" shadow DOM. And it really is different then native shadow DOM in a number of ways. A lot of those details are contained in this post here if someone wants to explore them in more detail #6058
But essentially, SF overrides a number of functions so that sites that use LWC will behave in a shadow like manner, with a service they call Locker Service. So this can lead to a number issues with cypress, particularly the cy.get and cy.contains commands as well as the selector playground. So once I figure out how to best deal with this locker service, then I can get to the last part of penetrating their shadow like barriers with custom cypress commands. I believe that will involve a lot more learning from the libraries that I mentioned that have been presented here in various threads. I just have other things to focus on for now =(
I have been testing for the past couple of weeks and I think I will be able to implement this feature.
I think we will have a hard time getting css selectors to work, since the css should not have full access to the shadowRoot of a web component. Even though the extra command might be prone to implementation, I feel it is the best alternative at the moment.
My proof of concept can be viewed over here, (fast and crappy code) and I think it only supports assertions on the shadowDOM elements.
And a small screenshot of the test result:
cy.get('puik-header')
.shadow('.puik-header')
.should('have.class', 'puik-header--regular');
@chrisbreiding do you know if there is someone I can have a chat with?
@raldenhoven Feel free to take a look at:
https://github.com/cypress-io/cypress/compare/develop...TomCaserta:issue-144
It covers a few more parts where Cypress fails to handle ShadowDOM (quick and dirty code as well as a POC). Theres a few things I'm not completely certain how to handle such as visibility assertions on slotted content (eg. clicking a button in a slot child will cause Cypress to think that the parent element is covering it). But for the most part this works. This only handles making assetions and commands work with ShadowDOM children and not about actually selecting items in the shadow dom.
@raldenhoven @TomCaserta Opening a WIP PR to our repo would be the best way to get feedback from the team on any proposed changes.
I will say, just from the initial proof of concept that there will need a be a LOT more testing as well as complete documentation written for any new commands, so there will be extra work there required to deliver.
Hi i am trying to check best way for checking if element is visible, hidden enabled or disabled for shadowDOM. what would be the best way that is similar to this for shadowdom?
cy.get('#id').should('be.visible')
i am trying to find a way without using jquery.
cy.shadowGet('#shadowid').shadowFind('#shadowcheckbox').then(($element) => {
const $el = $jquery($element);
console.log('jquery Shadow DOM display', $el.is(':visible'));
});
can someone help with this?
Any update on the timeline about this major feature.
The whole web is moving to reusable web components using shadowRoot.
So the ability to access the shadow dom is quite a essential I would say.
Other then that awesome work CY Team :)
Hey Cypress! We love your product. This is a key feature gap that we would like to see closed.
Any idea on when this may be pulled in?
I'm able to test my ionic app using https://www.npmjs.com/package/cypress-shadow-dom
But I still have this issue: https://github.com/cypress-io/cypress/issues/5776 DOM Snapshots are blank
Snapshot of last test is displaying, but snapshots of other tests are blank
So 4 years to the day. Any updates on this?
Personally not sure this will ever be a priority, could consider to use playwright they added automatic shadow Dom support. Or playwright via other frameworks like CodeceptJS
This issue is still in the 'proposal' stage, which means no work has been done on this issue as of today by the Cypress team. You can see our up to date roadmap of things we're working on here: https://on.cypress.io/roadmap
There are a lot of considerations made when decided what is within our current scope of work. At this time there are other features that are higher priority.
This is always subject to change in the future and showing your support with 馃憤 or comments explaining how this feature would directly affect your use of Cypress will be helpful in these future evaluations.
After a week's evaluation we chose to go with Cypress and stuck within 2 days because of this issue. I was happy when I saw the issue was raised 4 years before. Thought the feature would have been added by now :(.
Our team is also stuck with this limitation. We are building some smaller angular web components with the new version 9. Everything was joy and happiness until this problem with shadow dom occurred - such a bummer. I really hope Cypress can push this forward because as people already mentioned in this thread web components are really great at building re-usable code.
Seeing as there's been activity and this still hasn't been resolved, I have opened #7469 as a work in progress of implementing some shadow DOM support.
I have no clue anymore if its anything like the existing (stale) PR that was opened some months ago. But if you're suffering from not having this functionality, please take a look at it so i'm aware of its worth pushing ahead with.
Of course keep in mind the cypress maintainers can shoot it down, in which case fair enough.
Really disappointed to see after 4 years there still is no support for this. We have built so much around our product in Cypress already. Pretty hard now to decide for another testing tool that does support getting elements in a shadow DOM. I would really like to see this becoming a priority.
@Herbrandus we also invested a lot of R&D into Cypress, but due to growing demand of web components and shadow-dom, we are forced to abandon it. The Cypress team has their reasons and cannot argue whatever those reasons are. Unfortunately, we just cannot continue to use this solution.
Folks, please refrain from the "I need this too" kinds of comments (you can upvote the original post instead). There's quite a lot of noise here recently. Thank you!
@kamituel literally 5 comments above, a cypress team member wrote:
This is always subject to change in the future and showing your support with 馃憤 or comments explaining how this feature would directly affect your use of Cypress will be helpful in these future evaluations.
So I don't agree with your recommendation - and Github, after all, supports an unsubscribe feature.
So I don't agree with your recommendation - and Github, after all, supports an unsubscribe feature.
You can disagree of course. But unsubscribing is not a solution because then I wouldn't be notified when something actually meaningful happens with regard to the Shadow DOM support.
then I wouldn't be notified when something actually meaningful happens with regard to the Shadow DOM support
You can customize your notifications, and receive notifications when the issue is closed/re-opened:
Yeah, I might need to do that at some point.
In all fairness I was hoping to also be notified when a proposal on the Shadow DOM support is posted, or any other meaningful updates. So I knew about them in advance.
That's a pity Github doesn't allow us to limit notification to only those coming from project members. That'd be just perfect in this situation.
Anyway, I'll just close my mouth now not to generate that noise I complained about myself ;) Cheers.
Like i said above, i have a fairly solid implementation so far in #7469.
it would be very useful if people who want this could give their opinion in there too, so im aware if it achieves the use cases people have
I wonder if some people could be less blocked then they think they are. Here is a list of attempts to provide support for Shadow DOM. They might not be super robust or up-to-date, but certainly something that could be played with or one can learn from.
https://github.com/JaySunSyn/cypress-daywalker from JaySunSyn
https://github.com/abramenal/cypress-shadow-dom from abramenal
https://www.npmjs.com/package/query-selector-shadow-dom from Georgegriff
These are more conceptual than potentially working solutions:
https://github.com/cypress-io/cypress/compare/develop...TomCaserta:issue-144?expand=1 from TomCaserta
https://github.com/cypress-io/cypress/pull/6233 from raldenhoven
https://github.com/cypress-io/cypress/pull/7469 from 43081j
There are probably others, these are the ones I could find relatively quickly. I also think this would be a good feature for Cypress to add direct support for, but I can also see that there are probably other features that are more important for the time being.
@Herbrandus we also invested a lot of R&D into Cypress, but due to growing demand of web components and shadow-dom, we are forced to abandon it. The Cypress team has their reasons and cannot argue whatever those reasons are. Unfortunately, we just cannot continue to use this solution.
@PerfectedApp what did you switch to? Looks like we will have to abandon again second project in a row.
I need an alternative too. What do you use in place of cypress?
We are considering forking Cypress to support Shadow DOM and https://github.com/cypress-io/cypress/issues/95 as both issues are still open after over 4 years. This issue along with fetch makes Cypress feel like its already becoming a legacy tool to work with
OMG i didn't notice the #95 issue.
Using cypress for component was the first step.
Next is testing app but i cannot imagine having issues everywhere cause of lack of native support with fetch.
After 4 days trying to make it works it looks like a very immature tool.
Moreover relying on jquery in 2020 ...
Ladies, not every tool is for everyone and I assume the cypress team is juggling priorities and do not consider shadow dom support as one of them - which is IMO fine.
Either consider using one of the plugins @JasonFairchild mentioned or look into things like https://github.com/microsoft/playwright where shadow dom is supported.
There are a lot of other options out there. Depends on what you or your organisation is needing. Some of these options have a more professional, nonsexist, and friendly community than others.
For those that are interested, our group is currently looking at CodeceptJS. Although, I still have hope for Cypress.
@PerfectedApp thanks
It looks like creator of CodeceptJS is working on testomat.io
In the last day there's been some progress on the #7469 issue with getting some reviewing and a relative plan based on the review that I believe to be going on right now
https://github.com/cypress-io/cypress/pull/7469#issuecomment-635517482
Yeah, given the great work done in #7469, we intend to ship shadow DOM support behind an experimental flag when the PR is merged (latest). The outstanding steps are outlined in that PR.
Major props to @43081j for taking initiative and starting the work being done on #7469
Seems like it has been quite a lot of effort, nice job!
The code for this is done in cypress-io/cypress#7469, but has yet to be released.
We'll update this issue and reference the changelog when it's released.
Released in 4.8.0
.
This comment thread has been locked. If you are still experiencing this issue after upgrading to
Cypress v4.8.0, please open a new issue.
We've added experimental shadow DOM support through the experimentalShadowDomSupport
configuration option. (Big thanks to @43081j!)
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.
Most helpful comment
Another +1 here!
I agree with you. Nowadays it's a common scenario big companies bet for WebComponents for build their front-end, and it's a shame we can't use cypress for testing :(
Hope it get's prioritized.