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

Shadow dom #728

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions integration_test/cases/browser/shadow_dom_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
defmodule Wallaby.Integration.Browser.ShadowDomTest do
superchris marked this conversation as resolved.
Show resolved Hide resolved

use Wallaby.Integration.SessionCase, async: true

import Wallaby.Query, only: [css: 1]

setup %{session: session} do
page =
session
|> visit("shadow_dom.html")

{:ok, page: page}
end

test "can find a shadow root", %{session: session} do
shadow_root =
session
|> find(Query.css("shadow-test"))
|> shadow_root()

assert shadow_root
Copy link
Member

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

end

test "can find stuff within da shadow dom", %{session: session} do
Copy link
Member

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

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

test "can click stuff within da shadow dom", %{session: session} do
element =
session
|> find(Query.css("shadow-test"))
|> shadow_root()
|> click(Query.css("button"))
end

test "attempting to access a shadow root where there aint one", %{session: session} do
shadow_root =
session
|> find(Query.css("#outside-shadow"))
|> shadow_root()
refute shadow_root
end

end
21 changes: 21 additions & 0 deletions integration_test/support/pages/shadow_dom.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<html>
<head>
<script>
class ShadowTestElement extends HTMLElement {
connectedCallback() {
this.attachShadow({mode: 'open'});
this.shadowRoot.innerHTML = `
<div id="in-shadow">I am in shadow</div>
<button>Hi!</button>
`;
}
}
window.customElements.define('shadow-test', ShadowTestElement);
</script>
</head>

<body>
<div id="outside-shadow">I am out of shadow</div>
<shadow-test></shadow-test>
</body>
</html>
7 changes: 7 additions & 0 deletions lib/wallaby/browser.ex
Original file line number Diff line number Diff line change
Expand Up @@ -1012,6 +1012,13 @@ defmodule Wallaby.Browser do
)
end

def shadow_root(%{driver: driver} = element) do
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please add an @spec annotation

case driver.shadow_root(element) do
{:ok, element} -> element
{:error, _error} -> nil
end
end

@doc """
Validates that the query returns a result. This can be used to define other
types of matchers.
Expand Down
3 changes: 3 additions & 0 deletions lib/wallaby/chrome.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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

defdelegate shadow_root(element), to: WebdriverClient

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change

@doc false
defdelegate parse_log(log), to: Wallaby.Chrome.Logger

Expand Down
5 changes: 5 additions & 0 deletions lib/wallaby/driver.ex
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,11 @@ defmodule Wallaby.Driver do
@callback find_elements(Session.t() | Element.t(), Query.compiled()) ::
{:ok, [Element.t()]} | {:error, reason}

@doc """
Invoked to find shadow root of given element
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Invoked to find shadow root of given element
Invoked to find shadow root of the given element.

"""
@callback shadow_root(Element.t()) :: {:ok, Element.t()} | {:error, reason}

@doc """
Invoked to execute JavaScript in the browser.
"""
Expand Down
3 changes: 3 additions & 0 deletions lib/wallaby/httpclient.ex
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,9 @@ defmodule Wallaby.HTTPClient do
%{"message" => "stale element reference" <> _} ->
{:error, :stale_reference}

%{"message" => "no such shadow root" <> _} ->
{:error, :shadow_root_not_found}

%{
"message" =>
"An element command failed because the referenced element is no longer available" <> _
Expand Down
23 changes: 23 additions & 0 deletions lib/wallaby/webdriver_client.ex
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ defmodule Wallaby.WebdriverClient do
| Session.t()

@web_element_identifier "element-6066-11e4-a52e-4f735466cecf"
@shadow_root_identifier "shadow-6066-11e4-a52e-4f735466cecf"

@button_mapping %{left: 0, middle: 1, right: 2}

Expand Down Expand Up @@ -48,6 +49,18 @@ defmodule Wallaby.WebdriverClient do
do: {:ok, elements}
end

@doc """
Finds the shadow root for a given element.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Finds the shadow root for a given element.
Finds the shadow root for the given element.

"""
@spec shadow_root(Element.t()) :: {:ok, Element.t()}
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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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


@doc """
Sets the value of an element.
"""
Expand Down Expand Up @@ -699,6 +712,16 @@ defmodule Wallaby.WebdriverClient do
}
end

defp cast_as_element(parent, %{@shadow_root_identifier => id}) do
%Wallaby.Element{
id: id,
session_url: parent.session_url,
url: parent.session_url <> "/shadow/#{id}",
parent: parent,
driver: parent.driver
}
end

# Retrieves the text from an alert, prompt or confirm.
@spec alert_text(Session.t()) :: {:ok, String.t()}
defp alert_text(session) do
Expand Down
14 changes: 14 additions & 0 deletions test/wallaby/http_client_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,20 @@ 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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
test "with a non-existant shadow root response", %{bypass: bypass} do
test "with a non-existent shadow root", %{bypass: bypass} do

Copy link
Member

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

Bypass.expect(bypass, fn conn ->
send_json_resp(conn, 404, %{
"sessionId" => "abc123",
"status" => 10,
"value" => %{
"message" => "no such shadow root\n (Session info: headless chrome=111.0.5563.64)\n (Driver info: chromedriver=110.0.5481.77 (65ed616c6e8ee3fe0ad64fe83796c020644d42af-refs/branch-heads/5481@{#839}),platform=Mac OS X 12.0.1 arm64)"
}
})
end)

assert {:error, :shadow_root_not_found} = Client.request(:post, bypass_url(bypass, "/my_url"))
end

test "with an obscure status code", %{bypass: bypass} do
expected_message = "message from an obscure error"

Expand Down