Conversation
|
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 element =
session
|> find(Query.css("shadow-test"))
|> shadow_root()
|> find(Query.css("#in-shadow"))This will query for an element with id |
|
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. |
|
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 |
|
@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 |
| |> find(Query.css("shadow-test")) | ||
| |> shadow_root() | ||
|
|
||
| assert shadow_root |
There was a problem hiding this comment.
Will this be an Element struct? if yes let's assert on that
| assert shadow_root | ||
| end | ||
|
|
||
| test "can find stuff within da shadow dom", %{session: session} do |
There was a problem hiding this comment.
let's make these test names slightly more formal
| |> shadow_root() | ||
| |> find(Query.css("#in-shadow")) | ||
|
|
||
| assert element |
There was a problem hiding this comment.
let's actually assert on the contents of these values rather than just on truthiness
lib/wallaby/browser.ex
Outdated
| Finds the shadow DOM for the specified element and returns an element corresponding | ||
| to this shadow DOM. Subsequent queries made within element retured by calling `shadow_root` | ||
| will return elements within the shadow DOM, e. g. |
There was a problem hiding this comment.
| Finds the shadow DOM for the specified element and returns an element corresponding | |
| to this shadow DOM. Subsequent queries made within element retured by calling `shadow_root` | |
| will return elements within the shadow DOM, e. g. | |
| Finds and returns the shadow root for the given element. | |
| Queries executed on the returned shadow root will be scoped to the root's shadow DOM. |
| ``` | ||
| element = | ||
| session | ||
| |> find(Query.css("shadow-test")) | ||
| |> shadow_root() | ||
| |> find(Query.css("#in-shadow")) | ||
| ``` |
There was a problem hiding this comment.
| ``` | |
| element = | |
| session | |
| |> find(Query.css("shadow-test")) | |
| |> shadow_root() | |
| |> find(Query.css("#in-shadow")) | |
| ``` | |
| ```elixir | |
| session | |
| |> find(Query.css("shadow-test")) | |
| |> shadow_root() | |
| |> find(Query.css("#in-shadow")) |
lib/wallaby/webdriver_client.ex
Outdated
| end | ||
|
|
||
| @doc """ | ||
| Finds the shadow root for a given element. |
There was a problem hiding this comment.
| Finds the shadow root for a given element. | |
| Finds the shadow root for the given element. |
| def shadow_root(element) do | ||
| with {:ok, resp} <- request(:get, element.url <> "/shadow"), | ||
| {:ok, shadow_root} <- Map.fetch(resp, "value"), | ||
| element <- cast_as_element(element, shadow_root) do | ||
| {:ok, element} | ||
| end | ||
| end |
There was a problem hiding this comment.
| def shadow_root(element) do | |
| with {:ok, resp} <- request(:get, element.url <> "/shadow"), | |
| {:ok, shadow_root} <- Map.fetch(resp, "value"), | |
| element <- cast_as_element(element, shadow_root) do | |
| {:ok, element} | |
| end | |
| end | |
| def shadow_root(element) do | |
| with {:ok, resp} <- request(:get, element.url <> "/shadow"), | |
| {:ok, shadow_root} <- Map.fetch(resp, "value") do | |
| {:ok, cast_as_element(element, shadow_root)} | |
| end | |
| end |
test/wallaby/http_client_test.exs
Outdated
| assert {:error, :stale_reference} = Client.request(:post, bypass_url(bypass, "/my_url")) | ||
| end | ||
|
|
||
| test "with a non-existant shadow root response", %{bypass: bypass} do |
There was a problem hiding this comment.
| test "with a non-existant shadow root response", %{bypass: bypass} do | |
| test "with a non-existent shadow root", %{bypass: bypass} do |
test/wallaby/http_client_test.exs
Outdated
| assert {:error, :stale_reference} = Client.request(:post, bypass_url(bypass, "/my_url")) | ||
| end | ||
|
|
||
| test "with a non-existant shadow root response", %{bypass: bypass} do |
| defdelegate dismiss_prompt(session, fun), to: WebdriverClient | ||
| @doc false | ||
| defdelegate shadow_root(element), to: WebdriverClient | ||
|
|
|
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? |
@mhanberg this is what we ended up with. We added a
shadow_root/1function onWallaby.Browserthat 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