Web Components is a growing trend and statistics show it is used more often than major libraries like React. I've got no statistics on how often the shadow DOM is used though. In any instance, the shadow DOM is standardized, so I would assume people will expect support out-of-the-box.
The project https://github.com/Georgegriff/query-selector-shadow-dom works perfectly for now.
const { selectorEngine } = require("query-selector-shadow-dom/plugins/playwright");
const playwright = require('playwright');
...
await playwright.selectors.register(selectorEngine, { name: 'shadow' })
...
await page.goto('chrome://downloads');
// shadow= allows a css query selector that automatically pierces shadow roots.
await page.waitForSelector('shadow=#no-downloads span', {timeout: 3000})
https://github.com/mmarkelov/jest-playwright#configuration also supports it nicely.
But seeing as it requires a new selectorEngine, means the nice selector engines like css, xpath and text do not work on the shadow DOM by default.
If Playwright would support adding a penetrateShadowDOM on a browser, browserContext or page, it would be amazing for usability.
@dgozman Could you explain a bit how the native implementation you are working on is supposed to be used in certain cases. As an example, a heavy Web Component page will have nested upon nested CSS in the Shadow DOM. If I use Playwright, I do not really care about this, I just want to scrape or write an e2e test and know that I have a deeply nested element called <my-awesome-element> and deeply nested under this I know I want to select the first #foo id and type into it. I want to achieve this very easily, for example like this:
page.type('my-awesome-element #foo') // Using css engine by default
This is nice and not too verbose. I'd prefer to not have to write this:
page.type('css=my-awesome-element >> css=#foo')
Or does that even work if #foo is deeply nested under my-awesome-element?
page.type('my-awesome-element #foo') // Using css engine by defaultThis is nice and not too verbose.
I totally agree. However, we cannot change css selector to not behave according to CSS specification. As you mentioned, third-party project https://github.com/Georgegriff/query-selector-shadow-dom works pretty good for now. Do you have any issues using it?
I'd prefer to not have to write this:
page.type('css=my-awesome-element >> css=#foo')Or does that even work if
#foois deeply nested undermy-awesome-element?
This would not work. Your usecase is "any space separator is treated as /deep/ combinator". The /deep/ combinator was removed from the spec, that's why using css selector does not work. The query-selector-shadow-dom defines that semantic as far as I understand.
In fact, I am inclined to remove support for >> piercing shadow to avoid confusion. Alternative options are:
text=foo to pierce shadow and search for text;shadow=foo to pierce shadow and search for css-like selector;shadow=foo #bar >> text=hello.It's not necessarily that query-selector-shadow-dom is not good, it is. It is more-so that I've got a feeling you want Playwright to work out of the box and since Web Components might become very common, it will not for scraping/testing such sites.
In a Web Component riddled site, can you explain how I would use Playwright? Let's say I have an <my-cool-calendar> element. Inside that calendar, there is a <my-date-picker> and inside that there is a <my-spinner> to spin the dates up and down, with a <button id="up">. This is a very real scenario.
How do I use Playwright to click the up arrow?
We definitely want to address this usecase, as it is a part of web standards. Our current approach with >> does not work nicely, that's why I am changing built-in selectors to pierce shadow. We'll likely introduce some kind of built-in deep css selector, inspired by query-selector-shadow-dom.
cc @Georgegriff
Awesome! I'll follow this with keen interest :)
Executing a walk of nodes in shadow from the browser context will be limited to mode: open only.
I suggest using the pierce option (from DOM.getDocument) in the CDP session and wrapping the resulting nodes (much like axNodes are now). This could provide the accurate tree (open, closed, and user-agent), then use the custom selector engine against the the POJO.
If this is done, you'll need to allow custom selectors to work against arbitrary trees (supplied by an async fn) instead of always running in the page context. This will allow me to create a custom engine plugin for the axTree as well since it needs to operate on the CDP session object and not the DOM.
Instead of shadow=..., using the full DOM tree would be a clear choice since the selector would get delegated to the _client. Lastly, I'll point out that since we're no longer bound by the engine's CSS capabilities, we can use CSS4 selector patterns to traverse the tree.
NOTE: this would also allow selectors to traverse iframes.
I noticed #152 was merged with this issue. There was a comment indicating support for entering iframes and using any selector inside the iframe. Does this issue also encompass that as well or should I open a separate issue?
The reason I ask is because iframe support is the most requested feature we have. It would be great if we could enter an iframe and use our custom selector engine inside it:
page.click("#iframe-id >> html=our-custom-selector")
We decided to not enter iframes using >> because that introduces asynchronous non-atomic selector resolution. Currently, the workaround is:
const frame = await (await page.waitForSelector('#iframe-id')).contentFrame();
await frame.click('html=our-custom-selector');
Note that we are actively exploring the space, with a possibility of playwright-side selectors that can hide the frames complexity. For example (not implemented, just a possibility):
await page.click(playwright.selector('frame=#iframe-id >> html=our-custom-selector'));
// or
await page.$frame('#iframe-id').click('html=our-custom-selector');
@AutoSponge We thought about this world of playwright-side selectors, and decided to not rush into it. The problem is that it introduces asynchrony in the selector process and a lot of additional overhead, so we have to be careful with it. We would also ensure cross-browser support before going this route.
Thanks for the clarification 馃檹.
await page.$frame('#iframe-id').click('html=our-custom-selector') would be a nice API.
@dgozman I noticed in https://github.com/microsoft/playwright/pull/1784 that you now make css default pierce shadow dom? Previously it was said:
However, we cannot change css selector to not behave according to CSS specification.
What has changed since that this stance was changed? I assume it is in effect not changed, as you have the new css:light so it is just your API that changed.
What has changed since that this stance was changed? I assume it is in effect not changed, as you have the new
css:lightso it is just your API that changed.
Exactly. We'll keep the strict spec-compliant behavior with css:light, but default (and auto-detect) to a more testing-friendly shadow-piercing css. There are pros and cons of course, but it seems that convenience wins in this case 馃し
I think shadow dom handling is in a good shape now, shipping in next release 馃殌
Please file separate issues for any problems found.
Just took a little look at the code changes this look fantastic!
After years of trying to say to people tests shouldn't have to care about shadow Dom boundaries and having to manually pierce shadow Dom is lame, so much so I had to write a library around it, I'm so happy that my library will be redundant in playwright :D excited to try it out
Great work @dgozman and the Playwright team, and thank you so much to @Georgegriff for starting this kind of support with his awesome npm package.
@dgozman I'm looking forward to this one. Another option for iframes is to pass selector[] the way axe-core does.
// previous suggestion
await page.$frame('#iframe-id').click('html=our-custom-selector')
// alternate
await page.click(['#iframe-id', 'html=our-custom-selector'])
// or variadic
await page.click('#iframe-id', 'html=our-custom-selector')
The down side is that would overload your interfaces that take a selector.
@AutoSponge Thank you for the suggestion. I think variadic won't work for us, because we have options at the end, but something with an array might work. We'll keep looking.
Most helpful comment
We definitely want to address this usecase, as it is a part of web standards. Our current approach with
>>does not work nicely, that's why I am changing built-in selectors to pierce shadow. We'll likely introduce some kind of built-indeepcss selector, inspired by query-selector-shadow-dom.cc @Georgegriff