-
-
Notifications
You must be signed in to change notification settings - Fork 198
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
Shadow dom #728
base: main
Are you sure you want to change the base?
Shadow dom #728
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is looking good.
I think for another PR, it would be good start create a guide that gives a more in depth example. You can see an example of how to add a guide here
|> find(Query.css("shadow-test")) | ||
|> shadow_root() | ||
|
||
assert shadow_root |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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. |
lib/wallaby/browser.ex
Outdated
``` | ||
element = | ||
session | ||
|> find(Query.css("shadow-test")) | ||
|> shadow_root() | ||
|> find(Query.css("#in-shadow")) | ||
``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
``` | |
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
@@ -48,6 +49,18 @@ defmodule Wallaby.WebdriverClient do | |||
do: {:ok, elements} | |||
end | |||
|
|||
@doc """ | |||
Finds the shadow root for a given element. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Finds the shadow root for a given element. | |
Finds the shadow root for the given element. |
lib/wallaby/webdriver_client.ex
Outdated
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
@@ -55,6 +55,22 @@ defmodule Wallaby.HTTPClientTest do | |||
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
@@ -55,6 +55,22 @@ defmodule Wallaby.HTTPClientTest do | |||
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please add a success case test
@@ -410,6 +410,9 @@ defmodule Wallaby.Chrome do | |||
defdelegate accept_prompt(session, input, fun), to: WebdriverClient | |||
@doc false | |||
defdelegate dismiss_prompt(session, fun), to: WebdriverClient | |||
@doc false | |||
defdelegate shadow_root(element), to: WebdriverClient | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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/1
function onWallaby.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