Skip to content
Closed
Show file tree
Hide file tree
Changes from all 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
7 changes: 7 additions & 0 deletions lib/playwright/browser.ex
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,13 @@ defmodule Playwright.Browser do
# API
# ---------------------------------------------------------------------------

@spec browser_type(Playwright.Browser.t()) :: {:error, Playwright.Channel.Error.t()} | struct
def browser_type(%Browser{name: name, session: session}) do
playwright = Channel.find(session, {:guid, "Playwright"})
client_guid = get_in(playwright, [Access.key(String.to_atom(name)), Access.key(:guid)])
Channel.find(session, {:guid, client_guid})
end

@doc """
Closes the browser.

Expand Down
78 changes: 61 additions & 17 deletions lib/playwright/browser_type.ex
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,46 @@ defmodule Playwright.BrowserType do
end
end

# ---
@doc """
Attaches Playwright to an existing browser instance using the Chrome DevTools Protocol.

## Returns

- `{session, %Playwright.Browser{}}`

## Arguments

... (many)

## Example

{_, browser} = Playwright.BrowserType.connect_over_cdp(:chromium, "http://127.0.0.1:9222")

"""
@spec connect_over_cdp(client(), url(), options()) :: Playwright.Browser.t()
def connect_over_cdp(client, endpoint_url \\ nil, options \\ %{})

def connect_over_cdp(client, endpoint_url, options)
when is_atom(client)
when client in [:chromium] do
with {:ok, session} <- new_session(Transport.Driver, options),
browser_type <- chromium(session),
browser <- _connect_over_cdp(browser_type, Map.merge(%{"endpointURL" => endpoint_url}, options)) do
{session, browser}
end
end

# @spec connect_over_cdp(BrowserType.t(), url(), options()) :: Playwright.Browser.t()
# def connect_over_cdp(browser_type, endpoint_url, options \\ %{})
def connect_over_cdp(client, _endpoint_url, _options)
when is_atom(client)
when client in [:firefox, :webkit] do
raise RuntimeError, message: "not yet implemented"
end

defp _connect_over_cdp(%BrowserType{session: session, guid: guid}, params) do
%{browser: browser, default_context: browser_context} = Channel.post(session, {:guid, guid}, "connectOverCDP", params)
if browser_context, do: Channel.patch(browser_context.session, {:guid, browser_context.guid}, %{browser: browser})
browser
end

# @spec executable_path(BrowserType.t()) :: String.t()
# def executable_path(browser_type)
Expand Down Expand Up @@ -125,7 +161,7 @@ defmodule Playwright.BrowserType do
> describes some differences for Linux users.
"""
@spec launch(client() | nil, any()) :: {pid(), Playwright.Browser.t()}
def launch(client \\ nil, options \\ %{})
def launch(client \\ nil, options \\ Config.launch_options(true))

def launch(nil, options) do
launch(:chromium, options)
Expand All @@ -134,8 +170,11 @@ defmodule Playwright.BrowserType do
def launch(client, options)
when is_atom(client)
when client in [:chromium] do
{:ok, session} = new_session(Transport.Driver, options)
{session, chromium(session)}
with {:ok, session} <- new_session(Transport.Driver, options),
browser_type <- chromium(session),
browser <- browser(browser_type, options) do
{session, browser}
end
end

def launch(client, _options)
Expand Down Expand Up @@ -164,19 +203,14 @@ defmodule Playwright.BrowserType do
# private
# ----------------------------------------------------------------------------

defp browser(%BrowserType{} = browser_type) do
Channel.post(browser_type.session, {:guid, browser_type.guid}, :launch, Config.launch_options(true))
defp browser(%BrowserType{} = browser_type, launch_options) do
Channel.post(browser_type.session, {:guid, browser_type.guid}, :launch, launch_options)
end

