Skip to content

Commit

Permalink
Add wallaby driver
Browse files Browse the repository at this point in the history
```
** (FunctionClauseError) no function clause matching in Floki.Selector.Parser.do_parse/2

     The following arguments were given to Floki.Selector.Parser.do_parse/2:

         # 1
         [{~c"]", 1}]

         # 2
         %Floki.Selector{id: nil, type: "input", classes: [], attributes: [%Floki.Selector.AttributeSelector{match_type: :equal, attribute: "name", value: "payer", flag: nil}, %Floki.Selector.AttributeSelector{match_type: :equal, attribute: "type", value: "hidden", flag: nil}], namespace: nil, pseudo_classes: [], combinator: nil}

     Attempted function clauses (showing 10 out of 21):

         defp do_parse([], selector)
         defp do_parse([{:close_parentesis, _} | t], selector)
         defp do_parse([{:comma, _} | t], selector)
         defp do_parse([{:identifier, _, namespace}, {:namespace_pipe, _} | t], selector)
         defp do_parse([{:identifier, _, type} | t], selector)
         defp do_parse([{~c"*", _} | t], selector)
         defp do_parse([{:hash, _, id} | t], selector)
         defp do_parse([{:class, _, class} | t], selector)
         defp do_parse([{~c"[", _} | t], selector)
         defp do_parse([{:pseudo_not, _} | t], selector)
         ...
         (11 clauses not shown)
```

This commit escapes brackets in the hidden input name by wrapping it in single quotes.
  • Loading branch information
soundmonster authored and ftes committed Jun 17, 2024
1 parent 2e032ef commit e3f3557
Show file tree
Hide file tree
Showing 23 changed files with 853 additions and 299 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,6 @@ phoenix_test-*.tar

# Temporary files, for example, from tests.
/tmp/

/priv/static/assets/
.envrc
24 changes: 23 additions & 1 deletion config/test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,26 @@ import Config

config :phoenix_test, :endpoint, PhoenixTest.Endpoint

config :logger, level: :warning
config :phoenix_test, PhoenixTest.Endpoint,
server: true,
http: [port: 4000],
live_view: [signing_salt: "112345678212345678312345678412"],
secret_key_base: String.duplicate("57689", 50)

config :logger, level: :error

config :wallaby,
otp_app: :phoenix_test,
js_logger: nil,
chromedriver: [
headless: System.get_env("WALLABY_HEADLESS", "t") in ["t", "true"],
binary: System.get_env("CHROME_EXECUTABLE", "")
]

config :esbuild,
version: "0.17.11",
default: [
args: ~w(js/app.js --bundle --target=es2017 --outdir=../../priv/static/assets),
cd: Path.expand("../test/assets", __DIR__),
env: %{"NODE_PATH" => Path.expand("../deps", __DIR__)}
]
50 changes: 40 additions & 10 deletions lib/phoenix_test.ex
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,6 @@ defmodule PhoenixTest do

import Phoenix.ConnTest

alias PhoenixTest.Assertions
alias PhoenixTest.Driver

@endpoint Application.compile_env(:phoenix_test, :endpoint)
Expand All @@ -211,7 +210,12 @@ 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{assigns: %{phoenix_test_js: true}} = conn, path) do
PhoenixTest.Wallaby.build(conn, path)
end

def visit(%Plug.Conn{} = conn, path) do
case get(conn, path) do
%{assigns: %{live_module: _}} = conn ->
PhoenixTest.Live.build(conn)
Expand All @@ -225,6 +229,32 @@ defmodule PhoenixTest do
end
end

@doc """
Helper to run tests with the Javascript (Wallaby) driver via `@tag :js`.
```elixir
setup %{conn: conn} = context do
conn = if context[:js], do: with_js_driver(conn), else: conn
%{conn: conn}
end
@tag :js
test "with wallaby", %{conn: conn} do
visit(conn, "/path")
end
test "without wallaby", %{conn: conn} do
visit(conn, "/path")
end
```
You can also use `@describetag :js` and `@moduletag :js`.
Refer to the [ExUnit docs](https://hexdocs.pm/ex_unit/1.16.3/ExUnit.Case.html#module-tags) for more details.
"""
def with_js_driver(%Plug.Conn{} = conn) do
Plug.Conn.assign(conn, :phoenix_test_js, true)
end

@doc """
Clicks a link with given text and performs the action.
Expand Down Expand Up @@ -691,7 +721,7 @@ defmodule PhoenixTest do
assert_has(session, "#user")
```
"""
defdelegate assert_has(session, selector), to: Assertions
defdelegate assert_has(session, selector), to: Driver

@doc """
Assert helper to ensure an element with given CSS selector and options.
Expand Down Expand Up @@ -734,7 +764,7 @@ defmodule PhoenixTest do
assert_has(session, ".posts", at: 2, text: "Hello")
```
"""
defdelegate assert_has(session, selector, opts), to: Assertions
defdelegate assert_has(session, selector, opts), to: Driver

@doc """
Opposite of `assert_has/2` helper. Verifies that element with
Expand All @@ -754,7 +784,7 @@ defmodule PhoenixTest do
refute_has(session, "#user")
```
"""
defdelegate refute_has(session, selector), to: Assertions
defdelegate refute_has(session, selector), to: Driver

@doc """
Opposite of `assert_has/3` helper. Verifies that element with
Expand Down Expand Up @@ -795,7 +825,7 @@ defmodule PhoenixTest do
refute_has(session, ".posts", at: 2, text: "Hello")
```
"""
defdelegate refute_has(session, selector, opts), to: Assertions
defdelegate refute_has(session, selector, opts), to: Driver

@doc """
Assert helper to verify current request path. Takes an optional `query_params`
Expand All @@ -822,12 +852,12 @@ defmodule PhoenixTest do
|> assert_path("/users", query_params: %{name: "frodo"})
```
"""
defdelegate assert_path(session, path), to: Assertions
defdelegate assert_path(session, path), to: Driver

@doc """
Same as `assert_path/2` but takes an optional `query_params` map.
"""
defdelegate assert_path(session, path, opts), to: Assertions
defdelegate assert_path(session, path, opts), to: Driver

@doc """
Verifies current request path is NOT the one provided. Takes an optional
Expand All @@ -854,11 +884,11 @@ defmodule PhoenixTest do
|> refute_path("/users", query_params: %{name: "frodo"})
```
"""
defdelegate refute_path(session, path), to: Assertions
defdelegate refute_path(session, path), to: Driver

@doc """
Same as `refute_path/2` but takes an optional `query_params` for more specific
refutation.
"""
defdelegate refute_path(session, path, opts), to: Assertions
defdelegate refute_path(session, path, opts), to: Driver
end
8 changes: 4 additions & 4 deletions lib/phoenix_test/assertions.ex
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ defmodule PhoenixTest.Assertions do
end

def assert_path(session, path) do
uri = URI.parse(session.current_path)
uri = URI.parse(PhoenixTest.Driver.current_path(session))

if uri.path == path do
assert true
Expand All @@ -215,7 +215,7 @@ defmodule PhoenixTest.Assertions do
defp assert_query_params(session, params) do
params = Utils.stringify_keys_and_values(params)

uri = URI.parse(session.current_path)
uri = URI.parse(PhoenixTest.Driver.current_path(session))
query_params = uri.query && URI.decode_query(uri.query)

if query_params == params do
Expand All @@ -234,7 +234,7 @@ defmodule PhoenixTest.Assertions do
end

def refute_path(session, path) do
uri = URI.parse(session.current_path)
uri = URI.parse(PhoenixTest.Driver.current_path(session))

if uri.path == path do
msg = """
Expand All @@ -258,7 +258,7 @@ defmodule PhoenixTest.Assertions do
defp refute_query_params(session, params) do
params = Utils.stringify_keys_and_values(params)

uri = URI.parse(session.current_path)
uri = URI.parse(PhoenixTest.Driver.current_path(session))
query_params = uri.query && URI.decode_query(uri.query)

if query_params == params do
Expand Down
10 changes: 10 additions & 0 deletions lib/phoenix_test/driver.ex
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,14 @@ defprotocol PhoenixTest.Driver do
def unwrap(session, fun)
def open_browser(session)
def open_browser(session, open_fun)
def current_path(session)

def assert_has(session, selector)
def assert_has(session, selector, opts)
def refute_has(session, selector)
def refute_has(session, selector, opts)
def assert_path(session, path)
def assert_path(session, path, opts)
def refute_path(session, path)
def refute_path(session, path, opts)
end
18 changes: 11 additions & 7 deletions lib/phoenix_test/field.ex
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ defmodule PhoenixTest.Field do
alias PhoenixTest.Query
alias PhoenixTest.Utils

@enforce_keys ~w[source_raw label id name value selector]a
defstruct ~w[source_raw label id name value selector]a
@enforce_keys ~w[source_raw label id name value selector raw]a
defstruct ~w[source_raw label id name value selector raw]a

def find_input!(html, label) do
field = Query.find_by_label!(html, label)
Expand All @@ -22,7 +22,8 @@ defmodule PhoenixTest.Field do
id: id,
name: name,
value: value,
selector: Element.build_selector(field)
selector: Element.build_selector(field),
raw: field
}
end

Expand Down Expand Up @@ -67,7 +68,8 @@ defmodule PhoenixTest.Field do
id: id,
name: name,
value: value,
selector: Element.build_selector(field)
selector: Element.build_selector(field),
raw: field
}
end

Expand All @@ -83,7 +85,8 @@ defmodule PhoenixTest.Field do
id: id,
name: name,
value: value,
selector: Element.build_selector(field)
selector: Element.build_selector(field),
raw: field
}
end

Expand All @@ -92,7 +95,7 @@ defmodule PhoenixTest.Field do
id = Html.attribute(field, "id")
name = Html.attribute(field, "name")

hidden_input = Query.find!(html, "input[type='hidden'][name=#{name}]")
hidden_input = Query.find!(html, "input[type='hidden'][name='#{name}']")
value = Html.attribute(hidden_input, "value")

%__MODULE__{
Expand All @@ -101,7 +104,8 @@ defmodule PhoenixTest.Field do
id: id,
name: name,
value: value,
selector: Element.build_selector(field)
selector: Element.build_selector(field),
raw: field
}
end

Expand Down
18 changes: 10 additions & 8 deletions lib/phoenix_test/form.ex
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ defmodule PhoenixTest.Form do
@checked_radio_buttons "input:not([disabled])[type=radio][checked=checked][value]"
@checked_checkboxes "input:not([disabled])[type=checkbox][checked=checked][value]"
@pre_filled_text_inputs "input:not([disabled])[type=text][value]"
@pre_filled_number_inputs "input:not([disabled])[type=number][value]"
@pre_filled_default_text_inputs "input:not([disabled]):not([type])[value]"

defp form_data(form) do
Expand All @@ -69,6 +70,7 @@ defmodule PhoenixTest.Form do
|> put_form_data(@checked_radio_buttons, form)
|> put_form_data(@checked_checkboxes, form)
|> put_form_data(@pre_filled_text_inputs, form)
|> put_form_data(@pre_filled_number_inputs, form)
|> put_form_data(@pre_filled_default_text_inputs, form)
|> put_form_data_select(form)
end
Expand All @@ -78,28 +80,28 @@ defmodule PhoenixTest.Form do
form
|> Html.all(selector)
|> Enum.map(&to_form_field/1)
|> Enum.reduce(%{}, fn value, acc -> Map.merge(acc, value) end)
|> Enum.reduce(%{}, fn value, acc -> DeepMerge.deep_merge(acc, value) end)

Map.merge(form_data, input_fields)
DeepMerge.deep_merge(form_data, input_fields)
end

defp put_form_data_select(form_data, form) do
selects =
form
|> Html.all("select")
|> Enum.reduce(%{}, fn select, acc ->
multiple = Html.attribute(select, "multiple") == "multiple"
multiple = !is_nil(Html.attribute(select, "multiple"))

case {Html.all(select, "option"), multiple, Html.all(select, "option[selected]")} do
{[], _, _} -> acc
{_, false, [only_selected]} -> Map.merge(acc, to_form_field(select, only_selected))
{_, true, [_ | _] = all_selected} -> Map.merge(acc, to_form_field(select, all_selected))
{[first | _], false, _} -> Map.merge(acc, to_form_field(select, first))
{[first | _], true, _} -> Map.merge(acc, to_form_field(select, [first]))
{_, false, [only_selected]} -> DeepMerge.deep_merge(acc, to_form_field(select, only_selected))
{_, true, [_ | _] = all_selected} -> DeepMerge.deep_merge(acc, to_form_field(select, all_selected))
{[first | _], false, _} -> DeepMerge.deep_merge(acc, to_form_field(select, first))
{_, true, _} -> DeepMerge.deep_merge(acc, to_form_field(select, []))
end
end)

Map.merge(form_data, selects)
DeepMerge.deep_merge(form_data, selects)
end

def put_button_data(form, nil), do: form
Expand Down
13 changes: 13 additions & 0 deletions lib/phoenix_test/live.ex
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ defmodule PhoenixTest.Live do
%__MODULE__{view: view, conn: conn, current_path: current_path}
end

def current_path(session), do: session.current_path

defp append_query_string(path, ""), do: path
defp append_query_string(path, query), do: path <> "?" <> query

Expand Down Expand Up @@ -300,6 +302,7 @@ defmodule PhoenixTest.Live do
end

defimpl PhoenixTest.Driver, for: PhoenixTest.Live do
alias PhoenixTest.Assertions
alias PhoenixTest.Live

defdelegate render_page_title(session), to: Live
Expand All @@ -318,4 +321,14 @@ defimpl PhoenixTest.Driver, for: PhoenixTest.Live do
defdelegate open_browser(session), to: Live
defdelegate open_browser(session, open_fun), to: Live
defdelegate unwrap(session, fun), to: Live
defdelegate current_path(session), to: Live

defdelegate assert_has(session, selector), to: Assertions
defdelegate assert_has(session, selector, opts), to: Assertions
defdelegate refute_has(session, selector), to: Assertions
defdelegate refute_has(session, selector, opts), to: Assertions
defdelegate assert_path(session, path), to: Assertions
defdelegate assert_path(session, path, opts), to: Assertions
defdelegate refute_path(session, path), to: Assertions
defdelegate refute_path(session, path, opts), to: Assertions
end
13 changes: 13 additions & 0 deletions lib/phoenix_test/static.ex
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ defmodule PhoenixTest.Static do
%__MODULE__{conn: conn, current_path: current_path}
end

def current_path(session), do: session.current_path

defp append_query_string(path, ""), do: path
defp append_query_string(path, query), do: path <> "?" <> query

Expand Down Expand Up @@ -269,6 +271,7 @@ defmodule PhoenixTest.Static do
end

defimpl PhoenixTest.Driver, for: PhoenixTest.Static do
alias PhoenixTest.Assertions
alias PhoenixTest.Static

defdelegate render_page_title(session), to: Static
Expand All @@ -287,4 +290,14 @@ defimpl PhoenixTest.Driver, for: PhoenixTest.Static do
defdelegate open_browser(session), to: Static
defdelegate open_browser(session, open_fun), to: Static
defdelegate unwrap(session, fun), to: Static
defdelegate current_path(session), to: Static

defdelegate assert_has(session, selector), to: Assertions
defdelegate assert_has(session, selector, opts), to: Assertions
defdelegate refute_has(session, selector), to: Assertions
defdelegate refute_has(session, selector, opts), to: Assertions
defdelegate assert_path(session, path), to: Assertions
defdelegate assert_path(session, path, opts), to: Assertions
defdelegate refute_path(session, path), to: Assertions
defdelegate refute_path(session, path, opts), to: Assertions
end
Loading

0 comments on commit e3f3557

Please sign in to comment.