Packages

Playwright is an Elixir library to automate Chromium, Firefox and WebKit browsers with a single API. Playwright delivers automation that is ever-green, capable, reliable and fast.

Current section

Files

Jump to
playwrightais lib playwright channel connection.ex
Raw

lib/playwright/channel/connection.ex

defmodule Playwright.Channel.Connection do
@moduledoc false
use GenServer
require Logger
alias Playwright.{Channel, Extra, Transport}
defstruct(
callbacks: %{},
session: nil,
transport: nil
)
# module init
# ---------------------------------------------------------------------------
def start_link(arg) do
GenServer.start_link(__MODULE__, arg)
end
# @impl init
# ---------------------------------------------------------------------------
@impl GenServer
def init({session, transport}) do
state = %__MODULE__{
session: session,
transport: Transport.start_link!({self(), transport})
}
{:ok, state, {:continue, :initialize}}
end
@impl GenServer
def handle_continue(:initialize, %{transport: transport} = state) do
message = %{
guid: "",
method: "initialize",
params: %{sdkLanguage: "elixir"},
metadata: %{}
}
# message = Channel.Message.new("", :initialize, %{sdkLanguage: "elixir"})
Transport.post(transport, message)
{:noreply, state}
end
# module API
# ---------------------------------------------------------------------------
# Transport-bound + callback(`GenServer.reply`)
# - Is the one "API function" that sends to/over the Transport.
# - Registers a "from" to receive the reply (knowing it will NOT be in the Catalog).
# - ...in fact, any related Catalog changes are side-effects, likely delivered via an Event.
# @spec post(pid(), Channel.Message.t()) :: {:ok, term()} | {:error, term()}
def post(connection, message, timeout) do
GenServer.call(connection, {:post, message}, timeout)
end
# Transport-bound.
# - Is the one "API function" that receives from the Transport.
# - ...therefore, all `reply`, `handler`, etc. "clearing" MUST originate here.
def recv(connection, message) do
GenServer.cast(connection, {:recv, message})
end
# @spec wait_for(pid(), {atom(), struct()}, (() -> any())) :: {:ok, Event.t()}
def wait(connection, owner, event, timeout, trigger \\ nil) do
GenServer.call(connection, {:wait, {owner, event}, trigger}, timeout)
end
# @impl callbacks
# ----------------------------------------------------------------------------
@impl GenServer
def handle_call({:post, message}, from, %{callbacks: callbacks, transport: transport} = state) do
Transport.post(transport, message)
key = {:message, message.id}
{:noreply, %{state | callbacks: Map.put(callbacks, key, from)}}
end
@impl GenServer
def handle_call({:wait, {{:guid, guid}, event}, trigger}, from, %{callbacks: callbacks} = state) do
# Would `:continue` (which is allowed) be better here?
if trigger do
Task.start_link(trigger)
end
key = {as_atom(event), guid}
{:noreply, %{state | callbacks: Map.put(callbacks, key, from)}}
end
@impl GenServer
def handle_cast({:recv, response}, %{callbacks: callbacks, session: session} = state) do
update =
case response do
%{id: message_id} ->
key = {:message, message_id}
{from, callbacks} = Map.pop!(callbacks, key)
Channel.recv(session, {from, response})
%{state | callbacks: callbacks}
%{guid: guid, method: method} ->
key = {as_atom(method), guid}
{from, callbacks} = Map.pop(callbacks, key)
Channel.recv(session, {from, response})
%{state | callbacks: callbacks}
%{result: _result} ->
Channel.recv(session, {nil, response})
state
end
{:noreply, update}
end
# private
# ----------------------------------------------------------------------------
defp as_atom(value) when is_atom(value) do
value
end
defp as_atom(value) when is_binary(value) do
Extra.Atom.snakecased(value)
end
end