defp chromium(session) do
case Channel.find(session, {:guid, "Playwright"}) do
%Playwright{} = playwright ->
%{guid: guid} = playwright.chromium
Channel.find(session, {:guid, guid}) |> browser()

other ->
raise("expected chromium to return a `Playwright`, received: #{inspect(other)}")
end
playwright = playwright(session)
%{guid: guid} = playwright.chromium
Channel.find(session, {:guid, guid})
end

defp new_session(transport, args) do
Expand All @@ -187,7 +221,17 @@ defmodule Playwright.BrowserType do
end

defp launched_browser(session) do
playwright = Channel.find(session, {:guid, "Playwright"})
playwright = playwright(session)
playwright.initializer.preLaunchedBrowser
end

defp playwright(session) do
case Channel.find(session, {:guid, "Playwright"}) do
%Playwright{} = playwright ->
playwright

other ->
raise("expected to return a `Playwright`, received: #{inspect(other)}")
end
end
end
7 changes: 7 additions & 0 deletions lib/playwright/channel/response.ex
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,13 @@ defmodule Playwright.Channel.Response do
result
end

defp parse([browser: %{guid: browser_guid}, defaultContext: %{guid: context_guid}], catalog) do
%{
browser: Catalog.get(catalog, browser_guid),
default_context: Catalog.get(catalog, context_guid)
}
end

defp parse([{:binary, value}], _catalog) do
value
end
Expand Down
20 changes: 9 additions & 11 deletions lib/playwright_test/case.ex
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,9 @@ defmodule PlaywrightTest.Case do
launch_options = Map.merge(Config.launch_options(), inline_options)
runner_options = Map.merge(Config.playwright_test(), inline_options)

Application.put_env(:playwright, LaunchOptions, launch_options)
{:ok, _} = Application.ensure_all_started(:playwright)

{_session, browser} = setup_browser(runner_options.transport)
{_session, browser} = setup_browser(runner_options.transport, runner_options)
[browser: browser, transport: runner_options.transport]
end

Expand All @@ -78,17 +77,16 @@ defmodule PlaywrightTest.Case do
end

# ---
defp setup_browser(transport, transport_options \\ %{})

defp setup_browser(transport) do
case transport do
:driver ->
options = Config.launch_options()
Playwright.BrowserType.launch(options)
defp setup_browser(:driver, transport_options) do
options = Map.merge(Config.launch_options(), transport_options)
Playwright.BrowserType.launch(options)
end

