-
Notifications
You must be signed in to change notification settings - Fork 29
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
Add Playwright driver (no deps) #145
Changes from all commits
9b74cc8
fbbf51a
48bb144
f16ab9d
e16c76c
61cc852
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,2 @@ | ||
erlang 27.0 | ||
elixir 1.17.2-otp-27 | ||
elixir main | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,14 @@ | ||
import Config | ||
|
||
config :phoenix_test, :endpoint, PhoenixTest.Endpoint | ||
config :phoenix_test, | ||
endpoint: PhoenixTest.Endpoint, | ||
ecto_repos: [PhoenixTest.Repo], | ||
otp_app: :phoenix_test, | ||
playwright: [ | ||
browser: [browser: :chromium, headless: System.get_env("PLAYWRIGHT_HEADLESS", "t") in ~w(t true)], | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 nice to have this out of the box. |
||
cli: "priv/static/assets/node_modules/playwright/cli.js", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What version of playwright are we using/supporting? I imagine we're testing with a specific version? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good question. I honestly have no idea how stable the playwright protocol is. |
||
trace: System.get_env("PLAYWRIGHT_TRACE", "false") in ~w(t true) | ||
] | ||
|
||
config :logger, level: :warning | ||
|
||
|
@@ -20,3 +28,11 @@ config :esbuild, | |
cd: Path.expand("../test/assets", __DIR__), | ||
env: %{"NODE_PATH" => Path.expand("../deps", __DIR__)} | ||
] | ||
|
||
config :phoenix_test, PhoenixTest.Repo, | ||
username: "postgres", | ||
password: "postgres", | ||
hostname: "localhost", | ||
database: "phoenix_test_test#{System.get_env("MIX_TEST_PARTITION")}", | ||
pool: Ecto.Adapters.SQL.Sandbox, | ||
pool_size: 10 |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -213,7 +213,7 @@ defmodule PhoenixTest do | |
LiveView or a static view. You don't need to worry about which type of page | ||
you're visiting. | ||
""" | ||
def visit(conn, path) do | ||
def visit(%Plug.Conn{} = conn, path) do | ||
case get(conn, path) do | ||
%{assigns: %{live_module: _}} = conn -> | ||
PhoenixTest.Live.build(conn) | ||
|
@@ -230,6 +230,10 @@ defmodule PhoenixTest do | |
end | ||
end | ||
|
||
def visit(driver, path) when is_struct(driver) do | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This seems like the easiest, extensible way of adding custom drivers. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, seems sensible to me. 👍 |
||
Driver.visit(driver, path) | ||
end | ||
|
||
defp all_headers(conn) do | ||
Enum.map(conn.req_headers, &elem(&1, 0)) | ||
end | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
defmodule PhoenixTest.Case do | ||
@moduledoc """ | ||
ExUnit case module to assist with browser based tests. | ||
See `PhoenixTest.Playwright` for more information. | ||
""" | ||
|
||
use ExUnit.CaseTemplate | ||
|
||
alias PhoenixTest.Case | ||
|
||
using opts do | ||
quote do | ||
import PhoenixTest | ||
|
||
setup do | ||
[phoenix_test: unquote(opts)] | ||
end | ||
end | ||
end | ||
|
||
@playwright_opts [ | ||
browser: :chromium, | ||
headless: true, | ||
slowMo: 0 | ||
] | ||
|
||
setup_all context do | ||
trace = Application.fetch_env!(:phoenix_test, :playwright)[:trace] | ||
|
||
case context do | ||
%{playwright: true} -> | ||
[browser_id: Case.Playwright.launch_browser(@playwright_opts), trace: trace] | ||
|
||
%{playwright: opts} when is_list(opts) -> | ||
opts = Keyword.merge(@playwright_opts, opts) | ||
[browser_id: Case.Playwright.launch_browser(opts), trace: trace] | ||
Comment on lines
+27
to
+36
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since this is in a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, exactly. The browser is only launched once. And |
||
|
||
_ -> | ||
:ok | ||
end | ||
end | ||
|
||
setup context do | ||
case context do | ||
%{playwright: p} when p != false -> | ||
[conn: Case.Playwright.new_session(context)] | ||
|
||
_ -> | ||
[conn: Phoenix.ConnTest.build_conn()] | ||
end | ||
end | ||
|
||
defmodule Playwright do | ||
@moduledoc false | ||
import PhoenixTest.Playwright.Connection | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't have to change, but might be easier to follow the code below if we alias |
||
|
||
alias PhoenixTest.Playwright.Browser | ||
alias PhoenixTest.Playwright.BrowserContext | ||
|
||
@includes_ecto Code.ensure_loaded?(Ecto.Adapters.SQL.Sandbox) && | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 |
||
Code.ensure_loaded?(Phoenix.Ecto.SQL.Sandbox) | ||
|
||
def launch_browser(opts) do | ||
ensure_started() | ||
browser = Keyword.fetch!(opts, :browser) | ||
browser_id = launch_browser(browser, opts) | ||
on_exit(fn -> post(guid: browser_id, method: "close") end) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice. I love to see this. I know we're working with a port to do this. Have you seen any zombie OS processes being left open if we crash in a weird way during the tests? I'm thinking of this https://hexdocs.pm/elixir/Port.html#module-zombie-operating-system-processes |
||
browser_id | ||
end | ||
|
||
def new_session(%{browser_id: browser_id} = context) do | ||
context_id = Browser.new_context(browser_id) | ||
subscribe(context_id) | ||
|
||
page_id = BrowserContext.new_page(context_id) | ||
post(%{method: :updateSubscription, guid: page_id, params: %{event: "console", enabled: true}}) | ||
frame_id = initializer(page_id).mainFrame.guid | ||
on_exit(fn -> post(guid: context_id, method: "close") end) | ||
|
||
if context[:trace] do | ||
BrowserContext.start_tracing(context_id) | ||
|
||
dir = :phoenix_test |> Application.fetch_env!(:playwright) |> Keyword.fetch!(:trace_dir) | ||
File.mkdir_p!(dir) | ||
|
||
"Elixir." <> case = to_string(context.case) | ||
session_id = System.unique_integer([:positive, :monotonic]) | ||
file = String.replace("#{case}.#{context.test}_#{session_id}.zip", ~r/[^a-zA-Z0-9 \.]/, "_") | ||
path = Path.join(dir, file) | ||
|
||
on_exit(fn -> BrowserContext.stop_tracing(context_id, path) end) | ||
end | ||
|
||
PhoenixTest.Playwright.build(page_id, frame_id) | ||
end | ||
|
||
if @includes_ecto do | ||
def checkout_ecto_repos(async?) do | ||
otp_app = Application.fetch_env!(:phoenix_test, :otp_app) | ||
repos = Application.fetch_env!(otp_app, :ecto_repos) | ||
|
||
repos | ||
|> Enum.map(&checkout_ecto_repo(&1, async?)) | ||
|> Phoenix.Ecto.SQL.Sandbox.metadata_for(self()) | ||
|> Phoenix.Ecto.SQL.Sandbox.encode_metadata() | ||
end | ||
|
||
defp checkout_ecto_repo(repo, async?) do | ||
:ok = Ecto.Adapters.SQL.Sandbox.checkout(repo) | ||
unless async?, do: Ecto.Adapters.SQL.Sandbox.mode(repo, {:shared, self()}) | ||
|
||
repo | ||
end | ||
else | ||
def checkout_ecto_repos(_) do | ||
nil | ||
end | ||
end | ||
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.
We need the 1.18 parameterized tests!