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
84 changes: 84 additions & 0 deletions lib/playwright_ex/channels/browser_context.ex
Original file line number Diff line number Diff line change
Expand Up @@ -261,4 +261,88 @@ defmodule PlaywrightEx.BrowserContext do
|> Connection.send(%{guid: context_id, method: :addInitScript, params: Map.new(opts)}, timeout)
|> ChannelResponse.unwrap(& &1)
end

schema =
NimbleOptions.new!(
connection: PlaywrightEx.Channel.connection_opt(),
timeout: PlaywrightEx.Channel.timeout_opt(),
time: [
type: {:or, [:non_neg_integer, :string, {:struct, DateTime}]},
required: false,
doc:
"Optional base time to install, as milliseconds since epoch, an ISO8601 datetime, or a string accepted by Playwright."
]
)

@doc """
Install fake implementations for the other time-related functions (e.g. `clock_fast_forward/2`).

Reference: https://playwright.dev/docs/api/class-clock#clock-install

## Options
#{NimbleOptions.docs(schema)}
"""
@schema schema
@type clock_install_opt :: unquote(NimbleOptions.option_typespec(schema))
@spec clock_install(PlaywrightEx.guid(), [clock_install_opt() | PlaywrightEx.unknown_opt()]) ::
{:ok, any()} | {:error, any()}
def clock_install(context_id, opts \\ []) do
{connection, opts} = opts |> PlaywrightEx.Channel.validate_known!(@schema) |> Keyword.pop!(:connection)
{timeout, opts} = Keyword.pop!(opts, :timeout)
{time, opts} = Keyword.pop(opts, :time)

params =
case time do
nil -> %{}
time when is_integer(time) -> %{time_number: time}
time when is_binary(time) -> %{time_string: time}
%DateTime{} = time -> %{time_string: DateTime.to_iso8601(time)}
end

connection
|> Connection.send(%{guid: context_id, method: :clock_install, params: Map.merge(params, Map.new(opts))}, timeout)
|> ChannelResponse.unwrap(& &1)
end

schema =
NimbleOptions.new!(
connection: PlaywrightEx.Channel.connection_opt(),
timeout: PlaywrightEx.Channel.timeout_opt(),
ticks: [
type: {:or, [:non_neg_integer, :string]},
required: true,
doc: "Time to advance, in milliseconds or in `ss` / `mm:ss` / `hh:mm:ss` string format."
]
)

@doc """
Advance the clock by jumping forward in time. Only fires due timers at most once. This is equivalent to user closing the laptop lid for a while and reopening it later, after given time..

Reference: https://playwright.dev/docs/api/class-clock#clock-fast-forward

## Options
#{NimbleOptions.docs(schema)}
"""
@schema schema
@type clock_fast_forward_opt :: unquote(NimbleOptions.option_typespec(schema))
@spec clock_fast_forward(PlaywrightEx.guid(), [clock_fast_forward_opt() | PlaywrightEx.unknown_opt()]) ::
{:ok, any()} | {:error, any()}
def clock_fast_forward(context_id, opts \\ []) do
{connection, opts} = opts |> PlaywrightEx.Channel.validate_known!(@schema) |> Keyword.pop!(:connection)
{timeout, opts} = Keyword.pop!(opts, :timeout)
{ticks, opts} = Keyword.pop!(opts, :ticks)

params =
case ticks do
ticks when is_integer(ticks) -> %{ticks_number: ticks}
ticks when is_binary(ticks) -> %{ticks_string: ticks}
end

connection
|> Connection.send(
%{guid: context_id, method: :clock_fast_forward, params: Map.merge(params, Map.new(opts))},
timeout
)
|> ChannelResponse.unwrap(& &1)
end
end
52 changes: 52 additions & 0 deletions test/playwright_ex/browser_context_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,56 @@ defmodule PlaywrightEx.BrowserContextTest do
assert {:ok, "ok"} = eval(page.main_frame.guid, "() => window.__browser_context_add_init_script")
end
end

describe "clock_install/2" do
test "installs the clock from a DateTime", %{browser_context: browser_context, frame: frame} do
datetime = ~U[2024-01-02 03:04:05Z]
expected_now = DateTime.to_unix(datetime, :millisecond)

assert {:ok, _} = Frame.goto(frame.guid, url: "about:blank", timeout: @timeout)
assert {:ok, _} = BrowserContext.clock_install(browser_context.guid, time: datetime, timeout: @timeout)
assert {:ok, installed_now} = eval(frame.guid, "() => Date.now()")
assert installed_now in (expected_now - 100)..(expected_now + 100)

assert {:ok, _} = BrowserContext.clock_fast_forward(browser_context.guid, ticks: 60_001, timeout: @timeout)

assert {:ok, advanced_now} = eval(frame.guid, "() => Date.now()")
assert advanced_now in (expected_now + 60_001)..(expected_now + 60_101)
end
end

describe "clock_fast_forward/2" do
test "advances Date.now after installing the clock", %{browser_context: browser_context, frame: frame} do
assert {:ok, _} = Frame.goto(frame.guid, url: "about:blank", timeout: @timeout)
assert {:ok, before_now} = eval(frame.guid, "() => Date.now()")

assert {:ok, _} = BrowserContext.clock_install(browser_context.guid, timeout: @timeout)
assert {:ok, _} = BrowserContext.clock_fast_forward(browser_context.guid, ticks: 60_001, timeout: @timeout)

assert {:ok, after_now} = eval(frame.guid, "() => Date.now()")
assert after_now in (before_now + 60_001)..(before_now + 60_101)
end

test "starts the clock near zero without installing first", %{browser_context: browser_context, frame: frame} do
assert {:ok, _} = Frame.goto(frame.guid, url: "about:blank", timeout: @timeout)
assert {:ok, before_now} = eval(frame.guid, "() => Date.now()")

assert {:ok, _} = BrowserContext.clock_fast_forward(browser_context.guid, ticks: 60_001, timeout: @timeout)

assert {:ok, after_now} = eval(frame.guid, "() => Date.now()")
assert before_now > 1_000_000
assert after_now in 60_001..60_101
end

test "accepts string ticks", %{browser_context: browser_context, frame: frame} do
assert {:ok, _} = Frame.goto(frame.guid, url: "about:blank", timeout: @timeout)
assert {:ok, before_now} = eval(frame.guid, "() => Date.now()")

assert {:ok, _} = BrowserContext.clock_fast_forward(browser_context.guid, ticks: "01:01", timeout: @timeout)

assert {:ok, after_now} = eval(frame.guid, "() => Date.now()")
assert before_now > 1_000_000
assert after_now in 61_000..61_100
end
end
end
Loading