Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature] Native shadow DOM support #1375

Closed
thernstig opened this issue Mar 13, 2020 · 17 comments
Closed

[Feature] Native shadow DOM support #1375

thernstig opened this issue Mar 13, 2020 · 17 comments
Assignees

Comments

@thernstig
Copy link
Contributor

thernstig commented Mar 13, 2020

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.

@thernstig
Copy link
Contributor Author

thernstig commented Apr 4, 2020

@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?

@dgozman
Copy link
Contributor

dgozman commented Apr 6, 2020

page.type('my-awesome-element #foo') // Using css engine by default

This 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 #foo is deeply nested under my-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:

  • use text=foo to pierce shadow and search for text;
  • use third-party shadow=foo to pierce shadow and search for css-like selector;
  • chain selectors however you want, e.g. shadow=foo #bar >> text=hello.

@thernstig
Copy link
Contributor Author

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?

@dgozman
Copy link
Contributor

dgozman commented Apr 8, 2020

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

@Georgegriff
Copy link
Contributor

Awesome! I'll follow this with keen interest :)

@AutoSponge
Copy link

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.

@jperl
Copy link
Contributor

jperl commented Apr 14, 2020

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")

@dgozman
Copy link
Contributor

dgozman commented Apr 14, 2020

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');

@dgozman
Copy link
Contributor

dgozman commented Apr 14, 2020

@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.

@jperl
Copy link
Contributor

jperl commented Apr 14, 2020

Thanks for the clarification 🙏.

await page.$frame('#iframe-id').click('html=our-custom-selector') would be a nice API.

@thernstig
Copy link
Contributor Author

thernstig commented Apr 15, 2020

@dgozman I noticed in #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.

@dgozman
Copy link
Contributor

dgozman commented Apr 15, 2020

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.

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 🤷

@dgozman
Copy link
Contributor

dgozman commented Apr 16, 2020

I think shadow dom handling is in a good shape now, shipping in next release 🚀
Please file separate issues for any problems found.

@dgozman dgozman closed this as completed Apr 16, 2020
@Georgegriff
Copy link
Contributor

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

@thernstig
Copy link
Contributor Author

thernstig commented Apr 17, 2020

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.

@AutoSponge
Copy link

AutoSponge commented Apr 17, 2020

@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.

@dgozman
Copy link
Contributor

dgozman commented Apr 17, 2020

@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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants