Skip to content

Commit 73ebf10

Browse files
authored
Fix: assert_has input by value should not fail if multiple nodes match (#73)
1 parent ae63989 commit 73ebf10

File tree

9 files changed

+61
-11
lines changed

9 files changed

+61
-11
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

88
## tbd
9+
### Fixed
10+
- `assert_has/refute_has`: don't raise if multiple nodes found when using `value` option (playwright strict mode)
911
### Changed
1012
- Return result tuples from all playwright channel functions for consistency and to surface errors early.
1113
- Most notably may affect callers of `Frame.evaluate/3`
14+
### Added
15+
- Config option `selector_engines`.
1216

1317
## [0.8.0] 2025-09-17
1418
### Removed

lib/phoenix_test/playwright.ex

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -636,25 +636,19 @@ defmodule PhoenixTest.Playwright do
636636
|> Selector.and(Selector.label(opts[:label], exact: true))
637637
|> Selector.concat("visible=true")
638638
|> Selector.concat(Selector.text(opts[:text], opts))
639-
640-
at = &(selector |> Selector.concat(Selector.at(&1)) |> Selector.build())
639+
|> Selector.concat(Selector.value(opts[:value]))
641640

642641
params =
643642
case Map.new(opts) do
644-
%{value: _, count: _} ->
645-
raise(ArgumentError, message: "Options `value` and `count` can not be used together")
646-
647643
%{count: _, at: _} ->
648644
raise(ArgumentError, message: "Options `count` and `at` can not be used together")
649645

650-
%{value: value} ->
651-
%{expression: "to.have.value", expected_text: [%{string: value}], selector: at.(opts[:at])}
652-
653646
%{count: count} ->
654647
%{expression: "to.have.count", expected_number: count, selector: Selector.build(selector)}
655648

656649
_ ->
657-
%{expression: "to.be.visible", selector: at.(opts[:at] || 0)}
650+
selector = Selector.concat(selector, Selector.at(opts[:at] || 0))
651+
%{expression: "to.be.visible", selector: selector}
658652
end
659653

660654
params = Enum.into(params, Enum.into(other_params, %{timeout: timeout(opts)}))

lib/phoenix_test/playwright/browser_context.ex

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,4 +72,12 @@ defmodule PhoenixTest.Playwright.BrowserContext do
7272
|> post()
7373
|> Result.from_response(& &1)
7474
end
75+
76+
def register_selector_engine(context_id, name, source, opts \\ []) do
77+
params = %{selector_engine: Enum.into(opts, %{name: name, source: source})}
78+
79+
[guid: context_id, method: :register_selector_engine, params: params]
80+
|> post()
81+
|> Result.from_response(& &1)
82+
end
7583
end

lib/phoenix_test/playwright/case.ex

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ defmodule PhoenixTest.Playwright.Case do
8686
})
8787

8888
{:ok, browser_context_id} = Playwright.Browser.new_context(context.browser_id, browser_context_opts)
89+
register_selector_engines!(browser_context_id)
8990

9091
{:ok, page_id} = Playwright.BrowserContext.new_page(browser_context_id, config[:browser_page_opts])
9192
{:ok, _} = Playwright.Page.update_subscription(page_id, event: :console, enabled: true)
@@ -105,6 +106,12 @@ defmodule PhoenixTest.Playwright.Case do
105106
})
106107
end
107108

109+
defp register_selector_engines!(browser_context_id) do
110+
for {name, source} <- Playwright.Selector.Engines.custom() do
111+
{:ok, _} = Playwright.BrowserContext.register_selector_engine(browser_context_id, to_string(name), source)
112+
end
113+
end
114+
108115
defp trace(browser_context_id, config, context) do
109116
{:ok, _} = Playwright.BrowserContext.start_tracing(browser_context_id)
110117

lib/phoenix_test/playwright/config.ex

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,12 @@ schema =
9999
doc:
100100
"Additional arguments passed to Playwright [Browser.newPage](https://playwright.dev/docs/api/class-browser#browser-new-page).\n" <>
101101
~s(E.g. `[accept_downloads: false]`)
102+
],
103+
selector_engines: [
104+
default: [],
105+
type: {:or, [:map, :keyword_list]},
106+
doc:
107+
"Define custom Playwright [selector engines](https://playwright.dev/docs/extensibility#custom-selector-engines)."
102108
]
103109
)
104110

lib/phoenix_test/playwright/selector.ex

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@ defmodule PhoenixTest.Playwright.Selector do
5252
def css(selector) when is_binary(selector), do: css([selector])
5353
def css(selectors) when is_list(selectors), do: "css=#{Enum.join(selectors, ",")}"
5454

55+
# Custom
56+
def value(nil), do: :none
57+
def value(value), do: "phoenix_test_value='#{value}'"
58+
5559
defp exact_suffix(opts) when is_list(opts), do: opts |> Keyword.get(:exact, false) |> exact_suffix()
5660

5761
defp exact_suffix(true), do: "s"
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
defmodule PhoenixTest.Playwright.Selector.Engines do
2+
@moduledoc """
3+
Custom selector engines.
4+
https://playwright.dev/docs/extensibility#custom-selector-engines
5+
"""
6+
7+
@paths Path.wildcard(Path.join(__DIR__, "engines/*.js"))
8+
@paths_hash :erlang.md5(@paths)
9+
10+
for path <- @paths do
11+
@external_resource path
12+
end
13+
14+
def __mix_recompile__? do
15+
:erlang.md5(@paths) != @paths_hash
16+
end
17+
18+
def custom do
19+
from_config = PhoenixTest.Playwright.Config.global(:selector_engines)
20+
default = Map.new(@paths, &{Path.basename(&1, ".js"), File.read!(&1)})
21+
Enum.into(from_config, default)
22+
end
23+
end
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
queryAll(root, selector) {
3+
const selectorWithoutQuotes = selector.substring(1, selector.length - 1)
4+
return root.value === selectorWithoutQuotes ? [root] : []
5+
}
6+
}

test/phoenix_test/playwright_test.exs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -624,15 +624,13 @@ defmodule PhoenixTest.PlaywrightTest do
624624
|> assert_has("#pre-rendered-data-form input[name=comments]")
625625
end
626626

627-
@tag skip: "FIXME #71"
628627
test "succed with value option if CSS selector matches multiple nodes", %{conn: conn} do
629628
conn
630629
|> visit("/live/index")
631630
|> within("#owner-form", &fill_in(&1, "Name", with: "Gandalf"))
632631
|> assert_has("input[name=name]", value: "Gandalf")
633632
end
634633

635-
@tag skip: "FIXME #71"
636634
test "succeed with value option if label selector matches multiple nodes", %{conn: conn} do
637635
conn
638636
|> visit("/live/index")

0 commit comments

Comments
 (0)