elixir-wallaby / wallaby

Concurrent browser tests for your Elixir web apps.
https://twitter.com/elixir_wallaby
MIT License
1.68k stars 198 forks source link

Shadow dom #728

Open superchris opened 1 year ago

superchris commented 1 year ago

@mhanberg this is what we ended up with. We added a shadow_root/1 function on Wallaby.Browser that takes an element and narrows the scope to the shadow root of the element. This seems to line up really nicely with what webdriver wants and pretty much lets everything "just work" in what seems like a nice way IMO. Curious to hear your thoughts. If you think it's ok I'll add docs and get it mergable

mhanberg commented 1 year ago

for posterity and myself, would you mind commenting with a lay person's explanation of what the shadow root is and how wallaby has to talk with the webdriver in order for the user to interact with them?

superchris commented 1 year ago

for posterity and myself, would you mind commenting with a lay person's explanation of what the shadow root is and how wallaby has to talk with the webdriver in order for the user to interact with them?

Sure! Here is my attempt: Shadow DOM is a sub-specification of the Web Components group of specs that allow a developer to create an encapsulated DOM within an element. This encapsulated DOM, or shadow DOM, can contain CSS, markup, and anything else a DOM can contain but is isolated from the rest of the document such that styles from the containing DOM do not apply to the shadow DOM (with specific exceptions) and vice versa. This allows a developer of a custom element to have complete control over the precise rendering of their element while avoiding collisions with or dependencies on the containing document's CSS.

Because the Shadow DOM is isolated from the containing DOM, queries within the containing document will not find elements in the shadow DOM of a custom element. In order to overcome this limitation, several new API calls have been added to the WebDriver protocol to "pierce" a shadow DOM and query elements within it. The way we interact with these in wallaby is to scope our query to a shadow DOM of a specific element by using the shadow_root function, eg:

    element =
      session
      |> find(Query.css("shadow-test"))
      |> shadow_root()
      |> find(Query.css("#in-shadow"))

This will query for an element with id in-shadow that is within the shadow DOM for a <shadow-test> element. The name shadow_root corresponds the DOM property shadowRoot which is the accessor property for a shadow DOM within an element.

mhanberg commented 1 year ago

Excellent explanation!!

I have a question about the API.

When we query an element that contains shadow dom, is it possible to know that before we call the shadow_dom function?

If we can mark an element has containing shadow dom, we can potentially hide the shadow_dom function from the user and they can just pretend that they are querying inside of it like normal.

superchris commented 1 year ago

Thanks! I don't know of a way to detect if an element has a shadow root, other than with javascript or doing the api call I am doing already in shadow_root. Doing this implicitly for every time we return or scope within an element seems tricky to me. I would be more in favor of being explicit I think. I also think the use case is fairly specific, my guess is for now the percentage of people using wallaby to interact with shadow DOM will be small so adding another api call all the time seems maybe not the best tradeoff.

superchris commented 1 year ago

@mhanberg in doing some further experimentation, I came across an example where we really need to know whether the user is intending to query inside the shadow dom or the 'light dom' of a custom elements. Custom elements can also have markup placed inside them in the surrounding document, but then have this content merged into the shadow DOM using a feature called slots. This gets complicated, but the result is the element has both light and shadow DOMs :) To query for things in this "light dom" you do not want to scope using shadow_root. I'll try to come up with a test case that demonstrates this.

Timmitry commented 2 weeks ago

Hello 👋 What is the state of this PR, is this still something you would be open to add support for? What is still missing to get this merged?