:websocket ->
options = Config.connect_options()
Playwright.BrowserType.connect(options.ws_endpoint)
end
defp setup_browser(:websocket, transport_options) do
options = Map.merge(Config.connect_options(), transport_options)
Playwright.BrowserType.connect(options.ws_endpoint)
end
end
end
Expand Down
1 change: 1 addition & 0 deletions mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ defmodule Playwright.MixProject do
{:ex_doc, "~> 0.25", only: :dev, runtime: false},
{:esbuild, "~> 0.4", runtime: Mix.env() == :dev},
{:gun, "~> 1.3.3"},
{:httpoison, "~> 1.8", only: [:test]},
{:jason, "~> 1.2"},
{:mix_audit, "~> 1.0", only: [:dev, :test], runtime: false},
{:playwright_assets, "~> 1.18.1", only: [:test]},
Expand Down
9 changes: 9 additions & 0 deletions mix.lock
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
%{
"bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"},
"castore": {:hex, :castore, "0.1.13", "ccf3ab251ffaebc4319f41d788ce59a6ab3f42b6c27e598ad838ffecee0b04f9", [:mix], [], "hexpm", "a14a7eecfec7e20385493dbb92b0d12c5d77ecfd6307de10102d58c94e8c49c0"},
"certifi": {:hex, :certifi, "2.9.0", "6f2a475689dd47f19fb74334859d460a2dc4e3252a3324bd2111b8f0429e7e21", [:rebar3], [], "hexpm", "266da46bdb06d6c6d35fde799bcb28d36d985d424ad7c08b5bb48f5b5cdd4641"},
"cowboy": {:hex, :cowboy, "2.6.3", "99aa50e94e685557cad82e704457336a453d4abcb77839ad22dbe71f311fcc06", [:rebar3], [{:cowlib, "~> 2.7.3", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "e5580029080f3f1ad17436fb97b0d5ed2ed4e4815a96bac36b5a992e20f58db6"},
"cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"},
"cowlib": {:hex, :cowlib, "2.7.3", "a7ffcd0917e6d50b4d5fb28e9e2085a0ceb3c97dea310505f7460ff5ed764ce9", [:rebar3], [], "hexpm", "1e1a3d176d52daebbecbbcdfd27c27726076567905c2a9d7398c54da9d225761"},
Expand All @@ -12,21 +13,29 @@
"ex_doc": {:hex, :ex_doc, "0.25.5", "ac3c5425a80b4b7c4dfecdf51fa9c23a44877124dd8ca34ee45ff608b1c6deb9", [:mix], [{:earmark_parser, "~> 1.4.0", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "688cfa538cdc146bc4291607764a7f1fcfa4cce8009ecd62de03b27197528350"},
"file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"},
"gun": {:hex, :gun, "1.3.3", "cf8b51beb36c22b9c8df1921e3f2bc4d2b1f68b49ad4fbc64e91875aa14e16b4", [:rebar3], [{:cowlib, "~> 2.7.0", [hex: :cowlib, repo: "hexpm", optional: false]}], "hexpm", "3106ce167f9c9723f849e4fb54ea4a4d814e3996ae243a1c828b256e749041e0"},
"hackney": {:hex, :hackney, "1.18.1", "f48bf88f521f2a229fc7bae88cf4f85adc9cd9bcf23b5dc8eb6a1788c662c4f6", [:rebar3], [{:certifi, "~>2.9.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~>6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~>1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~>1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "a4ecdaff44297e9b5894ae499e9a070ea1888c84afdd1fd9b7b2bc384950128e"},
"httpoison": {:hex, :httpoison, "1.8.2", "9eb9c63ae289296a544842ef816a85d881d4a31f518a0fec089aaa744beae290", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "2bb350d26972e30c96e2ca74a1aaf8293d61d0742ff17f01e0279fef11599921"},
"idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"},
"jason": {:hex, :jason, "1.3.0", "fa6b82a934feb176263ad2df0dbd91bf633d4a46ebfdffea0c8ae82953714946", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "53fc1f51255390e0ec7e50f9cb41e751c260d065dcba2bf0d08dc51a4002c2ac"},
"json_diff": {:hex, :json_diff, "0.1.3", "c80d5ca5416e785867e765e906e9a91b7efc35bfd505af276654d108f4995736", [:mix], [], "hexpm", "a5332e8293e7e9f384d34ea44645d7961334db73739165178fd4a7728d06f7d1"},
"makeup": {:hex, :makeup, "1.0.5", "d5a830bc42c9800ce07dd97fa94669dfb93d3bf5fcf6ea7a0c67b2e0e4a7f26c", [:mix], [{:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cfa158c02d3f5c0c665d0af11512fed3fba0144cf1aadee0f2ce17747fba2ca9"},
"makeup_elixir": {:hex, :makeup_elixir, "0.15.2", "dc72dfe17eb240552857465cc00cce390960d9a0c055c4ccd38b70629227e97c", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.1", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "fd23ae48d09b32eff49d4ced2b43c9f086d402ee4fd4fcb2d7fad97fa8823e75"},
"makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"},
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"},
"mime": {:hex, :mime, "2.0.2", "0b9e1a4c840eafb68d820b0e2158ef5c49385d17fb36855ac6e7e087d4b1dcc5", [:mix], [], "hexpm", "e6a3f76b4c277739e36c2e21a2c640778ba4c3846189d5ab19f97f126df5f9b7"},
"mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"},
"mix_audit": {:hex, :mix_audit, "1.0.0", "d2b5adbd69f34ba6b5b7d52812b1ba06f9110367e196d3ba5dba7753124cf8be", [:make, :mix], [{:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:yaml_elixir, "~> 2.8.0", [hex: :yaml_elixir, repo: "hexpm", optional: false]}], "hexpm", "1b1ff6694e6eb12818ce5dcc276a39bbe03e27fcd11376c381bfe6b4900f2aa8"},
"nimble_parsec": {:hex, :nimble_parsec, "1.1.0", "3a6fca1550363552e54c216debb6a9e95bd8d32348938e13de5eda962c0d7f89", [:mix], [], "hexpm", "08eb32d66b706e913ff748f11694b17981c0b04a33ef470e33e11b3d3ac8f54b"},
"parse_trans": {:hex, :parse_trans, "3.3.1", "16328ab840cc09919bd10dab29e431da3af9e9e7e7e6f0089dd5a2d2820011d8", [:rebar3], [], "hexpm", "07cd9577885f56362d414e8c4c4e6bdf10d43a8767abb92d24cbe8b24c54888b"},
"playwright_assets": {:hex, :playwright_assets, "1.18.1", "3398a85f0281f05cbe12c89709ff60f831f262356f6275e536ab8d2e0f4fb6ec", [:mix], [{:cowlib, "~> 2.7.3", [hex: :cowlib, repo: "hexpm", optional: false]}, {:plug, "~> 1.12", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.1.3", [hex: :plug_cowboy, repo: "hexpm", optional: false]}], "hexpm", "78e6940c3bac4f21f206597b9c264db005a6dc54b3135976ef2723042147e54e"},
"plug": {:hex, :plug, "1.13.2", "33aba8e2b43ddd68d9d49b818ed2fb46da85f4ec3229bc4bcd0c981a640a4e71", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a95cdfe599e3524b98684376c3f3494cbfbc1f41fcddefc380cac3138dd7619d"},
"plug_cowboy": {:hex, :plug_cowboy, "2.1.3", "38999a3e85e39f0e6bdfdf820761abac61edde1632cfebbacc445cdcb6ae1333", [:mix], [{:cowboy, "~> 2.5", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "056f41f814dbb38ea44613e0f613b3b2b2f2c6afce64126e252837669eba84db"},
"plug_crypto": {:hex, :plug_crypto, "1.2.2", "05654514ac717ff3a1843204b424477d9e60c143406aa94daf2274fdd280794d", [:mix], [], "hexpm", "87631c7ad914a5a445f0a3809f99b079113ae4ed4b867348dd9eec288cecb6db"},
"ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm", "451d8527787df716d99dc36162fca05934915db0b6141bbdac2ea8d3c7afc7d7"},
"recase": {:hex, :recase, "0.7.0", "3f2f719f0886c7a3b7fe469058ec539cb7bbe0023604ae3bce920e186305e5ae", [:mix], [], "hexpm", "36f5756a9f552f4a94b54a695870e32f4e72d5fad9c25e61bc4a3151c08a4e0c"},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"},
"telemetry": {:hex, :telemetry, "1.0.0", "0f453a102cdf13d506b7c0ab158324c337c41f1cc7548f0bc0e130bbf0ae9452", [:rebar3], [], "hexpm", "73bc09fa59b4a0284efb4624335583c528e07ec9ae76aca96ea0673850aec57a"},
"unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"},
"uuid": {:hex, :uuid, "1.1.8", "e22fc04499de0de3ed1116b770c7737779f226ceefa0badb3592e64d5cfb4eb9", [:mix], [], "hexpm", "c790593b4c3b601f5dc2378baae7efaf5b3d73c4c6456ba85759905be792f2ac"},
"yamerl": {:hex, :yamerl, "0.8.1", "07da13ffa1d8e13948943789665c62ccd679dfa7b324a4a2ed3149df17f453a4", [:rebar3], [], "hexpm", "96cb30f9d64344fed0ef8a92e9f16f207de6c04dfff4f366752ca79f5bceb23f"},
"yaml_elixir": {:hex, :yaml_elixir, "2.8.0", "c7ff0034daf57279c2ce902788ce6fdb2445532eb4317e8df4b044209fae6832", [:mix], [{:yamerl, "~> 0.8", [hex: :yamerl, repo: "hexpm", optional: false]}], "hexpm", "4b674bd881e373d1ac6a790c64b2ecb69d1fd612c2af3b22de1619c15473830b"},
Expand Down
77 changes: 73 additions & 4 deletions test/integration/browser_type/connect_cdp_test.exs
Original file line number Diff line number Diff line change
@@ -1,7 +1,76 @@
defmodule Playwright.BrowserType.ConnectCDPTest do
use Playwright.TestCase, async: true
@remote_debug_port "9223"

# test_connect_to_an_existing_cdp_session
# test_connect_to_an_existing_cdp_session_twice
# test_conect_over_a_ws_endpoint
use Playwright.TestCase, async: true, args: ["--remote-debugging-port=#{@remote_debug_port}"]

alias Playwright.{Browser, BrowserType, BrowserContext, Page}

describe "BrowserType.connect_over_cdp/3" do
test "can connect to an existing cdp session", %{browser: browser} do
{_, cdp_browser} = BrowserType.connect_over_cdp(:chromium, "http://localhost:#{@remote_debug_port}")

assert_browser_context_count(cdp_browser, 1)
assert_page_count(cdp_browser, 1)
assert browser.session != cdp_browser.session
end

@tag exclude: [:page]
test "can connect to an existing CDP Session twice", %{browser: _browser, assets: assets} do
{_, cdp_browser1} = BrowserType.connect_over_cdp(:chromium, "http://localhost:#{@remote_debug_port}")
{_, cdp_browser2} = BrowserType.connect_over_cdp(:chromium, "http://localhost:#{@remote_debug_port}")

assert_browser_context_count(cdp_browser1, 1)
assert_browser_context_count(cdp_browser2, 1)

assert_page_count(cdp_browser1, 0)
assert_page_count(cdp_browser2, 0)

page1 = create_new_page(cdp_browser1, assets.empty)
assert_page_count(cdp_browser1, 1)
assert_page_count(cdp_browser2, 1)

page2 = create_new_page(cdp_browser2, assets.empty)
assert_page_count(cdp_browser1, 2)
assert_page_count(cdp_browser2, 2)

close_page(page1)
close_page(page2)
end

test "can connect over a websocket endpoint", %{browser: _browser} do
ws_endpoint = ws_endpoint_for_url("http://localhost:#{@remote_debug_port}/json/version")
{_, cdp_browser1} = BrowserType.connect_over_cdp(:chromium, ws_endpoint)

assert_browser_context_count(cdp_browser1, 1)

{_, cdp_browser2} = BrowserType.connect_over_cdp(:chromium, ws_endpoint)
assert_browser_context_count(cdp_browser2, 1)
end

defp ws_endpoint_for_url(url) do
url
|> HTTPoison.get!()
|> Map.get(:body)
|> Jason.decode!()
|> Map.get("webSocketDebuggerUrl")
end

defp create_new_page(%Browser{} = browser, url) do
page = browser |> Browser.contexts() |> Enum.at(0) |> BrowserContext.new_page()
page |> Page.goto(url)
page
end

defp close_page(%Page{} = page) do
page |> Page.close()
end

defp assert_browser_context_count(%Browser{} = browser, expected_count) do
assert browser |> Browser.contexts() |> length() == expected_count
end

defp assert_page_count(%Browser{} = browser, expected_count) do
assert browser |> Browser.contexts() |> Enum.at(0) |> BrowserContext.pages() |> length() == expected_count
end
end
end