Skip to content
Merged
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: 6 additions & 1 deletion lib/playwright_ex/channels/frame.ex
Original file line number Diff line number Diff line change
Expand Up @@ -277,9 +277,14 @@ defmodule PlaywrightEx.Frame do

connection
|> Connection.send(%{guid: frame_id, method: :expect, params: Map.new(opts)}, timeout)
|> ChannelResponse.unwrap_expect(is_not)
|> ChannelResponse.unwrap(& &1)
|> matches?(is_not)
end

defp matches?({:error, {%{error: %{name: "ExpectError"}}, %{timed_out: true}}}, is_not), do: {:ok, is_not}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Handle non-timeout ExpectError responses as non-matches

When Playwright 1.61 returns a valid Frame.expect assertion failure with error_details.timed_out: false—for example, an expectation that terminates before its deadline—this clause does not match and the next clause exposes it as {:error, ...}. Playwright identifies an unsatisfied expectation by the ExpectError plus assertion details, while timed_out only describes the failure; recognize these responses regardless of that boolean so Frame.expect/2 consistently returns the documented match result.

Useful? React with 👍 / 👎.

defp matches?({:error, error}, _is_not), do: {:error, error}
defp matches?({:ok, _}, is_not), do: {:ok, not is_not}

schema =
NimbleOptions.new!(
connection: PlaywrightEx.Channel.connection_opt(),
Expand Down
16 changes: 6 additions & 10 deletions lib/playwright_ex/channels/page.ex
Original file line number Diff line number Diff line change
Expand Up @@ -448,24 +448,20 @@ defmodule PlaywrightEx.Page do
@schema schema
@type expect_screenshot_opt :: unquote(NimbleOptions.option_typespec(schema))
@spec expect_screenshot(PlaywrightEx.guid(), [expect_screenshot_opt() | PlaywrightEx.unknown_opt()]) ::
{:ok, binary() | nil} | {:error, any()}
{:ok, binary() | nil} | {:error, {map(), map()}} | {:error, any()}
def expect_screenshot(page_id, opts \\ []) do
{connection, opts} = opts |> PlaywrightEx.Channel.validate_known!(@schema) |> Keyword.pop!(:connection)
{timeout, opts} = Keyword.pop!(opts, :timeout)

connection
|> Connection.send(%{guid: page_id, method: :expectScreenshot, params: Map.new(opts)}, timeout)
|> unwrap_expect()
|> ChannelResponse.unwrap(& &1[:actual])
|> case do
{:ok, result} -> {:ok, result}
{:error, error} -> {:error, error}
end
end

# Playwright >= 1.61.0
defp unwrap_expect(%{error_details: details}), do: {:error, details}
# Playwright < 1.61.0
defp unwrap_expect(%{result: %{error_message: msg} = result}), do: {:error, Map.put(result, :custom_error_message, msg)}

defp unwrap_expect(%{result: result}), do: {:ok, result[:actual]}
defp unwrap_expect(result), do: {:ok, result}

defp main_frame_id!(connection, page_id) do
page_initializer = Connection.initializer!(connection, page_id)
page_initializer.main_frame.guid
Expand Down
16 changes: 1 addition & 15 deletions lib/playwright_ex/periphery/channel_response.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,11 @@ defmodule PlaywrightEx.ChannelResponse do
alias PlaywrightEx.Connection

@spec unwrap(any(), (any() -> result)) :: {:ok, result} | {:error, any()} when result: any()
def unwrap(%{error: %{} = error, error_details: details}, _), do: {:error, {error, details}}
def unwrap(%{error: error}, _), do: {:error, error}
def unwrap(%{result: result}, fun) when is_function(fun, 1), do: {:ok, fun.(result)}
def unwrap(other, fun) when is_function(fun, 1), do: {:ok, other}

@doc """
Unwraps a `Frame.expect` reply into `{:ok, matches?}`, applying `is_not`.

Playwright 1.60 replied `%{result: %{matches: boolean}}`. 1.61 dropped that
field and signals the outcome by success-vs-error: a non-match returns an
error carrying `error_details`. Mirrors playwright-core's `Frame._expect`, so
`matches?` (raw positive-condition result, `is_not` applied) stays identical
across drivers; a genuine 1.60 error still propagates as `{:error, _}`.
"""
@spec unwrap_expect(map(), boolean()) :: {:ok, boolean()} | {:error, any()}
def unwrap_expect(%{result: %{matches: matches}}, _is_not) when is_boolean(matches), do: {:ok, matches}
def unwrap_expect(%{error_details: _details}, is_not) when is_boolean(is_not), do: {:ok, is_not}
def unwrap_expect(%{error: error}, _is_not), do: {:error, error}
def unwrap_expect(reply, is_not) when is_map(reply) and is_boolean(is_not), do: {:ok, not is_not}

@spec unwrap_create(any(), atom(), GenServer.name()) :: {:ok, any()} | {:error, any()}
def unwrap_create(value, resource_name, connection) when is_atom(resource_name) do
unwrap(value, fn result ->
Expand Down
17 changes: 9 additions & 8 deletions test/playwright_ex/page_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -61,14 +61,15 @@ defmodule PlaywrightEx.PageTest do
{:ok, _} = Frame.goto(frame.guid, url: "about:blank", timeout: @timeout)

assert {:error,
%{
timed_out: false,
custom_error_message:
"Expected an image 1px by 1px, received 1280px by 720px. 1 pixels (ratio 0.01 of all image pixels) are different.",
log: _,
diff: _,
actual: _
}} =
{_,
%{
timed_out: false,
custom_error_message:
"Expected an image 1px by 1px, received 1280px by 720px. 1 pixels (ratio 0.01 of all image pixels) are different.",
log: _,
diff: _,
actual: _
}}} =
Page.expect_screenshot(page.guid, expected: @red_px_png, timeout: @timeout)
end

Expand Down
40 changes: 0 additions & 40 deletions test/playwright_ex/periphery/channel_response_test.exs

This file was deleted.