diff --git a/.credo.exs b/.credo.exs
index dfa26a9c..1f43a5a8 100644
--- a/.credo.exs
+++ b/.credo.exs
@@ -103,7 +103,8 @@
{Credo.Check.Readability.ModuleNames, []},
{Credo.Check.Readability.ParenthesesInCondition, []},
{Credo.Check.Readability.ParenthesesOnZeroArityDefs, []},
- {Credo.Check.Readability.PredicateFunctionNames, []},
+ # Disabled: is_* functions match Playwright's JavaScript API for consistency
+ {Credo.Check.Readability.PredicateFunctionNames, false},
{Credo.Check.Readability.PreferImplicitTry, []},
{Credo.Check.Readability.RedundantBlankLines, []},
{Credo.Check.Readability.Semicolons, []},
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 7d2ddf78..45139a34 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -18,8 +18,8 @@ jobs:
- name: Set up Elixir
uses: erlef/setup-beam@v1
with:
- elixir-version: '1.16.2'
- otp-version: '26.2.5'
+ elixir-version: '1.18.4'
+ otp-version: '27.3.4.6'
- name: Install Elixir dependencies
run: mix deps.get
- name: Install Playwright dependencies (e.g., browsers)
diff --git a/.gitignore b/.gitignore
index 5c4953a9..c97992c0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -38,4 +38,5 @@ playwright-*.tar
.doctor.out
.vscode
*.iml
-/.idea/
\ No newline at end of file
+/.idea/
+.claude/
diff --git a/.mise.toml b/.mise.toml
new file mode 100644
index 00000000..797a684e
--- /dev/null
+++ b/.mise.toml
@@ -0,0 +1,4 @@
+[tools]
+elixir = "1.18.4-otp-27"
+erlang = "27.3.4.6"
+nodejs = "22.21.1"
diff --git a/.tool-versions b/.tool-versions
deleted file mode 100644
index 31fe85c1..00000000
--- a/.tool-versions
+++ /dev/null
@@ -1,3 +0,0 @@
-elixir 1.16.2-otp-26
-erlang 26.2.5
-nodejs 22.2.0
diff --git a/README.md b/README.md
index 406eaf8b..8a9dfe5c 100644
--- a/README.md
+++ b/README.md
@@ -13,7 +13,7 @@ The package can be installed by adding `playwright` to your list of dependencies
```elixir
def deps do
[
- {:playwright, "~> 1.49.1-alpha.2"}
+ {:playwright, "~> 1.59.1-alpha.1"}
]
end
```
@@ -68,6 +68,6 @@ This project aims to track the release versioning found in [Playwright proper](h
1. Update the version in `mix.exs`
a. Search for and update the version anywhere it appears in the repo, such as this README
-2. `git tag -a v${version_number}` such that the tag look like `v1.49.1-alpha.2` or `v1.49.1`
+2. `git tag -a v${version_number}` such that the tag look like `v1.59.1-alpha.1` or `v1.59.1`
3. `git push --tags`
4. `mix hex.publish`
diff --git a/lib/playwright.ex b/lib/playwright.ex
index 6b780f4d..901bb9d4 100644
--- a/lib/playwright.ex
+++ b/lib/playwright.ex
@@ -48,9 +48,9 @@ defmodule Playwright do
| key/name | typ | | description |
| ----------| ----- | ----------- | ----------- |
| `client` | param | `client()` | The type of client (browser) to launch. |
- | `options` | param | `options()` | `Playwright.SDK.Config.connect_options()` |
+ | `options` | param | `options()` | Connection options (see Config module) |
"""
- @spec launch(client(), Config.connect_options() | map()) :: {:ok, Playwright.Browser.t()}
+ @spec connect(client(), map()) :: {:ok, Playwright.Browser.t()}
def connect(client, options \\ %{}) do
options = Map.merge(Config.connect_options(), options)
{:ok, session} = new_session(Playwright.SDK.Transport.WebSocket, options)
@@ -70,9 +70,9 @@ defmodule Playwright do
| key/name | typ | | description |
| ----------| ----- | ----------- | ----------- |
| `client` | param | `client()` | The type of client (browser) to launch. |
- | `options` | param | `options()` | `Playwright.SDK.Config.launch_options()` |
+ | `options` | param | `options()` | Launch options (see Config module) |
"""
- @spec launch(client(), Config.launch_options() | map()) :: {:ok, Playwright.Browser.t()}
+ @spec launch(client(), map()) :: {:ok, Playwright.Browser.t()}
def launch(client, options \\ %{}) do
options = Map.merge(Config.launch_options(), options)
{:ok, session} = new_session(Playwright.SDK.Transport.Driver, options)
diff --git a/lib/playwright/api_request_context.ex b/lib/playwright/api_request_context.ex
index 66692506..59ade49d 100644
--- a/lib/playwright/api_request_context.ex
+++ b/lib/playwright/api_request_context.ex
@@ -41,7 +41,7 @@ defmodule Playwright.APIRequestContext do
# @spec patch(t(), binary(), options()) :: APIResponse.t()
# def patch(context, url, options \\ %{})
- @spec post(t(), binary(), fetch_options()) :: Playwright.APIResponse.t()
+ @spec post(t(), binary(), fetch_options()) :: struct()
def post(%APIRequestContext{session: session} = context, url, options \\ %{}) do
Channel.post(
session,
@@ -64,7 +64,7 @@ defmodule Playwright.APIRequestContext do
# def storage_state(context, options \\ %{})
# TODO: move to `APIResponse.body`, probably.
- @spec body(t(), Playwright.APIResponse.t()) :: any()
+ @spec body(t(), struct()) :: any()
def body(%APIRequestContext{session: session} = context, response) do
Channel.post(session, {:guid, context.guid}, :fetch_response_body, %{
fetchUid: response.fetchUid
diff --git a/lib/playwright/artifact.ex b/lib/playwright/artifact.ex
new file mode 100644
index 00000000..8b1dbce0
--- /dev/null
+++ b/lib/playwright/artifact.ex
@@ -0,0 +1,64 @@
+defmodule Playwright.Artifact do
+ @moduledoc false
+ use Playwright.SDK.ChannelOwner
+
+ @doc """
+ Returns the path to the downloaded file after it has finished downloading.
+ """
+ @spec path_after_finished(t()) :: binary() | {:error, term()}
+ def path_after_finished(%__MODULE__{session: session} = artifact) do
+ case Channel.post(session, {:guid, artifact.guid}, :path_after_finished) do
+ %{value: path} -> path
+ path when is_binary(path) -> path
+ {:error, _} = error -> error
+ end
+ end
+
+ @doc """
+ Saves the artifact to the specified path.
+ """
+ @spec save_as(t(), binary()) :: :ok | {:error, term()}
+ def save_as(%__MODULE__{session: session} = artifact, path) do
+ case Channel.post(session, {:guid, artifact.guid}, :save_as, %{path: path}) do
+ {:ok, _} -> :ok
+ :ok -> :ok
+ {:error, _} = error -> error
+ end
+ end
+
+ @doc """
+ Returns the error message if the artifact failed to download, nil otherwise.
+ """
+ @spec failure(t()) :: binary() | nil | {:error, term()}
+ def failure(%__MODULE__{session: session} = artifact) do
+ case Channel.post(session, {:guid, artifact.guid}, :failure) do
+ %{error: error} -> error
+ %{} -> nil
+ {:error, _} = error -> error
+ end
+ end
+
+ @doc """
+ Cancels the download.
+ """
+ @spec cancel(t()) :: :ok | {:error, term()}
+ def cancel(%__MODULE__{session: session} = artifact) do
+ case Channel.post(session, {:guid, artifact.guid}, :cancel) do
+ {:ok, _} -> :ok
+ :ok -> :ok
+ {:error, _} = error -> error
+ end
+ end
+
+ @doc """
+ Deletes the downloaded file.
+ """
+ @spec delete(t()) :: :ok | {:error, term()}
+ def delete(%__MODULE__{session: session} = artifact) do
+ case Channel.post(session, {:guid, artifact.guid}, :delete) do
+ {:ok, _} -> :ok
+ :ok -> :ok
+ {:error, _} = error -> error
+ end
+ end
+end
diff --git a/lib/playwright/browser.ex b/lib/playwright/browser.ex
index e741ba7a..38348029 100644
--- a/lib/playwright/browser.ex
+++ b/lib/playwright/browser.ex
@@ -21,7 +21,7 @@ defmodule Playwright.Browser do
- `:version`
"""
use Playwright.SDK.ChannelOwner
- alias Playwright.{Browser, BrowserContext, Page}
+ alias Playwright.{Browser, BrowserContext, BrowserType, CDPSession, Page}
alias Playwright.SDK.{Channel, ChannelOwner, Extra}
@property :name
@@ -44,8 +44,32 @@ defmodule Playwright.Browser do
# API
# ---------------------------------------------------------------------------
- # @spec browser_type(t()) :: BrowserType.t()
- # def browser_type(browser)
+ @doc """
+ Returns the BrowserType that was used to launch this browser.
+
+ ## Returns
+
+ - `BrowserType.t()`
+ """
+ @spec browser_type(t()) :: BrowserType.t()
+ def browser_type(%Browser{parent: parent}), do: parent
+
+ @doc """
+ Returns whether the browser is still connected.
+
+ Returns `false` after the browser has been closed.
+
+ ## Returns
+
+ - `boolean()`
+ """
+ @spec is_connected(t()) :: boolean()
+ def is_connected(%Browser{session: session, guid: guid}) do
+ case Channel.find(session, {:guid, guid}, %{timeout: 100}) do
+ %Browser{} -> true
+ {:error, _} -> false
+ end
+ end
@doc """
Closes the browser.
@@ -94,9 +118,6 @@ defmodule Playwright.Browser do
Channel.list(browser.session, {:guid, browser.guid}, "BrowserContext")
end
- # @spec is_connected(BrowserContext.t()) :: boolean()
- # def is_connected(browser)
-
# @spec new_browser_cdp_session(BrowserContext.t()) :: Playwright.CDPSession.t()
# def new_browser_cdp_session(browser)
@@ -146,33 +167,118 @@ defmodule Playwright.Browser do
This is a convenience API function that should only be used for single-page
scenarios and short snippets. Production code and testing frameworks should
explicitly create via `Playwright.Browser.new_context/2` followed by
- `Playwright.BrowserContext.new_page/2`, given the new context, to manage
+ `Playwright.BrowserContext.new_page/1`, given the new context, to manage
resource lifecycles.
"""
- @spec new_page(t(), options()) :: Page.t()
+ @spec new_page(t(), options()) :: {:ok, Page.t()} | {:error, term()}
def new_page(browser, options \\ %{})
def new_page(%Browser{session: session} = browser, options) do
- context = new_context(browser, options)
- page = BrowserContext.new_page(context)
+ with context when is_struct(context) <- new_context(browser, options),
+ page when is_struct(page) <- BrowserContext.new_page(context) do
+ # establish co-dependency
+ Channel.patch(session, {:guid, context.guid}, %{owner_page: page})
+ Channel.patch(session, {:guid, page.guid}, %{owned_context: context})
+ {:ok, page}
+ end
+ end
+
+ # ---
+
+ @doc """
+ Start tracing for Chromium browser.
+
+ Records a trace that can be viewed in Chrome DevTools or Playwright Trace Viewer.
+
+ ## Arguments
+
+ | key/name | type | description |
+ | -------------- | ------------- | ---------------------------------------- |
+ | `page` | `Page.t()` | Optional page to trace (default: all) |
+ | `:screenshots` | `boolean()` | Capture screenshots during trace |
+ | `:categories` | `[binary()]` | Trace categories to record |
+
+ ## Returns
- # TODO: handle the following, for `page`:
- # ** (KeyError) key :guid not found in: {:error, %Playwright.Channel.Error{message: "Target closed"}}
+ - `:ok`
- # establish co-dependency
- Channel.patch(session, {:guid, context.guid}, %{owner_page: page})
- Channel.patch(session, {:guid, page.guid}, %{owned_context: context})
+ ## Example
+
+ Browser.start_tracing(browser)
+ # ... perform actions ...
+ trace = Browser.stop_tracing(browser)
+ File.write!("trace.json", trace)
+
+ ## Note
+
+ Only supported on Chromium browsers.
+ """
+ @spec start_tracing(t(), Page.t() | nil, options()) :: :ok
+ def start_tracing(%Browser{session: session, guid: guid}, page \\ nil, options \\ %{}) do
+ params = %{}
+ params = if page, do: Map.put(params, :page, %{guid: page.guid}), else: params
+ params = if options[:screenshots], do: Map.put(params, :screenshots, options[:screenshots]), else: params
+ params = if options[:categories], do: Map.put(params, :categories, options[:categories]), else: params
+
+ Channel.post(session, {:guid, guid}, :start_tracing, params)
+ :ok
end
- # ---
+ @doc """
+ Stop tracing and return the trace data.
+
+ Returns the trace data as binary which can be saved to a file.
+
+ ## Returns
+
+ - `binary()` - Trace data (JSON format, save as .json file)
+
+ ## Example
- # test_chromium_tracing.py
- # @spec start_tracing(t(), Page.t(), options()) :: :ok
- # def start_tracing(browser, page \\ nil, options \\ %{})
+ Browser.start_tracing(browser, page, %{screenshots: true})
+ Page.goto(page, "https://example.com")
+ trace = Browser.stop_tracing(browser)
+ File.write!("trace.json", trace)
+ """
+ @spec stop_tracing(t()) :: binary()
+ def stop_tracing(%Browser{session: session, guid: guid}) do
+ artifact =
+ case Channel.post(session, {:guid, guid}, :stop_tracing, %{}) do
+ %{artifact: %{guid: artifact_guid}} ->
+ Channel.find(session, {:guid, artifact_guid})
+
+ %Playwright.Artifact{} = art ->
+ art
+ end
+
+ # Save to temp file, read contents, then clean up
+ temp_path = Path.join(System.tmp_dir!(), "pw_trace_#{:erlang.unique_integer([:positive])}.json")
+ Playwright.Artifact.save_as(artifact, temp_path)
+ data = File.read!(temp_path)
+ File.rm(temp_path)
+ Playwright.Artifact.delete(artifact)
+ data
+ end
- # test_chromium_tracing.py
- # @spec stop_tracing(t()) :: binary()
- # def stop_tracing(browser)
+ @doc """
+ Creates a new CDP session attached to the browser.
+
+ This is Chromium-specific.
+
+ ## Returns
+
+ - `CDPSession.t()`
+
+ ## Example
+
+ session = Browser.new_browser_cdp_session(browser)
+ # Use CDP commands...
+ CDPSession.detach(session)
+ """
+ @spec new_browser_cdp_session(t()) :: CDPSession.t()
+ def new_browser_cdp_session(%Browser{session: session, guid: guid}) do
+ Channel.post(session, {:guid, guid}, "newBrowserCDPSession")
+ end
# @spec version(BrowserContext.t()) :: binary
# def version(browser)
diff --git a/lib/playwright/browser_context.ex b/lib/playwright/browser_context.ex
index 4324ef5b..01bad102 100644
--- a/lib/playwright/browser_context.ex
+++ b/lib/playwright/browser_context.ex
@@ -29,7 +29,7 @@ defmodule Playwright.BrowserContext do
The second argument is the event type.
The third argument is a callback function that will be executed when the
- event fires, and is passed an instance of `Playwright.SDK.Channel.Event`.
+ event fires, and is passed an event struct containing the event details.
### Details for `expect_event/5`
@@ -61,17 +61,6 @@ defmodule Playwright.BrowserContext do
The `expect_event/5` and `on/3` functions support the following event types:
- - `:background_page`
-
- Emitted when a new background page is created in the context. The event
- target is a `Playwright.Page`.
-
- ...
-
- > NOTE:
- >
- > - Only works with Chromium browser's persistent context.
-
- `:close`
Emitted when the `Playwright.BrowserContext` is closed. The event target
@@ -160,13 +149,15 @@ defmodule Playwright.BrowserContext do
"""
use Playwright.SDK.ChannelOwner
- alias Playwright.{BrowserContext, Frame, Page}
+ alias Playwright.{BrowserContext, Frame, Page, Worker}
alias Playwright.SDK.{Channel, ChannelOwner, Helpers}
@property :bindings
@property :browser
@property :owner_page
@property :routes
+ @property :tracing
+ @property :websocket_routes
@typedoc "Recognized cookie fields"
@type cookie :: %{
@@ -178,13 +169,13 @@ defmodule Playwright.BrowserContext do
expires: float,
httpOnly: boolean,
secure: boolean,
- sameSite: String.t()
+ sameSite: String.t(),
+ partitionKey: String.t()
}
@typedoc "Supported events"
@type event ::
- :background_page
- | :close
+ :close
| :page
| :request
| :request_failed
@@ -215,7 +206,12 @@ defmodule Playwright.BrowserContext do
# NOTE: will patch here
end)
- {:ok, %{context | bindings: %{}, browser: context.parent, routes: []}}
+ Channel.bind(session, {:guid, context.guid}, :web_socket_route, fn %{params: params, target: target} ->
+ on_web_socket_route(target, params)
+ :ok
+ end)
+
+ {:ok, %{context | bindings: %{}, browser: context.parent, routes: [], websocket_routes: []}}
end
# API
@@ -304,11 +300,9 @@ defmodule Playwright.BrowserContext do
params = %{source: script}
case Channel.post(session, {:guid, context.guid}, :add_init_script, params) do
- {:ok, _} ->
- :ok
-
- {:error, error} ->
- {:error, error}
+ {:ok, _} -> :ok
+ {:error, error} -> {:error, error}
+ %{guid: _} -> :ok
end
end
@@ -318,8 +312,20 @@ defmodule Playwright.BrowserContext do
# ---
- # @spec background_pages(t()) :: [Playwright.Page.t()]
- # def background_pages(context)
+ @doc """
+ Returns all background pages in the context.
+
+ This only works with Chromium persistent contexts.
+
+ ## Returns
+
+ - `[Page.t()]`
+ """
+ @doc deprecated: "backgroundPage event was removed in Playwright 1.57"
+ @spec background_pages(t()) :: [Page.t()]
+ def background_pages(%BrowserContext{}) do
+ []
+ end
# @spec browser(t()) :: Playwright.Browser.t()
# def browser(context)
@@ -386,18 +392,18 @@ defmodule Playwright.BrowserContext do
predicate function.
Returns when the predicate returns a truthy value. Throws an error if the
- context closes before the event is fired. Returns a `Playwright.SDK.Channel.Event`.
+ context closes before the event is fired. Returns a channel event struct.
## Arguments
- `event`: Event name; the same as those passed to `Playwright.BrowserContext.on/3`
- - `predicate`: Receives the `Playwright.SDK.Channel.Event` and resolves to a
+ - `predicate`: Receives the channel event and resolves to a
"truthy" value when the waiting should resolve.
- `options`:
- `predicate`: ...
- `timeout`: The maximum time to wait in milliseconds. Defaults to 30000
(30 seconds). Pass 0 to disable timeout. The default value can be changed
- via `Playwright.BrowserContext.set_default_timeout/2`.
+ via BrowserContext timeout settings.
## Example
@@ -416,7 +422,7 @@ defmodule Playwright.BrowserContext do
the `Playwright.BrowserContext`.
If `predicate` is provided, it passes the `Playwright.Page` value into the
- predicate function, wrapped in `Playwright.SDK.Channel.Event`, and waits for
+ predicate function, wrapped in a channel event struct, and waits for
`predicate/1` to return a "truthy" value. Throws an error if the context
closes before new `Playwright.Page` is created.
@@ -428,10 +434,9 @@ defmodule Playwright.BrowserContext do
- `predicate`: ...
- `timeout`: The maximum time to wait in milliseconds. Defaults to 30000
(30 seconds). Pass 0 to disable timeout. The default value can be changed
- via `Playwright.BrowserContext.set_default_timeout/2`.
+ via BrowserContext timeout settings.
"""
- # Temporarily disable spec:
- # @spec expect_page(t(), map(), function()) :: Playwright.SDK.Channel.Event.t()
+ # Temporarily disable spec (channel event type is internal)
def expect_page(context, options \\ %{}, trigger \\ nil) do
expect_event(context, :page, options, trigger)
end
@@ -476,13 +481,13 @@ defmodule Playwright.BrowserContext do
end)
end
- @spec grant_permissions(t(), [String.t()], options()) :: :ok | {:error, Channel.Error.t()}
+ @spec grant_permissions(t(), [String.t()], options()) :: :ok | {:error, term()}
def grant_permissions(%BrowserContext{session: session} = context, permissions, options \\ %{}) do
params = Map.merge(%{permissions: permissions}, options)
Channel.post(session, {:guid, context.guid}, :grant_permissions, params)
end
- @spec new_cdp_session(t(), Frame.t() | Page.t()) :: Playwright.CDPSession.t()
+ @spec new_cdp_session(t(), Frame.t() | Page.t()) :: struct()
def new_cdp_session(context, owner)
def new_cdp_session(%BrowserContext{session: session} = context, %Frame{} = frame) do
@@ -533,7 +538,7 @@ defmodule Playwright.BrowserContext do
Channel.list(context.session, {:guid, context.guid}, "Page")
end
- @spec route(t(), binary(), function(), map()) :: :ok
+ @spec route(t(), binary() | Regex.t(), function(), map()) :: t() | {:error, term()}
def route(context, pattern, handler, options \\ %{})
def route(%BrowserContext{session: session} = context, pattern, handler, _options) do
@@ -554,30 +559,202 @@ defmodule Playwright.BrowserContext do
# @spec route_from_har(t(), binary(), map()) :: :ok
# def route(context, har, options \\ %{})
- # ???
- # @spec service_workers(t()) :: [Playwright.Worker.t()]
- # def service_workers(context)
+ @doc """
+ Routes WebSocket connections matching the URL pattern to the handler.
+
+ This applies to all pages within the context.
+
+ ## Example
+
+ BrowserContext.route_web_socket(context, "**/ws", fn ws ->
+ server = WebSocketRoute.connect_to_server(ws)
+
+ WebSocketRoute.on_message(ws, fn msg ->
+ IO.puts("Page -> Server: \#{msg}")
+ WebSocketRoute.Server.send(server, msg)
+ end)
+
+ WebSocketRoute.Server.on_message(server, fn msg ->
+ IO.puts("Server -> Page: \#{msg}")
+ WebSocketRoute.send(ws, msg)
+ end)
+ end)
+ """
+ @spec route_web_socket(t(), binary() | Regex.t(), (Playwright.WebSocketRoute.t() -> any())) ::
+ t() | {:error, term()}
+ def route_web_socket(%BrowserContext{session: session} = context, pattern, handler)
+ when is_function(handler, 1) do
+ with_latest(context, fn context ->
+ matcher = Helpers.URLMatcher.new(pattern)
+ ws_handler = Helpers.WebSocketRouteHandler.new(matcher, handler)
+
+ websocket_routes = [ws_handler | context.websocket_routes]
+ patterns = Helpers.WebSocketRouteHandler.prepare(websocket_routes)
+
+ Channel.patch(session, {:guid, context.guid}, %{websocket_routes: websocket_routes})
+ Channel.post(session, {:guid, context.guid}, :set_web_socket_interception_patterns, %{patterns: patterns})
+ end)
+ end
+
+ @doc """
+ Returns all service workers in the context.
+
+ ## Returns
+
+ - `[Worker.t()]`
+
+ ## Note
+
+ Currently returns an empty list. Full tracking of service workers
+ via events is a future enhancement.
+ """
+ @spec service_workers(t()) :: [Worker.t()]
+ def service_workers(%BrowserContext{}) do
+ []
+ end
+
+ @doc """
+ Sets the default timeout for all context operations.
+
+ This setting will change the default maximum time for all the methods
+ accepting a `timeout` option.
+
+ ## Arguments
+
+ | key/name | type | description |
+ | --------- | ---------- | -------------------------------- |
+ | `timeout` | `number()` | Maximum time in milliseconds. |
+
+ ## Returns
+
+ - `:ok`
+
+ ## Example
+
+ BrowserContext.set_default_timeout(context, 60_000) # 60 seconds
+ """
+ @spec set_default_timeout(t(), number()) :: :ok
+ def set_default_timeout(%BrowserContext{session: session, guid: guid}, timeout) do
+ Channel.post(session, {:guid, guid}, :set_default_timeout_no_reply, %{timeout: timeout})
+ :ok
+ end
+
+ @doc """
+ Sets the default timeout for navigation operations.
+
+ This setting will change the default maximum navigation time for the
+ following methods and related shortcuts on page: `goto`, `go_back`,
+ `go_forward`, `reload`, `wait_for_navigation`.
+
+ ## Arguments
+
+ | key/name | type | description |
+ | --------- | ---------- | -------------------------------- |
+ | `timeout` | `number()` | Maximum time in milliseconds. |
+
+ ## Returns
+
+ - `:ok`
+
+ ## Example
+
+ BrowserContext.set_default_navigation_timeout(context, 90_000) # 90 seconds
+ """
+ @spec set_default_navigation_timeout(t(), number()) :: :ok
+ def set_default_navigation_timeout(%BrowserContext{session: session, guid: guid}, timeout) do
+ Channel.post(session, {:guid, guid}, :set_default_navigation_timeout_no_reply, %{timeout: timeout})
+ :ok
+ end
+
+ @doc """
+ Sets extra HTTP headers to be sent with every request.
+
+ These headers will be sent with every request initiated by any page in
+ the context. Headers set with `Page.set_extra_http_headers/2` take
+ precedence over headers set with this method.
+
+ ## Arguments
+
+ | key/name | type | description |
+ | --------- | -------- | ------------------------------------------ |
+ | `headers` | `map()` | Map of header names to values. |
+
+ ## Returns
+
+ - `:ok`
+
+ ## Example
+
+ BrowserContext.set_extra_http_headers(context, %{
+ "Authorization" => "Bearer token123",
+ "X-Custom-Header" => "value"
+ })
+ """
+ @spec set_extra_http_headers(t(), map()) :: :ok
+ def set_extra_http_headers(%BrowserContext{session: session, guid: guid}, headers) when is_map(headers) do
+ header_list = Enum.map(headers, fn {name, value} -> %{name: to_string(name), value: value} end)
+ Channel.post(session, {:guid, guid}, :set_extra_http_headers, %{headers: header_list})
+ :ok
+ end
+
+ @doc """
+ Sets the geolocation for this browser context.
+
+ All pages in this context will use the overridden geolocation.
+
+ ## Parameters
- # test_navigation.py
- # @spec set_default_navigation_timeout(t(), number()) :: :ok
- # def set_default_navigation_timeout(context, timeout)
+ - `geolocation` - A map with `:latitude`, `:longitude`, and optional `:accuracy`.
+ - `:latitude` - Latitude between -90 and 90
+ - `:longitude` - Longitude between -180 and 180
+ - `:accuracy` - Non-negative accuracy in meters (default: 0)
- # test_navigation.py
- # @spec set_default_timeout(t(), number()) :: :ok
- # def set_default_timeout(context, timeout)
+ ## Examples
- # test_interception.py
- # test_network.py
- # @spec set_extra_http_headers(t(), headers()) :: :ok
- # def set_extra_http_headers(context, headers)
+ # Set geolocation to San Francisco
+ BrowserContext.set_geolocation(context, %{latitude: 37.7749, longitude: -122.4194})
- # test_geolocation.py
- # @spec set_geolocation(t(), geolocation()) :: :ok
- # def set_geolocation(context, geolocation)
+ # With accuracy
+ BrowserContext.set_geolocation(context, %{latitude: 37.7749, longitude: -122.4194, accuracy: 100})
- # ???
- # @spec set_http_credentials(t(), http_credentials()) :: :ok
- # def set_http_credentials(context, http_credentials)
+ ## Note
+
+ Consider also granting the 'geolocation' permission:
+
+ BrowserContext.grant_permissions(context, ["geolocation"])
+ """
+ @spec set_geolocation(t(), map()) :: :ok
+ def set_geolocation(%BrowserContext{session: session} = context, geolocation) do
+ Channel.post(session, {:guid, context.guid}, :set_geolocation, %{geolocation: geolocation})
+ end
+
+ @doc """
+ Sets HTTP authentication credentials for requests.
+
+ Pass `nil` to disable authentication.
+
+ ## Arguments
+
+ | key/name | type | description |
+ | ------------- | -------------------------------------------------- | ------------------------------- |
+ | `credentials` | `%{username: binary(), password: binary()} \| nil` | Credentials or nil to disable. |
+
+ ## Returns
+
+ - `:ok`
+
+ ## Example
+
+ BrowserContext.set_http_credentials(context, %{username: "user", password: "pass"})
+ # Later, to disable:
+ BrowserContext.set_http_credentials(context, nil)
+ """
+ @spec set_http_credentials(t(), %{username: binary(), password: binary()} | nil) :: :ok
+ def set_http_credentials(%BrowserContext{session: session, guid: guid}, credentials) do
+ params = %{httpCredentials: credentials}
+ Channel.post(session, {:guid, guid}, :set_http_credentials, params)
+ :ok
+ end
# ---
@@ -588,12 +765,53 @@ defmodule Playwright.BrowserContext do
# ---
- # @spec storage_state(t(), String.t()) :: {:ok, storage_state()}
- # def storage_state(context, path \\ nil)
+ @doc """
+ Returns storage state for this browser context, contains current cookies and local storage snapshot.
+
+ ## Options
+
+ - `:path` - Optional path to save the storage state as JSON file.
+
+ ## Examples
+
+ # Get storage state as map
+ state = BrowserContext.storage_state(context)
+
+ # Save to file
+ state = BrowserContext.storage_state(context, path: "/tmp/auth.json")
+
+ ## Returns
+
+ A map with `:cookies` and `:origins` keys.
+ """
+ @spec storage_state(t(), keyword()) :: map() | {:error, term()}
+ def storage_state(%BrowserContext{session: session} = context, options \\ []) do
+ params =
+ case Keyword.get(options, :indexed_db) do
+ nil -> %{}
+ val -> %{indexedDB: val}
+ end
+
+ case Channel.post(session, {:guid, context.guid}, :storage_state, params) do
+ {:error, _} = error ->
+ error
+
+ state when is_map(state) ->
+ case Keyword.get(options, :path) do
+ nil ->
+ state
+
+ path ->
+ json = Jason.encode!(state, pretty: true)
+ File.write!(path, json)
+ state
+ end
+ end
+ end
# ---
- @spec unroute(t(), binary(), function() | nil) :: :ok
+ @spec unroute(t(), binary() | Regex.t(), function() | nil) :: t() | {:error, term()}
def unroute(%BrowserContext{session: session} = context, pattern, callback \\ nil) do
with_latest(context, fn context ->
remaining =
@@ -606,8 +824,41 @@ defmodule Playwright.BrowserContext do
end)
end
- # @spec unroute_all(t(), map()) :: :ok
- # def unroute_all(context, options \\ %{})
+ @doc """
+ Removes all routes registered with `route/4`.
+
+ ## Options
+
+ | key/name | type | description |
+ | ---------- | ---------- | ------------------------------------------------ |
+ | `:behavior`| `binary()` | How to handle in-flight requests. One of: |
+ | | | `"default"` - abort in-flight requests |
+ | | | `"wait"` - wait for in-flight handlers |
+ | | | `"ignoreErrors"` - ignore handler errors |
+
+ ## Returns
+
+ - `:ok`
+
+ ## Example
+
+ # Add a route
+ BrowserContext.route(context, "**/*", fn route -> Route.abort(route) end)
+
+ # Later, remove all routes
+ BrowserContext.unroute_all(context)
+
+ # Or wait for in-flight handlers to complete
+ BrowserContext.unroute_all(context, %{behavior: "wait"})
+ """
+ @spec unroute_all(t(), map()) :: :ok
+ def unroute_all(%BrowserContext{session: session, guid: guid}, options \\ %{}) do
+ params = if options[:behavior], do: %{behavior: options[:behavior]}, else: %{}
+ Channel.post(session, {:guid, guid}, :unroute_all, params)
+ Channel.patch(session, {:guid, guid}, %{routes: []})
+ Channel.post(session, {:guid, guid}, :set_network_interception_patterns, %{patterns: []})
+ :ok
+ end
# @spec wait_for_event(t(), binary(), map()) :: map()
# def wait_for_event(context, event, options \\ %{})
@@ -647,4 +898,25 @@ defmodule Playwright.BrowserContext do
end
end)
end
+
+ defp on_web_socket_route(context, %{webSocketRoute: ws_route}) do
+ # ws_route is already hydrated by Event.new
+ handle_web_socket_route(context, ws_route)
+ end
+
+ @doc false
+ # Called by Page when it has no matching handler
+ def handle_web_socket_route(context, ws_route) do
+ handler =
+ Enum.find(context.websocket_routes, fn h ->
+ Helpers.WebSocketRouteHandler.matches(h, ws_route.url)
+ end)
+
+ if handler do
+ Helpers.WebSocketRouteHandler.handle(handler, ws_route)
+ else
+ # No handler, connect through
+ Playwright.WebSocketRoute.connect_to_server(ws_route)
+ end
+ end
end
diff --git a/lib/playwright/clock.ex b/lib/playwright/clock.ex
new file mode 100644
index 00000000..0bc8e2e5
--- /dev/null
+++ b/lib/playwright/clock.ex
@@ -0,0 +1,194 @@
+defmodule Playwright.Clock do
+ @moduledoc """
+ Clock provides methods for controlling time in tests.
+
+ Accessed via BrowserContext. Call `Clock.install/2` before using other methods.
+
+ ## Example
+
+ context = Browser.new_context(browser)
+ Clock.install(context, %{time: "2024-01-01T00:00:00Z"})
+ Clock.fast_forward(context, "01:00:00") # 1 hour
+
+ ## Time Formats
+
+ Time can be specified as:
+ - Number: milliseconds since epoch (e.g., `1704067200000`)
+ - String: ISO 8601 format (e.g., `"2024-01-01T00:00:00Z"`)
+ - DateTime: Elixir DateTime struct
+
+ ## Ticks Formats
+
+ Ticks (duration) can be specified as:
+ - Number: milliseconds (e.g., `1000` = 1 second)
+ - String: `"mm:ss"` or `"hh:mm:ss"` format (e.g., `"01:30"` = 90 seconds)
+ """
+
+ alias Playwright.BrowserContext
+ alias Playwright.SDK.Channel
+
+ @doc """
+ Install fake timers. Must be called before using other clock methods.
+
+ ## Arguments
+
+ | key/name | type | description |
+ | -------- | ---- | ----------- |
+ | `context` | `BrowserContext.t()` | The browser context |
+ | `options` | `map()` | Optional settings |
+
+ ## Options
+
+ - `:time` - Initial time (number ms, ISO string, or DateTime)
+
+ ## Returns
+
+ - `:ok`
+ """
+ @spec install(BrowserContext.t(), map()) :: :ok
+ def install(%BrowserContext{session: session, guid: guid}, options \\ %{}) do
+ params = parse_time_param(options[:time])
+ Channel.post(session, {:guid, guid}, :clock_install, params)
+ :ok
+ end
+
+ @doc """
+ Advance time by jumping forward, firing due timers along the way.
+
+ ## Arguments
+
+ | key/name | type | description |
+ | -------- | ---- | ----------- |
+ | `context` | `BrowserContext.t()` | The browser context |
+ | `ticks` | `number() \\| String.t()` | Duration to advance (ms or "hh:mm:ss") |
+
+ ## Returns
+
+ - `:ok`
+ """
+ @spec fast_forward(BrowserContext.t(), number() | String.t()) :: :ok
+ def fast_forward(%BrowserContext{session: session, guid: guid}, ticks) do
+ params = parse_ticks_param(ticks)
+ Channel.post(session, {:guid, guid}, :clock_fast_forward, params)
+ :ok
+ end
+
+ @doc """
+ Pause the clock at the specified time.
+
+ ## Arguments
+
+ | key/name | type | description |
+ | -------- | ---- | ----------- |
+ | `context` | `BrowserContext.t()` | The browser context |
+ | `time` | `number() \\| String.t() \\| DateTime.t()` | Time to pause at |
+
+ ## Returns
+
+ - `:ok`
+ """
+ @spec pause_at(BrowserContext.t(), number() | String.t() | DateTime.t()) :: :ok
+ def pause_at(%BrowserContext{session: session, guid: guid}, time) do
+ params = parse_time_param(time)
+ Channel.post(session, {:guid, guid}, :clock_pause_at, params)
+ :ok
+ end
+
+ @doc """
+ Resume the clock after it was paused.
+
+ ## Arguments
+
+ | key/name | type | description |
+ | -------- | ---- | ----------- |
+ | `context` | `BrowserContext.t()` | The browser context |
+
+ ## Returns
+
+ - `:ok`
+ """
+ @spec resume(BrowserContext.t()) :: :ok
+ def resume(%BrowserContext{session: session, guid: guid}) do
+ Channel.post(session, {:guid, guid}, :clock_resume, %{})
+ :ok
+ end
+
+ @doc """
+ Run the clock for the specified time, firing all due timers.
+
+ Unlike `fast_forward/2`, this executes timers synchronously.
+
+ ## Arguments
+
+ | key/name | type | description |
+ | -------- | ---- | ----------- |
+ | `context` | `BrowserContext.t()` | The browser context |
+ | `ticks` | `number() \\| String.t()` | Duration to run (ms or "hh:mm:ss") |
+
+ ## Returns
+
+ - `:ok`
+ """
+ @spec run_for(BrowserContext.t(), number() | String.t()) :: :ok
+ def run_for(%BrowserContext{session: session, guid: guid}, ticks) do
+ params = parse_ticks_param(ticks)
+ Channel.post(session, {:guid, guid}, :clock_run_for, params)
+ :ok
+ end
+
+ @doc """
+ Set the clock to a fixed time. Time will not advance automatically.
+
+ Useful for testing time-dependent behavior at a specific moment.
+
+ ## Arguments
+
+ | key/name | type | description |
+ | -------- | ---- | ----------- |
+ | `context` | `BrowserContext.t()` | The browser context |
+ | `time` | `number() \\| String.t() \\| DateTime.t()` | Time to fix at |
+
+ ## Returns
+
+ - `:ok`
+ """
+ @spec set_fixed_time(BrowserContext.t(), number() | String.t() | DateTime.t()) :: :ok
+ def set_fixed_time(%BrowserContext{session: session, guid: guid}, time) do
+ params = parse_time_param(time)
+ Channel.post(session, {:guid, guid}, :clock_set_fixed_time, params)
+ :ok
+ end
+
+ @doc """
+ Set the system time but allow it to advance naturally.
+
+ Unlike `set_fixed_time/2`, time will continue to flow after being set.
+
+ ## Arguments
+
+ | key/name | type | description |
+ | -------- | ---- | ----------- |
+ | `context` | `BrowserContext.t()` | The browser context |
+ | `time` | `number() \\| String.t() \\| DateTime.t()` | Time to set |
+
+ ## Returns
+
+ - `:ok`
+ """
+ @spec set_system_time(BrowserContext.t(), number() | String.t() | DateTime.t()) :: :ok
+ def set_system_time(%BrowserContext{session: session, guid: guid}, time) do
+ params = parse_time_param(time)
+ Channel.post(session, {:guid, guid}, :clock_set_system_time, params)
+ :ok
+ end
+
+ # Private helpers
+
+ defp parse_time_param(nil), do: %{}
+ defp parse_time_param(time) when is_number(time), do: %{timeNumber: time}
+ defp parse_time_param(time) when is_binary(time), do: %{timeString: time}
+ defp parse_time_param(%DateTime{} = dt), do: %{timeNumber: DateTime.to_unix(dt, :millisecond)}
+
+ defp parse_ticks_param(ticks) when is_number(ticks), do: %{ticksNumber: ticks}
+ defp parse_ticks_param(ticks) when is_binary(ticks), do: %{ticksString: ticks}
+end
diff --git a/lib/playwright/coverage.ex b/lib/playwright/coverage.ex
new file mode 100644
index 00000000..aa4a7ba7
--- /dev/null
+++ b/lib/playwright/coverage.ex
@@ -0,0 +1,108 @@
+defmodule Playwright.Coverage do
+ @moduledoc """
+ Coverage module for collecting JavaScript and CSS code coverage.
+
+ This is Chromium-specific functionality.
+
+ ## Example
+
+ # Start JS coverage
+ Coverage.start_js_coverage(page)
+
+ # Navigate and interact
+ Page.goto(page, "https://example.com")
+
+ # Stop and get coverage data
+ entries = Coverage.stop_js_coverage(page)
+ """
+
+ alias Playwright.Page
+ alias Playwright.SDK.Channel
+
+ @type js_coverage_options :: %{
+ optional(:reset_on_navigation) => boolean(),
+ optional(:report_anonymous_scripts) => boolean()
+ }
+
+ @type css_coverage_options :: %{
+ optional(:reset_on_navigation) => boolean()
+ }
+
+ @doc """
+ Starts JavaScript coverage collection.
+
+ ## Options
+
+ - `:reset_on_navigation` - Whether to reset coverage on every navigation (default: true)
+ - `:report_anonymous_scripts` - Whether to report anonymous scripts (default: false)
+
+ ## Returns
+
+ - `:ok`
+ """
+ @spec start_js_coverage(Page.t(), js_coverage_options()) :: :ok
+ def start_js_coverage(%Page{session: session, guid: guid}, options \\ %{}) do
+ params = camelize_options(options)
+ Channel.post(session, {:guid, guid}, "startJSCoverage", params)
+ :ok
+ end
+
+ @doc """
+ Stops JavaScript coverage collection and returns the coverage data.
+
+ ## Returns
+
+ A list of coverage entries, each containing:
+ - `:url` - Script URL
+ - `:scriptId` - Script ID
+ - `:source` - Script source (optional)
+ - `:functions` - List of function coverage data
+ """
+ @spec stop_js_coverage(Page.t()) :: [map()]
+ def stop_js_coverage(%Page{session: session, guid: guid}) do
+ Channel.post(session, {:guid, guid}, "stopJSCoverage")
+ end
+
+ @doc """
+ Starts CSS coverage collection.
+
+ ## Options
+
+ - `:reset_on_navigation` - Whether to reset coverage on every navigation (default: true)
+
+ ## Returns
+
+ - `:ok`
+ """
+ @spec start_css_coverage(Page.t(), css_coverage_options()) :: :ok
+ def start_css_coverage(%Page{session: session, guid: guid}, options \\ %{}) do
+ params = camelize_options(options)
+ Channel.post(session, {:guid, guid}, "startCSSCoverage", params)
+ :ok
+ end
+
+ @doc """
+ Stops CSS coverage collection and returns the coverage data.
+
+ ## Returns
+
+ A list of coverage entries, each containing:
+ - `:url` - Stylesheet URL
+ - `:text` - Stylesheet text (optional)
+ - `:ranges` - List of used ranges
+ """
+ @spec stop_css_coverage(Page.t()) :: [map()]
+ def stop_css_coverage(%Page{session: session, guid: guid}) do
+ Channel.post(session, {:guid, guid}, "stopCSSCoverage")
+ end
+
+ defp camelize_options(options) do
+ options
+ |> Enum.map(fn
+ {:reset_on_navigation, v} -> {:resetOnNavigation, v}
+ {:report_anonymous_scripts, v} -> {:reportAnonymousScripts, v}
+ other -> other
+ end)
+ |> Map.new()
+ end
+end
diff --git a/lib/playwright/fetch_request.ex b/lib/playwright/debugger.ex
similarity index 52%
rename from lib/playwright/fetch_request.ex
rename to lib/playwright/debugger.ex
index 655a4f9b..b971cbb1 100644
--- a/lib/playwright/fetch_request.ex
+++ b/lib/playwright/debugger.ex
@@ -1,6 +1,4 @@
-defmodule Playwright.FetchRequest do
+defmodule Playwright.Debugger do
@moduledoc false
use Playwright.SDK.ChannelOwner
-
- # obsolete?
end
diff --git a/lib/playwright/dialog.ex b/lib/playwright/dialog.ex
index d19b916a..017c55ef 100644
--- a/lib/playwright/dialog.ex
+++ b/lib/playwright/dialog.ex
@@ -1,22 +1,107 @@
defmodule Playwright.Dialog do
- @moduledoc false
+ @moduledoc """
+ `Playwright.Dialog` instances are dispatched by page and handled via
+ `Playwright.Page.on/3` for the `:dialog` event type.
+
+ ## Dialog Types
+
+ - `"alert"` - Alert dialog with OK button
+ - `"confirm"` - Confirm dialog with OK and Cancel buttons
+ - `"prompt"` - Prompt dialog with text input
+ - `"beforeunload"` - Before unload dialog
+
+ ## Example
+
+ Page.on(page, :dialog, fn dialog ->
+ IO.puts("Dialog message: \#{Dialog.message(dialog)}")
+ Dialog.accept(dialog)
+ end)
+
+ # For prompts with input:
+ Page.on(page, :dialog, fn dialog ->
+ Dialog.accept(dialog, "my input")
+ end)
+ """
use Playwright.SDK.ChannelOwner
+ alias Playwright.SDK.{Channel, ChannelOwner}
+
+ @property :default_value
+ @property :message
+ @property :dialog_type
+
+ # callbacks
+ # ---------------------------------------------------------------------------
+
+ @impl ChannelOwner
+ def init(dialog, initializer) do
+ {:ok,
+ %{
+ dialog
+ | default_value: initializer.default_value,
+ message: initializer.message,
+ dialog_type: initializer.type
+ }}
+ end
+
+ @doc """
+ Get the dialog type.
+
+ Returns one of: `"alert"`, `"confirm"`, `"prompt"`, `"beforeunload"`.
+ """
+ @spec type(t()) :: binary()
+ def type(dialog) do
+ dialog_type(dialog)
+ end
+
+ # API
+ # ---------------------------------------------------------------------------
+
+ @doc """
+ Accept the dialog.
+
+ For prompt dialogs, optionally provide text input.
+
+ ## Arguments
+
+ - `prompt_text` - Text to enter in prompt dialog (optional)
+
+ ## Examples
- # @spec accept(Dialog.t(), binary()) :: :ok
- # def accept(dialog, prompt \\ "")
+ Dialog.accept(dialog)
+ Dialog.accept(dialog, "my input")
+ """
+ @spec accept(t(), binary() | nil) :: :ok
+ def accept(dialog, prompt_text \\ nil)
- # @spec default_value(Dialog.t()) :: binary()
- # def default_value(dialog)
+ def accept(%__MODULE__{session: session} = dialog, nil) do
+ Channel.post(session, {:guid, dialog.guid}, :accept, %{})
+ :ok
+ end
- # @spec dismiss(Dialog.t()) :: :ok
- # def dismiss(dialog)
+ def accept(%__MODULE__{session: session} = dialog, prompt_text) when is_binary(prompt_text) do
+ Channel.post(session, {:guid, dialog.guid}, :accept, %{promptText: prompt_text})
+ :ok
+ end
- # @spec message(Dialog.t()) :: binary()
- # def message(dialog)
+ @doc """
+ Dismiss the dialog (click Cancel or close).
+ """
+ @spec dismiss(t()) :: :ok
+ def dismiss(%__MODULE__{session: session} = dialog) do
+ Channel.post(session, {:guid, dialog.guid}, :dismiss, %{})
+ :ok
+ end
- # @spec page(Dialog.t()) :: nil | Page.t()
- # def page(dialog)
+ @doc """
+ Get the page that initiated the dialog.
- # @spec type(Dialog.t()) :: binary()
- # def type(dialog)
+ Returns `nil` if dialog was triggered by a different context.
+ """
+ @spec page(t()) :: Playwright.Page.t() | nil
+ def page(%__MODULE__{session: session, parent: parent}) do
+ case parent do
+ %{guid: guid} -> Channel.find(session, {:guid, guid})
+ _ -> nil
+ end
+ end
end
diff --git a/lib/playwright/disposable.ex b/lib/playwright/disposable.ex
new file mode 100644
index 00000000..9571400c
--- /dev/null
+++ b/lib/playwright/disposable.ex
@@ -0,0 +1,4 @@
+defmodule Playwright.Disposable do
+ @moduledoc false
+ use Playwright.SDK.ChannelOwner
+end
diff --git a/lib/playwright/element_handle.ex b/lib/playwright/element_handle.ex
index dd01580d..36bf624b 100644
--- a/lib/playwright/element_handle.ex
+++ b/lib/playwright/element_handle.ex
@@ -16,8 +16,8 @@ defmodule Playwright.ElementHandle do
:ok = ElementHandle.click(handle)
`ElementHandle` prevents DOM elements from garbage collection unless the
- handle is disposed with `Playwright.JSHandle.dispose/1`. `ElementHandles`
- are auto-disposed when their origin frame is navigated.
+ handle is disposed. `ElementHandles` are auto-disposed when their origin
+ frame is navigated.
An `ElementHandle` instance can be used as an argument in
`Playwright.Page.eval_on_selector/5` and `Playwright.Page.evaluate/3`.
@@ -138,6 +138,10 @@ defmodule Playwright.ElementHandle do
When all steps combined have not finished during the specified `:timeout`,
this function raises a `TimeoutError`. Passing zero (`0`) for timeout
disables this.
+
+ ## Options
+
+ - `:steps` - Number of intermediate mouse move events. `(default: 1)`
"""
@spec click(t(), options()) :: :ok
def click(%ElementHandle{session: session} = handle, options \\ %{}) do
@@ -306,9 +310,68 @@ defmodule Playwright.ElementHandle do
# @spec set_checked(ElementHandle.t(), boolean(), options()) :: :ok
# def set_checked(handle, checked, options \\ %{})
- # ⚠️ DISCOURAGED
- # @spec set_input_files(ElementHandle.t(), file_list(), options()) :: :ok
- # def set_input_files(handle, files, options \\ %{})
+ @doc """
+ Sets the value of file input elements.
+
+ ## Arguments
+
+ - `files` - File path(s) or file payload(s)
+ - `options` - Optional settings
+
+ ## File payload format
+
+ %{name: "file.txt", mimeType: "text/plain", buffer: "base64content"}
+ """
+ @spec set_input_files(t(), binary() | [binary()] | map() | [map()], map()) :: :ok | {:error, term()}
+ def set_input_files(%ElementHandle{session: session} = handle, files, options \\ %{}) do
+ file_payloads = prepare_files(files)
+ timeout = Map.get(options, :timeout, 30_000)
+ params = %{payloads: file_payloads, timeout: timeout}
+
+ case Channel.post(session, {:guid, handle.guid}, :set_input_files, params) do
+ {:ok, _} -> :ok
+ :ok -> :ok
+ nil -> :ok
+ {:error, _} = error -> error
+ end
+ end
+
+ defp prepare_files(files) when is_binary(files), do: prepare_files([files])
+
+ defp prepare_files(files) when is_list(files) do
+ Enum.map(files, fn
+ %{name: _, buffer: _} = payload ->
+ payload
+
+ path when is_binary(path) ->
+ %{
+ name: Path.basename(path),
+ buffer: Base.encode64(File.read!(path)),
+ mimeType: mime_type(path)
+ }
+ end)
+ end
+
+ defp prepare_files(%{} = file), do: [file]
+
+ @mime_types %{
+ ".txt" => "text/plain",
+ ".html" => "text/html",
+ ".css" => "text/css",
+ ".js" => "application/javascript",
+ ".json" => "application/json",
+ ".png" => "image/png",
+ ".jpg" => "image/jpeg",
+ ".jpeg" => "image/jpeg",
+ ".gif" => "image/gif",
+ ".pdf" => "application/pdf",
+ ".xml" => "application/xml",
+ ".zip" => "application/zip"
+ }
+
+ defp mime_type(path) do
+ Map.get(@mime_types, Path.extname(path), "application/octet-stream")
+ end
# ⚠️ DISCOURAGED
# @spec tap(ElementHandle.t(), options()) :: :ok
diff --git a/lib/playwright/frame.ex b/lib/playwright/frame.ex
index e635054c..d5fc4d27 100644
--- a/lib/playwright/frame.ex
+++ b/lib/playwright/frame.ex
@@ -1,8 +1,7 @@
defmodule Playwright.Frame do
@moduledoc """
At any point of time, `Playwright.Page` exposes its current frame tree via
- the `Playwright.Page.main_frame/1` and `Playwright.Frame.child_frames/1`
- functions.
+ the page's main frame and frame child frames functions.
A `Frame` instance lifecycle is governed by three events, dispatched on the
`Playwright.Page` resource:
@@ -19,6 +18,8 @@ defmodule Playwright.Frame do
alias Playwright.SDK.{ChannelOwner, Helpers}
@property :load_states
+ @property :name
+ @property :parent_frame
@property :url
@type evaluation_argument :: any()
@@ -45,7 +46,7 @@ defmodule Playwright.Frame do
end)
Channel.bind(session, {:guid, frame.guid}, :navigated, fn event ->
- {:patch, %{event.target | url: event.params.url}}
+ {:patch, %{event.target | url: event.params.url, name: event.params.name || ""}}
end)
{:ok, frame}
@@ -56,11 +57,74 @@ defmodule Playwright.Frame do
# ---
- # @spec add_script_tag(Frame.t(), options()) :: ElementHandle.t()
- # def add_script_tag(frame, options \\ %{})
+ @doc """
+ Adds a `")
+ messages = Page.console_messages(page)
+ """
+ @spec console_messages(t()) :: [map()]
+ def console_messages(%Page{session: session, guid: guid}) do
+ Channel.post(session, {:guid, guid}, :console_messages)
+ end
+
@doc """
A shortcut for the main frame's `Playwright.Frame.dblclick/3`.
"""
@@ -266,6 +495,28 @@ defmodule Playwright.Page do
main_frame(page) |> Frame.dispatch_event(selector, type, event_init, options)
end
+ @doc """
+ Drags the source element towards the target element and drops it.
+
+ ## Options
+
+ - `:force` - Bypass actionability checks. Defaults to `false`.
+ - `:trial` - Perform the drag without dropping. Useful for testing.
+ - `:steps` - Number of intermediate mouse positions. More steps = smoother drag.
+ Helpful for JS libraries (like SortableJS) that rely on mousemove events.
+ - `:source_position` - `%{x: n, y: n}` - Click position relative to source.
+ - `:target_position` - `%{x: n, y: n}` - Drop position relative to target.
+ - `:strict` - Throw if selector matches multiple elements.
+ - `:timeout` - Maximum time in milliseconds.
+
+ ## Example
+
+ # For libraries like SortableJS that need mouse events:
+ Page.drag_and_drop(page, "#item1", "#item2", %{
+ force: true,
+ steps: 20
+ })
+ """
@spec drag_and_drop(Page.t(), binary(), binary(), options()) :: Page.t()
def drag_and_drop(page, source, target, options \\ %{}) do
with_latest(page, fn page ->
@@ -275,8 +526,50 @@ defmodule Playwright.Page do
# ---
- # @spec emulate_media(t(), options()) :: :ok
- # def emulate_media(page, options \\ %{})
+ @doc """
+ Emulates CSS media features on the page.
+
+ This method changes the CSS media type and media features for the page.
+ Pass `nil` for any option to reset it to the default value.
+
+ ## Arguments
+
+ | key/name | type | description |
+ | ----------------- | ------------------------------------------------- | ---------------------------- |
+ | `:media` | `"screen"` \\| `"print"` \\| `nil` | Media type to emulate. |
+ | `:color_scheme` | `"dark"` \\| `"light"` \\| `"no-preference"` \\| `nil` | Color scheme to emulate. |
+ | `:reduced_motion` | `"reduce"` \\| `"no-preference"` \\| `nil` | Reduced motion preference. |
+ | `:forced_colors` | `"active"` \\| `"none"` \\| `nil` | Forced colors mode. |
+ | `:contrast` | `"more"` \\| `"no-preference"` \\| `nil` | Contrast preference. |
+
+ ## Returns
+
+ - `:ok`
+
+ ## Example
+
+ # Emulate dark mode
+ Page.emulate_media(page, %{color_scheme: "dark"})
+
+ # Emulate print media
+ Page.emulate_media(page, %{media: "print"})
+
+ # Reset to defaults
+ Page.emulate_media(page, %{color_scheme: nil, media: nil})
+ """
+ @spec emulate_media(t(), map()) :: :ok
+ def emulate_media(%Page{session: session, guid: guid}, options \\ %{}) do
+ params = %{
+ media: normalize_media_option(options[:media]),
+ colorScheme: normalize_media_option(options[:color_scheme]),
+ reducedMotion: normalize_media_option(options[:reduced_motion]),
+ forcedColors: normalize_media_option(options[:forced_colors]),
+ contrast: normalize_media_option(options[:contrast])
+ }
+
+ Channel.post(session, {:guid, guid}, :emulate_media, params)
+ :ok
+ end
# ---
@@ -286,6 +579,35 @@ defmodule Playwright.Page do
|> Frame.eval_on_selector(selector, expression, arg, options)
end
+ @doc """
+ Evaluates JavaScript expression on all elements matching selector.
+
+ The expression is executed in the browser context. If the expression returns
+ a non-serializable value, the function returns `nil`.
+
+ ## Arguments
+
+ | key/name | type | description |
+ | ------------ | ---------- | ---------------------------------------- |
+ | `selector` | `binary()` | CSS selector to match elements. |
+ | `expression` | `binary()` | JavaScript expression to evaluate. |
+ | `arg` | `term()` | Optional argument to pass to expression. |
+
+ ## Returns
+
+ - Result of the JavaScript expression.
+
+ ## Example
+
+ # Get all link hrefs
+ hrefs = Page.eval_on_selector_all(page, "a", "elements => elements.map(e => e.href)")
+ """
+ @spec eval_on_selector_all(t(), binary(), binary(), term()) :: term()
+ def eval_on_selector_all(%Page{} = page, selector, expression, arg \\ nil) do
+ main_frame(page)
+ |> Frame.eval_on_selector_all(selector, expression, arg)
+ end
+
@spec evaluate(t(), expression(), any()) :: serializable()
def evaluate(page, expression, arg \\ nil)
@@ -313,13 +635,96 @@ defmodule Playwright.Page do
# ---
- # @spec expect_request(t(), binary() | function(), options()) :: :ok
- # def expect_request(page, url_or_predicate, options \\ %{})
- # ...defdelegate wait_for_request
+ @doc """
+ Waits for a matching request and returns it.
+
+ The request can be matched by:
+ - A URL glob pattern (e.g., `"**/api/users"`)
+ - A `Regex` (e.g., `~r/\\/api\\/users$/`)
+ - A function that receives the `Request` and returns a boolean
+
+ ## Options
+
+ - `:timeout` - Maximum time in milliseconds. Defaults to 30000 (30 seconds).
+
+ ## Examples
+
+ # Wait for a request matching a glob pattern
+ request = Page.wait_for_request(page, "**/api/users", %{}, fn ->
+ Page.click(page, "button#submit")
+ end)
+
+ # Wait for a request matching a regex
+ request = Page.wait_for_request(page, ~r/\\/api\\/users$/, %{}, fn ->
+ Page.click(page, "button")
+ end)
+
+ # Wait for a request with custom predicate
+ request = Page.wait_for_request(page, fn req ->
+ req.method == "POST" and String.contains?(req.url, "/api")
+ end, %{}, fn ->
+ Page.click(page, "button")
+ end)
+ """
+ @spec wait_for_request(t(), binary() | Regex.t() | function(), options(), function() | nil) ::
+ Request.t() | {:error, term()}
+ def wait_for_request(%Page{session: session} = page, url_or_predicate, options \\ %{}, trigger \\ nil) do
+ predicate = build_request_predicate(session, url_or_predicate)
+ timeout = Map.get(options, :timeout, 30_000)
+
+ Channel.post(session, {:guid, page.guid}, :update_subscription, %{event: "request", enabled: true})
- # @spec expect_response(t(), binary() | function(), options()) :: :ok
- # def expect_response(page, url_or_predicate, options \\ %{})
- # ...defdelegate wait_for_response
+ case Channel.wait(session, {:guid, context(page).guid}, :request, %{timeout: timeout, predicate: predicate}, trigger) do
+ %Playwright.SDK.Channel.Event{params: %{request: %{guid: guid}}} ->
+ Channel.find(session, {:guid, guid})
+
+ {:error, _} = error ->
+ error
+ end
+ end
+
+ @doc """
+ Waits for a matching response and returns it.
+
+ The response can be matched by:
+ - A URL glob pattern (e.g., `"**/api/users"`)
+ - A `Regex` (e.g., `~r/\\/api\\/users$/`)
+ - A function that receives the `Response` and returns a boolean
+
+ ## Options
+
+ - `:timeout` - Maximum time in milliseconds. Defaults to 30000 (30 seconds).
+
+ ## Examples
+
+ # Wait for a response matching a glob pattern
+ response = Page.wait_for_response(page, "**/api/users", %{}, fn ->
+ Page.click(page, "button#submit")
+ end)
+
+ # Wait for a response with custom predicate
+ response = Page.wait_for_response(page, fn resp ->
+ resp.status == 200 and String.contains?(resp.url, "/api")
+ end, %{}, fn ->
+ Page.click(page, "button")
+ end)
+ """
+ @spec wait_for_response(t(), binary() | Regex.t() | function(), options(), function() | nil) ::
+ Response.t() | {:error, term()}
+ def wait_for_response(%Page{session: session} = page, url_or_predicate, options \\ %{}, trigger \\ nil) do
+ predicate = build_response_predicate(session, url_or_predicate)
+ timeout = Map.get(options, :timeout, 30_000)
+
+ Channel.post(session, {:guid, page.guid}, :update_subscription, %{event: "response", enabled: true})
+
+ case Channel.wait(session, {:guid, context(page).guid}, :response, %{timeout: timeout, predicate: predicate}, trigger) do
+ %Playwright.SDK.Channel.Event{params: %{response: %{guid: guid}}} ->
+ Channel.find(session, {:guid, guid})
+
+ {:error, _} = error ->
+ error
+ end
+ end
@doc """
Adds a function called `param:name` on the `window` object of every frame in
@@ -380,8 +785,53 @@ defmodule Playwright.Page do
# ---
- # @spec frame(t(), binary()) :: Frame.t() | nil
- # def frame(page, selector)
+ @doc """
+ Returns a frame matching the specified criteria.
+
+ ## Arguments
+
+ | key/name | type | description |
+ | -------- | ---- | ----------- |
+ | `selector` | `String.t()` or `map()` | Frame name or criteria map with `:name` or `:url` |
+
+ ## Examples
+
+ # By name (string shorthand)
+ Page.frame(page, "frame-name")
+
+ # By name (explicit)
+ Page.frame(page, %{name: "frame-name"})
+
+ # By URL - glob pattern
+ Page.frame(page, %{url: "**/frame.html"})
+
+ # By URL - regex
+ Page.frame(page, %{url: ~r/.*frame.*/})
+
+ # By URL - predicate function
+ Page.frame(page, %{url: fn url -> String.contains?(url, "frame") end})
+
+ ## Returns
+
+ - `Playwright.Frame.t()` - The matching frame
+ - `nil` - If no frame matches
+ """
+ @spec frame(t(), String.t() | map()) :: Frame.t() | nil
+ def frame(%Page{} = page, name) when is_binary(name) do
+ frame(page, %{name: name})
+ end
+
+ def frame(%Page{} = page, %{name: name}) when is_binary(name) do
+ frames(page)
+ |> Enum.find(fn f -> Frame.name(f) == name end)
+ end
+
+ def frame(%Page{} = page, %{url: url_pattern}) do
+ matcher = Helpers.URLMatcher.new(url_pattern)
+
+ frames(page)
+ |> Enum.find(fn f -> Helpers.URLMatcher.matches(matcher, Frame.url(f)) end)
+ end
# ---
@@ -404,20 +854,87 @@ defmodule Playwright.Page do
# ---
- # @spec get_by_alt_text(Page.t(), binary(), options()) :: Playwright.Locator.t() | nil
- # def get_by_alt_text(page, text, options \\ %{})
+ @doc """
+ Allows locating elements by their alt text.
+
+ ## Arguments
+
+ | key/name | type | | description |
+ | ---------- | ------ | ---------- | ----------- |
+ | `text` | param | `binary()` | Alt text to locate. |
+ | `:exact` | option | `boolean()`| Whether to find an exact match. Default to false. |
+ """
+ @spec get_by_alt_text(t(), binary(), %{optional(:exact) => boolean()}) :: Playwright.Locator.t()
+ def get_by_alt_text(page, text, options \\ %{}) when is_binary(text) do
+ main_frame(page) |> Frame.get_by_alt_text(text, options)
+ end
+
+ @doc """
+ Allows locating elements by their associated label text.
+
+ ## Arguments
+
+ | key/name | type | | description |
+ | ---------- | ------ | ---------- | ----------- |
+ | `text` | param | `binary()` | Label text to locate. |
+ | `:exact` | option | `boolean()`| Whether to find an exact match. Default to false. |
+ """
+ @spec get_by_label(t(), binary(), %{optional(:exact) => boolean()}) :: Playwright.Locator.t()
+ def get_by_label(page, text, options \\ %{}) when is_binary(text) do
+ main_frame(page) |> Frame.get_by_label(text, options)
+ end
+
+ @doc """
+ Allows locating input elements by their placeholder text.
+
+ ## Arguments
+
+ | key/name | type | | description |
+ | ---------- | ------ | ---------- | ----------- |
+ | `text` | param | `binary()` | Placeholder text to locate. |
+ | `:exact` | option | `boolean()`| Whether to find an exact match. Default to false. |
+ """
+ @spec get_by_placeholder(t(), binary(), %{optional(:exact) => boolean()}) :: Playwright.Locator.t()
+ def get_by_placeholder(page, text, options \\ %{}) when is_binary(text) do
+ main_frame(page) |> Frame.get_by_placeholder(text, options)
+ end
+
+ @doc """
+ Allows locating elements by ARIA role.
+
+ ## Arguments
- # @spec get_by_label(Page.t(), binary(), options()) :: Playwright.Locator.t() | nil
- # def get_by_label(page, text, options \\ %{})
+ | key/name | type | | description |
+ | ---------------- | ------ | ---------- | ----------- |
+ | `role` | param | `binary()` | ARIA role (e.g., "button", "heading"). |
+ | `:name` | option | `binary()` | Filter by accessible name. |
+ | `:exact` | option | `boolean()`| Exact name match. Default to false. |
+ | `:checked` | option | `boolean()`| Filter by checked state. |
+ | `:disabled` | option | `boolean()`| Filter by disabled state. |
+ | `:expanded` | option | `boolean()`| Filter by expanded state. |
+ | `:include_hidden`| option | `boolean()`| Include hidden elements. |
+ | `:level` | option | `integer()`| Heading level (1-6). |
+ | `:pressed` | option | `boolean()`| Filter by pressed state. |
+ | `:selected` | option | `boolean()`| Filter by selected state. |
+ """
+ @spec get_by_role(t(), binary(), map()) :: Playwright.Locator.t()
+ def get_by_role(page, role, options \\ %{}) when is_binary(role) do
+ main_frame(page) |> Frame.get_by_role(role, options)
+ end
- # @spec get_by_placeholder(Page.t(), binary(), options()) :: Playwright.Locator.t() | nil
- # def get_by_placeholder(page, text, options \\ %{})
+ @doc """
+ Allows locating elements by their test id attribute (data-testid by default).
- # @spec get_by_role(Page.t(), binary(), options()) :: Playwright.Locator.t() | nil
- # def get_by_role(page, text, options \\ %{})
+ ## Arguments
- # @spec get_by_test_id(Page.t(), binary(), options()) :: Playwright.Locator.t() | nil
- # def get_by_test_id(page, text, options \\ %{})
+ | key/name | type | | description |
+ | ---------- | ------ | ---------- | ----------- |
+ | `test_id` | param | `binary()` | The test id to locate. |
+ """
+ @spec get_by_test_id(t(), binary()) :: Playwright.Locator.t()
+ def get_by_test_id(page, test_id) when is_binary(test_id) do
+ main_frame(page) |> Frame.get_by_test_id(test_id)
+ end
@doc """
Allows locating elements that contain given text.
@@ -434,33 +951,87 @@ defmodule Playwright.Page do
main_frame(page) |> Frame.get_by_text(text, options)
end
- # @spec get_by_title(Page.t(), binary(), options()) :: Playwright.Locator.t() | nil
- # def get_by_title(page, text, options \\ %{})
-
- # @spec go_back(t(), options()) :: Response.t() | nil
- # def go_back(page, options \\ %{})
-
- # @spec go_forward(t(), options()) :: Response.t() | nil
- # def go_forward(page, options \\ %{})
+ @doc """
+ Allows locating elements by their title attribute.
- # ---
+ ## Arguments
- @spec goto(t(), binary(), options()) :: Response.t() | nil | {:error, term()}
- def goto(%Page{} = page, url, options \\ %{}) do
- main_frame(page) |> Frame.goto(url, options)
+ | key/name | type | | description |
+ | ---------- | ------ | ---------- | ----------- |
+ | `text` | param | `binary()` | Title text to locate. |
+ | `:exact` | option | `boolean()`| Whether to find an exact match. Default to false. |
+ """
+ @spec get_by_title(t(), binary(), %{optional(:exact) => boolean()}) :: Playwright.Locator.t()
+ def get_by_title(page, text, options \\ %{}) when is_binary(text) do
+ main_frame(page) |> Frame.get_by_title(text, options)
end
@doc """
- A shortcut for the main frame's `Playwright.Frame.hover/2`.
- """
- def hover(%Page{} = page, selector) do
- main_frame(page) |> Frame.hover(selector)
- end
+ Navigate to the previous page in history.
- # ---
+ ## Options
- # @spec is_closed(t()) :: boolean()
- # def is_closed(page)
+ - `:timeout` - Maximum time in milliseconds. Defaults to 30000 (30 seconds).
+ - `:wait_until` - When to consider navigation succeeded. Defaults to `"load"`.
+ - `"load"` - wait for the load event
+ - `"domcontentloaded"` - wait for DOMContentLoaded event
+ - `"networkidle"` - wait until no network connections for 500ms
+ - `"commit"` - wait for network response and document started loading
+
+ ## Returns
+
+ - `Playwright.Response.t()` - Response of the main resource
+ - `nil` - if navigation did not happen (e.g., no previous page)
+ """
+ @spec go_back(t(), options()) :: Response.t() | nil
+ def go_back(%Page{session: session} = page, options \\ %{}) do
+ case Channel.post(session, {:guid, page.guid}, :goBack, options) do
+ %{response: nil} -> nil
+ %{response: %{guid: _} = response} -> Channel.find(session, {:guid, response.guid})
+ other -> other
+ end
+ end
+
+ @doc """
+ Navigate to the next page in history.
+
+ ## Options
+
+ - `:timeout` - Maximum time in milliseconds. Defaults to 30000 (30 seconds).
+ - `:wait_until` - When to consider navigation succeeded. Defaults to `"load"`.
+
+ ## Returns
+
+ - `Playwright.Response.t()` - Response of the main resource
+ - `nil` - if navigation did not happen (e.g., no next page)
+ """
+ @spec go_forward(t(), options()) :: Response.t() | nil
+ def go_forward(%Page{session: session} = page, options \\ %{}) do
+ case Channel.post(session, {:guid, page.guid}, :goForward, options) do
+ %{response: nil} -> nil
+ %{response: %{guid: _} = response} -> Channel.find(session, {:guid, response.guid})
+ other -> other
+ end
+ end
+
+ # ---
+
+ @spec goto(t(), binary(), options()) :: Response.t() | nil | {:error, term()}
+ def goto(%Page{} = page, url, options \\ %{}) do
+ main_frame(page) |> Frame.goto(url, options)
+ end
+
+ @doc """
+ A shortcut for the main frame's `Playwright.Frame.hover/2`.
+ """
+ def hover(%Page{} = page, selector) do
+ main_frame(page) |> Frame.hover(selector)
+ end
+
+ # ---
+
+ # @spec is_closed(t()) :: boolean()
+ # def is_closed(page)
# ---
@@ -469,14 +1040,94 @@ defmodule Playwright.Page do
Playwright.Locator.new(page, selector)
end
+ @doc """
+ Returns a FrameLocator for a frame on the page.
+
+ When working with iframes, you can create a frame locator that will enter the iframe
+ and allow locating elements in that iframe.
+
+ ## Example
+
+ page
+ |> Page.frame_locator("#my-frame")
+ |> FrameLocator.get_by_role("button", name: "Submit")
+ |> Locator.click()
+ """
+ @spec frame_locator(t(), selector()) :: Playwright.Page.FrameLocator.t()
+ def frame_locator(%Page{} = page, selector) do
+ Playwright.Page.FrameLocator.new(main_frame(page), selector)
+ end
+
# @spec main_frame(t()) :: Frame.t()
# def main_frame(page)
- # @spec opener(t()) :: Frame.t() | nil
- # def opener(page)
+ @doc """
+ Returns the page that opened this popup, or nil.
+
+ Popup pages are opened by `window.open()` from JavaScript or by clicking
+ a link with `target="_blank"`.
+
+ ## Returns
+
+ - `Page.t()` - The opener page
+ - `nil` - If this page was not opened as a popup
+ """
+ @spec opener(t()) :: t() | nil
+ def opener(%Page{session: session, initializer: %{opener: %{guid: guid}}}) do
+ Channel.find(session, {:guid, guid})
+ end
+
+ def opener(%Page{}), do: nil
+
+ @doc """
+ Returns up to 200 last page errors from this page.
+
+ Page errors are uncaught exceptions thrown in the page's JavaScript.
- # @spec pause(t()) :: :ok
- # def pause(page)
+ ## Returns
+
+ - `[map()]` - List of error data
+
+ ## Example
+
+ Page.goto(page, "data:text/html,")
+ errors = Page.page_errors(page)
+ """
+ @spec page_errors(t()) :: [map()]
+ def page_errors(%Page{session: session, guid: guid}) do
+ Channel.post(session, {:guid, guid}, :page_errors)
+ end
+
+ @doc """
+ Returns up to 100 last network requests from this page.
+
+ ## Returns
+
+ - `[Playwright.Request.t()]`
+ """
+ @spec requests(t()) :: [Playwright.Request.t()]
+ def requests(%Page{session: session, guid: guid}) do
+ Channel.post(session, {:guid, guid}, :requests)
+ end
+
+ @doc """
+ Pauses script execution and opens the Playwright Inspector.
+
+ This is useful for debugging. The page will be paused until the user
+ resumes from the Inspector or closes it.
+
+ Note: This method is primarily useful in headed mode where the Inspector
+ window can be displayed.
+ """
+ @spec pause(t()) :: :ok | {:error, term()}
+ def pause(%Page{} = page) do
+ ctx = context(page)
+
+ case Channel.post(ctx.session, {:guid, ctx.guid}, :pause, %{}) do
+ {:error, _} = error -> error
+ _ -> :ok
+ end
+ end
# ---
@@ -499,24 +1150,34 @@ defmodule Playwright.Page do
# - worker
def on(%Page{} = page, event, callback) when is_binary(event) do
- on(page, String.to_atom(event), callback)
+ atom = String.to_atom(event)
+
+ if atom in @valid_events do
+ on(page, atom, callback)
+ else
+ {:error, %ArgumentError{message: "Invalid Page event: #{event}"}}
+ end
end
- # NOTE: These events will be recv'd from Playwright server with the parent
- # BrowserContext as the context/bound :guid. So, we need to add our handlers
- # there, on that (BrowserContext) parent.
+ # NOTE: These events are recv'd from Playwright server via the parent
+ # BrowserContext channel. So, we need to add our handlers there.
#
# For :update_subscription, :event is one of:
- # (console|dialog|fileChooser|request|response|requestFinished|requestFailed)
+ # (console|dialog|request|response|requestFinished|requestFailed)
def on(%Page{session: session} = page, event, callback)
- when event in [:console, :dialog, :file_chooser, :request, :response, :request_finished, :request_failed] do
- # HACK!
+ when event in [:console, :dialog, :request, :response, :request_finished, :request_failed] do
e = Atom.to_string(event) |> Recase.to_camel()
Channel.post(session, {:guid, page.guid}, :update_subscription, %{event: e, enabled: true})
Channel.bind(session, {:guid, context(page).guid}, event, callback)
end
+ # NOTE: FileChooser events are recv'd directly on the Page channel.
+ def on(%Page{session: session} = page, :file_chooser, callback) do
+ Channel.post(session, {:guid, page.guid}, :update_subscription, %{event: "fileChooser", enabled: true})
+ Channel.bind(session, {:guid, page.guid}, :file_chooser, callback)
+ end
+
def on(%Page{session: session} = page, event, callback) when is_atom(event) do
Channel.bind(session, {:guid, page.guid}, event, callback)
end
@@ -563,7 +1224,7 @@ defmodule Playwright.Page do
| key/name | type | | description |
| ------------- | ------ | ---------- | ----------- |
- | `:timeout` | option | `number()` | Maximum time in milliseconds. Pass `0` to disable timeout. The default value can be changed via `Playwright.BrowserContext.set_default_timeout/2` or `Playwright.Page.set_default_timeout/2`. `(default: 30 seconds)` |
+ | `:timeout` | option | `number()` | Maximum time in milliseconds. Pass `0` to disable timeout. The default value can be changed via BrowserContext or Page timeout settings. `(default: 30 seconds)` |
| `:wait_until` | option | `binary()` | "load", "domcontentloaded", "networkidle", or "commit". When to consider the operation as having succeeded. `(default: "load")` |
## On Wait Events
@@ -580,18 +1241,66 @@ defmodule Playwright.Page do
# ---
- # @spec remove_locator_handler(t(), Locator.t()) :: :ok
- # def remove_locator_handler(page, locator)
+ @doc """
+ Removes a previously registered locator handler.
+
+ Removes all handlers registered for the given locator (matched by selector).
+
+ ## Arguments
+
+ | key/name | type | description |
+ | --------- | ------------- | ---------------------------- |
+ | `locator` | `Locator.t()` | The locator to stop handling |
+
+ ## Returns
+
+ - `:ok`
+
+ ## Example
+
+ dialog = Page.locator(page, "#cookie-dialog")
+ Page.add_locator_handler(page, dialog, fn _loc -> ... end)
+
+ # Later, remove the handler
+ Page.remove_locator_handler(page, dialog)
+ """
+ @spec remove_locator_handler(t(), Playwright.Locator.t()) :: :ok
+ def remove_locator_handler(%Page{session: session, guid: guid}, %Playwright.Locator{} = locator) do
+ handlers = Playwright.LocatorHandlers.find_by_selector(guid, locator.selector)
+
+ for {uid, _data} <- handlers do
+ Playwright.LocatorHandlers.delete(guid, uid)
+ Channel.post(session, {:guid, guid}, :unregister_locator_handler, %{uid: uid})
+ end
+
+ :ok
+ end
# ---
@spec request(t()) :: Playwright.APIRequestContext.t()
def request(%Page{session: session} = page) do
- Channel.list(session, {:guid, page.owned_context.browser.guid}, "APIRequestContext")
+ # Fetch latest page state to get patched owned_context field
+ fresh_page = Channel.find(session, {:guid, page.guid})
+
+ Channel.list(session, {:guid, fresh_page.owned_context.browser.guid}, "APIRequestContext")
|> List.first()
end
- @spec route(t(), binary(), function(), map()) :: :ok
+ @doc """
+ Requests garbage collection in the browser.
+
+ Useful for memory testing scenarios.
+ """
+ @spec request_gc(t()) :: :ok | {:error, term()}
+ def request_gc(%Page{session: session, guid: guid}) do
+ case Channel.post(session, {:guid, guid}, :requestGC, %{}) do
+ {:error, _} = error -> error
+ _ -> :ok
+ end
+ end
+
+ @spec route(t(), binary() | Regex.t(), function(), map()) :: t() | {:error, term()}
def route(page, pattern, handler, options \\ %{})
def route(%Page{session: session} = page, pattern, handler, _options) do
@@ -614,6 +1323,63 @@ defmodule Playwright.Page do
# ---
+ @doc """
+ Routes WebSocket connections matching the URL pattern to the handler.
+
+ The handler receives a `Playwright.WebSocketRoute` that can be used to
+ intercept, mock, or modify WebSocket communication.
+
+ ## Example
+
+ # Mock all WebSocket connections
+ Page.route_web_socket(page, "**/*", fn ws ->
+ # Don't connect to server, just handle locally
+ WebSocketRoute.on_message(ws, fn msg ->
+ # Echo messages back
+ WebSocketRoute.send(ws, "Echo: \#{msg}")
+ end)
+ end)
+
+ # Proxy with logging
+ Page.route_web_socket(page, "**/ws", fn ws ->
+ server = WebSocketRoute.connect_to_server(ws)
+
+ WebSocketRoute.on_message(ws, fn msg ->
+ IO.puts("Page -> Server: \#{msg}")
+ WebSocketRoute.Server.send(server, msg)
+ end)
+
+ WebSocketRoute.Server.on_message(server, fn msg ->
+ IO.puts("Server -> Page: \#{msg}")
+ WebSocketRoute.send(ws, msg)
+ end)
+ end)
+
+ ## Arguments
+
+ | key/name | type | description |
+ | --------- | ------------------------ | ----------- |
+ | `page` | `t()` | The page |
+ | `pattern` | `binary()` or `Regex.t()` | URL pattern to match |
+ | `handler` | `function()` | Handler receiving WebSocketRoute |
+ """
+ @spec route_web_socket(t(), binary() | Regex.t(), (Playwright.WebSocketRoute.t() -> any())) ::
+ t() | {:error, term()}
+ def route_web_socket(%Page{session: session} = page, pattern, handler) when is_function(handler, 1) do
+ with_latest(page, fn page ->
+ matcher = Helpers.URLMatcher.new(pattern)
+ ws_handler = Helpers.WebSocketRouteHandler.new(matcher, handler)
+
+ websocket_routes = [ws_handler | page.websocket_routes]
+ patterns = Helpers.WebSocketRouteHandler.prepare(websocket_routes)
+
+ Channel.patch(session, {:guid, page.guid}, %{websocket_routes: websocket_routes})
+ Channel.post(session, {:guid, page.guid}, :set_web_socket_interception_patterns, %{patterns: patterns})
+ end)
+ end
+
+ # ---
+
@spec screenshot(t(), options()) :: binary()
def screenshot(%Page{session: session} = page, options \\ %{}) do
case Map.pop(options, :path) do
@@ -629,6 +1395,117 @@ defmodule Playwright.Page do
end
end
+ @doc """
+ Generates a PDF of the page.
+
+ Only supported in Chromium headless mode.
+
+ ## Options
+
+ - `:scale` - Scale of the webpage rendering. Default: `1`.
+ - `:display_header_footer` - Display header and footer. Default: `false`.
+ - `:header_template` - HTML template for the print header.
+ - `:footer_template` - HTML template for the print footer.
+ - `:print_background` - Print background graphics. Default: `false`.
+ - `:landscape` - Paper orientation. Default: `false`.
+ - `:page_ranges` - Paper ranges to print, e.g., `"1-5, 8, 11-13"`.
+ - `:format` - Paper format. If set, takes priority over width/height.
+ - `:width` - Paper width, accepts values labeled with units.
+ - `:height` - Paper height, accepts values labeled with units.
+ - `:prefer_css_page_size` - Prefer page size as defined by CSS. Default: `false`.
+ - `:margin` - Paper margins as map with `:top`, `:right`, `:bottom`, `:left` keys.
+ - `:tagged` - Generate tagged (accessible) PDF. Default: `false`.
+ - `:outline` - Generate document outline. Default: `false`.
+ - `:path` - File path to save the PDF to.
+ """
+ @spec pdf(t(), options()) :: binary()
+ def pdf(%Page{session: session} = page, options \\ %{}) do
+ {path, params} = Map.pop(options, :path)
+
+ params =
+ params
+ |> rename_key(:display_header_footer, :displayHeaderFooter)
+ |> rename_key(:header_template, :headerTemplate)
+ |> rename_key(:footer_template, :footerTemplate)
+ |> rename_key(:print_background, :printBackground)
+ |> rename_key(:page_ranges, :pageRanges)
+ |> rename_key(:prefer_css_page_size, :preferCSSPageSize)
+
+ data = Channel.post(session, {:guid, page.guid}, :pdf, params)
+
+ if path do
+ File.write!(path, Base.decode64!(data))
+ end
+
+ data
+ end
+
+ defp rename_key(map, old_key, new_key) do
+ case Map.pop(map, old_key) do
+ {nil, map} -> map
+ {value, map} -> Map.put(map, new_key, value)
+ end
+ end
+
+ defp normalize_media_option(nil), do: "no-override"
+ defp normalize_media_option(value), do: value
+
+ defp build_request_predicate(session, predicate) when is_function(predicate) do
+ fn _resource, event ->
+ case event.params do
+ %{request: %{guid: guid}} ->
+ request = Channel.find(session, {:guid, guid})
+ predicate.(request)
+
+ _ ->
+ false
+ end
+ end
+ end
+
+ defp build_request_predicate(session, url_pattern) do
+ matcher = Helpers.URLMatcher.new(url_pattern)
+
+ fn _resource, event ->
+ case event.params do
+ %{request: %{guid: guid}} ->
+ request = Channel.find(session, {:guid, guid})
+ Helpers.URLMatcher.matches(matcher, request.url)
+
+ _ ->
+ false
+ end
+ end
+ end
+
+ defp build_response_predicate(session, predicate) when is_function(predicate) do
+ fn _resource, event ->
+ case event.params do
+ %{response: %{guid: guid}} ->
+ response = Channel.find(session, {:guid, guid})
+ predicate.(response)
+
+ _ ->
+ false
+ end
+ end
+ end
+
+ defp build_response_predicate(session, url_pattern) do
+ matcher = Helpers.URLMatcher.new(url_pattern)
+
+ fn _resource, event ->
+ case event.params do
+ %{response: %{guid: guid}} ->
+ response = Channel.find(session, {:guid, guid})
+ Helpers.URLMatcher.matches(matcher, response.url)
+
+ _ ->
+ false
+ end
+ end
+ end
+
@doc """
A shortcut for the main frame's `Playwright.Frame.select_option/4`.
"""
@@ -639,8 +1516,24 @@ defmodule Playwright.Page do
# ---
- # @spec set_checked(t(), binary(), boolean(), options()) :: :ok
- # def set_checked(page, selector, checked, options \\ %{})
+ @doc """
+ Sets the checked state of a checkbox or radio element.
+
+ ## Arguments
+
+ | key/name | type | | description |
+ | ---------- | ------ | ----------- | ------------------------------------ |
+ | `selector` | param | `binary()` | Selector to search for the element. |
+ | `checked` | param | `boolean()` | Whether to check or uncheck. |
+
+ ## Returns
+
+ - `:ok`
+ """
+ @spec set_checked(t(), binary(), boolean(), options()) :: :ok
+ def set_checked(%Page{} = page, selector, checked, options \\ %{}) do
+ main_frame(page) |> Frame.set_checked(selector, checked, options)
+ end
# ---
@@ -649,22 +1542,153 @@ defmodule Playwright.Page do
main_frame(page) |> Frame.set_content(html, options)
end
- # NOTE: these 2 are good examples of functions that should `cast` instead of `call`.
- # ...
- # @spec set_default_navigation_timeout(t(), number()) :: nil (???)
- # def set_default_navigation_timeout(page, timeout)
+ # ---
+
+ @doc """
+ Sets the value of a file input element.
+
+ ## Arguments
+
+ | key/name | type | | description |
+ | ---------- | ------ | ----------- | ------------------------------------ |
+ | `selector` | param | `binary()` | Selector to search for the element. |
+ | `files` | param | `any()` | File path(s) or file payload(s). |
+
+ ## Returns
+
+ - `:ok`
+ """
+ @spec set_input_files(t(), binary(), any(), options()) :: :ok
+ def set_input_files(%Page{} = page, selector, files, options \\ %{}) do
+ main_frame(page) |> Frame.set_input_files(selector, files, options)
+ end
+
+ @doc """
+ Sets the default timeout for all page operations.
+
+ This setting will change the default maximum time for all the methods
+ accepting a `timeout` option.
+
+ ## Arguments
+
+ | key/name | type | description |
+ | --------- | ---------- | -------------------------------- |
+ | `timeout` | `number()` | Maximum time in milliseconds. |
+
+ ## Returns
+
+ - `:ok`
+
+ ## Example
+
+ Page.set_default_timeout(page, 60_000) # 60 seconds
+ """
+ @spec set_default_timeout(t(), number()) :: :ok
+ def set_default_timeout(%Page{session: session, guid: guid}, timeout) do
+ Channel.post(session, {:guid, guid}, :set_default_timeout_no_reply, %{timeout: timeout})
+ :ok
+ end
+
+ @doc """
+ Sets the default timeout for navigation operations.
+
+ This setting will change the default maximum navigation time for the
+ following methods: `goto/3`, `go_back/2`, `go_forward/2`, `reload/2`,
+ `wait_for_navigation/3`.
+
+ ## Arguments
+
+ | key/name | type | description |
+ | --------- | ---------- | -------------------------------- |
+ | `timeout` | `number()` | Maximum time in milliseconds. |
+
+ ## Returns
+
+ - `:ok`
+
+ ## Example
+
+ Page.set_default_navigation_timeout(page, 90_000) # 90 seconds
+ """
+ @spec set_default_navigation_timeout(t(), number()) :: :ok
+ def set_default_navigation_timeout(%Page{session: session, guid: guid}, timeout) do
+ Channel.post(session, {:guid, guid}, :set_default_navigation_timeout_no_reply, %{timeout: timeout})
+ :ok
+ end
+
+ @doc """
+ Sets extra HTTP headers to be sent with every request.
+
+ These headers will be merged with (and override) headers set by
+ `BrowserContext.set_extra_http_headers/2`.
- # @spec set_default_timeout(t(), number()) :: nil (???)
- # def set_default_timeout(page, timeout)
+ ## Arguments
+
+ | key/name | type | description |
+ | --------- | -------- | ------------------------------------------ |
+ | `headers` | `map()` | Map of header names to values. |
- # @spec set_extra_http_headers(t(), map()) :: :ok
- # def set_extra_http_headers(page, headers)
+ ## Returns
+
+ - `:ok`
+
+ ## Example
+
+ Page.set_extra_http_headers(page, %{
+ "Authorization" => "Bearer token123",
+ "X-Custom-Header" => "value"
+ })
+ """
+ @spec set_extra_http_headers(t(), map()) :: :ok
+ def set_extra_http_headers(%Page{session: session, guid: guid}, headers) when is_map(headers) do
+ header_list = Enum.map(headers, fn {name, value} -> %{name: to_string(name), value: value} end)
+ Channel.post(session, {:guid, guid}, :set_extra_http_headers, %{headers: header_list})
+ :ok
+ end
+
+ @doc """
+ Removes all routes registered with `route/4`.
+
+ ## Options
+
+ | key/name | type | description |
+ | ---------- | ---------- | ------------------------------------------------ |
+ | `:behavior`| `binary()` | How to handle in-flight requests. One of: |
+ | | | `"default"` - abort in-flight requests |
+ | | | `"wait"` - wait for in-flight handlers |
+ | | | `"ignoreErrors"` - ignore handler errors |
+
+ ## Returns
+
+ - `:ok`
+
+ ## Example
+
+ # Add a route
+ Page.route(page, "**/*", fn route -> Route.abort(route) end)
+
+ # Later, remove all routes
+ Page.unroute_all(page)
+
+ # Or wait for in-flight handlers to complete
+ Page.unroute_all(page, %{behavior: "wait"})
+ """
+ @spec unroute_all(t(), map()) :: :ok
+ def unroute_all(%Page{session: session, guid: guid}, options \\ %{}) do
+ params = if options[:behavior], do: %{behavior: options[:behavior]}, else: %{}
+ Channel.post(session, {:guid, guid}, :unroute_all, params)
+ Channel.patch(session, {:guid, guid}, %{routes: []})
+ Channel.post(session, {:guid, guid}, :set_network_interception_patterns, %{patterns: []})
+ :ok
+ end
# ---
@spec set_viewport_size(t(), dimensions()) :: :ok
def set_viewport_size(%Page{session: session} = page, dimensions) do
Channel.post(session, {:guid, page.guid}, :set_viewport_size, %{viewport_size: dimensions})
+ Channel.patch(session, {:guid, page.guid}, %{viewport_size: dimensions})
+ :ok
end
@spec text_content(t(), binary(), map()) :: binary() | nil
@@ -672,6 +1696,51 @@ defmodule Playwright.Page do
main_frame(page) |> Frame.text_content(selector, options)
end
+ @spec inner_text(t(), binary(), map()) :: binary()
+ def inner_text(%Page{} = page, selector, options \\ %{}) do
+ main_frame(page) |> Frame.inner_text(selector, options)
+ end
+
+ @spec inner_html(t(), binary(), map()) :: binary()
+ def inner_html(%Page{} = page, selector, options \\ %{}) do
+ main_frame(page) |> Frame.inner_html(selector, options)
+ end
+
+ @spec input_value(t(), binary(), map()) :: binary()
+ def input_value(%Page{} = page, selector, options \\ %{}) do
+ main_frame(page) |> Frame.input_value(selector, options)
+ end
+
+ @spec is_checked(t(), binary(), map()) :: boolean()
+ def is_checked(%Page{} = page, selector, options \\ %{}) do
+ main_frame(page) |> Frame.is_checked(selector, options)
+ end
+
+ @spec is_disabled(t(), binary(), map()) :: boolean()
+ def is_disabled(%Page{} = page, selector, options \\ %{}) do
+ main_frame(page) |> Frame.is_disabled(selector, options)
+ end
+
+ @spec is_editable(t(), binary(), map()) :: boolean()
+ def is_editable(%Page{} = page, selector, options \\ %{}) do
+ main_frame(page) |> Frame.is_editable(selector, options)
+ end
+
+ @spec is_enabled(t(), binary(), map()) :: boolean()
+ def is_enabled(%Page{} = page, selector, options \\ %{}) do
+ main_frame(page) |> Frame.is_enabled(selector, options)
+ end
+
+ @spec is_hidden(t(), binary(), map()) :: boolean()
+ def is_hidden(%Page{} = page, selector, options \\ %{}) do
+ main_frame(page) |> Frame.is_hidden(selector, options)
+ end
+
+ @spec is_visible(t(), binary(), map()) :: boolean()
+ def is_visible(%Page{} = page, selector, options \\ %{}) do
+ main_frame(page) |> Frame.is_visible(selector, options)
+ end
+
@spec title(t()) :: binary()
def title(%Page{} = page) do
main_frame(page) |> Frame.title()
@@ -679,6 +1748,26 @@ defmodule Playwright.Page do
# ---
+ @doc """
+ Unchecks a checkbox or radio element.
+
+ ## Arguments
+
+ | key/name | type | | description |
+ | ---------- | ------ | ----------- | ------------------------------------ |
+ | `selector` | param | `binary()` | Selector to search for the element. |
+
+ ## Returns
+
+ - `:ok`
+ """
+ @spec uncheck(t(), binary(), options()) :: :ok
+ def uncheck(%Page{} = page, selector, options \\ %{}) do
+ main_frame(page) |> Frame.uncheck(selector, options)
+ end
+
+ # ---
+
# @spec unroute(t(), function()) :: :ok
# def unroute(page, handler \\ nil)
@@ -694,11 +1783,31 @@ defmodule Playwright.Page do
# ---
- # @spec video(t()) :: Video.t() | nil
- # def video(page, handler \\ nil)
+ @doc """
+ Returns the video object for this page.
+
+ Returns `nil` if video recording is not enabled. Video recording is enabled
+ by passing `record_video: %{dir: path}` option when creating a browser context.
+
+ ## Returns
+
+ - `Video.t()` - The video object
+ - `nil` - If video recording is not enabled
+
+ ## Example
+
+ context = Browser.new_context(browser, %{record_video: %{dir: "/tmp/videos"}})
+ page = BrowserContext.new_page(context)
+ Page.goto(page, "https://example.com")
+ Page.close(page)
+
+ video = Page.video(page)
+ if video, do: Video.save_as(video, "recording.webm")
+ """
+ @spec video(t()) :: Playwright.Video.t() | nil
+ def video(%Page{guid: guid}), do: Playwright.Video.lookup(guid)
- # @spec viewport_size(t()) :: dimensions() | nil
- # def viewport_size(page)
+ # ---
# @spec wait_for_event(t(), binary(), map()) :: map()
# def wait_for_event(page, event, options \\ %{})
@@ -726,6 +1835,45 @@ defmodule Playwright.Page do
wait_for_load_state(page, "load", options)
end
+ @doc """
+ Waits for the main frame to navigate to a new URL.
+
+ Returns when the page navigates and reaches the required load state.
+ This is a shortcut for `Frame.wait_for_navigation/3` on the page's main frame.
+
+ ## Options
+
+ - `:timeout` - Maximum time in milliseconds. Defaults to 30000 (30 seconds).
+ - `:wait_until` - When to consider navigation succeeded. Defaults to `"load"`.
+ - `:url` - URL pattern to wait for (glob, regex, or function).
+
+ ## Examples
+
+ # With a trigger function (recommended)
+ Page.wait_for_navigation(page, fn -> Page.click(page, "a") end)
+
+ # With options and trigger
+ Page.wait_for_navigation(page, %{url: "**/success"}, fn -> Page.click(page, "a") end)
+
+ ## Returns
+
+ - `Page.t()` - The page after navigation
+ - `{:error, term()}` - If timeout occurs or navigation fails
+ """
+ @spec wait_for_navigation(t(), options() | function(), function() | nil) :: t() | {:error, term()}
+ def wait_for_navigation(page, options_or_trigger \\ %{}, trigger \\ nil)
+
+ def wait_for_navigation(%Page{} = page, trigger, nil) when is_function(trigger) do
+ wait_for_navigation(page, %{}, trigger)
+ end
+
+ def wait_for_navigation(%Page{} = page, options, trigger) when is_map(options) do
+ case main_frame(page) |> Frame.wait_for_navigation(options, trigger) do
+ {:error, _} = error -> error
+ _frame -> page
+ end
+ end
+
@spec wait_for_selector(t(), binary(), map()) :: ElementHandle.t() | nil
def wait_for_selector(%Page{} = page, selector, options \\ %{}) do
main_frame(page) |> Frame.wait_for_selector(selector, options)
@@ -733,8 +1881,37 @@ defmodule Playwright.Page do
# ---
- # @spec wait_for_url(Page.t(), binary(), options()) :: :ok
- # def wait_for_url(page, url, options \\ %{})
+ @doc """
+ Wait until the page URL matches the given pattern.
+
+ The pattern can be:
+ - A string with glob patterns (e.g., `"**/login"`)
+ - A regex (e.g., `~r/\\/login$/`)
+ - A function that receives URL and returns boolean
+
+ ## Options
+
+ - `:timeout` - Maximum time in milliseconds. Defaults to 30000 (30 seconds).
+ - `:wait_until` - When to consider navigation succeeded. Defaults to `"load"`.
+
+ ## Examples
+
+ Page.wait_for_url(page, "**/login")
+ Page.wait_for_url(page, ~r/\\/dashboard$/)
+ Page.wait_for_url(page, fn url -> String.contains?(url, "success") end)
+
+ ## Returns
+
+ - `Page.t()` - The page after URL matches
+ - `{:error, term()}` - If timeout occurs
+ """
+ @spec wait_for_url(t(), binary() | Regex.t() | function(), options()) :: t() | {:error, term()}
+ def wait_for_url(%Page{} = page, url_pattern, options \\ %{}) do
+ case main_frame(page) |> Frame.wait_for_url(url_pattern, options) do
+ {:error, _} = error -> error
+ _frame -> page
+ end
+ end
# @spec workers(t()) :: [Worker.t()]
# def workers(page)
@@ -775,4 +1952,28 @@ defmodule Playwright.Page do
end
end)
end
+
+ defp on_web_socket_route(page, %{webSocketRoute: ws_route}) do
+ # ws_route is already hydrated by Event.new
+
+ # Find first matching handler
+ handler =
+ Enum.find(page.websocket_routes, fn h ->
+ Helpers.WebSocketRouteHandler.matches(h, ws_route.url)
+ end)
+
+ if handler do
+ Helpers.WebSocketRouteHandler.handle(handler, ws_route)
+ else
+ # No page handler, try context
+ context = page.owned_context || Channel.find(page.session, {:guid, page.parent.guid})
+
+ if context do
+ BrowserContext.handle_web_socket_route(context, ws_route)
+ else
+ # No handler at all, just connect through
+ Playwright.WebSocketRoute.connect_to_server(ws_route)
+ end
+ end
+ end
end
diff --git a/lib/playwright/page/accessibility.ex b/lib/playwright/page/accessibility.ex
deleted file mode 100644
index dd8a2236..00000000
--- a/lib/playwright/page/accessibility.ex
+++ /dev/null
@@ -1,184 +0,0 @@
-defmodule Playwright.Page.Accessibility do
- @moduledoc """
- `Playwright.Page.Accessibility` provides functions for inspecting Chromium's accessibility tree.
-
- The accessibility tree is used by assistive technology such as [screen readers][1] or [switches][2].
-
- Accessibility is a very platform-specific thing. On different platforms, there are different screen readers that
- might have wildly different output.
-
- Rendering engines of Chromium, Firefox and WebKit have a concept of "accessibility tree", which is then translated
- into different platform-specific APIs. Accessibility namespace gives access to this Accessibility Tree.
-
- Most of the accessibility tree gets filtered out when converting from internal browser AX Tree to Platform-specific
- AX-Tree or by assistive technologies themselves. By default, Playwright tries to approximate this filtering,
- exposing only the "interesting" nodes of the tree.
-
- [1]: https://en.wikipedia.org/wiki/Screen_reader
- [2]: https://en.wikipedia.org/wiki/Switch_access
- """
-
- alias Playwright.{ElementHandle, Page}
- alias Playwright.SDK.{Channel, Extra}
-
- @typedoc """
- Options given to `snapshot/2`
-
- - `:interesting_only` - Prune uninteresting nodes from the tree (default: true)
- - `:root` - The root DOM element for the snapshot (default: page)
- """
- @type options() ::
- %{}
- | %{
- interesting_only: boolean(),
- root: ElementHandle.t()
- }
-
- @typedoc """
- Snapshot result returned from `snapshot/2`
-
- - `:name` - A human readable name for the node
- - `:description` - An additional human readable description of the node, if applicable
- - `:role` - The role
- - `:value` - The current value of the node, if applicable
- - `:children` - Child nodes, if any, if applicable
- - `:autocomplete` - What kind of autocomplete is supported by a control, if applicable
- - `:checked` - Whether the checkbox is checked, or "mixed", if applicable
- - `:disabled` - Whether the node is disabled, if applicable
- - `:expanded` - Whether the node is expanded or collapsed, if applicable
- - `:focused` - Whether the node is focused, if applicable
- - `:haspopup` - What kind of popup is currently being shown for a node, if applicable
- - `:invalid` - Whether and in what way this node's value is invalid, if applicable
- - `:keyshortcuts` - Keyboard shortcuts associated with this node, if applicable
- - `:level` - The level of a heading, if applicable
- - `:modal` - Whether the node is modal, if applicable
- - `:multiline` - Whether the node text input supports multiline, if applicable
- - `:multiselectable` - Whether more than one child can be selected, if applicable
- - `:orientation` - Whether the node is oriented horizontally or vertically, if applicable
- - `:pressed` - Whether the toggle button is checked, or "mixed", if applicable
- - `:readonly` - Whether the node is read only, if applicable
- - `:required` - Whether the node is required, if applicable
- - `:roledescription` - A human readable alternative to the role, if applicable
- - `:selected` - Whether the node is selected in its parent node, if applicable
- - `:valuemax` - The maximum value in a node, if applicable
- - `:valuemin` - The minimum value in a node, if applicable
- - `:valuetext` - A description of the current value, if applicable
- """
- @type snapshot() :: %{
- name: String.t(),
- description: String.t(),
- role: String.t(),
- value: String.t() | number(),
- children: list(),
- autocomplete: String.t(),
- checked: boolean() | String.t(),
- disabled: boolean(),
- expanded: boolean(),
- focused: boolean(),
- haspopup: String.t(),
- invalid: String.t(),
- keyshortcuts: String.t(),
- level: number(),
- modal: boolean(),
- multiline: boolean(),
- multiselectable: boolean(),
- orientation: String.t(),
- pressed: boolean() | String.t(),
- readonly: boolean(),
- required: boolean(),
- roledescription: String.t(),
- selected: boolean(),
- valuemax: number(),
- valuemin: number(),
- valuetext: String.t()
- }
-
- @doc """
- Captures the current state of the accessibility tree.
-
- The result represents the root accessible node of the page.
-
- ## Examples
-
- Dumping an entire accessibility tree:
-
- Browser.new_page(browser)
- |> Page.set_content("
Hello!
")
- |> Page.Accessibility.snapshot()
- %{children: [%{name: "Hello!", role: "text"}], name: "", role: "WebArea"}
-
- Retrieving the name of a focused node:
-
- body = ""
- Browser.new_page(browser)
- |> Page.set_content(body)
- |> Page.Accessibility.snapshot()
- |> (&(Enum.find(&1.children, fn e -> e.readonly end))).()
- %{name: "pick me", readonly: true, role: "textbox"}
- """
- @doc deprecated: "Please use other libraries such as [Axe](https://www.deque.com/axe/) if you need to test page accessibility.
- See the Playwright.dev Node.js [guide](https://playwright.dev/docs/accessibility-testing) for integration with Axe."
- def snapshot(page, options \\ %{})
-
- def snapshot(%Page{session: session} = page, options) do
- Channel.post(session, {:guid, page.guid}, :accessibility_snapshot, prepare(options))
- |> ax_node_from_protocol()
- end
-
- # private
- # ---------------------------------------------------------------------------
-
- defp ax_node_from_protocol(nil) do
- nil
- end
-
- defp ax_node_from_protocol(%{role: role} = input)
- when role in ["text"] do
- ax_node_from_protocol(input, fn e -> e.role != "text" end)
- end
-
- defp ax_node_from_protocol(input) do
- ax_node_from_protocol(input, fn _ -> true end)
- end
-
- defp ax_node_from_protocol(input, filter) do
- Enum.reduce(input, %{}, fn {k, v}, acc ->
- cond do
- is_list(v) ->
- normal =
- v
- |> Enum.map(&ax_node_from_protocol/1)
- |> Enum.filter(filter)
-
- Map.put(acc, k, normal)
-
- k == :checked ->
- Map.put(acc, k, normalize_checked(v))
-
- k == :valueString ->
- Map.put(acc, :value, v)
-
- true ->
- Map.put(acc, k, v)
- end
- end)
- end
-
- defp normalize_checked(value) do
- case value do
- "checked" -> true
- "unchecked" -> false
- other -> other
- end
- end
-
- defp prepare(opts) when is_map(opts) do
- Enum.reduce(opts, %{}, fn {k, v}, acc -> Map.put(acc, prepare(k), v) end)
- end
-
- defp prepare(atom) when is_atom(atom) do
- Extra.Atom.to_string(atom)
- |> Recase.to_camel()
- |> Extra.Atom.from_string()
- end
-end
diff --git a/lib/playwright/page/coverage.ex b/lib/playwright/page/coverage.ex
deleted file mode 100644
index 824377ca..00000000
--- a/lib/playwright/page/coverage.ex
+++ /dev/null
@@ -1,16 +0,0 @@
-defmodule Playwright.Coverage do
- @moduledoc false
-
- # @spec start_css_coverage(t(), options()) :: :ok
- # def start_css_coverage(coverage, options \\ %{})
-
- # @spec start_js_coverage(t(), options()) :: :ok
- # def start_js_coverage(coverage, options \\ %{})
-
- # @spec stop_css_coverage(t()) :: result()
- # def stop_css_coverage(coverage)
-
- # @spec stop_js_coverage(t()) :: result()
- # def stop_js_coverage(coverage)
-
-end
diff --git a/lib/playwright/page/download.ex b/lib/playwright/page/download.ex
index 5ac99516..b7f0b6f8 100644
--- a/lib/playwright/page/download.ex
+++ b/lib/playwright/page/download.ex
@@ -1,31 +1,95 @@
defmodule Playwright.Download do
- @moduledoc false
+ @moduledoc """
+ Download objects are dispatched by page via the `:download` event.
- # @spec cancel(t()) :: :ok
- # def cancel(download)
+ ## Example
- # @spec create_read_stream(t()) :: readable()
- # def create_read_stream(download)
+ Page.on(page, :download, fn download ->
+ Download.save_as(download, "/tmp/my-file.pdf")
+ end)
+ """
- # @spec delete(t()) :: :ok
- # def delete(download)
+ alias Playwright.Artifact
- # @spec failure(t()) :: nil | binary()
- # def failure(download)
+ defstruct [:url, :suggested_filename, :page, :artifact]
- # @spec page(t()) :: Page.t()
- # def page(download)
+ @type t :: %__MODULE__{
+ url: binary(),
+ suggested_filename: binary(),
+ page: Playwright.Page.t(),
+ artifact: Artifact.t()
+ }
- # @spec path(t()) :: binary()
- # def path(download)
+ @doc false
+ def new(page, url, suggested_filename, artifact) do
+ %__MODULE__{
+ url: url,
+ suggested_filename: suggested_filename,
+ page: page,
+ artifact: artifact
+ }
+ end
- # @spec save_as(t(), binary()) :: :ok
- # def save_as(download, path)
+ @doc """
+ Creates a Download from a download event.
- # @spec suggested_filename(t()) :: binary()
- # def suggested_filename(download)
+ ## Example
- # @spec url(t()) :: binary()
- # def url(download)
+ Page.on(page, :download, fn event ->
+ download = Download.from_event(event)
+ Download.save_as(download, "/tmp/file.txt")
+ end)
+ """
+ @spec from_event(Playwright.SDK.Channel.Event.t()) :: t()
+ def from_event(%{target: page, params: params}) do
+ new(page, params.url, params.suggestedFilename, params.artifact)
+ end
+ @doc "Returns the download URL."
+ @spec url(t()) :: binary()
+ def url(%__MODULE__{url: url}), do: url
+
+ @doc "Returns the suggested filename for the download."
+ @spec suggested_filename(t()) :: binary()
+ def suggested_filename(%__MODULE__{suggested_filename: name}), do: name
+
+ @doc "Returns the page that initiated the download."
+ @spec page(t()) :: Playwright.Page.t()
+ def page(%__MODULE__{page: page}), do: page
+
+ @doc """
+ Returns the path to the downloaded file after it has finished downloading.
+ """
+ @spec path(t()) :: binary() | {:error, term()}
+ def path(%__MODULE__{artifact: artifact}) do
+ Artifact.path_after_finished(artifact)
+ end
+
+ @doc """
+ Saves the download to the specified path.
+ """
+ @spec save_as(t(), binary()) :: :ok | {:error, term()}
+ def save_as(%__MODULE__{artifact: artifact}, path) do
+ Artifact.save_as(artifact, path)
+ end
+
+ @doc """
+ Returns the error message if download failed, nil otherwise.
+ """
+ @spec failure(t()) :: binary() | nil | {:error, term()}
+ def failure(%__MODULE__{artifact: artifact}) do
+ Artifact.failure(artifact)
+ end
+
+ @doc "Cancels the download."
+ @spec cancel(t()) :: :ok | {:error, term()}
+ def cancel(%__MODULE__{artifact: artifact}) do
+ Artifact.cancel(artifact)
+ end
+
+ @doc "Deletes the downloaded file."
+ @spec delete(t()) :: :ok | {:error, term()}
+ def delete(%__MODULE__{artifact: artifact}) do
+ Artifact.delete(artifact)
+ end
end
diff --git a/lib/playwright/page/file_chooser.ex b/lib/playwright/page/file_chooser.ex
index c6e25648..f8349084 100644
--- a/lib/playwright/page/file_chooser.ex
+++ b/lib/playwright/page/file_chooser.ex
@@ -1,16 +1,96 @@
defmodule Playwright.FileChooser do
- @moduledoc false
+ @moduledoc """
+ FileChooser instances are dispatched by the page via the `:file_chooser` event.
- # @spec element(t()) :: ElementHandle.t()
- # def element(file_chooser)
+ ## Example
- # @spec is_multiple(t()) :: boolean()
- # def is_multiple(file_chooser)
+ Page.on(page, :file_chooser, fn event ->
+ file_chooser = FileChooser.from_event(event)
+ FileChooser.set_files(file_chooser, "/path/to/file.txt")
+ end)
- # @spec page(t()) :: Page.t()
- # def page(file_chooser)
+ # Or with expect_event
+ event = Page.expect_event(page, :file_chooser, fn ->
+ Page.click(page, "input[type=file]")
+ end)
+ file_chooser = FileChooser.from_event(event)
+ FileChooser.set_files(file_chooser, ["/path/to/file1.txt", "/path/to/file2.txt"])
+ """
- # @spec set_files(t(), any(), options()) :: :ok
- # def cancel(file_chooser, files, options \\ %{})
+ alias Playwright.ElementHandle
+ defstruct [:page, :element, :is_multiple]
+
+ @type t :: %__MODULE__{
+ page: Playwright.Page.t(),
+ element: ElementHandle.t(),
+ is_multiple: boolean()
+ }
+
+ @doc false
+ def new(page, element, is_multiple) do
+ %__MODULE__{
+ page: page,
+ element: element,
+ is_multiple: is_multiple
+ }
+ end
+
+ @doc """
+ Creates a FileChooser from a file_chooser event.
+
+ ## Example
+
+ Page.on(page, :file_chooser, fn event ->
+ file_chooser = FileChooser.from_event(event)
+ FileChooser.set_files(file_chooser, "/path/to/file.txt")
+ end)
+ """
+ @spec from_event(Playwright.SDK.Channel.Event.t()) :: t()
+ def from_event(%{target: page, params: params}) do
+ new(page, params.element, params.isMultiple)
+ end
+
+ @doc """
+ Returns the input element associated with this file chooser.
+ """
+ @spec element(t()) :: ElementHandle.t()
+ def element(%__MODULE__{element: element}), do: element
+
+ @doc """
+ Returns whether this file chooser accepts multiple files.
+ """
+ @spec is_multiple(t()) :: boolean()
+ def is_multiple(%__MODULE__{is_multiple: is_multiple}), do: is_multiple
+
+ @doc """
+ Returns the page this file chooser belongs to.
+ """
+ @spec page(t()) :: Playwright.Page.t()
+ def page(%__MODULE__{page: page}), do: page
+
+ @doc """
+ Sets the value of the file input.
+
+ ## Arguments
+
+ - `files` - Single file path, list of file paths, or file payload map(s)
+ - `options` - Optional settings like `:timeout`, `:no_wait_after`
+
+ ## Examples
+
+ FileChooser.set_files(file_chooser, "/path/to/file.txt")
+ FileChooser.set_files(file_chooser, ["/path/to/file1.txt", "/path/to/file2.txt"])
+
+ # With file payload
+ FileChooser.set_files(file_chooser, %{
+ name: "file.txt",
+ mimeType: "text/plain",
+ buffer: Base.encode64("Hello World")
+ })
+ """
+ @spec set_files(t(), binary() | [binary()] | map() | [map()], map()) :: :ok | {:error, term()}
+ def set_files(%__MODULE__{element: element}, files, options \\ %{}) do
+ ElementHandle.set_input_files(element, files, options)
+ end
end
diff --git a/lib/playwright/page/frame_locator.ex b/lib/playwright/page/frame_locator.ex
index 6e1130d4..1db0117c 100644
--- a/lib/playwright/page/frame_locator.ex
+++ b/lib/playwright/page/frame_locator.ex
@@ -1,43 +1,225 @@
defmodule Playwright.Page.FrameLocator do
- @moduledoc false
+ @moduledoc """
+ FrameLocator represents a view to the iframe on the page.
- # @spec first(t()) :: FrameLocator.t()
- # def first(locator)
+ It captures the logic sufficient to retrieve the iframe and locate elements in that iframe.
+ FrameLocator can be created with either `Page.frame_locator/2` or `Frame.frame_locator/2`.
- # @spec frame_locator(t(), binary()) :: FrameLocator.t()
- # def frame_locator(locator, selector)
+ ## Examples
- # @spec get_by_alt_text(t(), binary(), options()) :: Playwright.Locator.t() | nil
- # def get_by_alt_text(locator, text, options \\ %{})
+ # Locate element inside an iframe
+ page
+ |> Page.frame_locator("#my-frame")
+ |> FrameLocator.get_by_role("button", name: "Submit")
+ |> Locator.click()
- # @spec get_by_label(t(), binary(), options()) :: Playwright.Locator.t() | nil
- # def get_by_label(locator, text, options \\ %{})
+ # Nested iframes
+ page
+ |> Page.frame_locator("#outer-frame")
+ |> FrameLocator.frame_locator("#inner-frame")
+ |> FrameLocator.get_by_text("Hello")
+ |> Locator.text_content()
+ """
- # @spec get_by_placeholder(t(), binary(), options()) :: Playwright.Locator.t() | nil
- # def get_by_placeholder(locator, text, options \\ %{})
+ alias Playwright.{Frame, Locator}
- # @spec get_by_role(t(), binary(), options()) :: Playwright.Locator.t() | nil
- # def get_by_role(locator, text, options \\ %{})
+ @enforce_keys [:frame, :selector]
+ defstruct [:frame, :selector]
- # @spec get_by_test_id(t(), binary(), options()) :: Playwright.Locator.t() | nil
- # def get_by_test_id(locator, text, options \\ %{})
+ @type t() :: %__MODULE__{
+ frame: Frame.t(),
+ selector: binary()
+ }
- # @spec get_by_text(t(), binary(), options()) :: Playwright.Locator.t() | nil
- # def get_by_text(locator, text, options \\ %{})
+ @doc false
+ @spec new(Frame.t(), binary()) :: t()
+ def new(%Frame{} = frame, selector) when is_binary(selector) do
+ %__MODULE__{frame: frame, selector: selector}
+ end
- # @spec get_by_title(t(), binary(), options()) :: Playwright.Locator.t() | nil
- # def get_by_title(locator, text, options \\ %{})
+ # ---------------------------------------------------------------------------
+ # Chain methods (return FrameLocator)
+ # ---------------------------------------------------------------------------
- # @spec last(t()) :: FrameLocator.t()
- # def last(locator)
+ @doc """
+ Returns locator to the first matching frame.
+ """
+ @spec first(t()) :: t()
+ def first(%__MODULE__{} = frame_locator) do
+ %{frame_locator | selector: frame_locator.selector <> " >> nth=0"}
+ end
- # @spec locator(t(), selector_or_locator(), options()) :: Locator.t()
- # def locator(locator, selector, options \\ %{})
- # def locator(locator, locator, options \\ %{})
+ @doc """
+ Returns locator to the last matching frame.
+ """
+ @spec last(t()) :: t()
+ def last(%__MODULE__{} = frame_locator) do
+ %{frame_locator | selector: frame_locator.selector <> " >> nth=-1"}
+ end
- # @spec nth(t(), number()) :: FrameLocator.t()
- # def nth(locator, index)
+ @doc """
+ Returns locator to the n-th matching frame (zero-based).
+ """
+ @spec nth(t(), integer()) :: t()
+ def nth(%__MODULE__{} = frame_locator, index) when is_integer(index) do
+ %{frame_locator | selector: frame_locator.selector <> " >> nth=#{index}"}
+ end
- # @spec owner(t()) :: Locator.t()
- # def owner(locator)
+ @doc """
+ Returns a FrameLocator for a nested iframe.
+
+ When working with nested iframes, this method allows you to locate iframes
+ inside the current frame.
+ """
+ @spec frame_locator(t(), binary()) :: t()
+ def frame_locator(%__MODULE__{} = frame_locator, selector) when is_binary(selector) do
+ new_selector = "#{frame_locator.selector} >> internal:control=enter-frame >> #{selector}"
+ %{frame_locator | selector: new_selector}
+ end
+
+ # ---------------------------------------------------------------------------
+ # Locator methods (return Locator)
+ # ---------------------------------------------------------------------------
+
+ @doc """
+ Returns a Locator for elements matching the selector inside the frame.
+
+ The method finds an element matching the specified selector in the FrameLocator's
+ subtree. It also accepts `Locator` as an argument.
+ """
+ @spec locator(t(), binary() | Locator.t()) :: Locator.t()
+ def locator(%__MODULE__{} = frame_locator, selector) when is_binary(selector) do
+ full_selector = "#{frame_locator.selector} >> internal:control=enter-frame >> #{selector}"
+ Locator.new(frame_locator.frame, full_selector)
+ end
+
+ def locator(%__MODULE__{} = frame_locator, %Locator{selector: selector}) do
+ locator(frame_locator, selector)
+ end
+
+ @doc """
+ Returns a Locator pointing to the frame element itself.
+
+ This is useful when you need to interact with the iframe element itself,
+ rather than elements inside it.
+ """
+ @spec owner(t()) :: Locator.t()
+ def owner(%__MODULE__{} = frame_locator) do
+ Locator.new(frame_locator.frame, frame_locator.selector)
+ end
+
+ # ---------------------------------------------------------------------------
+ # getBy* methods (return Locator)
+ # ---------------------------------------------------------------------------
+
+ @doc """
+ Allows locating elements by their alt text.
+
+ ## Options
+
+ - `:exact` - Whether to find an exact match: case-sensitive and whole-string.
+ Default: `false`.
+ """
+ @spec get_by_alt_text(t(), binary(), map()) :: Locator.t()
+ def get_by_alt_text(%__MODULE__{} = frame_locator, text, options \\ %{}) when is_binary(text) do
+ locator(frame_locator, get_by_attr_selector("alt", text, options))
+ end
+
+ @doc """
+ Allows locating input elements by their label text.
+
+ ## Options
+
+ - `:exact` - Whether to find an exact match: case-sensitive and whole-string.
+ Default: `false`.
+ """
+ @spec get_by_label(t(), binary(), map()) :: Locator.t()
+ def get_by_label(%__MODULE__{} = frame_locator, text, options \\ %{}) when is_binary(text) do
+ locator(frame_locator, Locator.get_by_label_selector(text, options))
+ end
+
+ @doc """
+ Allows locating input elements by their placeholder text.
+
+ ## Options
+
+ - `:exact` - Whether to find an exact match: case-sensitive and whole-string.
+ Default: `false`.
+ """
+ @spec get_by_placeholder(t(), binary(), map()) :: Locator.t()
+ def get_by_placeholder(%__MODULE__{} = frame_locator, text, options \\ %{}) when is_binary(text) do
+ locator(frame_locator, get_by_attr_selector("placeholder", text, options))
+ end
+
+ @doc """
+ Allows locating elements by their ARIA role, ARIA attributes and accessible name.
+
+ ## Options
+
+ - `:checked` - An attribute that is usually set by `aria-checked` or native input checkbox.
+ - `:disabled` - An attribute that is usually set by `aria-disabled` or `disabled`.
+ - `:exact` - Whether `name` is matched exactly: case-sensitive and whole-string.
+ - `:expanded` - An attribute that is usually set by `aria-expanded`.
+ - `:include_hidden` - Option to match hidden elements.
+ - `:level` - A number attribute that is usually present for roles `heading`, `listitem`, etc.
+ - `:name` - Option to match the accessible name.
+ - `:pressed` - An attribute that is usually set by `aria-pressed`.
+ - `:selected` - An attribute that is usually set by `aria-selected`.
+ """
+ @spec get_by_role(t(), binary(), map()) :: Locator.t()
+ def get_by_role(%__MODULE__{} = frame_locator, role, options \\ %{}) when is_binary(role) do
+ locator(frame_locator, Locator.get_by_role_selector(role, options))
+ end
+
+ @doc """
+ Locate element by the test id.
+
+ By default, the `data-testid` attribute is used as a test id.
+ """
+ @spec get_by_test_id(t(), binary()) :: Locator.t()
+ def get_by_test_id(%__MODULE__{} = frame_locator, test_id) when is_binary(test_id) do
+ locator(frame_locator, Locator.get_by_test_id_selector(test_id))
+ end
+
+ @doc """
+ Allows locating elements that contain given text.
+
+ ## Options
+
+ - `:exact` - Whether to find an exact match: case-sensitive and whole-string.
+ Default: `false`.
+ """
+ @spec get_by_text(t(), binary(), map()) :: Locator.t()
+ def get_by_text(%__MODULE__{} = frame_locator, text, options \\ %{}) when is_binary(text) do
+ locator(frame_locator, Locator.get_by_text_selector(text, options))
+ end
+
+ @doc """
+ Allows locating elements by their title attribute.
+
+ ## Options
+
+ - `:exact` - Whether to find an exact match: case-sensitive and whole-string.
+ Default: `false`.
+ """
+ @spec get_by_title(t(), binary(), map()) :: Locator.t()
+ def get_by_title(%__MODULE__{} = frame_locator, text, options \\ %{}) when is_binary(text) do
+ locator(frame_locator, get_by_attr_selector("title", text, options))
+ end
+
+ # ---------------------------------------------------------------------------
+ # Private helpers
+ # ---------------------------------------------------------------------------
+
+ defp get_by_attr_selector(attr_name, text, options) do
+ exact = Map.get(options, :exact, false)
+ escaped = escape_for_attribute_selector(text, exact)
+ "internal:attr=[#{attr_name}=#{escaped}]"
+ end
+
+ defp escape_for_attribute_selector(value, exact) do
+ escaped = value |> String.replace("\\", "\\\\") |> String.replace("\"", "\\\"")
+ suffix = if exact, do: "s", else: "i"
+ "\"#{escaped}\"#{suffix}"
+ end
end
diff --git a/lib/playwright/page/mouse.ex b/lib/playwright/page/mouse.ex
index 2e9dd318..f81e4650 100644
--- a/lib/playwright/page/mouse.ex
+++ b/lib/playwright/page/mouse.ex
@@ -1,22 +1,122 @@
defmodule Playwright.Mouse do
- @moduledoc false
+ @moduledoc """
+ Mouse provides methods for interacting with a virtual mouse.
- # @spec click(t(), number(), number(), options()) :: :ok
- # def click(mouse, x, y, options \\ %{})
+ Every Page has its own Mouse, accessible via the page functions.
- # @spec dblclick(t(), number(), number(), options()) :: :ok
- # def dblclick(mouse, x, y, options \\ %{})
+ ## Examples
- # @spec down(t(), options()) :: :ok
- # def down(mouse, options \\ %{})
+ # Click at coordinates
+ Mouse.click(page, 100, 200)
- # @spec move(t(), number(), number(), options()) :: :ok
- # def move(mouse, x, y, options \\ %{})
+ # Right-click
+ Mouse.click(page, 100, 200, button: "right")
- # @spec up(t(), options()) :: :ok
- # def up(mouse, options \\ %{})
+ # Double-click
+ Mouse.dblclick(page, 100, 200)
- # @spec wheel(t(), number(), number()) :: :ok
- # def wheel(mouse, delta_x, delta_y)
+ # Drag and drop
+ Mouse.move(page, 0, 0)
+ Mouse.down(page)
+ Mouse.move(page, 100, 100, steps: 10)
+ Mouse.up(page)
+ # Scroll
+ Mouse.wheel(page, 0, 100)
+ """
+
+ use Playwright.SDK.ChannelOwner
+ alias Playwright.Page
+
+ @type button :: String.t()
+
+ @doc """
+ Clicks at the specified coordinates.
+
+ ## Options
+
+ - `:button` - `"left"`, `"right"`, or `"middle"` (default: `"left"`)
+ - `:click_count` - Number of clicks (default: 1)
+ - `:delay` - Time between mousedown and mouseup in ms (default: 0)
+ """
+ @spec click(Page.t(), number(), number(), keyword()) :: Page.t()
+ def click(page, x, y, options \\ []) do
+ params = %{x: x, y: y}
+ params = if options[:button], do: Map.put(params, :button, options[:button]), else: params
+ params = if options[:click_count], do: Map.put(params, :clickCount, options[:click_count]), else: params
+ params = if options[:delay], do: Map.put(params, :delay, options[:delay]), else: params
+ post!(page, :mouse_click, params)
+ end
+
+ @doc """
+ Double-clicks at the specified coordinates.
+
+ ## Options
+
+ - `:button` - `"left"`, `"right"`, or `"middle"` (default: `"left"`)
+ - `:delay` - Time between mousedown and mouseup in ms (default: 0)
+ """
+ @spec dblclick(Page.t(), number(), number(), keyword()) :: Page.t()
+ def dblclick(page, x, y, options \\ []) do
+ click(page, x, y, Keyword.put(options, :click_count, 2))
+ end
+
+ @doc """
+ Dispatches a mousedown event.
+
+ ## Options
+
+ - `:button` - `"left"`, `"right"`, or `"middle"` (default: `"left"`)
+ - `:click_count` - Number of clicks (default: 1)
+ """
+ @spec down(Page.t(), keyword()) :: Page.t()
+ def down(page, options \\ []) do
+ params = %{}
+ params = if options[:button], do: Map.put(params, :button, options[:button]), else: params
+ params = if options[:click_count], do: Map.put(params, :clickCount, options[:click_count]), else: params
+ post!(page, :mouse_down, params)
+ end
+
+ @doc """
+ Dispatches a mouseup event.
+
+ ## Options
+
+ - `:button` - `"left"`, `"right"`, or `"middle"` (default: `"left"`)
+ - `:click_count` - Number of clicks (default: 1)
+ """
+ @spec up(Page.t(), keyword()) :: Page.t()
+ def up(page, options \\ []) do
+ params = %{}
+ params = if options[:button], do: Map.put(params, :button, options[:button]), else: params
+ params = if options[:click_count], do: Map.put(params, :clickCount, options[:click_count]), else: params
+ post!(page, :mouse_up, params)
+ end
+
+ @doc """
+ Moves the mouse to the specified coordinates.
+
+ ## Options
+
+ - `:steps` - Number of intermediate mousemove events (default: 1)
+ """
+ @spec move(Page.t(), number(), number(), keyword()) :: Page.t()
+ def move(page, x, y, options \\ []) do
+ params = %{x: x, y: y}
+ params = if options[:steps], do: Map.put(params, :steps, options[:steps]), else: params
+ post!(page, :mouse_move, params)
+ end
+
+ @doc """
+ Dispatches a wheel event (scroll).
+
+ ## Parameters
+
+ - `delta_x` - Horizontal scroll amount in pixels
+ - `delta_y` - Vertical scroll amount in pixels
+ """
+ @spec wheel(Page.t(), number(), number()) :: Page.t()
+ def wheel(page, delta_x, delta_y) do
+ post!(page, :mouse_wheel, %{deltaX: delta_x, deltaY: delta_y})
+ end
end
diff --git a/lib/playwright/page/touchscreen.ex b/lib/playwright/page/touchscreen.ex
index 4ec7c2a5..27feee56 100644
--- a/lib/playwright/page/touchscreen.ex
+++ b/lib/playwright/page/touchscreen.ex
@@ -1,7 +1,40 @@
defmodule Playwright.Touchscreen do
- @moduledoc false
+ @moduledoc """
+ Touchscreen provides methods for dispatching touch events.
- # @spec tap(t(), number(), number()) :: :ok
- # def tap(touchscreen, x, y)
+ Touch events are dispatched on the page. To use touchscreen methods,
+ you typically need to enable touch emulation via browser context options.
+ ## Example
+
+ # Create a context with touch enabled
+ context = Browser.new_context(browser, %{has_touch: true})
+ page = BrowserContext.new_page(context)
+
+ # Tap at coordinates
+ Touchscreen.tap(page, 100, 200)
+ """
+
+ alias Playwright.SDK.Channel
+
+ @doc """
+ Dispatches a `touchstart` and `touchend` event at the given coordinates.
+
+ ## Arguments
+
+ | key/name | type | description |
+ | -------- | ---- | ----------- |
+ | `page` | `Page.t()` | The page to dispatch the tap event on |
+ | `x` | `number()` | X coordinate relative to the viewport |
+ | `y` | `number()` | Y coordinate relative to the viewport |
+
+ ## Returns
+
+ - `:ok`
+ """
+ @spec tap(Playwright.Page.t(), number(), number()) :: :ok
+ def tap(%Playwright.Page{session: session, guid: guid}, x, y) do
+ Channel.post(session, {:guid, guid}, :touchscreen_tap, %{x: x, y: y})
+ :ok
+ end
end
diff --git a/lib/playwright/page/video.ex b/lib/playwright/page/video.ex
index e51a6b42..d6c96221 100644
--- a/lib/playwright/page/video.ex
+++ b/lib/playwright/page/video.ex
@@ -1,13 +1,117 @@
defmodule Playwright.Video do
- @moduledoc false
+ @moduledoc """
+ Video object associated with a page.
- # @spec delete(t()) :: :ok
- # def delete(video)
+ Access video recordings when `record_video` option is enabled in browser context.
- # @spec path(t()) :: binary()
- # def path(video)
+ ## Example
- # @spec save_as(t(), binary()) :: :ok
- # def save_as(video, path)
+ context = Browser.new_context(browser, %{record_video: %{dir: "/tmp/videos"}})
+ page = BrowserContext.new_page(context)
+ Page.goto(page, "https://example.com")
+ Page.close(page)
+ video = Page.video(page)
+ Video.save_as(video, "recording.webm")
+ """
+
+ alias Playwright.Artifact
+
+ @table :playwright_videos
+
+ defstruct [:artifact]
+
+ @type t :: %__MODULE__{artifact: Artifact.t() | nil}
+
+ @doc false
+ def ensure_table do
+ case :ets.whereis(@table) do
+ :undefined ->
+ :ets.new(@table, [:set, :public, :named_table])
+
+ _ ->
+ :ok
+ end
+ end
+
+ @doc false
+ def store(page_guid, video) do
+ ensure_table()
+ :ets.insert(@table, {page_guid, video})
+ end
+
+ @doc false
+ def lookup(page_guid, timeout \\ 2000) do
+ ensure_table()
+ wait_for_video(page_guid, timeout)
+ end
+
+ defp wait_for_video(page_guid, timeout) when timeout <= 0 do
+ case :ets.lookup(@table, page_guid) do
+ [{^page_guid, video}] -> video
+ [] -> nil
+ end
+ end
+
+ defp wait_for_video(page_guid, timeout) do
+ case :ets.lookup(@table, page_guid) do
+ [{^page_guid, video}] ->
+ video
+
+ [] ->
+ Process.sleep(50)
+ wait_for_video(page_guid, timeout - 50)
+ end
+ end
+
+ @doc false
+ def delete_entry(page_guid) do
+ ensure_table()
+ :ets.delete(@table, page_guid)
+ end
+
+ @doc """
+ Returns the path to the video file.
+
+ Note: Only works for local connections. For remote connections,
+ use `save_as/2` to save a copy locally.
+
+ ## Returns
+
+ - `binary()` - The file path
+ - `{:error, term()}` - If no video was recorded or remote connection
+ """
+ @spec path(t()) :: binary() | {:error, term()}
+ def path(%__MODULE__{artifact: nil}), do: {:error, "Page did not produce any video frames"}
+ def path(%__MODULE__{artifact: artifact}), do: Artifact.path_after_finished(artifact)
+
+ @doc """
+ Saves the video to the specified path.
+
+ Safe to call while video is recording or after page closes.
+ Works for both local and remote connections.
+
+ ## Returns
+
+ - `:ok`
+ - `{:error, term()}` - If no video was recorded
+ """
+ @spec save_as(t(), binary()) :: :ok | {:error, term()}
+ def save_as(%__MODULE__{artifact: nil}, _path), do: {:error, "Page did not produce any video frames"}
+ def save_as(%__MODULE__{artifact: artifact}, path), do: Artifact.save_as(artifact, path)
+
+ @doc """
+ Deletes the video file.
+
+ ## Returns
+
+ - `:ok`
+ - `{:error, term()}` - If deletion fails
+ """
+ @spec delete(t()) :: :ok | {:error, term()}
+ def delete(%__MODULE__{artifact: nil}), do: :ok
+ def delete(%__MODULE__{artifact: artifact}), do: Artifact.delete(artifact)
+
+ @doc false
+ def new(artifact \\ nil), do: %__MODULE__{artifact: artifact}
end
diff --git a/lib/playwright/route.ex b/lib/playwright/route.ex
index c3fe1ddf..25cee434 100644
--- a/lib/playwright/route.ex
+++ b/lib/playwright/route.ex
@@ -11,8 +11,31 @@ defmodule Playwright.Route do
# ---
- # @spec abort(t(), binary()) :: :ok
- # def abort(route, error_code \\ nil)
+ @doc """
+ Aborts the route's request.
+
+ ## Arguments
+
+ | key/name | type | description |
+ | ------------ | ---------- | ------------------------------------------------ |
+ | `error_code` | `binary()` | Optional error code (e.g., "aborted", "failed"). |
+
+ ## Returns
+
+ - `:ok`
+
+ ## Example
+
+ Page.route(page, "**/*.png", fn route, _request ->
+ Route.abort(route)
+ end)
+ """
+ @spec abort(t(), binary() | nil) :: :ok
+ def abort(%Route{session: session, guid: guid}, error_code \\ nil) do
+ params = if error_code, do: %{errorCode: error_code}, else: %{}
+ Channel.post(session, {:guid, guid}, :abort, params)
+ :ok
+ end
# ---
diff --git a/lib/playwright/sdk/channel.ex b/lib/playwright/sdk/channel.ex
index 5c168a61..d48ab3aa 100644
--- a/lib/playwright/sdk/channel.ex
+++ b/lib/playwright/sdk/channel.ex
@@ -28,6 +28,7 @@ defmodule Playwright.SDK.Channel do
end
def post(session, {:guid, guid}, action, params \\ %{}) when is_binary(guid) when is_pid(session) do
+ params = Map.put_new(params, :timeout, 30_000)
connection = Session.connection(session)
message = Message.new(guid, action, params)
@@ -82,11 +83,7 @@ defmodule Playwright.SDK.Channel do
if predicate do
with_timeout(options, fn timeout ->
- task =
- Task.async(fn ->
- evaluate(predicate, event.target, event)
- end)
-
+ task = async_evaluate(predicate, event)
Task.await(task, timeout)
end)
else
@@ -94,6 +91,10 @@ defmodule Playwright.SDK.Channel do
end
end
+ defp async_evaluate(predicate, event) do
+ Task.async(fn -> evaluate(predicate, event.target, event) end)
+ end
+
defp evaluate(predicate, resource, event) do
case predicate.(resource, event) do
false ->
diff --git a/lib/playwright/sdk/channel/catalog.ex b/lib/playwright/sdk/channel/catalog.ex
index 7b02432c..10932f64 100644
--- a/lib/playwright/sdk/channel/catalog.ex
+++ b/lib/playwright/sdk/channel/catalog.ex
@@ -3,11 +3,10 @@ defmodule Playwright.SDK.Channel.Catalog do
Provides storage and management of ChannelOwner instances.
`Catalog` implements `GenServer` to maintain state, while domain logic is
- expected to be handled within caller modules such as `Playwright.SDK.Channel`.
+ expected to be handled within caller modules such as the Channel module.
"""
use GenServer
import Playwright.SDK.Helpers.ErrorHandling
- alias Playwright.SDK.Channel
defstruct [:awaiting, :storage]
@@ -74,7 +73,7 @@ defmodule Playwright.SDK.Channel.Catalog do
| `guid` | param | `binary()` | GUID to look up |
| `:timeout` | option | `float()` | Maximum time to wait, in milliseconds. Defaults to `30_000` (30 seconds). |
"""
- @spec get(pid(), binary(), map()) :: struct() | {:error, Channel.Error.t()}
+ @spec get(pid(), binary(), map()) :: struct() | {:error, term()}
def get(catalog, guid, options \\ %{}) do
with_timeout(options, fn timeout ->
GenServer.call(catalog, {:get, {:guid, guid}}, timeout)
diff --git a/lib/playwright/sdk/channel/response.ex b/lib/playwright/sdk/channel/response.ex
index 211c4f59..c2351ae9 100644
--- a/lib/playwright/sdk/channel/response.ex
+++ b/lib/playwright/sdk/channel/response.ex
@@ -77,10 +77,31 @@ defmodule Playwright.SDK.Channel.Response do
value
end
+ defp parse([{:pdf, value}], _catalog) do
+ value
+ end
+
+ defp parse([{:snapshot, value}], _catalog) do
+ value
+ end
+
+ defp parse([{:traceName, value}], _catalog) do
+ %{traceName: value}
+ end
+
defp parse([{:cookies, cookies}], _catalog) do
cookies
end
+ # Storage state response: %{cookies: [...], origins: [...]}
+ defp parse([{:cookies, cookies}, {:origins, origins}], _catalog) do
+ %{cookies: cookies, origins: origins}
+ end
+
+ defp parse([{:origins, origins}, {:cookies, cookies}], _catalog) do
+ %{cookies: cookies, origins: origins}
+ end
+
defp parse([{:elements, value}], catalog) do
Enum.map(value, fn %{guid: guid} -> Channel.Catalog.get(catalog, guid) end)
end
@@ -93,6 +114,27 @@ defmodule Playwright.SDK.Channel.Response do
values
end
+ # Locator handler registration returns uid
+ defp parse([{:uid, uid}], _catalog) do
+ %{uid: uid}
+ end
+
+ defp parse([{:entries, entries}], _catalog) do
+ entries
+ end
+
+ defp parse([{:messages, messages}], _catalog) do
+ messages
+ end
+
+ defp parse([{:errors, errors}], _catalog) do
+ errors
+ end
+
+ defp parse([{:requests, requests}], catalog) do
+ Enum.map(requests, fn %{guid: guid} -> Channel.Catalog.get(catalog, guid) end)
+ end
+
defp parse([], _catalog) do
nil
end
diff --git a/lib/playwright/sdk/channel_owner.ex b/lib/playwright/sdk/channel_owner.ex
index 924da9a3..f019bd89 100644
--- a/lib/playwright/sdk/channel_owner.ex
+++ b/lib/playwright/sdk/channel_owner.ex
@@ -1,5 +1,6 @@
defmodule Playwright.SDK.ChannelOwner do
@moduledoc false
+ require Logger
@callback init(struct(), map()) :: {atom(), struct()}
@optional_callbacks init: 2
@@ -102,6 +103,10 @@ defmodule Playwright.SDK.ChannelOwner do
# simple "success": send "self"
{:ok, %{id: _}} ->
Channel.find(owner.session, {:guid, owner.guid})
+
+ # server returned a resource (e.g., Disposable) — treat as success
+ %{guid: _} ->
+ Channel.find(owner.session, {:guid, owner.guid})
end
end
@@ -111,7 +116,8 @@ defmodule Playwright.SDK.ChannelOwner do
end
defp with_latest(subject, task) do
- Channel.find(subject.session, {:guid, subject.guid}) |> task.()
+ # First call passes fresh state to task, second returns updated state
+ _ = Channel.find(subject.session, {:guid, subject.guid}) |> task.()
Channel.find(subject.session, {:guid, subject.guid})
end
end
@@ -133,8 +139,8 @@ defmodule Playwright.SDK.ChannelOwner do
String.to_existing_atom("Elixir.Playwright.#{type}")
rescue
ArgumentError ->
- message = "ChannelOwner of type #{inspect(type)} is not yet defined"
- exit(message)
+ Logger.warning("ChannelOwner of type #{inspect(type)} is not yet defined, using GenericChannelOwner")
+ Playwright.GenericChannelOwner
end
# ChannelOwner macros
diff --git a/lib/playwright/sdk/cli.ex b/lib/playwright/sdk/cli.ex
index c80881b2..66db185a 100644
--- a/lib/playwright/sdk/cli.ex
+++ b/lib/playwright/sdk/cli.ex
@@ -8,14 +8,79 @@ defmodule Playwright.SDK.CLI do
def install do
Logger.info("Installing playwright browsers and dependencies")
cli_path = config_cli() || default_cli()
- {result, exit_status} = System.cmd(cli_path, ["install", "--with-deps"])
- Logger.info(result)
- if exit_status != 0, do: raise("Failed to install playwright browsers")
+
+ case detect_os() do
+ :arch_linux ->
+ Logger.info("Detected Arch Linux, installing dependencies via pacman")
+ install_arch_dependencies()
+ {result, exit_status} = System.cmd(cli_path, ["install", "chromium", "firefox"])
+ Logger.info(result)
+ if exit_status != 0, do: raise("Failed to install playwright browsers")
+
+ :ubuntu ->
+ Logger.info("Detected Ubuntu/Debian, using --with-deps")
+ {result, exit_status} = System.cmd(cli_path, ["install", "--with-deps", "chromium", "firefox"])
+ Logger.info(result)
+ if exit_status != 0, do: raise("Failed to install playwright browsers")
+
+ :unknown ->
+ Logger.warning("Unknown OS, attempting install without system dependencies")
+ {result, exit_status} = System.cmd(cli_path, ["install", "chromium", "firefox"])
+ Logger.info(result)
+ if exit_status != 0, do: raise("Failed to install playwright browsers")
+ end
end
# private
# ----------------------------------------------------------------------------
+ defp detect_os do
+ cond do
+ File.exists?("/etc/arch-release") -> :arch_linux
+ File.exists?("/etc/debian_version") or File.exists?("/etc/lsb-release") -> :ubuntu
+ true -> :unknown
+ end
+ end
+
+ defp install_arch_dependencies do
+ # Playwright browser dependencies for Arch Linux
+ packages = [
+ "nss",
+ "nspr",
+ "atk",
+ "at-spi2-atk",
+ "cups",
+ "dbus",
+ "libxkbcommon",
+ "libxcomposite",
+ "libxdamage",
+ "libxrandr",
+ "mesa",
+ "pango",
+ "cairo",
+ "alsa-lib",
+ "libxshmfence"
+ ]
+
+ Logger.info("Installing system dependencies: #{Enum.join(packages, ", ")}")
+
+ # Check if running as root or with sudo
+ {result, exit_status} =
+ if System.get_env("EUID") == "0" or System.get_env("SUDO_USER") do
+ System.cmd("pacman", ["-S", "--needed", "--noconfirm" | packages], stderr_to_stdout: true)
+ else
+ Logger.warning("Not running as root, attempting with sudo")
+ System.cmd("sudo", ["pacman", "-S", "--needed", "--noconfirm" | packages], stderr_to_stdout: true)
+ end
+
+ Logger.info(result)
+
+ if exit_status != 0 do
+ Logger.error("Failed to install system dependencies")
+ Logger.warning("You may need to install these packages manually: sudo pacman -S #{Enum.join(packages, " ")}")
+ end
+ end
+
defp config_cli do
Application.get_env(:playwright, LaunchOptions)[:driver_path]
end
diff --git a/lib/playwright/sdk/config.ex b/lib/playwright/sdk/config.ex
index a0ba5485..b20aa389 100644
--- a/lib/playwright/sdk/config.ex
+++ b/lib/playwright/sdk/config.ex
@@ -239,20 +239,20 @@ defmodule Playwright.SDK.Config do
@doc false
@spec connect_options() :: connect_options
- def connect_options() do
+ def connect_options do
config_for(ConnectOptions, %Types.ConnectOptions{}) || %{}
end
@doc false
@spec launch_options() :: map()
- def launch_options() do
+ def launch_options do
config_for(LaunchOptions, %Types.LaunchOptions{}) || %{}
# |> clean()
end
@doc false
@spec playwright_test() :: Types.PlaywrightTest
- def playwright_test() do
+ def playwright_test do
config_for(PlaywrightTest, %Types.PlaywrightTest{})
# |> Map.from_struct()
end
diff --git a/lib/playwright/sdk/helpers/route_handler.ex b/lib/playwright/sdk/helpers/route_handler.ex
index e06da5a2..16e09b2e 100644
--- a/lib/playwright/sdk/helpers/route_handler.ex
+++ b/lib/playwright/sdk/helpers/route_handler.ex
@@ -40,8 +40,18 @@ defmodule Playwright.SDK.Helpers.RouteHandler do
defp prepare_matcher(%URLMatcher{regex: %Regex{} = regex}) do
%{
- regex_source: Regex.source(regex),
- regex_flags: Regex.opts(regex)
+ regexSource: Regex.source(regex),
+ regexFlags: regex_opts_to_flags(Regex.opts(regex))
}
end
+
+ defp regex_opts_to_flags(opts) do
+ Enum.map_join(opts, "", fn
+ :caseless -> "i"
+ :multiline -> "m"
+ :dotall -> "s"
+ :unicode -> "u"
+ _ -> ""
+ end)
+ end
end
diff --git a/lib/playwright/sdk/helpers/serialization.ex b/lib/playwright/sdk/helpers/serialization.ex
index d9982393..3b2dde0f 100644
--- a/lib/playwright/sdk/helpers/serialization.ex
+++ b/lib/playwright/sdk/helpers/serialization.ex
@@ -3,6 +3,10 @@ defmodule Playwright.SDK.Helpers.Serialization do
require Logger
import Playwright.SDK.Extra.Map
+ def deserialize({:error, _} = error) do
+ error
+ end
+
def deserialize(:ok) do
Logger.warning("Received `Playwright.SDK.Helpers.Serialization.deserialize/1` with `:ok`. It's unclear why this is happening")
:ok
@@ -36,6 +40,9 @@ defmodule Playwright.SDK.Helpers.Serialization do
%{s: string} ->
string
+ %{ta: %{b: binary, k: kind}} ->
+ %{binary: binary, kind: kind}
+
%{v: "null"} ->
nil
@@ -75,8 +82,8 @@ defmodule Playwright.SDK.Helpers.Serialization do
require Logger
- def serialize(value, _handles, _depth) when is_float(value) do
- Logger.error("not implemented: `serialize` for float: #{inspect(value)}")
+ def serialize(value, handles, _depth) when is_float(value) do
+ {%{n: value}, handles}
end
def serialize(value, handles, _depth) when is_integer(value) do
diff --git a/lib/playwright/sdk/helpers/web_socket_route_handler.ex b/lib/playwright/sdk/helpers/web_socket_route_handler.ex
new file mode 100644
index 00000000..7aab773b
--- /dev/null
+++ b/lib/playwright/sdk/helpers/web_socket_route_handler.ex
@@ -0,0 +1,57 @@
+defmodule Playwright.SDK.Helpers.WebSocketRouteHandler do
+ @moduledoc false
+
+ alias Playwright.SDK.Helpers.{URLMatcher, WebSocketRouteHandler}
+
+ defstruct [:matcher, :callback]
+
+ def new(%URLMatcher{} = matcher, callback) do
+ %__MODULE__{
+ matcher: matcher,
+ callback: callback
+ }
+ end
+
+ def handle(%WebSocketRouteHandler{callback: callback}, web_socket_route) do
+ Task.start(fn ->
+ # Run the handler
+ callback.(web_socket_route)
+ # Ensure the WebSocket is opened even if handler doesn't call connect_to_server
+ # This allows sending messages without a real server connection
+ Playwright.WebSocketRoute.ensure_opened(web_socket_route)
+ end)
+ end
+
+ def matches(%WebSocketRouteHandler{matcher: matcher}, url) do
+ URLMatcher.matches(matcher, url)
+ end
+
+ def prepare(handlers) when is_list(handlers) do
+ Enum.into(handlers, [], fn handler ->
+ prepare_matcher(handler.matcher)
+ end)
+ end
+
+ # Private
+
+ defp prepare_matcher(%URLMatcher{match: match}) when is_binary(match) do
+ %{glob: match}
+ end
+
+ defp prepare_matcher(%URLMatcher{regex: %Regex{} = regex}) do
+ %{
+ regexSource: Regex.source(regex),
+ regexFlags: regex_opts_to_flags(Regex.opts(regex))
+ }
+ end
+
+ defp regex_opts_to_flags(opts) do
+ Enum.map_join(opts, "", fn
+ :caseless -> "i"
+ :multiline -> "m"
+ :dotall -> "s"
+ :unicode -> "u"
+ _ -> ""
+ end)
+ end
+end
diff --git a/lib/playwright/sdk/transport/websocket.ex b/lib/playwright/sdk/transport/websocket.ex
index 69dcff3d..f9131abf 100644
--- a/lib/playwright/sdk/transport/websocket.ex
+++ b/lib/playwright/sdk/transport/websocket.ex
@@ -5,7 +5,8 @@ defmodule Playwright.SDK.Transport.WebSocket do
defstruct([
:process,
- :monitor
+ :monitor,
+ :stream_ref
])
# module API
@@ -16,21 +17,22 @@ defmodule Playwright.SDK.Transport.WebSocket do
with {:ok, process} <- :gun.open(to_charlist(uri.host), port(uri), %{connect_timeout: 30_000}),
{:ok, _protocol} <- :gun.await_up(process, :timer.seconds(5)),
- {:ok, _stream_ref} <- ws_upgrade(process, uri.path),
+ {:ok, stream_ref} <- ws_upgrade(process, uri.path),
:ok <- wait_for_ws_upgrade() do
monitor = Process.monitor(process)
%__MODULE__{
process: process,
- monitor: monitor
+ monitor: monitor,
+ stream_ref: stream_ref
}
else
error -> error
end
end
- def post(message, %{process: process}) do
- :gun.ws_send(process, {:text, message})
+ def post(message, %{process: process, stream_ref: stream_ref}) do
+ :gun.ws_send(process, stream_ref, {:text, message})
end
def parse({:gun_ws, _process, _stream_ref, {:text, message}}, state) do
diff --git a/lib/playwright/selectors.ex b/lib/playwright/selectors.ex
deleted file mode 100644
index 81386898..00000000
--- a/lib/playwright/selectors.ex
+++ /dev/null
@@ -1,18 +0,0 @@
-defmodule Playwright.Selectors do
- @moduledoc """
- ...
- """
- use Playwright.SDK.ChannelOwner
-
- @type options :: map()
-
- # ---
-
- # @spec register(Selectors.t(), binary(), any(), options()) :: :ok
- # def register(selectors, name, script, options \\ %{})
-
- # @spec set_testid_attribute(Selectors.t(), binary()) :: :ok
- # def set_testid_attribute(selectors, attribute_name)
-
- # ---
-end
diff --git a/lib/playwright/tracing.ex b/lib/playwright/tracing.ex
index d118b404..f635c89b 100644
--- a/lib/playwright/tracing.ex
+++ b/lib/playwright/tracing.ex
@@ -1,6 +1,137 @@
defmodule Playwright.Tracing do
@moduledoc """
- ...
+ Tracing provides methods for recording browser traces.
+
+ Traces can be opened with Playwright Trace Viewer for debugging.
+
+ ## Example
+
+ context = Browser.new_context(browser)
+ tracing = BrowserContext.tracing(context)
+ Tracing.start(tracing, %{screenshots: true, snapshots: true})
+ Page.goto(page, "https://example.com")
+ Tracing.stop(tracing, %{path: "trace.zip"})
"""
use Playwright.SDK.ChannelOwner
+ alias Playwright.SDK.Channel
+
+ @doc """
+ Start tracing.
+
+ ## Options
+
+ - `:name` - Trace file name prefix
+ - `:title` - Trace title shown in viewer
+ - `:screenshots` - Capture screenshots (default: false)
+ - `:snapshots` - Capture DOM snapshots (default: false)
+ """
+ @spec start(t(), map()) :: :ok | {:error, term()}
+ def start(%__MODULE__{session: session} = tracing, options \\ %{}) do
+ params = %{}
+ params = if options[:name], do: Map.put(params, :name, options[:name]), else: params
+ params = if options[:screenshots], do: Map.put(params, :screenshots, options[:screenshots]), else: params
+ params = if options[:snapshots], do: Map.put(params, :snapshots, options[:snapshots]), else: params
+
+ case Channel.post(session, {:guid, tracing.guid}, :tracing_start, params) do
+ {:ok, _} -> start_chunk(tracing, options)
+ :ok -> start_chunk(tracing, options)
+ nil -> start_chunk(tracing, options)
+ {:error, _} = error -> error
+ end
+ end
+
+ @doc """
+ Start a new trace chunk.
+
+ ## Options
+
+ - `:name` - Chunk name prefix
+ - `:title` - Chunk title in viewer
+ """
+ @spec start_chunk(t(), map()) :: :ok | {:error, term()}
+ def start_chunk(%__MODULE__{session: session} = tracing, options \\ %{}) do
+ params = %{}
+ params = if options[:name], do: Map.put(params, :name, options[:name]), else: params
+ params = if options[:title], do: Map.put(params, :title, options[:title]), else: params
+
+ case Channel.post(session, {:guid, tracing.guid}, :tracing_start_chunk, params) do
+ %{traceName: _} -> :ok
+ {:ok, _} -> :ok
+ :ok -> :ok
+ {:error, _} = error -> error
+ end
+ end
+
+ @doc """
+ Stop tracing and export trace.
+
+ ## Options
+
+ - `:path` - File path to save trace zip
+ """
+ @spec stop(t(), map()) :: :ok | {:error, term()}
+ def stop(%__MODULE__{session: session} = tracing, options \\ %{}) do
+ stop_chunk(tracing, options)
+ Channel.post(session, {:guid, tracing.guid}, :tracing_stop, %{})
+ :ok
+ end
+
+ @doc """
+ Stop current trace chunk and export.
+
+ ## Options
+
+ - `:path` - File path to save trace zip
+ """
+ @spec stop_chunk(t(), map()) :: :ok | {:error, term()}
+ def stop_chunk(%__MODULE__{session: session} = tracing, options \\ %{}) do
+ mode = if options[:path], do: "archive", else: "discard"
+
+ case Channel.post(session, {:guid, tracing.guid}, :tracing_stop_chunk, %{mode: mode}) do
+ %Playwright.Artifact{} = artifact ->
+ if options[:path] do
+ Playwright.Artifact.save_as(artifact, options[:path])
+ else
+ :ok
+ end
+
+ _ ->
+ :ok
+ end
+ end
+
+ @doc """
+ Creates a named group in the trace.
+
+ Groups help organize actions in the trace viewer.
+
+ ## Options
+
+ - `:location` - Custom location map with `:file`, `:line`, `:column` keys
+ """
+ @spec group(t(), binary(), map()) :: :ok | {:error, term()}
+ def group(%__MODULE__{session: session} = tracing, name, options \\ %{}) do
+ params = %{name: name}
+ params = if options[:location], do: Map.put(params, :location, options[:location]), else: params
+
+ case Channel.post(session, {:guid, tracing.guid}, :tracing_group, params) do
+ {:ok, _} -> :ok
+ :ok -> :ok
+ nil -> :ok
+ {:error, _} = error -> error
+ end
+ end
+
+ @doc """
+ Ends the current group in the trace.
+ """
+ @spec group_end(t()) :: :ok | {:error, term()}
+ def group_end(%__MODULE__{session: session} = tracing) do
+ case Channel.post(session, {:guid, tracing.guid}, :tracing_group_end, %{}) do
+ {:ok, _} -> :ok
+ :ok -> :ok
+ nil -> :ok
+ {:error, _} = error -> error
+ end
+ end
end
diff --git a/lib/playwright/web_socket_route.ex b/lib/playwright/web_socket_route.ex
new file mode 100644
index 00000000..524c9f51
--- /dev/null
+++ b/lib/playwright/web_socket_route.ex
@@ -0,0 +1,306 @@
+defmodule Playwright.WebSocketRoute do
+ @moduledoc """
+ Provides methods for handling WebSocket connections during routing.
+
+ When a WebSocket route is set up using `Page.route_web_socket/3` or
+ `BrowserContext.route_web_socket/3`, the handler receives a `WebSocketRoute`
+ instance that can be used to intercept, modify, or mock WebSocket communication.
+
+ ## Example
+
+ Page.route_web_socket(page, "**/ws", fn ws_route ->
+ # Connect to the actual server and proxy messages
+ server = WebSocketRoute.connect_to_server(ws_route)
+
+ # Handle messages from the page
+ WebSocketRoute.on_message(ws_route, fn message ->
+ IO.puts("Page sent: \#{inspect(message)}")
+ # Forward to server
+ WebSocketRoute.Server.send(server, message)
+ end)
+
+ # Handle messages from the server
+ WebSocketRoute.Server.on_message(server, fn message ->
+ IO.puts("Server sent: \#{inspect(message)}")
+ # Forward to page
+ WebSocketRoute.send(ws_route, message)
+ end)
+ end)
+ """
+
+ use Playwright.SDK.ChannelOwner
+
+ @property :url
+
+ @typedoc "A WebSocket message, either text (binary) or binary data."
+ @type message :: binary()
+
+ @typedoc "A message handler callback."
+ @type message_handler :: (message() -> any())
+
+ @typedoc "A close handler callback."
+ @type close_handler :: (integer() | nil, binary() | nil -> any())
+
+ @doc """
+ Sends a message to the page.
+
+ ## Arguments
+
+ | key/name | type | description |
+ | --------- | --------- | ----------- |
+ | `route` | `t()` | The WebSocket route |
+ | `message` | `binary()` | Message to send (text or binary) |
+ """
+ @spec send(t(), message()) :: :ok | {:error, term()}
+ def send(%__MODULE__{session: session, guid: guid}, message) do
+ {msg, is_base64} = encode_message(message)
+
+ case Channel.post(session, {:guid, guid}, :send_to_page, %{message: msg, isBase64: is_base64}) do
+ {:error, _} = error -> error
+ _ -> :ok
+ end
+ end
+
+ @doc """
+ Closes the WebSocket connection from the page side.
+
+ ## Options
+
+ | key/name | type | description |
+ | -------- | --------- | ----------- |
+ | `:code` | `integer()` | Close code (default: 1000) |
+ | `:reason` | `binary()` | Close reason |
+ """
+ @spec close(t(), map()) :: :ok | {:error, term()}
+ def close(%__MODULE__{session: session, guid: guid}, options \\ %{}) do
+ params = %{
+ code: options[:code],
+ reason: options[:reason],
+ wasClean: true
+ }
+
+ case Channel.post(session, {:guid, guid}, :close_page, params) do
+ {:error, _} = error -> error
+ _ -> :ok
+ end
+ end
+
+ @doc """
+ Connects to the actual WebSocket server.
+
+ Returns a `Playwright.WebSocketRoute.Server` struct that can be used to
+ interact with the server side of the connection.
+ """
+ @spec connect_to_server(t()) :: Playwright.WebSocketRoute.Server.t()
+ def connect_to_server(%__MODULE__{session: session, guid: guid} = route) do
+ Channel.post(session, {:guid, guid}, :connect, %{})
+ Playwright.WebSocketRoute.Server.new(route)
+ end
+
+ @doc """
+ Ensures the WebSocket is open without connecting to the server.
+
+ This allows sending messages to the page even when not connected to a real server.
+ """
+ @spec ensure_opened(t()) :: :ok | {:error, term()}
+ def ensure_opened(%__MODULE__{session: session, guid: guid}) do
+ case Channel.post(session, {:guid, guid}, :ensure_opened, %{}) do
+ {:error, _} = error -> error
+ _ -> :ok
+ end
+ end
+
+ # Callbacks are stored in ETS for the route handlers
+ # See Playwright.WebSocketRouteHandlers module
+
+ @doc """
+ Registers a handler for messages received from the page.
+
+ If no handler is set, messages are automatically forwarded to the server
+ (if connected via `connect_to_server/1`).
+ """
+ @spec on_message(t(), message_handler()) :: :ok
+ def on_message(%__MODULE__{guid: guid}, handler) when is_function(handler, 1) do
+ Playwright.WebSocketRouteHandlers.set_page_message_handler(guid, handler)
+ end
+
+ @doc """
+ Registers a handler for when the page closes the WebSocket.
+
+ The handler receives the close code and reason.
+ """
+ @spec on_close(t(), close_handler()) :: :ok
+ def on_close(%__MODULE__{guid: guid}, handler) when is_function(handler, 2) do
+ Playwright.WebSocketRouteHandlers.set_page_close_handler(guid, handler)
+ end
+
+ # ChannelOwner callback
+ def init(%__MODULE__{session: session} = route, _initializer) do
+ # Bind events for this WebSocket route
+ Channel.bind(session, {:guid, route.guid}, :message_from_page, fn %{params: params} ->
+ handle_message_from_page(route.guid, params)
+ :ok
+ end)
+
+ Channel.bind(session, {:guid, route.guid}, :message_from_server, fn %{params: params} ->
+ handle_message_from_server(route.guid, params, session)
+ :ok
+ end)
+
+ Channel.bind(session, {:guid, route.guid}, :close_page, fn %{params: params} ->
+ handle_close_page(route.guid, params, session)
+ :ok
+ end)
+
+ Channel.bind(session, {:guid, route.guid}, :close_server, fn %{params: params} ->
+ handle_close_server(route.guid, params, session)
+ :ok
+ end)
+
+ {:ok, route}
+ end
+
+ # Private helpers
+
+ defp encode_message(message) when is_binary(message) do
+ if String.valid?(message) do
+ {message, false}
+ else
+ {Base.encode64(message), true}
+ end
+ end
+
+ defp decode_message(message, true), do: Base.decode64!(message)
+ defp decode_message(message, false), do: message
+
+ defp handle_message_from_page(guid, %{message: message, isBase64: is_base64}) do
+ decoded = decode_message(message, is_base64)
+
+ case Playwright.WebSocketRouteHandlers.get_page_message_handler(guid) do
+ nil ->
+ # No handler - auto-forward to server if connected (async to avoid deadlock)
+ Task.start(fn ->
+ Playwright.WebSocketRouteHandlers.forward_to_server(guid, message, is_base64)
+ end)
+
+ handler ->
+ Task.start(fn -> handler.(decoded) end)
+ end
+ end
+
+ defp handle_message_from_server(guid, %{message: message, isBase64: is_base64}, session) do
+ decoded = decode_message(message, is_base64)
+
+ case Playwright.WebSocketRouteHandlers.get_server_message_handler(guid) do
+ nil ->
+ # No handler - auto-forward to page (async to avoid deadlock)
+ Task.start(fn ->
+ Channel.post(session, {:guid, guid}, :send_to_page, %{message: message, isBase64: is_base64})
+ end)
+
+ handler ->
+ Task.start(fn -> handler.(decoded) end)
+ end
+ end
+
+ defp handle_close_page(guid, %{code: code, reason: reason, wasClean: was_clean}, session) do
+ case Playwright.WebSocketRouteHandlers.get_page_close_handler(guid) do
+ nil ->
+ # No handler - auto-forward to server (async to avoid deadlock)
+ Task.start(fn ->
+ Channel.post(session, {:guid, guid}, :close_server, %{code: code, reason: reason, wasClean: was_clean})
+ end)
+
+ handler ->
+ Task.start(fn -> handler.(code, reason) end)
+ end
+ end
+
+ defp handle_close_server(guid, %{code: code, reason: reason, wasClean: was_clean}, session) do
+ case Playwright.WebSocketRouteHandlers.get_server_close_handler(guid) do
+ nil ->
+ # No handler - auto-forward to page (async to avoid deadlock)
+ Task.start(fn ->
+ Channel.post(session, {:guid, guid}, :close_page, %{code: code, reason: reason, wasClean: was_clean})
+ end)
+
+ handler ->
+ Task.start(fn -> handler.(code, reason) end)
+ end
+
+ # Cleanup handlers when connection closes
+ Playwright.WebSocketRouteHandlers.cleanup(guid)
+ end
+end
+
+defmodule Playwright.WebSocketRoute.Server do
+ @moduledoc """
+ Represents the server side of a WebSocket route connection.
+
+ Returned by `Playwright.WebSocketRoute.connect_to_server/1`.
+ """
+
+ defstruct [:route]
+
+ @type t :: %__MODULE__{route: Playwright.WebSocketRoute.t()}
+
+ @doc false
+ def new(route), do: %__MODULE__{route: route}
+
+ @doc """
+ Sends a message to the actual WebSocket server.
+ """
+ @spec send(t(), binary()) :: :ok | {:error, term()}
+ def send(%__MODULE__{route: %{session: session, guid: guid}}, message) do
+ {msg, is_base64} = encode_message(message)
+
+ case Playwright.SDK.Channel.post(session, {:guid, guid}, :send_to_server, %{
+ message: msg,
+ isBase64: is_base64
+ }) do
+ {:error, _} = error -> error
+ _ -> :ok
+ end
+ end
+
+ @doc """
+ Closes the connection to the actual WebSocket server.
+ """
+ @spec close(t(), map()) :: :ok | {:error, term()}
+ def close(%__MODULE__{route: %{session: session, guid: guid}}, options \\ %{}) do
+ params = %{
+ code: options[:code],
+ reason: options[:reason],
+ wasClean: true
+ }
+
+ case Playwright.SDK.Channel.post(session, {:guid, guid}, :close_server, params) do
+ {:error, _} = error -> error
+ _ -> :ok
+ end
+ end
+
+ @doc """
+ Registers a handler for messages received from the server.
+ """
+ @spec on_message(t(), Playwright.WebSocketRoute.message_handler()) :: :ok
+ def on_message(%__MODULE__{route: %{guid: guid}}, handler) when is_function(handler, 1) do
+ Playwright.WebSocketRouteHandlers.set_server_message_handler(guid, handler)
+ end
+
+ @doc """
+ Registers a handler for when the server closes the WebSocket.
+ """
+ @spec on_close(t(), Playwright.WebSocketRoute.close_handler()) :: :ok
+ def on_close(%__MODULE__{route: %{guid: guid}}, handler) when is_function(handler, 2) do
+ Playwright.WebSocketRouteHandlers.set_server_close_handler(guid, handler)
+ end
+
+ defp encode_message(message) when is_binary(message) do
+ if String.valid?(message) do
+ {message, false}
+ else
+ {Base.encode64(message), true}
+ end
+ end
+end
diff --git a/lib/playwright/web_socket_route_handlers.ex b/lib/playwright/web_socket_route_handlers.ex
new file mode 100644
index 00000000..528abe84
--- /dev/null
+++ b/lib/playwright/web_socket_route_handlers.ex
@@ -0,0 +1,119 @@
+defmodule Playwright.WebSocketRouteHandlers do
+ @moduledoc false
+ # ETS-based storage for WebSocket route handlers and state.
+ # Used by WebSocketRoute for message and close event handling.
+
+ alias Playwright.SDK.Channel
+
+ @table :playwright_websocket_route_handlers
+
+ @doc false
+ def ensure_table do
+ case :ets.whereis(@table) do
+ :undefined ->
+ :ets.new(@table, [:set, :public, :named_table])
+
+ _ ->
+ :ok
+ end
+ end
+
+ # Page-side handlers
+
+ @doc false
+ def set_page_message_handler(guid, handler) do
+ ensure_table()
+ update_handlers(guid, :page_message, handler)
+ end
+
+ @doc false
+ def get_page_message_handler(guid) do
+ get_handler(guid, :page_message)
+ end
+
+ @doc false
+ def set_page_close_handler(guid, handler) do
+ ensure_table()
+ update_handlers(guid, :page_close, handler)
+ end
+
+ @doc false
+ def get_page_close_handler(guid) do
+ get_handler(guid, :page_close)
+ end
+
+ # Server-side handlers
+
+ @doc false
+ def set_server_message_handler(guid, handler) do
+ ensure_table()
+ update_handlers(guid, :server_message, handler)
+ end
+
+ @doc false
+ def get_server_message_handler(guid) do
+ get_handler(guid, :server_message)
+ end
+
+ @doc false
+ def set_server_close_handler(guid, handler) do
+ ensure_table()
+ update_handlers(guid, :server_close, handler)
+ end
+
+ @doc false
+ def get_server_close_handler(guid) do
+ get_handler(guid, :server_close)
+ end
+
+ # Connection state
+
+ @doc false
+ def set_connected(guid, session) do
+ ensure_table()
+ update_handlers(guid, :session, session)
+ end
+
+ @doc false
+ def forward_to_server(guid, message, is_base64) do
+ case get_handler(guid, :session) do
+ nil ->
+ :ok
+
+ session ->
+ Channel.post(session, {:guid, guid}, :send_to_server, %{message: message, isBase64: is_base64})
+ end
+ end
+
+ # Cleanup
+
+ @doc false
+ def cleanup(guid) do
+ ensure_table()
+ :ets.delete(@table, guid)
+ end
+
+ # Private helpers
+
+ defp get_handler(guid, key) do
+ ensure_table()
+
+ case :ets.lookup(@table, guid) do
+ [{^guid, handlers}] -> Map.get(handlers, key)
+ [] -> nil
+ end
+ end
+
+ defp update_handlers(guid, key, value) do
+ ensure_table()
+
+ handlers =
+ case :ets.lookup(@table, guid) do
+ [{^guid, existing}] -> existing
+ [] -> %{}
+ end
+
+ :ets.insert(@table, {guid, Map.put(handlers, key, value)})
+ :ok
+ end
+end
diff --git a/lib/playwright/worker.ex b/lib/playwright/worker.ex
index 2029a81a..365e1704 100644
--- a/lib/playwright/worker.ex
+++ b/lib/playwright/worker.ex
@@ -1,24 +1,27 @@
defmodule Playwright.Worker do
@moduledoc """
- ...
+ The Worker class represents a [WebWorker](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API).
"""
use Playwright.SDK.ChannelOwner
- # @spec evaluate(Worker.t(), function() | binary(), EvaluationArgument.t()) :: Serializable.t()
- # def evaluate(worker, page_function, arg \\ nil)
+ @property :url
- # @spec evaluate_handle(Worker.t(), function() | binary(), EvaluationArgument.t()) :: JSHandle.t()
- # def evaluate_handle(worker, page_function, arg \\ nil)
+ def init(%{session: session} = worker, _initializer) do
+ Channel.bind(session, {:guid, worker.guid}, :close, fn event ->
+ {:patch, %{event.target | is_closed: true}}
+ end)
- # @spec expect_event(t(), binary(), function(), options()) :: map()
- # def expect_event(worker, event, predicate \\ nil, options \\ %{})
- # ...delegate wait_for_event -> expect_event
+ {:ok, worker}
+ end
- # on(...):
- # - close
- # @spec on(t(), binary(), function()) :: nil
- # def on(worker, event, callback)
+ @doc """
+ Registers a callback for the given event.
- # @spec url(Worker.t()) :: binary()
- # def url(worker)
+ Supported events: `:close`, `:console`.
+ """
+ @spec on(t(), atom(), function()) :: t()
+ def on(%{session: session} = worker, event, callback) do
+ Channel.bind(session, {:guid, worker.guid}, event, callback)
+ worker
+ end
end
diff --git a/lib/playwright_test/case.ex b/lib/playwright_test/case.ex
index 19ae1118..49cac536 100644
--- a/lib/playwright_test/case.ex
+++ b/lib/playwright_test/case.ex
@@ -25,9 +25,7 @@ defmodule PlaywrightTest.Case do
describe "features w/out `page` context" do
@tag exclude: [:page]
test "goes to a page", %{browser: browser} do
- page =
- browser
- |> Playwright.Browser.new_page()
+ {:ok, page} = Playwright.Browser.new_page(browser)
text =
page
@@ -67,7 +65,7 @@ defmodule PlaywrightTest.Case do
context
false ->
- page = Playwright.Browser.new_page(context.browser)
+ {:ok, page} = Playwright.Browser.new_page(context.browser)
on_exit(:ok, fn ->
Playwright.Page.close(page)
diff --git a/man/guides/contributing.md b/man/guides/contributing.md
new file mode 100644
index 00000000..ca4b6da5
--- /dev/null
+++ b/man/guides/contributing.md
@@ -0,0 +1,175 @@
+# Contributing
+
+This document outlines known issues, improvement opportunities, and areas where contributions are welcome.
+
+## Critical Issues
+
+These should be addressed with priority:
+
+### Error Handling in `Browser.new_page/2`
+
+**File:** `lib/playwright/browser.ex:155`
+
+The `new_page/2` function doesn't handle errors from `new_context/2` or `BrowserContext.new_page/1`:
+
+```elixir
+def new_page(%Browser{session: session} = browser, options) do
+ context = new_context(browser, options)
+ page = BrowserContext.new_page(context)
+ # crashes if context or page is an error tuple
+end
+```
+
+**Fix:** Wrap in `with` statement to handle error tuples.
+
+### Unsafe Atom Creation in `Page.on/3`
+
+**File:** `lib/playwright/page.ex:501`
+
+```elixir
+def on(%Page{} = page, event, callback) when is_binary(event) do
+ on(page, String.to_atom(event), callback)
+end
+```
+
+**Risk:** Arbitrary string-to-atom conversion can exhaust atom table.
+
+**Fix:** Validate against known event atoms or use `String.to_existing_atom/1`.
+
+### Module Resolution Exit
+
+**File:** `lib/playwright/sdk/channel_owner.ex:133`
+
+```elixir
+defp module(%{type: type}) do
+ String.to_existing_atom("Elixir.Playwright.#{type}")
+rescue
+ ArgumentError ->
+ exit("ChannelOwner of type #{inspect(type)} is not yet defined")
+end
+```
+
+**Fix:** Return `{:error, reason}` instead of calling `exit/1`.
+
+## Code Quality Improvements
+
+### Naive Glob Implementation
+
+**File:** `lib/playwright/sdk/helpers/url_matcher.ex:50`
+
+Current implementation only handles `**` patterns:
+
+```elixir
+defp glob_to_regex(pattern) do
+ String.replace(pattern, ~r/\*{2,}/, ".*")
+end
+```
+
+**Missing:** `*` (single segment), `?` (single char), `[abc]` (character classes).
+
+**Suggestion:** Use a proper glob library like [path_glob](https://github.com/jonleighton/path_glob).
+
+### Duplicate Channel.find Call
+
+**File:** `lib/playwright/sdk/channel_owner.ex:108`
+
+```elixir
+defp with_latest(subject, task) do
+ Channel.find(subject.session, {:guid, subject.guid}) |> task.()
+ Channel.find(subject.session, {:guid, subject.guid}) # Called twice
+end
+```
+
+**Fix:** Store result of first call and return it.
+
+### HACK Comments
+
+These indicate fragile code that may break with Playwright updates:
+
+| File | Line | Description |
+|------|------|-------------|
+| `lib/playwright/route.ex` | 24, 47 | Workaround for v1.33.0 changes |
+| `lib/playwright/page.ex` | 513 | Event name conversion hack |
+
+## Dead Code
+
+These modules are empty stubs and can be removed:
+
+| File | Notes |
+|------|-------|
+| `lib/playwright/local_utils.ex` | Marked "obsolete?" - 6 lines |
+| `lib/playwright/fetch_request.ex` | Marked "obsolete?" - 6 lines |
+
+## Unimplemented Features
+
+### Config Options
+
+**File:** `lib/playwright/sdk/config.ex`
+
+These options are documented but silently ignored:
+
+- `env` - Environment variables for browser process
+- `downloads_path` - Custom downloads directory
+
+### Skipped Tests
+
+| File | Reason |
+|------|--------|
+| `test/api/page/accessibility_test.exs` | Needs `Page.wait_for_function` implementation |
+| `test/api/browser_context/expect_test.exs` | Multiple tests unreachable |
+
+## TODO/FIXME Items
+
+| File | Line | Comment |
+|------|------|---------|
+| `lib/playwright/route.ex` | 22 | "figure out what's up with is_fallback" |
+| `lib/playwright/browser.ex` | 159 | "handle the following, for page" |
+| `lib/playwright/frame.ex` | 934 | FIXME: incorrect documentation |
+| `lib/playwright/sdk/helpers/url_matcher.ex` | 49 | Replace with proper glob library |
+| `lib/playwright/api_request_context.ex` | 66 | "move to APIResponse.body, probably" |
+| `lib/playwright/sdk/channel/event.ex` | 14 | "consider promoting params as top-level fields" |
+
+## Refactoring Candidates
+
+Large files that could benefit from splitting:
+
+| File | Lines | Suggestion |
+|------|-------|------------|
+| `lib/playwright/locator.ex` | 1365 | Split into Locator.Input, Locator.Navigation, etc. |
+| `lib/playwright/frame.ex` | 1044 | Extract common patterns |
+| `lib/playwright/page.ex` | 778 | Well-organized but large |
+
+## Documentation Gaps
+
+- ~516 public functions lack `@doc` annotations
+- Many SDK modules have `@moduledoc false` but could use internal docs
+- Commented-out function stubs (527 total) indicate unimplemented API surface
+
+## Running Tests
+
+```bash
+# Run all tests
+mix test
+
+# Run with browser visible
+PLAYWRIGHT_HEADLESS=false mix test
+
+# Run specific test file
+mix test test/api/page_test.exs
+```
+
+## Code Style
+
+The project uses:
+
+- `mix format` for formatting
+- `mix credo` for linting
+- `mix dialyzer` for type checking
+
+Run all checks before submitting PRs:
+
+```bash
+mix format --check-formatted
+mix credo --strict
+mix dialyzer
+```
diff --git a/man/guides/feature_parity.md b/man/guides/feature_parity.md
new file mode 100644
index 00000000..352d83a4
--- /dev/null
+++ b/man/guides/feature_parity.md
@@ -0,0 +1,690 @@
+# Playwright Feature Parity Tracking
+
+This document tracks the implementation status of Playwright features in playwright-elixir compared to the official TypeScript client.
+
+**Current Target:** Playwright 1.59.1 (upgrading from 1.49.1)
+**Reference:** `/home/tristan/sources/playwright/packages/playwright-core/src/client/`
+
+**Legend:**
+- [x] Implemented
+- [~] Partially implemented / stubbed
+- [ ] Not implemented
+- [!] Priority implementation candidate
+
+---
+
+## Page Module
+
+**File:** `lib/playwright/page.ex`
+**Reference:** `page.ts`
+
+### Navigation & Loading
+
+| Method | Status | Notes |
+|--------|--------|-------|
+| `goto(url, options)` | [x] | |
+| `reload(options)` | [x] | |
+| `goBack(options)` | [x] | |
+| `goForward(options)` | [x] | |
+| `waitForLoadState(state, options)` | [x] | |
+| `waitForNavigation(options)` | [x] | |
+| `waitForURL(url, options)` | [x] | Polling-based implementation |
+| `waitForRequest(urlOrPredicate, options)` | [x] | |
+| `waitForResponse(urlOrPredicate, options)` | [x] | |
+| `waitForEvent(event, options)` | [~] | As `expect_event` |
+| `bringToFront()` | [x] | |
+
+### Content & State
+
+| Method | Status | Notes |
+|--------|--------|-------|
+| `url()` | [x] | |
+| `title()` | [x] | |
+| `content()` | [x] | Get page HTML |
+| `setContent(html, options)` | [x] | |
+| `setViewportSize(size)` | [x] | |
+| `viewportSize()` | [x] | |
+| `isClosed()` | [~] | Via `is_closed` field |
+| `close(options)` | [x] | |
+| `opener()` | [x] | |
+
+### Frames
+
+| Method | Status | Notes |
+|--------|--------|-------|
+| `mainFrame()` | [~] | Via `main_frame` field |
+| `frames()` | [x] | |
+| `frame(selector)` | [x] | Get frame by name/url |
+| `frameLocator(selector)` | [x] | |
+
+### Locators (getBy* methods)
+
+| Method | Status | Notes |
+|--------|--------|-------|
+| `locator(selector, options)` | [x] | |
+| `getByText(text, options)` | [x] | |
+| `getByRole(role, options)` | [x] | |
+| `getByTestId(testId)` | [x] | |
+| `getByLabel(text, options)` | [x] | |
+| `getByPlaceholder(text, options)` | [x] | |
+| `getByAltText(text, options)` | [x] | |
+| `getByTitle(text, options)` | [x] | |
+
+### Actions (selector-based)
+
+| Method | Status | Notes |
+|--------|--------|-------|
+| `click(selector, options)` | [x] | |
+| `dblclick(selector, options)` | [x] | |
+| `tap(selector, options)` | [x] | |
+| `fill(selector, value, options)` | [x] | |
+| `type(selector, text, options)` | [x] | Deprecated in 1.50, use fill/pressSequentially |
+| `press(selector, key, options)` | [x] | |
+| `hover(selector, options)` | [x] | |
+| `focus(selector, options)` | [x] | |
+| `selectOption(selector, values, options)` | [x] | |
+| `check(selector, options)` | [x] | |
+| `uncheck(selector, options)` | [x] | |
+| `setChecked(selector, checked, options)` | [x] | |
+| `setInputFiles(selector, files, options)` | [x] | |
+| `dragAndDrop(source, target, options)` | [x] | |
+| `dispatchEvent(selector, type, eventInit, options)` | [x] | |
+
+### Query Methods
+
+| Method | Status | Notes |
+|--------|--------|-------|
+| `textContent(selector, options)` | [x] | |
+| `innerText(selector, options)` | [x] | |
+| `innerHTML(selector, options)` | [x] | |
+| `getAttribute(selector, name, options)` | [x] | |
+| `inputValue(selector, options)` | [x] | |
+| `isChecked(selector, options)` | [x] | |
+| `isDisabled(selector, options)` | [x] | |
+| `isEditable(selector, options)` | [x] | |
+| `isEnabled(selector, options)` | [x] | |
+| `isHidden(selector, options)` | [x] | |
+| `isVisible(selector, options)` | [x] | |
+| `waitForSelector(selector, options)` | [x] | |
+
+### JavaScript Evaluation
+
+| Method | Status | Notes |
+|--------|--------|-------|
+| `evaluate(expression, arg)` | [x] | |
+| `evaluateHandle(expression, arg)` | [x] | |
+| `evalOnSelector(selector, expression, arg)` | [x] | |
+| `evalOnSelectorAll(selector, expression, arg)` | [x] | |
+| `exposeFunction(name, callback)` | [x] | |
+| `exposeBinding(name, callback, options)` | [x] | |
+| `addInitScript(script, arg)` | [x] | |
+| `addScriptTag(options)` | [x] | |
+| `addStyleTag(options)` | [x] | |
+
+### Routing & Network
+
+| Method | Status | Notes |
+|--------|--------|-------|
+| `route(url, handler, options)` | [x] | |
+| `unroute(url, handler)` | [x] | |
+| `unrouteAll(options)` | [x] | |
+| `routeFromHAR(har, options)` | [ ] | Requires LocalUtils |
+| `routeWebSocket(url, handler)` | [x] | |
+| `setExtraHTTPHeaders(headers)` | [x] | |
+
+### Screenshots & Media
+
+| Method | Status | Notes |
+|--------|--------|-------|
+| `screenshot(options)` | [x] | |
+| `pdf(options)` | [x] | Chromium only |
+| `video()` | [x] | |
+| `emulateMedia(options)` | [x] | New `contrast` option in 1.56 |
+
+### Events
+
+| Method | Status | Notes |
+|--------|--------|-------|
+| `on(event, callback)` | [x] | With event validation |
+| `waitForEvent(event, options)` | [~] | As `expect_event` |
+| `consoleMessages()` | [x] | `console_messages/1` |
+| `pageErrors()` | [x] | `page_errors/1` |
+| `requests()` | [x] | `requests/1` — New in 1.56 |
+| `ariaSnapshot()` | [x] | `aria_snapshot/2` — New in 1.59 |
+| `screencast()` | [ ] | Deferred — new Screencast ChannelOwner (1.50) |
+
+### Locator Handlers
+
+| Method | Status | Notes |
+|--------|--------|-------|
+| `addLocatorHandler(locator, handler, options)` | [x] | For auto-dismiss dialogs |
+| `removeLocatorHandler(locator)` | [x] | |
+
+### Timeouts
+
+| Method | Status | Notes |
+|--------|--------|-------|
+| `setDefaultTimeout(timeout)` | [x] | |
+| `setDefaultNavigationTimeout(timeout)` | [x] | |
+
+### Other
+
+| Method | Status | Notes |
+|--------|--------|-------|
+| `context()` | [x] | |
+| `pause()` | [x] | Opens Playwright Inspector |
+| `requestGC()` | [x] | `request_gc/1` |
+
+---
+
+## Locator Module
+
+**File:** `lib/playwright/locator.ex`
+**Reference:** `locator.ts`
+
+### Creation & Chaining
+
+| Method | Status | Notes |
+|--------|--------|-------|
+| `locator(selector, options)` | [x] | |
+| `first()` | [x] | |
+| `last()` | [x] | |
+| `nth(index)` | [x] | |
+| `filter(options)` | [x] | has_text, has_not_text, has, has_not, visible |
+| `and(locator)` | [x] | As `and_` |
+| `or(locator)` | [x] | As `or_` |
+| `getByText(text, options)` | [x] | |
+| `getByRole(role, options)` | [x] | |
+| `getByTestId(testId)` | [x] | |
+| `getByLabel(text, options)` | [x] | |
+| `getByPlaceholder(text, options)` | [x] | |
+| `getByAltText(text, options)` | [x] | |
+| `getByTitle(text, options)` | [x] | |
+| `frameLocator(selector)` | [x] | |
+| `contentFrame()` | [x] | |
+
+### Actions
+
+| Method | Status | Notes |
+|--------|--------|-------|
+| `click(options)` | [x] | |
+| `dblclick(options)` | [x] | |
+| `tap(options)` | [x] | |
+| `fill(value, options)` | [x] | |
+| `clear(options)` | [x] | |
+| `type(text, options)` | [x] | Deprecated in 1.50, use fill/pressSequentially |
+| `pressSequentially(text, options)` | [x] | As `press_sequentially` |
+| `press(key, options)` | [x] | |
+| `hover(options)` | [x] | |
+| `focus(options)` | [x] | |
+| `blur(options)` | [x] | |
+| `check(options)` | [x] | |
+| `uncheck(options)` | [x] | |
+| `setChecked(checked, options)` | [x] | |
+| `selectOption(values, options)` | [x] | |
+| `selectText(options)` | [x] | |
+| `setInputFiles(files, options)` | [x] | |
+| `dragTo(target, options)` | [x] | |
+| `scrollIntoViewIfNeeded(options)` | [~] | As `scroll_into_view` |
+| `dispatchEvent(type, eventInit, options)` | [x] | |
+| `highlight()` | [x] | |
+
+### Query Methods
+
+| Method | Status | Notes |
+|--------|--------|-------|
+| `count()` | [x] | |
+| `all()` | [x] | |
+| `textContent(options)` | [x] | |
+| `innerText(options)` | [x] | |
+| `innerHTML(options)` | [x] | |
+| `getAttribute(name, options)` | [x] | |
+| `inputValue(options)` | [x] | |
+| `boundingBox(options)` | [x] | |
+| `allTextContents()` | [x] | |
+| `allInnerTexts()` | [x] | |
+
+### State Checks
+
+| Method | Status | Notes |
+|--------|--------|-------|
+| `isChecked(options)` | [x] | |
+| `isDisabled(options)` | [x] | |
+| `isEditable(options)` | [x] | |
+| `isEnabled(options)` | [x] | |
+| `isHidden(options)` | [x] | |
+| `isVisible(options)` | [x] | |
+
+### Evaluation
+
+| Method | Status | Notes |
+|--------|--------|-------|
+| `evaluate(expression, arg, options)` | [x] | |
+| `evaluateAll(expression, arg)` | [x] | |
+| `evaluateHandle(expression, arg, options)` | [x] | |
+
+### Screenshots & Handles
+
+| Method | Status | Notes |
+|--------|--------|-------|
+| `screenshot(options)` | [x] | |
+| `elementHandle(options)` | [x] | |
+| `elementHandles()` | [x] | |
+| `ariaSnapshot(options)` | [x] | |
+
+### Waiting
+
+| Method | Status | Notes |
+|--------|--------|-------|
+| `waitFor(options)` | [x] | |
+
+### Other
+
+| Method | Status | Notes |
+|--------|--------|-------|
+| `page()` | [x] | |
+| `describe(description)` | [x] | New in 1.53 |
+
+---
+
+## BrowserContext Module
+
+**File:** `lib/playwright/browser_context.ex`
+**Reference:** `browserContext.ts`
+
+### Pages
+
+| Method | Status | Notes |
+|--------|--------|-------|
+| `newPage()` | [x] | |
+| `pages()` | [x] | |
+| `browser()` | [x] | |
+
+### Cookies
+
+| Method | Status | Notes |
+|--------|--------|-------|
+| `cookies(urls)` | [x] | |
+| `addCookies(cookies)` | [x] | |
+| `clearCookies(options)` | [x] | |
+
+### Permissions
+
+| Method | Status | Notes |
+|--------|--------|-------|
+| `grantPermissions(permissions, options)` | [x] | |
+| `clearPermissions()` | [x] | |
+
+### Settings
+
+| Method | Status | Notes |
+|--------|--------|-------|
+| `setGeolocation(geolocation)` | [x] | |
+| `setExtraHTTPHeaders(headers)` | [x] | |
+| `setOffline(offline)` | [x] | |
+| `setHTTPCredentials(credentials)` | [x] | |
+| `setDefaultTimeout(timeout)` | [x] | |
+| `setDefaultNavigationTimeout(timeout)` | [x] | |
+
+### Scripts & Bindings
+
+| Method | Status | Notes |
+|--------|--------|-------|
+| `addInitScript(script, arg)` | [x] | |
+| `exposeBinding(name, callback, options)` | [x] | |
+| `exposeFunction(name, callback)` | [x] | |
+
+### Routing
+
+| Method | Status | Notes |
+|--------|--------|-------|
+| `route(url, handler, options)` | [x] | |
+| `unroute(url, handler)` | [x] | |
+| `unrouteAll(options)` | [x] | |
+| `routeFromHAR(har, options)` | [ ] | Requires LocalUtils |
+| `routeWebSocket(url, handler)` | [x] | |
+
+### State
+
+| Method | Status | Notes |
+|--------|--------|-------|
+| `storageState(options)` | [x] | Saves cookies and localStorage |
+| `close(options)` | [x] | |
+
+### Events
+
+| Method | Status | Notes |
+|--------|--------|-------|
+| `on(event, callback)` | [x] | |
+| `waitForEvent(event, options)` | [~] | As `expect_event` |
+
+### Workers
+
+| Method | Status | Notes |
+|--------|--------|-------|
+| `backgroundPages()` | [x] | Deprecated in 1.57, returns empty list |
+| `serviceWorkers()` | [x] | Returns empty list (stub) |
+
+### CDP
+
+| Method | Status | Notes |
+|--------|--------|-------|
+| `newCDPSession(page)` | [x] | |
+
+---
+
+## Browser Module
+
+**File:** `lib/playwright/browser.ex`
+**Reference:** `browser.ts`
+
+| Method | Status | Notes |
+|--------|--------|-------|
+| `newContext(options)` | [x] | |
+| `newPage(options)` | [x] | Returns `{:ok, page}` |
+| `contexts()` | [x] | |
+| `close()` | [x] | |
+| `isConnected()` | [x] | |
+| `browserType()` | [x] | |
+| `version` | [x] | Property |
+| `name` | [x] | Property |
+| `newBrowserCDPSession()` | [x] | Chromium only |
+| `startTracing(page, options)` | [x] | Chromium only |
+| `stopTracing()` | [x] | Chromium only |
+
+---
+
+## Frame Module
+
+**File:** `lib/playwright/frame.ex`
+**Reference:** `frame.ts`
+
+### Navigation
+
+| Method | Status | Notes |
+|--------|--------|-------|
+| `goto(url, options)` | [x] | |
+| `waitForNavigation(options)` | [x] | |
+| `waitForURL(url, options)` | [x] | |
+| `waitForLoadState(state, options)` | [x] | |
+| `url()` | [x] | |
+| `name()` | [x] | |
+| `title()` | [x] | |
+
+### Content
+
+| Method | Status | Notes |
+|--------|--------|-------|
+| `content()` | [x] | |
+| `setContent(html, options)` | [x] | |
+
+### Hierarchy
+
+| Method | Status | Notes |
+|--------|--------|-------|
+| `page()` | [x] | |
+| `parentFrame()` | [x] | |
+| `childFrames()` | [x] | |
+| `isDetached()` | [x] | |
+| `frameElement()` | [x] | |
+| `frameLocator(selector)` | [x] | |
+
+### Locators
+
+| Method | Status | Notes |
+|--------|--------|-------|
+| `locator(selector, options)` | [x] | |
+| `getByText(text, options)` | [x] | |
+| `getByRole(role, options)` | [x] | |
+| `getByTestId(testId)` | [x] | |
+| `getByLabel(text, options)` | [x] | |
+| `getByPlaceholder(text, options)` | [x] | |
+| `getByAltText(text, options)` | [x] | |
+| `getByTitle(text, options)` | [x] | |
+
+### Actions
+
+| Method | Status | Notes |
+|--------|--------|-------|
+| `click(selector, options)` | [x] | |
+| `dblclick(selector, options)` | [x] | |
+| `tap(selector, options)` | [x] | |
+| `fill(selector, value, options)` | [x] | |
+| `type(selector, text, options)` | [x] | Deprecated in 1.50 |
+| `press(selector, key, options)` | [x] | |
+| `hover(selector, options)` | [x] | |
+| `focus(selector, options)` | [x] | |
+| `check(selector, options)` | [x] | |
+| `uncheck(selector, options)` | [x] | |
+| `selectOption(selector, values, options)` | [x] | |
+| `setInputFiles(selector, files, options)` | [x] | |
+| `dragAndDrop(source, target, options)` | [x] | `steps` option added in 1.54 |
+| `dispatchEvent(selector, type, eventInit, options)` | [x] | |
+
+### Query Methods
+
+| Method | Status | Notes |
+|--------|--------|-------|
+| `textContent(selector, options)` | [x] | |
+| `innerText(selector, options)` | [x] | |
+| `innerHTML(selector, options)` | [x] | |
+| `getAttribute(selector, name, options)` | [x] | |
+| `inputValue(selector, options)` | [x] | |
+| `isChecked(selector, options)` | [x] | |
+| `isDisabled(selector, options)` | [x] | |
+| `isEditable(selector, options)` | [x] | |
+| `isEnabled(selector, options)` | [x] | |
+| `isHidden(selector, options)` | [x] | |
+| `isVisible(selector, options)` | [x] | |
+| `waitForSelector(selector, options)` | [x] | |
+| `querySelector(selector)` | [x] | As `query_selector` |
+| `querySelectorAll(selector)` | [x] | As `query_selector_all` |
+
+### Evaluation
+
+| Method | Status | Notes |
+|--------|--------|-------|
+| `evaluate(expression, arg)` | [x] | |
+| `evaluateHandle(expression, arg)` | [x] | |
+| `evalOnSelector(selector, expression, arg)` | [x] | |
+| `evalOnSelectorAll(selector, expression, arg)` | [x] | |
+
+---
+
+## Additional Modules
+
+### Mouse (`lib/playwright/page/mouse.ex`)
+
+| Method | Status | Notes |
+|--------|--------|-------|
+| `click(x, y, options)` | [x] | |
+| `dblclick(x, y, options)` | [x] | |
+| `down(options)` | [x] | |
+| `up(options)` | [x] | |
+| `move(x, y, options)` | [x] | |
+| `wheel(deltaX, deltaY)` | [x] | |
+
+### Touchscreen (`lib/playwright/page/touchscreen.ex`)
+
+| Method | Status | Notes |
+|--------|--------|-------|
+| `tap(x, y)` | [x] | |
+
+### Dialog (`lib/playwright/dialog.ex`)
+
+| Method | Status | Notes |
+|--------|--------|-------|
+| `accept(promptText)` | [x] | |
+| `dismiss()` | [x] | |
+| `message()` | [x] | |
+| `defaultValue()` | [x] | `default_value/1` |
+| `type()` | [x] | |
+| `page()` | [x] | |
+
+### Download (`lib/playwright/page/download.ex`)
+
+| Method | Status | Notes |
+|--------|--------|-------|
+| `cancel()` | [x] | |
+| `delete()` | [x] | |
+| `failure()` | [x] | |
+| `page()` | [x] | |
+| `path()` | [x] | |
+| `saveAs(path)` | [x] | |
+| `suggestedFilename()` | [x] | |
+| `url()` | [x] | |
+
+### FileChooser (`lib/playwright/page/file_chooser.ex`)
+
+| Method | Status | Notes |
+|--------|--------|-------|
+| `element()` | [x] | |
+| `isMultiple()` | [x] | `is_multiple/1` |
+| `page()` | [x] | |
+| `setFiles(files, options)` | [x] | `set_files/3` via `from_event/1` |
+
+### Coverage (`lib/playwright/coverage.ex`)
+
+| Method | Status | Notes |
+|--------|--------|-------|
+| `startJSCoverage(options)` | [x] | `start_js_coverage/2` |
+| `stopJSCoverage()` | [x] | `stop_js_coverage/1` |
+| `startCSSCoverage(options)` | [x] | `start_css_coverage/2` |
+| `stopCSSCoverage()` | [x] | `stop_css_coverage/1` |
+
+### Tracing (`lib/playwright/tracing.ex`)
+
+| Method | Status | Notes |
+|--------|--------|-------|
+| `start(options)` | [x] | |
+| `startChunk(options)` | [x] | |
+| `stop(options)` | [x] | |
+| `stopChunk(options)` | [x] | |
+| `group(name, options)` | [x] | |
+| `groupEnd()` | [x] | |
+
+### FrameLocator (`lib/playwright/page/frame_locator.ex`)
+
+| Method | Status | Notes |
+|--------|--------|-------|
+| `first()` | [x] | |
+| `last()` | [x] | |
+| `nth(index)` | [x] | |
+| `frameLocator(selector)` | [x] | |
+| `locator(selector)` | [x] | |
+| `getByText(text, options)` | [x] | |
+| `getByRole(role, options)` | [x] | |
+| `getByTestId(testId)` | [x] | |
+| `getByLabel(text, options)` | [x] | |
+| `getByPlaceholder(text, options)` | [x] | |
+| `getByAltText(text, options)` | [x] | |
+| `getByTitle(text, options)` | [x] | |
+| `owner()` | [x] | |
+
+---
+
+### Clock (`lib/playwright/clock.ex`)
+
+| Method | Status | Notes |
+|--------|--------|-------|
+| `install(options)` | [x] | |
+| `fastForward(ticks)` | [x] | As `fast_forward` |
+| `pauseAt(time)` | [x] | As `pause_at` |
+| `resume()` | [x] | |
+| `runFor(ticks)` | [x] | As `run_for` |
+| `setFixedTime(time)` | [x] | As `set_fixed_time` |
+| `setSystemTime(time)` | [x] | As `set_system_time` |
+
+### Video
+
+| Method | Status | Notes |
+|--------|--------|-------|
+| `delete()` | [x] | |
+| `path()` | [x] | |
+| `saveAs(path)` | [x] | |
+
+---
+
+## Priority Implementation Roadmap
+
+### Completed Phases (1.49.1)
+
+Phases 1-4 are complete: core navigation, modern locators, session/state, and advanced features (Mouse, FrameLocator, PDF, Tracing, FileChooser, query methods).
+
+### Phase 5: Completeness ✅
+1. ~~Remaining Page query methods~~ DONE
+2. ~~`FileChooser` module~~ DONE
+3. ~~`Clock` module~~ DONE
+4. ~~`Video` module~~ DONE
+
+### Phase 6: Upgrade to Playwright 1.59.1 ✅
+
+#### 6a: Breaking Changes ✅
+1. ~~Package bump to 1.59.1, node >=18~~ DONE
+2. ~~Remove `Page.Accessibility` module~~ DONE
+3. ~~Remove `Selectors` module~~ DONE
+4. ~~Handle required timeouts~~ DONE
+5. ~~Remove `backgroundPage` event~~ DONE
+6. ~~Add typed array serialization support~~ DONE
+7. ~~Fix deserialize error handling~~ DONE
+8. ~~Handle video as initializer property~~ DONE
+9. ~~Handle Disposable/Debugger ChannelOwner types~~ DONE
+
+#### 6b: Deprecations ✅
+1. ~~Mark `Page.type/4`, `Frame.type/4`, `Locator.type/3` as deprecated~~ DONE
+
+#### 6c: New APIs ✅
+1. ~~`Page.requests/1`~~ DONE
+2. ~~`Page.aria_snapshot/2`~~ DONE
+3. ~~`Locator.describe/2`~~ DONE
+4. ~~`Frame.click`/`dblclick`/`dragAndDrop` `steps` option~~ DONE
+5. ~~Cookie `partitionKey` support~~ DONE
+6. ~~`Page.emulateMedia` `contrast` option~~ DONE (already implemented)
+7. ~~Worker `on/3` and `url` property~~ DONE
+8. ~~`BrowserContext.storageState` `indexedDB` option~~ DONE
+
+#### 6d: Remaining / Deferred
+1. `Page.screencast/1` — new Screencast ChannelOwner module (1.50)
+2. `Page.snapshotForAI` (internal, for MCP integrations)
+3. `routeFromHAR` — requires LocalUtils
+4. `BrowserType.launchPersistentContext` returning browser (not yet implemented)
+
+---
+
+## Implementation Notes
+
+### Event Names (from Playwright events.ts)
+
+```typescript
+Page: {
+ AgentTurn: 'agentturn',
+ Close: 'close',
+ Crash: 'crash',
+ Console: 'console',
+ Dialog: 'dialog',
+ Download: 'download',
+ FileChooser: 'filechooser',
+ DOMContentLoaded: 'domcontentloaded',
+ PageError: 'pageerror',
+ Request: 'request',
+ Response: 'response',
+ RequestFailed: 'requestfailed',
+ RequestFinished: 'requestfinished',
+ FrameAttached: 'frameattached',
+ FrameDetached: 'framedetached',
+ FrameNavigated: 'framenavigated',
+ Load: 'load',
+ Popup: 'popup',
+ WebSocket: 'websocket',
+ Worker: 'worker',
+}
+```
+
+### Channel Commands Reference
+
+When implementing new methods, refer to the protocol definitions in:
+- `/home/tristan/sources/playwright/packages/protocol/src/protocol.yml`
+
+### Testing Patterns
+
+Each new feature should include tests following the existing pattern in `test/api/`.
diff --git a/mix.exs b/mix.exs
index ad7cadae..60a63305 100644
--- a/mix.exs
+++ b/mix.exs
@@ -17,10 +17,10 @@ defmodule Playwright.MixProject do
elixirc_paths: elixirc_paths(Mix.env()),
homepage_url: @source_url,
package: package(),
- preferred_cli_env: [credo: :test, dialyzer: :test, docs: :docs],
+ preferred_cli_env: [credo: :test, dialyzer: :test, docs: :docs, precommit: :test],
source_url: @source_url,
start_permanent: Mix.env() == :prod,
- version: "1.49.1-alpha.2"
+ version: "1.59.1-alpha.1"
]
end
@@ -46,16 +46,16 @@ defmodule Playwright.MixProject do
# Run "mix help deps" to learn about dependencies.
defp deps do
[
- {:cowlib, "~> 2.7.0"},
- {:credo, "~> 1.6", only: [:dev, :test], runtime: false},
- {:dialyxir, "~> 1.1", only: [:dev, :test], runtime: false},
- {:esbuild, "~> 0.8.1", runtime: Mix.env() == :dev},
- {:ex_doc, "~> 0.34", only: :dev, runtime: false},
- {:gun, "~> 1.3.3"},
+ {:cowlib, "~> 2.16"},
+ {:credo, "~> 1.7", only: [:dev, :test], runtime: false},
+ {:dialyxir, "~> 1.4", only: [:dev, :test], runtime: false},
+ {:esbuild, "~> 0.10", runtime: Mix.env() == :dev},
+ {:ex_doc, "~> 0.39", only: :dev, runtime: false},
+ {:gun, "~> 2.2"},
{:jason, "~> 1.4"},
- {:mix_audit, "~> 1.0", only: [:dev, :test], runtime: false},
- {:playwright_assets, "1.49.1", only: [:test]},
- {:recase, "~> 0.7"},
+ {:mix_audit, "~> 2.1", only: [:dev, :test], runtime: false},
+ {:playwright_assets, github: "tristanperalta/playwright-assets", only: [:test]},
+ {:recase, "~> 0.9"},
{:elixir_uuid, "~> 1.2"}
]
end
@@ -84,6 +84,8 @@ defmodule Playwright.MixProject do
"man/guides/browsers.md": [filename: "guides-browsers"],
"man/guides/chrome-extensions.md": [filename: "guides-chrome-extensions"],
"man/guides/command-line-tools.md": [filename: "guides-command-line-tools"],
+ "man/guides/contributing.md": [filename: "guides-contributing"],
+ "man/guides/feature_parity.md": [filename: "guides-feature-parity"],
"man/guides/dialogs.md": [filename: "guides-dialogs"],
"man/guides/downloads.md": [filename: "guides-downloads"],
"man/guides/emulation.md": [filename: "guides-emulation"],
@@ -121,13 +123,11 @@ defmodule Playwright.MixProject do
Playwright.Locator,
Playwright.Page,
Playwright.JSHandle,
- Playwright.Page.Accessibility,
Playwright.Page.Locator,
Playwright.RemoteBrowser,
Playwright.Request,
Playwright.Response,
Playwright.Route,
- Playwright.Selectors,
Playwright.WebSocket,
Playwright.Worker
],
@@ -159,6 +159,13 @@ defmodule Playwright.MixProject do
[
"assets.build": [
"cmd echo 'NOT IMPLEMENTED'"
+ ],
+ precommit: [
+ "compile --warnings-as-errors",
+ "format --check-formatted",
+ "credo --strict",
+ "dialyzer",
+ "test"
]
]
end
diff --git a/mix.lock b/mix.lock
index b0b2aba4..5d6d0dff 100644
--- a/mix.lock
+++ b/mix.lock
@@ -1,31 +1,32 @@
%{
"bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"},
"castore": {:hex, :castore, "1.0.7", "b651241514e5f6956028147fe6637f7ac13802537e895a724f90bf3e36ddd1dd", [:mix], [], "hexpm", "da7785a4b0d2a021cd1292a60875a784b6caef71e76bf4917bdee1f390455cf5"},
- "cowboy": {:hex, :cowboy, "2.6.3", "99aa50e94e685557cad82e704457336a453d4abcb77839ad22dbe71f311fcc06", [:rebar3], [{:cowlib, "~> 2.7.3", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "e5580029080f3f1ad17436fb97b0d5ed2ed4e4815a96bac36b5a992e20f58db6"},
- "cowlib": {:hex, :cowlib, "2.7.3", "a7ffcd0917e6d50b4d5fb28e9e2085a0ceb3c97dea310505f7460ff5ed764ce9", [:rebar3], [], "hexpm", "1e1a3d176d52daebbecbbcdfd27c27726076567905c2a9d7398c54da9d225761"},
- "credo": {:hex, :credo, "1.7.6", "b8f14011a5443f2839b04def0b252300842ce7388f3af177157c86da18dfbeea", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "146f347fb9f8cbc5f7e39e3f22f70acbef51d441baa6d10169dd604bfbc55296"},
- "dialyxir": {:hex, :dialyxir, "1.4.3", "edd0124f358f0b9e95bfe53a9fcf806d615d8f838e2202a9f430d59566b6b53b", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "bf2cfb75cd5c5006bec30141b131663299c661a864ec7fbbc72dfa557487a986"},
- "earmark_parser": {:hex, :earmark_parser, "1.4.39", "424642f8335b05bb9eb611aa1564c148a8ee35c9c8a8bba6e129d51a3e3c6769", [:mix], [], "hexpm", "06553a88d1f1846da9ef066b87b57c6f605552cfbe40d20bd8d59cc6bde41944"},
+ "cowboy": {:hex, :cowboy, "2.14.2", "4008be1df6ade45e4f2a4e9e2d22b36d0b5aba4e20b0a0d7049e28d124e34847", [:make, :rebar3], [{:cowlib, ">= 2.16.0 and < 3.0.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, ">= 1.8.0 and < 3.0.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "569081da046e7b41b5df36aa359be71a0c8874e5b9cff6f747073fc57baf1ab9"},
+ "cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"},
+ "cowlib": {:hex, :cowlib, "2.16.0", "54592074ebbbb92ee4746c8a8846e5605052f29309d3a873468d76cdf932076f", [:make, :rebar3], [], "hexpm", "7f478d80d66b747344f0ea7708c187645cfcc08b11aa424632f78e25bf05db51"},
+ "credo": {:hex, :credo, "1.7.15", "283da72eeb2fd3ccf7248f4941a0527efb97afa224bcdef30b4b580bc8258e1c", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "291e8645ea3fea7481829f1e1eb0881b8395db212821338e577a90bf225c5607"},
+ "dialyxir": {:hex, :dialyxir, "1.4.7", "dda948fcee52962e4b6c5b4b16b2d8fa7d50d8645bbae8b8685c3f9ecb7f5f4d", [:mix], [{:erlex, ">= 0.2.8", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "b34527202e6eb8cee198efec110996c25c5898f43a4094df157f8d28f27d9efe"},
+ "earmark_parser": {:hex, :earmark_parser, "1.4.44", "f20830dd6b5c77afe2b063777ddbbff09f9759396500cdbe7523efd58d7a339c", [:mix], [], "hexpm", "4778ac752b4701a5599215f7030989c989ffdc4f6df457c5f36938cc2d2a2750"},
"elixir_uuid": {:hex, :elixir_uuid, "1.2.1", "dce506597acb7e6b0daeaff52ff6a9043f5919a4c3315abb4143f0b00378c097", [:mix], [], "hexpm", "f7eba2ea6c3555cea09706492716b0d87397b88946e6380898c2889d68585752"},
- "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"},
- "esbuild": {:hex, :esbuild, "0.8.1", "0cbf919f0eccb136d2eeef0df49c4acf55336de864e63594adcea3814f3edf41", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "25fc876a67c13cb0a776e7b5d7974851556baeda2085296c14ab48555ea7560f"},
- "ex_doc": {:hex, :ex_doc, "0.34.0", "ab95e0775db3df71d30cf8d78728dd9261c355c81382bcd4cefdc74610bef13e", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "60734fb4c1353f270c3286df4a0d51e65a2c1d9fba66af3940847cc65a8066d7"},
- "file_system": {:hex, :file_system, "1.0.0", "b689cc7dcee665f774de94b5a832e578bd7963c8e637ef940cd44327db7de2cd", [:mix], [], "hexpm", "6752092d66aec5a10e662aefeed8ddb9531d79db0bc145bb8c40325ca1d8536d"},
- "gun": {:hex, :gun, "1.3.3", "cf8b51beb36c22b9c8df1921e3f2bc4d2b1f68b49ad4fbc64e91875aa14e16b4", [:rebar3], [{:cowlib, "~> 2.7.0", [hex: :cowlib, repo: "hexpm", optional: false]}], "hexpm", "3106ce167f9c9723f849e4fb54ea4a4d814e3996ae243a1c828b256e749041e0"},
- "jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"},
- "makeup": {:hex, :makeup, "1.1.2", "9ba8837913bdf757787e71c1581c21f9d2455f4dd04cfca785c70bbfff1a76a3", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cce1566b81fbcbd21eca8ffe808f33b221f9eee2cbc7a1706fc3da9ff18e6cac"},
- "makeup_elixir": {:hex, :makeup_elixir, "0.16.2", "627e84b8e8bf22e60a2579dad15067c755531fea049ae26ef1020cad58fe9578", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "41193978704763f6bbe6cc2758b84909e62984c7752b3784bd3c218bb341706b"},
- "makeup_erlang": {:hex, :makeup_erlang, "1.0.0", "6f0eff9c9c489f26b69b61440bf1b238d95badae49adac77973cbacae87e3c2e", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "ea7a9307de9d1548d2a72d299058d1fd2339e3d398560a0e46c27dab4891e4d2"},
- "mime": {:hex, :mime, "2.0.5", "dc34c8efd439abe6ae0343edbb8556f4d63f178594894720607772a041b04b02", [:mix], [], "hexpm", "da0d64a365c45bc9935cc5c8a7fc5e49a0e0f9932a761c55d6c52b142780a05c"},
- "mix_audit": {:hex, :mix_audit, "1.0.1", "9dd114408961b8db214f42fee40b2f632ecd7e4fd29500403068c82c77db8361", [:make, :mix], [{:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:yaml_elixir, "~> 2.8.0", [hex: :yaml_elixir, repo: "hexpm", optional: false]}], "hexpm", "65066bb7757078aa49faaa2f7c1e2d52f56ff6fe6cff01723dbaf5be2a75771b"},
- "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"},
- "playwright_assets": {:hex, :playwright_assets, "1.49.1", "22f633af14bf2c16a4dcf64c9e08c21fe6e16750705ac7767f07797faf4d5756", [:mix], [{:cowlib, "~> 2.7.3", [hex: :cowlib, repo: "hexpm", optional: false]}, {:plug, "~> 1.12", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.1.3", [hex: :plug_cowboy, repo: "hexpm", optional: false]}], "hexpm", "688727f5bcbf8d6b8b83f5febc207130c83fb7e00e83853ab8c9d75c9a1642d4"},
- "plug": {:hex, :plug, "1.16.0", "1d07d50cb9bb05097fdf187b31cf087c7297aafc3fed8299aac79c128a707e47", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "cbf53aa1f5c4d758a7559c0bd6d59e286c2be0c6a1fac8cc3eee2f638243b93e"},
- "plug_cowboy": {:hex, :plug_cowboy, "2.1.3", "38999a3e85e39f0e6bdfdf820761abac61edde1632cfebbacc445cdcb6ae1333", [:mix], [{:cowboy, "~> 2.5", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "056f41f814dbb38ea44613e0f613b3b2b2f2c6afce64126e252837669eba84db"},
- "plug_crypto": {:hex, :plug_crypto, "2.1.0", "f44309c2b06d249c27c8d3f65cfe08158ade08418cf540fd4f72d4d6863abb7b", [:mix], [], "hexpm", "131216a4b030b8f8ce0f26038bc4421ae60e4bb95c5cf5395e1421437824c4fa"},
- "ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm", "451d8527787df716d99dc36162fca05934915db0b6141bbdac2ea8d3c7afc7d7"},
- "recase": {:hex, :recase, "0.8.0", "ec9500abee5d493d41e3cbfd7d51a4e10957a164570be0c805d5c6661b8cdbae", [:mix], [], "hexpm", "0d4b67b81e7897af77552bd1e6d6148717a4b45ec5c7b014a48b0ba9a28946b5"},
- "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"},
+ "erlex": {:hex, :erlex, "0.2.8", "cd8116f20f3c0afe376d1e8d1f0ae2452337729f68be016ea544a72f767d9c12", [:mix], [], "hexpm", "9d66ff9fedf69e49dc3fd12831e12a8a37b76f8651dd21cd45fcf5561a8a7590"},
+ "esbuild": {:hex, :esbuild, "0.10.0", "b0aa3388a1c23e727c5a3e7427c932d89ee791746b0081bbe56103e9ef3d291f", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "468489cda427b974a7cc9f03ace55368a83e1a7be12fba7e30969af78e5f8c70"},
+ "ex_doc": {:hex, :ex_doc, "0.39.3", "519c6bc7e84a2918b737aec7ef48b96aa4698342927d080437f61395d361dcee", [:mix], [{:earmark_parser, "~> 1.4.44", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "0590955cf7ad3b625780ee1c1ea627c28a78948c6c0a9b0322bd976a079996e1"},
+ "file_system": {:hex, :file_system, "1.1.1", "31864f4685b0148f25bd3fbef2b1228457c0c89024ad67f7a81a3ffbc0bbad3a", [:mix], [], "hexpm", "7a15ff97dfe526aeefb090a7a9d3d03aa907e100e262a0f8f7746b78f8f87a5d"},
+ "gun": {:hex, :gun, "2.2.0", "b8f6b7d417e277d4c2b0dc3c07dfdf892447b087f1cc1caff9c0f556b884e33d", [:make, :rebar3], [{:cowlib, ">= 2.15.0 and < 3.0.0", [hex: :cowlib, repo: "hexpm", optional: false]}], "hexpm", "76022700c64287feb4df93a1795cff6741b83fb37415c40c34c38d2a4645261a"},
+ "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"},
+ "makeup": {:hex, :makeup, "1.2.1", "e90ac1c65589ef354378def3ba19d401e739ee7ee06fb47f94c687016e3713d1", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d36484867b0bae0fea568d10131197a4c2e47056a6fbe84922bf6ba71c8d17ce"},
+ "makeup_elixir": {:hex, :makeup_elixir, "1.0.1", "e928a4f984e795e41e3abd27bfc09f51db16ab8ba1aebdba2b3a575437efafc2", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "7284900d412a3e5cfd97fdaed4f5ed389b8f2b4cb49efc0eb3bd10e2febf9507"},
+ "makeup_erlang": {:hex, :makeup_erlang, "1.0.3", "4252d5d4098da7415c390e847c814bad3764c94a814a0b4245176215615e1035", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "953297c02582a33411ac6208f2c6e55f0e870df7f80da724ed613f10e6706afd"},
+ "mime": {:hex, :mime, "2.0.7", "b8d739037be7cd402aee1ba0306edfdef982687ee7e9859bee6198c1e7e2f128", [:mix], [], "hexpm", "6171188e399ee16023ffc5b76ce445eb6d9672e2e241d2df6050f3c771e80ccd"},
+ "mix_audit": {:hex, :mix_audit, "2.1.5", "c0f77cee6b4ef9d97e37772359a187a166c7a1e0e08b50edf5bf6959dfe5a016", [:make, :mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:yaml_elixir, "~> 2.11", [hex: :yaml_elixir, repo: "hexpm", optional: false]}], "hexpm", "87f9298e21da32f697af535475860dc1d3617a010e0b418d2ec6142bc8b42d69"},
+ "nimble_parsec": {:hex, :nimble_parsec, "1.4.2", "8efba0122db06df95bfaa78f791344a89352ba04baedd3849593bfce4d0dc1c6", [:mix], [], "hexpm", "4b21398942dda052b403bbe1da991ccd03a053668d147d53fb8c4e0efe09c973"},
+ "playwright_assets": {:git, "https://github.com/tristanperalta/playwright-assets.git", "4ca3cda19af20672d987189e3ac82a3e7c2364eb", []},
+ "plug": {:hex, :plug, "1.19.1", "09bac17ae7a001a68ae393658aa23c7e38782be5c5c00c80be82901262c394c0", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "560a0017a8f6d5d30146916862aaf9300b7280063651dd7e532b8be168511e62"},
+ "plug_cowboy": {:hex, :plug_cowboy, "2.7.5", "261f21b67aea8162239b2d6d3b4c31efde4daa22a20d80b19c2c0f21b34b270e", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "20884bf58a90ff5a5663420f5d2c368e9e15ed1ad5e911daf0916ea3c57f77ac"},
+ "plug_crypto": {:hex, :plug_crypto, "2.1.1", "19bda8184399cb24afa10be734f84a16ea0a2bc65054e23a62bb10f06bc89491", [:mix], [], "hexpm", "6470bce6ffe41c8bd497612ffde1a7e4af67f36a15eea5f921af71cf3e11247c"},
+ "ranch": {:hex, :ranch, "2.2.0", "25528f82bc8d7c6152c57666ca99ec716510fe0925cb188172f41ce93117b1b0", [:make, :rebar3], [], "hexpm", "fa0b99a1780c80218a4197a59ea8d3bdae32fbff7e88527d7d8a4787eff4f8e7"},
+ "recase": {:hex, :recase, "0.9.1", "82d2e2e2d4f9e92da1ce5db338ede2e4f15a50ac1141fc082b80050b9f49d96e", [:mix], [], "hexpm", "19ba03ceb811750e6bec4a015a9f9e45d16a8b9e09187f6d72c3798f454710f3"},
+ "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"},
"yamerl": {:hex, :yamerl, "0.10.0", "4ff81fee2f1f6a46f1700c0d880b24d193ddb74bd14ef42cb0bcf46e81ef2f8e", [:rebar3], [], "hexpm", "346adb2963f1051dc837a2364e4acf6eb7d80097c0f53cbdc3046ec8ec4b4e6e"},
- "yaml_elixir": {:hex, :yaml_elixir, "2.8.0", "c7ff0034daf57279c2ce902788ce6fdb2445532eb4317e8df4b044209fae6832", [:mix], [{:yamerl, "~> 0.8", [hex: :yamerl, repo: "hexpm", optional: false]}], "hexpm", "4b674bd881e373d1ac6a790c64b2ecb69d1fd612c2af3b22de1619c15473830b"},
+ "yaml_elixir": {:hex, :yaml_elixir, "2.12.0", "30343ff5018637a64b1b7de1ed2a3ca03bc641410c1f311a4dbdc1ffbbf449c7", [:mix], [{:yamerl, "~> 0.10", [hex: :yamerl, repo: "hexpm", optional: false]}], "hexpm", "ca6bacae7bac917a7155dca0ab6149088aa7bc800c94d0fe18c5238f53b313c6"},
}
diff --git a/priv/static/node_modules/.package-lock.json b/priv/static/node_modules/.package-lock.json
index 82b13734..e2a5670d 100644
--- a/priv/static/node_modules/.package-lock.json
+++ b/priv/static/node_modules/.package-lock.json
@@ -17,11 +17,12 @@
}
},
"node_modules/playwright": {
- "version": "1.49.1",
- "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.49.1.tgz",
- "integrity": "sha512-VYL8zLoNTBxVOrJBbDuRgDWa3i+mfQgDTrL8Ah9QXZ7ax4Dsj0MSq5bYgytRnDVVe+njoKnfsYkH3HzqVj5UZA==",
+ "version": "1.59.1",
+ "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.59.1.tgz",
+ "integrity": "sha512-C8oWjPR3F81yljW9o5OxcWzfh6avkVwDD2VYdwIGqTkl+OGFISgypqzfu7dOe4QNLL2aqcWBmI3PMtLIK233lw==",
+ "license": "Apache-2.0",
"dependencies": {
- "playwright-core": "1.49.1"
+ "playwright-core": "1.59.1"
},
"bin": {
"playwright": "cli.js"
@@ -34,9 +35,10 @@
}
},
"node_modules/playwright-core": {
- "version": "1.49.1",
- "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.49.1.tgz",
- "integrity": "sha512-BzmpVcs4kE2CH15rWfzpjzVGhWERJfmnXmniSyKeRZUs9Ws65m+RGIi7mjJK/euCegfn3i7jvqWeWyHe9y3Vgg==",
+ "version": "1.59.1",
+ "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.59.1.tgz",
+ "integrity": "sha512-HBV/RJg81z5BiiZ9yPzIiClYV/QMsDCKUyogwH9p3MCP6IYjUFu/MActgYAvK0oWyV9NlwM3GLBjADyWgydVyg==",
+ "license": "Apache-2.0",
"bin": {
"playwright-core": "cli.js"
},
diff --git a/priv/static/node_modules/playwright-core/ThirdPartyNotices.txt b/priv/static/node_modules/playwright-core/ThirdPartyNotices.txt
index 89a6418a..88044820 100644
--- a/priv/static/node_modules/playwright-core/ThirdPartyNotices.txt
+++ b/priv/static/node_modules/playwright-core/ThirdPartyNotices.txt
@@ -4,108 +4,221 @@ THIRD-PARTY SOFTWARE NOTICES AND INFORMATION
This project incorporates components from the projects listed below. The original copyright notices and the licenses under which Microsoft received such components are set forth below. Microsoft reserves all rights not expressly granted herein, whether by implication, estoppel or otherwise.
-- @types/node@17.0.24 (https://github.com/DefinitelyTyped/DefinitelyTyped)
-- @types/yauzl@2.10.0 (https://github.com/DefinitelyTyped/DefinitelyTyped)
-- agent-base@7.1.1 (https://github.com/TooTallNate/proxy-agents)
+- @hono/node-server@1.19.11 (https://github.com/honojs/node-server)
+- @modelcontextprotocol/sdk@1.28.0 (https://github.com/modelcontextprotocol/typescript-sdk)
+- accepts@2.0.0 (https://github.com/jshttp/accepts)
+- agent-base@7.1.4 (https://github.com/TooTallNate/proxy-agents)
+- ajv-formats@3.0.1 (https://github.com/ajv-validator/ajv-formats)
+- ajv@8.18.0 (https://github.com/ajv-validator/ajv)
- balanced-match@1.0.2 (https://github.com/juliangruber/balanced-match)
-- brace-expansion@1.1.11 (https://github.com/juliangruber/brace-expansion)
+- body-parser@2.2.2 (https://github.com/expressjs/body-parser)
+- brace-expansion@1.1.12 (https://github.com/juliangruber/brace-expansion)
- buffer-crc32@0.2.13 (https://github.com/brianloveswords/buffer-crc32)
+- bytes@3.1.2 (https://github.com/visionmedia/bytes.js)
+- call-bind-apply-helpers@1.0.2 (https://github.com/ljharb/call-bind-apply-helpers)
+- call-bound@1.0.4 (https://github.com/ljharb/call-bound)
- codemirror@5.65.18 (https://github.com/codemirror/CodeMirror)
- colors@1.4.0 (https://github.com/Marak/colors.js)
-- commander@8.3.0 (https://github.com/tj/commander.js)
+- commander@13.1.0 (https://github.com/tj/commander.js)
- concat-map@0.0.1 (https://github.com/substack/node-concat-map)
+- content-disposition@1.0.1 (https://github.com/jshttp/content-disposition)
+- content-type@1.0.5 (https://github.com/jshttp/content-type)
+- cookie-signature@1.2.2 (https://github.com/visionmedia/node-cookie-signature)
+- cookie@0.7.2 (https://github.com/jshttp/cookie)
+- cors@2.8.5 (https://github.com/expressjs/cors)
+- cross-spawn@7.0.6 (https://github.com/moxystudio/node-cross-spawn)
- debug@4.3.4 (https://github.com/debug-js/debug)
+- debug@4.4.0 (https://github.com/debug-js/debug)
+- debug@4.4.3 (https://github.com/debug-js/debug)
- define-lazy-prop@2.0.0 (https://github.com/sindresorhus/define-lazy-prop)
-- diff@7.0.0 (https://github.com/kpdecker/jsdiff)
+- depd@2.0.0 (https://github.com/dougwilson/nodejs-depd)
+- diff@8.0.4 (https://github.com/kpdecker/jsdiff)
- dotenv@16.4.5 (https://github.com/motdotla/dotenv)
+- dunder-proto@1.0.1 (https://github.com/es-shims/dunder-proto)
+- ee-first@1.1.1 (https://github.com/jonathanong/ee-first)
+- encodeurl@2.0.0 (https://github.com/pillarjs/encodeurl)
- end-of-stream@1.4.4 (https://github.com/mafintosh/end-of-stream)
-- escape-string-regexp@2.0.0 (https://github.com/sindresorhus/escape-string-regexp)
-- extract-zip@2.0.1 (https://github.com/maxogden/extract-zip)
-- fd-slicer@1.1.0 (https://github.com/andrewrk/node-fd-slicer)
+- es-define-property@1.0.1 (https://github.com/ljharb/es-define-property)
+- es-errors@1.3.0 (https://github.com/ljharb/es-errors)
+- es-object-atoms@1.1.1 (https://github.com/ljharb/es-object-atoms)
+- escape-html@1.0.3 (https://github.com/component/escape-html)
+- etag@1.8.1 (https://github.com/jshttp/etag)
+- eventsource-parser@3.0.3 (https://github.com/rexxars/eventsource-parser)
+- eventsource@3.0.7 (git://git@github.com/EventSource/eventsource)
+- express-rate-limit@8.3.1 (https://github.com/express-rate-limit/express-rate-limit)
+- express@5.2.1 (https://github.com/expressjs/express)
+- fast-deep-equal@3.1.3 (https://github.com/epoberezkin/fast-deep-equal)
+- fast-uri@3.1.0 (https://github.com/fastify/fast-uri)
+- finalhandler@2.1.1 (https://github.com/pillarjs/finalhandler)
+- forwarded@0.2.0 (https://github.com/jshttp/forwarded)
+- fresh@2.0.0 (https://github.com/jshttp/fresh)
+- function-bind@1.1.2 (https://github.com/Raynos/function-bind)
+- get-intrinsic@1.3.0 (https://github.com/ljharb/get-intrinsic)
+- get-proto@1.0.1 (https://github.com/ljharb/get-proto)
- get-stream@5.2.0 (https://github.com/sindresorhus/get-stream)
+- gopd@1.2.0 (https://github.com/ljharb/gopd)
- graceful-fs@4.2.10 (https://github.com/isaacs/node-graceful-fs)
-- https-proxy-agent@7.0.5 (https://github.com/TooTallNate/proxy-agents)
+- has-symbols@1.1.0 (https://github.com/inspect-js/has-symbols)
+- hasown@2.0.2 (https://github.com/inspect-js/hasOwn)
+- hono@4.12.7 (https://github.com/honojs/hono)
+- http-errors@2.0.1 (https://github.com/jshttp/http-errors)
+- https-proxy-agent@7.0.6 (https://github.com/TooTallNate/proxy-agents)
+- iconv-lite@0.7.2 (https://github.com/pillarjs/iconv-lite)
+- inherits@2.0.4 (https://github.com/isaacs/inherits)
+- ini@6.0.0 (https://github.com/npm/ini)
+- ip-address@10.1.0 (https://github.com/beaugunderson/ip-address)
- ip-address@9.0.5 (https://github.com/beaugunderson/ip-address)
+- ipaddr.js@1.9.1 (https://github.com/whitequark/ipaddr.js)
- is-docker@2.2.1 (https://github.com/sindresorhus/is-docker)
+- is-promise@4.0.0 (https://github.com/then/is-promise)
- is-wsl@2.2.0 (https://github.com/sindresorhus/is-wsl)
+- isexe@2.0.0 (https://github.com/isaacs/isexe)
+- jose@6.1.3 (https://github.com/panva/jose)
- jpeg-js@0.4.4 (https://github.com/eugeneware/jpeg-js)
- jsbn@1.1.0 (https://github.com/andyperlitch/jsbn)
+- json-schema-traverse@1.0.0 (https://github.com/epoberezkin/json-schema-traverse)
+- json-schema-typed@8.0.2 (https://github.com/RemyRylan/json-schema-typed)
+- math-intrinsics@1.1.0 (https://github.com/es-shims/math-intrinsics)
+- media-typer@1.1.0 (https://github.com/jshttp/media-typer)
+- merge-descriptors@2.0.0 (https://github.com/sindresorhus/merge-descriptors)
+- mime-db@1.54.0 (https://github.com/jshttp/mime-db)
+- mime-types@3.0.2 (https://github.com/jshttp/mime-types)
- mime@3.0.0 (https://github.com/broofa/mime)
-- minimatch@3.1.2 (https://github.com/isaacs/minimatch)
+- minimatch@3.1.4 (https://github.com/isaacs/minimatch)
- ms@2.1.2 (https://github.com/zeit/ms)
+- ms@2.1.3 (https://github.com/vercel/ms)
+- negotiator@1.0.0 (https://github.com/jshttp/negotiator)
+- object-assign@4.1.1 (https://github.com/sindresorhus/object-assign)
+- object-inspect@1.13.4 (https://github.com/inspect-js/object-inspect)
+- on-finished@2.4.1 (https://github.com/jshttp/on-finished)
- once@1.4.0 (https://github.com/isaacs/once)
- open@8.4.0 (https://github.com/sindresorhus/open)
+- parseurl@1.3.3 (https://github.com/pillarjs/parseurl)
+- path-key@3.1.1 (https://github.com/sindresorhus/path-key)
+- path-to-regexp@8.3.0 (https://github.com/pillarjs/path-to-regexp)
- pend@1.2.0 (https://github.com/andrewrk/node-pend)
+- pkce-challenge@5.0.0 (https://github.com/crouchcd/pkce-challenge)
- pngjs@6.0.0 (https://github.com/lukeapage/pngjs)
- progress@2.0.3 (https://github.com/visionmedia/node-progress)
-- proxy-from-env@1.1.0 (https://github.com/Rob--W/proxy-from-env)
-- pump@3.0.0 (https://github.com/mafintosh/pump)
+- proxy-addr@2.0.7 (https://github.com/jshttp/proxy-addr)
+- proxy-from-env@2.0.0 (https://github.com/Rob--W/proxy-from-env)
+- pump@3.0.2 (https://github.com/mafintosh/pump)
+- qs@6.15.0 (https://github.com/ljharb/qs)
+- range-parser@1.2.1 (https://github.com/jshttp/range-parser)
+- raw-body@3.0.2 (https://github.com/stream-utils/raw-body)
+- require-from-string@2.0.2 (https://github.com/floatdrop/require-from-string)
- retry@0.12.0 (https://github.com/tim-kos/node-retry)
+- router@2.2.0 (https://github.com/pillarjs/router)
+- safer-buffer@2.1.2 (https://github.com/ChALkeR/safer-buffer)
+- send@1.2.1 (https://github.com/pillarjs/send)
+- serve-static@2.2.1 (https://github.com/expressjs/serve-static)
+- setprototypeof@1.2.0 (https://github.com/wesleytodd/setprototypeof)
+- shebang-command@2.0.0 (https://github.com/kevva/shebang-command)
+- shebang-regex@3.0.0 (https://github.com/sindresorhus/shebang-regex)
+- side-channel-list@1.0.0 (https://github.com/ljharb/side-channel-list)
+- side-channel-map@1.0.1 (https://github.com/ljharb/side-channel-map)
+- side-channel-weakmap@1.0.2 (https://github.com/ljharb/side-channel-weakmap)
+- side-channel@1.1.0 (https://github.com/ljharb/side-channel)
- signal-exit@3.0.7 (https://github.com/tapjs/signal-exit)
- smart-buffer@4.2.0 (https://github.com/JoshGlazebrook/smart-buffer)
-- socks-proxy-agent@8.0.4 (https://github.com/TooTallNate/proxy-agents)
+- socks-proxy-agent@8.0.5 (https://github.com/TooTallNate/proxy-agents)
- socks@2.8.3 (https://github.com/JoshGlazebrook/socks)
- sprintf-js@1.1.3 (https://github.com/alexei/sprintf.js)
-- stack-utils@2.0.5 (https://github.com/tapjs/stack-utils)
+- statuses@2.0.2 (https://github.com/jshttp/statuses)
+- toidentifier@1.0.1 (https://github.com/component/toidentifier)
+- type-is@2.0.1 (https://github.com/jshttp/type-is)
+- unpipe@1.0.0 (https://github.com/stream-utils/unpipe)
+- vary@1.1.2 (https://github.com/jshttp/vary)
+- which@2.0.2 (https://github.com/isaacs/node-which)
- wrappy@1.0.2 (https://github.com/npm/wrappy)
- ws@8.17.1 (https://github.com/websockets/ws)
-- yaml@2.6.0 (https://github.com/eemeli/yaml)
-- yauzl@2.10.0 (https://github.com/thejoshwolfe/yauzl)
+- yaml@2.8.3 (https://github.com/eemeli/yaml)
+- yauzl@3.2.1 (https://github.com/thejoshwolfe/yauzl)
- yazl@2.5.1 (https://github.com/thejoshwolfe/yazl)
+- zod-to-json-schema@3.25.1 (https://github.com/StefanTerdell/zod-to-json-schema)
+- zod@4.3.6 (https://github.com/colinhacks/zod)
-%% @types/node@17.0.24 NOTICES AND INFORMATION BEGIN HERE
+%% @hono/node-server@1.19.11 NOTICES AND INFORMATION BEGIN HERE
=========================================
MIT License
- Copyright (c) Microsoft Corporation.
+Copyright (c) 2022 - present, Yusuke Wada and Hono contributors
- Permission is hereby granted, free of charge, to any person obtaining a copy
- of this software and associated documentation files (the "Software"), to deal
- in the Software without restriction, including without limitation the rights
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- copies of the Software, and to permit persons to whom the Software is
- furnished to do so, subject to the following conditions:
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
- The above copyright notice and this permission notice shall be included in all
- copies or substantial portions of the Software.
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- SOFTWARE
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
=========================================
-END OF @types/node@17.0.24 AND INFORMATION
+END OF @hono/node-server@1.19.11 AND INFORMATION
-%% @types/yauzl@2.10.0 NOTICES AND INFORMATION BEGIN HERE
+%% @modelcontextprotocol/sdk@1.28.0 NOTICES AND INFORMATION BEGIN HERE
=========================================
MIT License
- Copyright (c) Microsoft Corporation.
+Copyright (c) 2024 Anthropic, PBC
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+=========================================
+END OF @modelcontextprotocol/sdk@1.28.0 AND INFORMATION
+
+%% accepts@2.0.0 NOTICES AND INFORMATION BEGIN HERE
+=========================================
+(The MIT License)
+
+Copyright (c) 2014 Jonathan Ong
+Copyright (c) 2015 Douglas Christopher Wilson
- Permission is hereby granted, free of charge, to any person obtaining a copy
- of this software and associated documentation files (the "Software"), to deal
- in the Software without restriction, including without limitation the rights
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- copies of the Software, and to permit persons to whom the Software is
- furnished to do so, subject to the following conditions:
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
- The above copyright notice and this permission notice shall be included in all
- copies or substantial portions of the Software.
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- SOFTWARE
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
=========================================
-END OF @types/yauzl@2.10.0 AND INFORMATION
+END OF accepts@2.0.0 AND INFORMATION
-%% agent-base@7.1.1 NOTICES AND INFORMATION BEGIN HERE
+%% agent-base@7.1.4 NOTICES AND INFORMATION BEGIN HERE
=========================================
(The MIT License)
@@ -130,7 +243,59 @@ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
=========================================
-END OF agent-base@7.1.1 AND INFORMATION
+END OF agent-base@7.1.4 AND INFORMATION
+
+%% ajv-formats@3.0.1 NOTICES AND INFORMATION BEGIN HERE
+=========================================
+MIT License
+
+Copyright (c) 2020 Evgeny Poberezkin
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+=========================================
+END OF ajv-formats@3.0.1 AND INFORMATION
+
+%% ajv@8.18.0 NOTICES AND INFORMATION BEGIN HERE
+=========================================
+The MIT License (MIT)
+
+Copyright (c) 2015-2021 Evgeny Poberezkin
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+=========================================
+END OF ajv@8.18.0 AND INFORMATION
%% balanced-match@1.0.2 NOTICES AND INFORMATION BEGIN HERE
=========================================
@@ -158,7 +323,35 @@ SOFTWARE.
=========================================
END OF balanced-match@1.0.2 AND INFORMATION
-%% brace-expansion@1.1.11 NOTICES AND INFORMATION BEGIN HERE
+%% body-parser@2.2.2 NOTICES AND INFORMATION BEGIN HERE
+=========================================
+(The MIT License)
+
+Copyright (c) 2014 Jonathan Ong
+Copyright (c) 2014-2015 Douglas Christopher Wilson
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+=========================================
+END OF body-parser@2.2.2 AND INFORMATION
+
+%% brace-expansion@1.1.12 NOTICES AND INFORMATION BEGIN HERE
=========================================
MIT License
@@ -182,7 +375,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
=========================================
-END OF brace-expansion@1.1.11 AND INFORMATION
+END OF brace-expansion@1.1.12 AND INFORMATION
%% buffer-crc32@0.2.13 NOTICES AND INFORMATION BEGIN HERE
=========================================
@@ -208,6 +401,86 @@ ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEAL
=========================================
END OF buffer-crc32@0.2.13 AND INFORMATION
+%% bytes@3.1.2 NOTICES AND INFORMATION BEGIN HERE
+=========================================
+(The MIT License)
+
+Copyright (c) 2012-2014 TJ Holowaychuk
+Copyright (c) 2015 Jed Watson
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+=========================================
+END OF bytes@3.1.2 AND INFORMATION
+
+%% call-bind-apply-helpers@1.0.2 NOTICES AND INFORMATION BEGIN HERE
+=========================================
+MIT License
+
+Copyright (c) 2024 Jordan Harband
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+=========================================
+END OF call-bind-apply-helpers@1.0.2 AND INFORMATION
+
+%% call-bound@1.0.4 NOTICES AND INFORMATION BEGIN HERE
+=========================================
+MIT License
+
+Copyright (c) 2024 Jordan Harband
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+=========================================
+END OF call-bound@1.0.4 AND INFORMATION
+
%% codemirror@5.65.18 NOTICES AND INFORMATION BEGIN HERE
=========================================
MIT License
@@ -264,7 +537,7 @@ THE SOFTWARE.
=========================================
END OF colors@1.4.0 AND INFORMATION
-%% commander@8.3.0 NOTICES AND INFORMATION BEGIN HERE
+%% commander@13.1.0 NOTICES AND INFORMATION BEGIN HERE
=========================================
(The MIT License)
@@ -289,7 +562,7 @@ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
=========================================
-END OF commander@8.3.0 AND INFORMATION
+END OF commander@13.1.0 AND INFORMATION
%% concat-map@0.0.1 NOTICES AND INFORMATION BEGIN HERE
=========================================
@@ -314,111 +587,147 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
=========================================
END OF concat-map@0.0.1 AND INFORMATION
-%% debug@4.3.4 NOTICES AND INFORMATION BEGIN HERE
+%% content-disposition@1.0.1 NOTICES AND INFORMATION BEGIN HERE
=========================================
(The MIT License)
-Copyright (c) 2014-2017 TJ Holowaychuk
-Copyright (c) 2018-2021 Josh Junon
+Copyright (c) 2014-2017 Douglas Christopher Wilson
-Permission is hereby granted, free of charge, to any person obtaining a copy of this software
-and associated documentation files (the 'Software'), to deal in the Software without restriction,
-including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
-and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
-subject to the following conditions:
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
-The above copyright notice and this permission notice shall be included in all copies or substantial
-portions of the Software.
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
-THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
-LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
-IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
-WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
=========================================
-END OF debug@4.3.4 AND INFORMATION
+END OF content-disposition@1.0.1 AND INFORMATION
-%% define-lazy-prop@2.0.0 NOTICES AND INFORMATION BEGIN HERE
+%% content-type@1.0.5 NOTICES AND INFORMATION BEGIN HERE
=========================================
-MIT License
+(The MIT License)
-Copyright (c) Sindre Sorhus (sindresorhus.com)
+Copyright (c) 2015 Douglas Christopher Wilson
-Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
-The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
=========================================
-END OF define-lazy-prop@2.0.0 AND INFORMATION
+END OF content-type@1.0.5 AND INFORMATION
-%% diff@7.0.0 NOTICES AND INFORMATION BEGIN HERE
+%% cookie-signature@1.2.2 NOTICES AND INFORMATION BEGIN HERE
=========================================
-BSD 3-Clause License
-
-Copyright (c) 2009-2015, Kevin Decker
-All rights reserved.
+(The MIT License)
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are met:
+Copyright (c) 2012–2024 LearnBoost and other contributors;
-1. Redistributions of source code must retain the above copyright notice, this
- list of conditions and the following disclaimer.
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
-2. Redistributions in binary form must reproduce the above copyright notice,
- this list of conditions and the following disclaimer in the documentation
- and/or other materials provided with the distribution.
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
-3. Neither the name of the copyright holder nor the names of its
- contributors may be used to endorse or promote products derived from
- this software without specific prior written permission.
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+=========================================
+END OF cookie-signature@1.2.2 AND INFORMATION
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
-FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
-SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
-CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+%% cookie@0.7.2 NOTICES AND INFORMATION BEGIN HERE
=========================================
-END OF diff@7.0.0 AND INFORMATION
+(The MIT License)
-%% dotenv@16.4.5 NOTICES AND INFORMATION BEGIN HERE
+Copyright (c) 2012-2014 Roman Shtylman
+Copyright (c) 2015 Douglas Christopher Wilson
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
=========================================
-Copyright (c) 2015, Scott Motte
-All rights reserved.
+END OF cookie@0.7.2 AND INFORMATION
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are met:
+%% cors@2.8.5 NOTICES AND INFORMATION BEGIN HERE
+=========================================
+(The MIT License)
-* Redistributions of source code must retain the above copyright notice, this
- list of conditions and the following disclaimer.
+Copyright (c) 2013 Troy Goode
-* Redistributions in binary form must reproduce the above copyright notice,
- this list of conditions and the following disclaimer in the documentation
- and/or other materials provided with the distribution.
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
-FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
-SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
-CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
=========================================
-END OF dotenv@16.4.5 AND INFORMATION
+END OF cors@2.8.5 AND INFORMATION
-%% end-of-stream@1.4.4 NOTICES AND INFORMATION BEGIN HERE
+%% cross-spawn@7.0.6 NOTICES AND INFORMATION BEGIN HERE
=========================================
The MIT License (MIT)
-Copyright (c) 2014 Mathias Buus
+Copyright (c) 2018 Made With MOXY Lda
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -438,81 +747,85 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
=========================================
-END OF end-of-stream@1.4.4 AND INFORMATION
+END OF cross-spawn@7.0.6 AND INFORMATION
-%% escape-string-regexp@2.0.0 NOTICES AND INFORMATION BEGIN HERE
+%% debug@4.3.4 NOTICES AND INFORMATION BEGIN HERE
=========================================
-MIT License
+(The MIT License)
-Copyright (c) Sindre Sorhus (sindresorhus.com)
+Copyright (c) 2014-2017 TJ Holowaychuk
+Copyright (c) 2018-2021 Josh Junon
-Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software
+and associated documentation files (the 'Software'), to deal in the Software without restriction,
+including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
-The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+The above copyright notice and this permission notice shall be included in all copies or substantial
+portions of the Software.
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
+LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
=========================================
-END OF escape-string-regexp@2.0.0 AND INFORMATION
+END OF debug@4.3.4 AND INFORMATION
-%% extract-zip@2.0.1 NOTICES AND INFORMATION BEGIN HERE
+%% debug@4.4.0 NOTICES AND INFORMATION BEGIN HERE
=========================================
-Copyright (c) 2014 Max Ogden and other contributors
-All rights reserved.
+(The MIT License)
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are met:
+Copyright (c) 2014-2017 TJ Holowaychuk
+Copyright (c) 2018-2021 Josh Junon
-* Redistributions of source code must retain the above copyright notice, this
- list of conditions and the following disclaimer.
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software
+and associated documentation files (the 'Software'), to deal in the Software without restriction,
+including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
-* Redistributions in binary form must reproduce the above copyright notice,
- this list of conditions and the following disclaimer in the documentation
- and/or other materials provided with the distribution.
+The above copyright notice and this permission notice shall be included in all copies or substantial
+portions of the Software.
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
-FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
-SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
-CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
+LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
=========================================
-END OF extract-zip@2.0.1 AND INFORMATION
+END OF debug@4.4.0 AND INFORMATION
-%% fd-slicer@1.1.0 NOTICES AND INFORMATION BEGIN HERE
+%% debug@4.4.3 NOTICES AND INFORMATION BEGIN HERE
=========================================
-Copyright (c) 2014 Andrew Kelley
+(The MIT License)
-Permission is hereby granted, free of charge, to any person
-obtaining a copy of this software and associated documentation files
-(the "Software"), to deal in the Software without restriction,
-including without limitation the rights to use, copy, modify, merge,
-publish, distribute, sublicense, and/or sell copies of the Software,
-and to permit persons to whom the Software is furnished to do so,
+Copyright (c) 2014-2017 TJ Holowaychuk
+Copyright (c) 2018-2021 Josh Junon
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software
+and associated documentation files (the 'Software'), to deal in the Software without restriction,
+including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
-The above copyright notice and this permission notice shall be
-included in all copies or substantial portions of the Software.
+The above copyright notice and this permission notice shall be included in all copies or substantial
+portions of the Software.
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
-BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
-ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
-CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
+LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
=========================================
-END OF fd-slicer@1.1.0 AND INFORMATION
+END OF debug@4.4.3 AND INFORMATION
-%% get-stream@5.2.0 NOTICES AND INFORMATION BEGIN HERE
+%% define-lazy-prop@2.0.0 NOTICES AND INFORMATION BEGIN HERE
=========================================
MIT License
-Copyright (c) Sindre Sorhus (https://sindresorhus.com)
+Copyright (c) Sindre Sorhus (sindresorhus.com)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
@@ -520,33 +833,13 @@ The above copyright notice and this permission notice shall be included in all c
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
=========================================
-END OF get-stream@5.2.0 AND INFORMATION
-
-%% graceful-fs@4.2.10 NOTICES AND INFORMATION BEGIN HERE
-=========================================
-The ISC License
-
-Copyright (c) 2011-2022 Isaac Z. Schlueter, Ben Noordhuis, and Contributors
-
-Permission to use, copy, modify, and/or distribute this software for any
-purpose with or without fee is hereby granted, provided that the above
-copyright notice and this permission notice appear in all copies.
-
-THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
-ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
-ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
-IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-=========================================
-END OF graceful-fs@4.2.10 AND INFORMATION
+END OF define-lazy-prop@2.0.0 AND INFORMATION
-%% https-proxy-agent@7.0.5 NOTICES AND INFORMATION BEGIN HERE
+%% depd@2.0.0 NOTICES AND INFORMATION BEGIN HERE
=========================================
(The MIT License)
-Copyright (c) 2013 Nathan Rajlich
+Copyright (c) 2014-2018 Douglas Christopher Wilson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
@@ -567,140 +860,101 @@ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
=========================================
-END OF https-proxy-agent@7.0.5 AND INFORMATION
-
-%% ip-address@9.0.5 NOTICES AND INFORMATION BEGIN HERE
-=========================================
-Copyright (C) 2011 by Beau Gunderson
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in
-all copies or substantial portions of the Software.
+END OF depd@2.0.0 AND INFORMATION
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-THE SOFTWARE.
+%% diff@8.0.4 NOTICES AND INFORMATION BEGIN HERE
=========================================
-END OF ip-address@9.0.5 AND INFORMATION
+BSD 3-Clause License
-%% is-docker@2.2.1 NOTICES AND INFORMATION BEGIN HERE
-=========================================
-MIT License
+Copyright (c) 2009-2015, Kevin Decker
+All rights reserved.
-Copyright (c) Sindre Sorhus (https://sindresorhus.com)
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
-Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+1. Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
-The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-=========================================
-END OF is-docker@2.2.1 AND INFORMATION
+3. Neither the name of the copyright holder nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
-%% is-wsl@2.2.0 NOTICES AND INFORMATION BEGIN HERE
-=========================================
-MIT License
-
-Copyright (c) Sindre Sorhus (sindresorhus.com)
-
-Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
=========================================
-END OF is-wsl@2.2.0 AND INFORMATION
+END OF diff@8.0.4 AND INFORMATION
-%% jpeg-js@0.4.4 NOTICES AND INFORMATION BEGIN HERE
+%% dotenv@16.4.5 NOTICES AND INFORMATION BEGIN HERE
=========================================
-Copyright (c) 2014, Eugene Ware
+Copyright (c) 2015, Scott Motte
All rights reserved.
-
+
Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are met:
+modification, are permitted provided that the following conditions are met:
-1. Redistributions of source code must retain the above copyright
- notice, this list of conditions and the following disclaimer.
-2. Redistributions in binary form must reproduce the above copyright
- notice, this list of conditions and the following disclaimer in the
- documentation and/or other materials provided with the distribution.
-3. Neither the name of Eugene Ware nor the names of its contributors
- may be used to endorse or promote products derived from this software
- without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY EUGENE WARE ''AS IS'' AND ANY
-EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-DISCLAIMED. IN NO EVENT SHALL EUGENE WARE BE LIABLE FOR ANY
-DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
-(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
-LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
-ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
-SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+* Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
=========================================
-END OF jpeg-js@0.4.4 AND INFORMATION
+END OF dotenv@16.4.5 AND INFORMATION
-%% jsbn@1.1.0 NOTICES AND INFORMATION BEGIN HERE
+%% dunder-proto@1.0.1 NOTICES AND INFORMATION BEGIN HERE
=========================================
-Licensing
----------
+MIT License
-This software is covered under the following copyright:
+Copyright (c) 2024 ECMAScript Shims
-/*
- * Copyright (c) 2003-2005 Tom Wu
- * All Rights Reserved.
- *
- * Permission is hereby granted, free of charge, to any person obtaining
- * a copy of this software and associated documentation files (the
- * "Software"), to deal in the Software without restriction, including
- * without limitation the rights to use, copy, modify, merge, publish,
- * distribute, sublicense, and/or sell copies of the Software, and to
- * permit persons to whom the Software is furnished to do so, subject to
- * the following conditions:
- *
- * The above copyright notice and this permission notice shall be
- * included in all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND,
- * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY
- * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
- *
- * IN NO EVENT SHALL TOM WU BE LIABLE FOR ANY SPECIAL, INCIDENTAL,
- * INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, OR ANY DAMAGES WHATSOEVER
- * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER OR NOT ADVISED OF
- * THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF LIABILITY, ARISING OUT
- * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- *
- * In addition, the following condition applies:
- *
- * All redistributions must retain an intact copy of this copyright notice
- * and disclaimer.
- */
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
-Address all questions regarding this license to:
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
- Tom Wu
- tjw@cs.Stanford.EDU
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
=========================================
-END OF jsbn@1.1.0 AND INFORMATION
+END OF dunder-proto@1.0.1 AND INFORMATION
-%% mime@3.0.0 NOTICES AND INFORMATION BEGIN HERE
+%% ee-first@1.1.1 NOTICES AND INFORMATION BEGIN HERE
=========================================
The MIT License (MIT)
-Copyright (c) 2010 Benjamin Thomas, Robert Kieffer
+Copyright (c) 2014 Jonathan Ong me@jongleberry.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -720,33 +974,66 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
=========================================
-END OF mime@3.0.0 AND INFORMATION
+END OF ee-first@1.1.1 AND INFORMATION
-%% minimatch@3.1.2 NOTICES AND INFORMATION BEGIN HERE
+%% encodeurl@2.0.0 NOTICES AND INFORMATION BEGIN HERE
=========================================
-The ISC License
+(The MIT License)
-Copyright (c) Isaac Z. Schlueter and Contributors
+Copyright (c) 2016 Douglas Christopher Wilson
-Permission to use, copy, modify, and/or distribute this software for any
-purpose with or without fee is hereby granted, provided that the above
-copyright notice and this permission notice appear in all copies.
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
-THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
-ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
-ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
-IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
=========================================
-END OF minimatch@3.1.2 AND INFORMATION
+END OF encodeurl@2.0.0 AND INFORMATION
-%% ms@2.1.2 NOTICES AND INFORMATION BEGIN HERE
+%% end-of-stream@1.4.4 NOTICES AND INFORMATION BEGIN HERE
=========================================
The MIT License (MIT)
-Copyright (c) 2016 Zeit, Inc.
+Copyright (c) 2014 Mathias Buus
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+=========================================
+END OF end-of-stream@1.4.4 AND INFORMATION
+
+%% es-define-property@1.0.1 NOTICES AND INFORMATION BEGIN HERE
+=========================================
+MIT License
+
+Copyright (c) 2024 Jordan Harband
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -766,74 +1053,1982 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
=========================================
-END OF ms@2.1.2 AND INFORMATION
+END OF es-define-property@1.0.1 AND INFORMATION
-%% once@1.4.0 NOTICES AND INFORMATION BEGIN HERE
+%% es-errors@1.3.0 NOTICES AND INFORMATION BEGIN HERE
=========================================
-The ISC License
+MIT License
-Copyright (c) Isaac Z. Schlueter and Contributors
+Copyright (c) 2024 Jordan Harband
-Permission to use, copy, modify, and/or distribute this software for any
-purpose with or without fee is hereby granted, provided that the above
-copyright notice and this permission notice appear in all copies.
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
-THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
-ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
-ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
-IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
=========================================
-END OF once@1.4.0 AND INFORMATION
+END OF es-errors@1.3.0 AND INFORMATION
-%% open@8.4.0 NOTICES AND INFORMATION BEGIN HERE
+%% es-object-atoms@1.1.1 NOTICES AND INFORMATION BEGIN HERE
=========================================
MIT License
-Copyright (c) Sindre Sorhus (https://sindresorhus.com)
+Copyright (c) 2024 Jordan Harband
-Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
-The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
=========================================
-END OF open@8.4.0 AND INFORMATION
+END OF es-object-atoms@1.1.1 AND INFORMATION
-%% pend@1.2.0 NOTICES AND INFORMATION BEGIN HERE
+%% escape-html@1.0.3 NOTICES AND INFORMATION BEGIN HERE
=========================================
-The MIT License (Expat)
+(The MIT License)
-Copyright (c) 2014 Andrew Kelley
+Copyright (c) 2012-2013 TJ Holowaychuk
+Copyright (c) 2015 Andreas Lubbe
+Copyright (c) 2015 Tiancheng "Timothy" Gu
-Permission is hereby granted, free of charge, to any person
-obtaining a copy of this software and associated documentation files
-(the "Software"), to deal in the Software without restriction,
-including without limitation the rights to use, copy, modify, merge,
-publish, distribute, sublicense, and/or sell copies of the Software,
-and to permit persons to whom the Software is furnished to do so,
-subject to the following conditions:
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+=========================================
+END OF escape-html@1.0.3 AND INFORMATION
+
+%% etag@1.8.1 NOTICES AND INFORMATION BEGIN HERE
+=========================================
+(The MIT License)
+
+Copyright (c) 2014-2016 Douglas Christopher Wilson
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+=========================================
+END OF etag@1.8.1 AND INFORMATION
+
+%% eventsource-parser@3.0.3 NOTICES AND INFORMATION BEGIN HERE
+=========================================
+MIT License
+
+Copyright (c) 2025 Espen Hovlandsdal
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+=========================================
+END OF eventsource-parser@3.0.3 AND INFORMATION
+
+%% eventsource@3.0.7 NOTICES AND INFORMATION BEGIN HERE
+=========================================
+The MIT License
+
+Copyright (c) EventSource GitHub organisation
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+=========================================
+END OF eventsource@3.0.7 AND INFORMATION
+
+%% express-rate-limit@8.3.1 NOTICES AND INFORMATION BEGIN HERE
+=========================================
+# MIT License
+
+Copyright 2023 Nathan Friedly, Vedant K
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+=========================================
+END OF express-rate-limit@8.3.1 AND INFORMATION
+
+%% express@5.2.1 NOTICES AND INFORMATION BEGIN HERE
+=========================================
+(The MIT License)
+
+Copyright (c) 2009-2014 TJ Holowaychuk
+Copyright (c) 2013-2014 Roman Shtylman
+Copyright (c) 2014-2015 Douglas Christopher Wilson
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+=========================================
+END OF express@5.2.1 AND INFORMATION
+
+%% fast-deep-equal@3.1.3 NOTICES AND INFORMATION BEGIN HERE
+=========================================
+MIT License
+
+Copyright (c) 2017 Evgeny Poberezkin
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+=========================================
+END OF fast-deep-equal@3.1.3 AND INFORMATION
+
+%% fast-uri@3.1.0 NOTICES AND INFORMATION BEGIN HERE
+=========================================
+Copyright (c) 2011-2021, Gary Court until https://github.com/garycourt/uri-js/commit/a1acf730b4bba3f1097c9f52e7d9d3aba8cdcaae
+Copyright (c) 2021-present The Fastify team
+All rights reserved.
+
+The Fastify team members are listed at https://github.com/fastify/fastify#team.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ * The names of any contributors may not be used to endorse or promote
+ products derived from this software without specific prior written
+ permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+ * * *
+
+The complete list of contributors can be found at:
+- https://github.com/garycourt/uri-js/graphs/contributors
+=========================================
+END OF fast-uri@3.1.0 AND INFORMATION
+
+%% finalhandler@2.1.1 NOTICES AND INFORMATION BEGIN HERE
+=========================================
+(The MIT License)
+
+Copyright (c) 2014-2022 Douglas Christopher Wilson
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+=========================================
+END OF finalhandler@2.1.1 AND INFORMATION
+
+%% forwarded@0.2.0 NOTICES AND INFORMATION BEGIN HERE
+=========================================
+(The MIT License)
+
+Copyright (c) 2014-2017 Douglas Christopher Wilson
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+=========================================
+END OF forwarded@0.2.0 AND INFORMATION
+
+%% fresh@2.0.0 NOTICES AND INFORMATION BEGIN HERE
+=========================================
+(The MIT License)
+
+Copyright (c) 2012 TJ Holowaychuk
+Copyright (c) 2016-2017 Douglas Christopher Wilson
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+=========================================
+END OF fresh@2.0.0 AND INFORMATION
+
+%% function-bind@1.1.2 NOTICES AND INFORMATION BEGIN HERE
+=========================================
+Copyright (c) 2013 Raynos.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+=========================================
+END OF function-bind@1.1.2 AND INFORMATION
+
+%% get-intrinsic@1.3.0 NOTICES AND INFORMATION BEGIN HERE
+=========================================
+MIT License
+
+Copyright (c) 2020 Jordan Harband
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+=========================================
+END OF get-intrinsic@1.3.0 AND INFORMATION
+
+%% get-proto@1.0.1 NOTICES AND INFORMATION BEGIN HERE
+=========================================
+MIT License
+
+Copyright (c) 2025 Jordan Harband
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+=========================================
+END OF get-proto@1.0.1 AND INFORMATION
+
+%% get-stream@5.2.0 NOTICES AND INFORMATION BEGIN HERE
+=========================================
+MIT License
+
+Copyright (c) Sindre Sorhus (https://sindresorhus.com)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+=========================================
+END OF get-stream@5.2.0 AND INFORMATION
+
+%% gopd@1.2.0 NOTICES AND INFORMATION BEGIN HERE
+=========================================
+MIT License
+
+Copyright (c) 2022 Jordan Harband
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+=========================================
+END OF gopd@1.2.0 AND INFORMATION
+
+%% graceful-fs@4.2.10 NOTICES AND INFORMATION BEGIN HERE
+=========================================
+The ISC License
+
+Copyright (c) 2011-2022 Isaac Z. Schlueter, Ben Noordhuis, and Contributors
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
+IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+=========================================
+END OF graceful-fs@4.2.10 AND INFORMATION
+
+%% has-symbols@1.1.0 NOTICES AND INFORMATION BEGIN HERE
+=========================================
+MIT License
+
+Copyright (c) 2016 Jordan Harband
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+=========================================
+END OF has-symbols@1.1.0 AND INFORMATION
+
+%% hasown@2.0.2 NOTICES AND INFORMATION BEGIN HERE
+=========================================
+MIT License
+
+Copyright (c) Jordan Harband and contributors
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+=========================================
+END OF hasown@2.0.2 AND INFORMATION
+
+%% hono@4.12.7 NOTICES AND INFORMATION BEGIN HERE
+=========================================
+MIT License
+
+Copyright (c) 2021 - present, Yusuke Wada and Hono contributors
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+=========================================
+END OF hono@4.12.7 AND INFORMATION
+
+%% http-errors@2.0.1 NOTICES AND INFORMATION BEGIN HERE
+=========================================
+The MIT License (MIT)
+
+Copyright (c) 2014 Jonathan Ong me@jongleberry.com
+Copyright (c) 2016 Douglas Christopher Wilson doug@somethingdoug.com
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+=========================================
+END OF http-errors@2.0.1 AND INFORMATION
+
+%% https-proxy-agent@7.0.6 NOTICES AND INFORMATION BEGIN HERE
+=========================================
+(The MIT License)
+
+Copyright (c) 2013 Nathan Rajlich
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+=========================================
+END OF https-proxy-agent@7.0.6 AND INFORMATION
+
+%% iconv-lite@0.7.2 NOTICES AND INFORMATION BEGIN HERE
+=========================================
+Copyright (c) 2011 Alexander Shtuchkin
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+=========================================
+END OF iconv-lite@0.7.2 AND INFORMATION
+
+%% inherits@2.0.4 NOTICES AND INFORMATION BEGIN HERE
+=========================================
+The ISC License
+
+Copyright (c) Isaac Z. Schlueter
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
+REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
+INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
+OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+PERFORMANCE OF THIS SOFTWARE.
+=========================================
+END OF inherits@2.0.4 AND INFORMATION
+
+%% ini@6.0.0 NOTICES AND INFORMATION BEGIN HERE
+=========================================
+The ISC License
+
+Copyright (c) Isaac Z. Schlueter and Contributors
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
+IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+=========================================
+END OF ini@6.0.0 AND INFORMATION
+
+%% ip-address@10.1.0 NOTICES AND INFORMATION BEGIN HERE
+=========================================
+Copyright (C) 2011 by Beau Gunderson
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+=========================================
+END OF ip-address@10.1.0 AND INFORMATION
+
+%% ip-address@9.0.5 NOTICES AND INFORMATION BEGIN HERE
+=========================================
+Copyright (C) 2011 by Beau Gunderson
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+=========================================
+END OF ip-address@9.0.5 AND INFORMATION
+
+%% ipaddr.js@1.9.1 NOTICES AND INFORMATION BEGIN HERE
+=========================================
+Copyright (C) 2011-2017 whitequark
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+=========================================
+END OF ipaddr.js@1.9.1 AND INFORMATION
+
+%% is-docker@2.2.1 NOTICES AND INFORMATION BEGIN HERE
+=========================================
+MIT License
+
+Copyright (c) Sindre Sorhus (https://sindresorhus.com)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+=========================================
+END OF is-docker@2.2.1 AND INFORMATION
+
+%% is-promise@4.0.0 NOTICES AND INFORMATION BEGIN HERE
+=========================================
+Copyright (c) 2014 Forbes Lindesay
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+=========================================
+END OF is-promise@4.0.0 AND INFORMATION
+
+%% is-wsl@2.2.0 NOTICES AND INFORMATION BEGIN HERE
+=========================================
+MIT License
+
+Copyright (c) Sindre Sorhus (sindresorhus.com)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+=========================================
+END OF is-wsl@2.2.0 AND INFORMATION
+
+%% isexe@2.0.0 NOTICES AND INFORMATION BEGIN HERE
+=========================================
+The ISC License
+
+Copyright (c) Isaac Z. Schlueter and Contributors
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
+IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+=========================================
+END OF isexe@2.0.0 AND INFORMATION
+
+%% jose@6.1.3 NOTICES AND INFORMATION BEGIN HERE
+=========================================
+The MIT License (MIT)
+
+Copyright (c) 2018 Filip Skokan
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+=========================================
+END OF jose@6.1.3 AND INFORMATION
+
+%% jpeg-js@0.4.4 NOTICES AND INFORMATION BEGIN HERE
+=========================================
+Copyright (c) 2014, Eugene Ware
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+3. Neither the name of Eugene Ware nor the names of its contributors
+ may be used to endorse or promote products derived from this software
+ without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY EUGENE WARE ''AS IS'' AND ANY
+EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL EUGENE WARE BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+=========================================
+END OF jpeg-js@0.4.4 AND INFORMATION
+
+%% jsbn@1.1.0 NOTICES AND INFORMATION BEGIN HERE
+=========================================
+Licensing
+---------
+
+This software is covered under the following copyright:
+
+/*
+ * Copyright (c) 2003-2005 Tom Wu
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY
+ * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ * IN NO EVENT SHALL TOM WU BE LIABLE FOR ANY SPECIAL, INCIDENTAL,
+ * INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, OR ANY DAMAGES WHATSOEVER
+ * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER OR NOT ADVISED OF
+ * THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF LIABILITY, ARISING OUT
+ * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * In addition, the following condition applies:
+ *
+ * All redistributions must retain an intact copy of this copyright notice
+ * and disclaimer.
+ */
+
+Address all questions regarding this license to:
+
+ Tom Wu
+ tjw@cs.Stanford.EDU
+=========================================
+END OF jsbn@1.1.0 AND INFORMATION
+
+%% json-schema-traverse@1.0.0 NOTICES AND INFORMATION BEGIN HERE
+=========================================
+MIT License
+
+Copyright (c) 2017 Evgeny Poberezkin
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+=========================================
+END OF json-schema-traverse@1.0.0 AND INFORMATION
+
+%% json-schema-typed@8.0.2 NOTICES AND INFORMATION BEGIN HERE
+=========================================
+BSD 2-Clause License
+
+Original source code is copyright (c) 2019-2025 Remy Rylan
+
+
+All JSON Schema documentation and descriptions are copyright (c):
+
+2009 [draft-0] IETF Trust , Kris Zyp ,
+and SitePen (USA) .
+
+2009 [draft-1] IETF Trust , Kris Zyp ,
+and SitePen (USA) .
+
+2010 [draft-2] IETF Trust , Kris Zyp ,
+and SitePen (USA) .
+
+2010 [draft-3] IETF Trust , Kris Zyp ,
+Gary Court , and SitePen (USA) .
+
+2013 [draft-4] IETF Trust ), Francis Galiegue
+, Kris Zyp , Gary Court
+, and SitePen (USA) .
+
+2018 [draft-7] IETF Trust , Austin Wright ,
+Henry Andrews , Geraint Luff , and
+Cloudflare, Inc. .
+
+2019 [draft-2019-09] IETF Trust , Austin Wright
+, Henry Andrews , Ben Hutton
+, and Greg Dennis .
+
+2020 [draft-2020-12] IETF Trust , Austin Wright
+, Henry Andrews , Ben Hutton
+, and Greg Dennis .
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+=========================================
+END OF json-schema-typed@8.0.2 AND INFORMATION
+
+%% math-intrinsics@1.1.0 NOTICES AND INFORMATION BEGIN HERE
+=========================================
+MIT License
+
+Copyright (c) 2024 ECMAScript Shims
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+=========================================
+END OF math-intrinsics@1.1.0 AND INFORMATION
+
+%% media-typer@1.1.0 NOTICES AND INFORMATION BEGIN HERE
+=========================================
+(The MIT License)
+
+Copyright (c) 2014-2017 Douglas Christopher Wilson
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+=========================================
+END OF media-typer@1.1.0 AND INFORMATION
+
+%% merge-descriptors@2.0.0 NOTICES AND INFORMATION BEGIN HERE
+=========================================
+MIT License
+
+Copyright (c) Jonathan Ong
+Copyright (c) Douglas Christopher Wilson
+Copyright (c) Sindre Sorhus (https://sindresorhus.com)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+=========================================
+END OF merge-descriptors@2.0.0 AND INFORMATION
+
+%% mime-db@1.54.0 NOTICES AND INFORMATION BEGIN HERE
+=========================================
+(The MIT License)
+
+Copyright (c) 2014 Jonathan Ong
+Copyright (c) 2015-2022 Douglas Christopher Wilson
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+=========================================
+END OF mime-db@1.54.0 AND INFORMATION
+
+%% mime-types@3.0.2 NOTICES AND INFORMATION BEGIN HERE
+=========================================
+(The MIT License)
+
+Copyright (c) 2014 Jonathan Ong
+Copyright (c) 2015 Douglas Christopher Wilson
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+=========================================
+END OF mime-types@3.0.2 AND INFORMATION
+
+%% mime@3.0.0 NOTICES AND INFORMATION BEGIN HERE
+=========================================
+The MIT License (MIT)
+
+Copyright (c) 2010 Benjamin Thomas, Robert Kieffer
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+=========================================
+END OF mime@3.0.0 AND INFORMATION
+
+%% minimatch@3.1.4 NOTICES AND INFORMATION BEGIN HERE
+=========================================
+The ISC License
+
+Copyright (c) Isaac Z. Schlueter and Contributors
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
+IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+=========================================
+END OF minimatch@3.1.4 AND INFORMATION
+
+%% ms@2.1.2 NOTICES AND INFORMATION BEGIN HERE
+=========================================
+The MIT License (MIT)
+
+Copyright (c) 2016 Zeit, Inc.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+=========================================
+END OF ms@2.1.2 AND INFORMATION
+
+%% ms@2.1.3 NOTICES AND INFORMATION BEGIN HERE
+=========================================
+The MIT License (MIT)
+
+Copyright (c) 2020 Vercel, Inc.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+=========================================
+END OF ms@2.1.3 AND INFORMATION
+
+%% negotiator@1.0.0 NOTICES AND INFORMATION BEGIN HERE
+=========================================
+(The MIT License)
+
+Copyright (c) 2012-2014 Federico Romero
+Copyright (c) 2012-2014 Isaac Z. Schlueter
+Copyright (c) 2014-2015 Douglas Christopher Wilson
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+=========================================
+END OF negotiator@1.0.0 AND INFORMATION
+
+%% object-assign@4.1.1 NOTICES AND INFORMATION BEGIN HERE
+=========================================
+The MIT License (MIT)
+
+Copyright (c) Sindre Sorhus (sindresorhus.com)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+=========================================
+END OF object-assign@4.1.1 AND INFORMATION
+
+%% object-inspect@1.13.4 NOTICES AND INFORMATION BEGIN HERE
+=========================================
+MIT License
+
+Copyright (c) 2013 James Halliday
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+=========================================
+END OF object-inspect@1.13.4 AND INFORMATION
+
+%% on-finished@2.4.1 NOTICES AND INFORMATION BEGIN HERE
+=========================================
+(The MIT License)
+
+Copyright (c) 2013 Jonathan Ong
+Copyright (c) 2014 Douglas Christopher Wilson
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+=========================================
+END OF on-finished@2.4.1 AND INFORMATION
+
+%% once@1.4.0 NOTICES AND INFORMATION BEGIN HERE
+=========================================
+The ISC License
+
+Copyright (c) Isaac Z. Schlueter and Contributors
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
+IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+=========================================
+END OF once@1.4.0 AND INFORMATION
+
+%% open@8.4.0 NOTICES AND INFORMATION BEGIN HERE
+=========================================
+MIT License
+
+Copyright (c) Sindre Sorhus (https://sindresorhus.com)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+=========================================
+END OF open@8.4.0 AND INFORMATION
+
+%% parseurl@1.3.3 NOTICES AND INFORMATION BEGIN HERE
+=========================================
+(The MIT License)
+
+Copyright (c) 2014 Jonathan Ong
+Copyright (c) 2014-2017 Douglas Christopher Wilson
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+=========================================
+END OF parseurl@1.3.3 AND INFORMATION
+
+%% path-key@3.1.1 NOTICES AND INFORMATION BEGIN HERE
+=========================================
+MIT License
+
+Copyright (c) Sindre Sorhus (sindresorhus.com)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+=========================================
+END OF path-key@3.1.1 AND INFORMATION
+
+%% path-to-regexp@8.3.0 NOTICES AND INFORMATION BEGIN HERE
+=========================================
+The MIT License (MIT)
+
+Copyright (c) 2014 Blake Embrey (hello@blakeembrey.com)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+=========================================
+END OF path-to-regexp@8.3.0 AND INFORMATION
+
+%% pend@1.2.0 NOTICES AND INFORMATION BEGIN HERE
+=========================================
+The MIT License (Expat)
+
+Copyright (c) 2014 Andrew Kelley
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation files
+(the "Software"), to deal in the Software without restriction,
+including without limitation the rights to use, copy, modify, merge,
+publish, distribute, sublicense, and/or sell copies of the Software,
+and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
=========================================
-END OF pend@1.2.0 AND INFORMATION
+END OF pend@1.2.0 AND INFORMATION
+
+%% pkce-challenge@5.0.0 NOTICES AND INFORMATION BEGIN HERE
+=========================================
+MIT License
+
+Copyright (c) 2019
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+=========================================
+END OF pkce-challenge@5.0.0 AND INFORMATION
+
+%% pngjs@6.0.0 NOTICES AND INFORMATION BEGIN HERE
+=========================================
+pngjs2 original work Copyright (c) 2015 Luke Page & Original Contributors
+pngjs derived work Copyright (c) 2012 Kuba Niegowski
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+=========================================
+END OF pngjs@6.0.0 AND INFORMATION
+
+%% progress@2.0.3 NOTICES AND INFORMATION BEGIN HERE
+=========================================
+(The MIT License)
+
+Copyright (c) 2017 TJ Holowaychuk
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+=========================================
+END OF progress@2.0.3 AND INFORMATION
+
+%% proxy-addr@2.0.7 NOTICES AND INFORMATION BEGIN HERE
+=========================================
+(The MIT License)
+
+Copyright (c) 2014-2016 Douglas Christopher Wilson
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+=========================================
+END OF proxy-addr@2.0.7 AND INFORMATION
+
+%% proxy-from-env@2.0.0 NOTICES AND INFORMATION BEGIN HERE
+=========================================
+The MIT License
+
+Copyright (C) 2016-2018 Rob Wu
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+=========================================
+END OF proxy-from-env@2.0.0 AND INFORMATION
+
+%% pump@3.0.2 NOTICES AND INFORMATION BEGIN HERE
+=========================================
+The MIT License (MIT)
+
+Copyright (c) 2014 Mathias Buus
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+=========================================
+END OF pump@3.0.2 AND INFORMATION
+
+%% qs@6.15.0 NOTICES AND INFORMATION BEGIN HERE
+=========================================
+BSD 3-Clause License
+
+Copyright (c) 2014, Nathan LaFreniere and other [contributors](https://github.com/ljharb/qs/graphs/contributors)
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+=========================================
+END OF qs@6.15.0 AND INFORMATION
+
+%% range-parser@1.2.1 NOTICES AND INFORMATION BEGIN HERE
+=========================================
+(The MIT License)
+
+Copyright (c) 2012-2014 TJ Holowaychuk
+Copyright (c) 2015-2016 Douglas Christopher Wilson
+Copyright (c) 2014-2022 Douglas Christopher Wilson
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+=========================================
+END OF raw-body@3.0.2 AND INFORMATION
+
+%% require-from-string@2.0.2 NOTICES AND INFORMATION BEGIN HERE
+=========================================
+The MIT License (MIT)
+
+Copyright (c) Vsevolod Strukchinsky (github.com/floatdrop)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+=========================================
+END OF require-from-string@2.0.2 AND INFORMATION
+
+%% retry@0.12.0 NOTICES AND INFORMATION BEGIN HERE
+=========================================
+Copyright (c) 2011:
+Tim Koschützki (tim@debuggable.com)
+Felix Geisendörfer (felix@debuggable.com)
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.
+=========================================
+END OF retry@0.12.0 AND INFORMATION
+
+%% router@2.2.0 NOTICES AND INFORMATION BEGIN HERE
+=========================================
+(The MIT License)
+
+Copyright (c) 2013 Roman Shtylman
+Copyright (c) 2014-2022 Douglas Christopher Wilson
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+=========================================
+END OF router@2.2.0 AND INFORMATION
+
+%% safer-buffer@2.1.2 NOTICES AND INFORMATION BEGIN HERE
+=========================================
+MIT License
+
+Copyright (c) 2018 Nikita Skovoroda
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+=========================================
+END OF safer-buffer@2.1.2 AND INFORMATION
+
+%% send@1.2.1 NOTICES AND INFORMATION BEGIN HERE
+=========================================
+(The MIT License)
+
+Copyright (c) 2012 TJ Holowaychuk
+Copyright (c) 2014-2022 Douglas Christopher Wilson
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+=========================================
+END OF send@1.2.1 AND INFORMATION
+
+%% serve-static@2.2.1 NOTICES AND INFORMATION BEGIN HERE
+=========================================
+(The MIT License)
+
+Copyright (c) 2010 Sencha Inc.
+Copyright (c) 2011 LearnBoost
+Copyright (c) 2011 TJ Holowaychuk
+Copyright (c) 2014-2016 Douglas Christopher Wilson
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+=========================================
+END OF serve-static@2.2.1 AND INFORMATION
+
+%% setprototypeof@1.2.0 NOTICES AND INFORMATION BEGIN HERE
+=========================================
+Copyright (c) 2015, Wes Todd
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+=========================================
+END OF setprototypeof@1.2.0 AND INFORMATION
+
+%% shebang-command@2.0.0 NOTICES AND INFORMATION BEGIN HERE
+=========================================
+MIT License
+
+Copyright (c) Kevin Mårtensson (github.com/kevva)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+=========================================
+END OF shebang-command@2.0.0 AND INFORMATION
+
+%% shebang-regex@3.0.0 NOTICES AND INFORMATION BEGIN HERE
+=========================================
+MIT License
+
+Copyright (c) Sindre Sorhus (sindresorhus.com)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+=========================================
+END OF shebang-regex@3.0.0 AND INFORMATION
+
+%% side-channel-list@1.0.0 NOTICES AND INFORMATION BEGIN HERE
+=========================================
+MIT License
+
+Copyright (c) 2024 Jordan Harband
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+=========================================
+END OF side-channel-list@1.0.0 AND INFORMATION
-%% pngjs@6.0.0 NOTICES AND INFORMATION BEGIN HERE
+%% side-channel-map@1.0.1 NOTICES AND INFORMATION BEGIN HERE
=========================================
-pngjs2 original work Copyright (c) 2015 Luke Page & Original Contributors
-pngjs derived work Copyright (c) 2012 Kuba Niegowski
+MIT License
+
+Copyright (c) 2024 Jordan Harband
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -842,76 +3037,50 @@ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
-The above copyright notice and this permission notice shall be included in
-all copies or substantial portions of the Software.
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-THE SOFTWARE.
-=========================================
-END OF pngjs@6.0.0 AND INFORMATION
-
-%% progress@2.0.3 NOTICES AND INFORMATION BEGIN HERE
-=========================================
-(The MIT License)
-
-Copyright (c) 2017 TJ Holowaychuk
-
-Permission is hereby granted, free of charge, to any person obtaining
-a copy of this software and associated documentation files (the
-'Software'), to deal in the Software without restriction, including
-without limitation the rights to use, copy, modify, merge, publish,
-distribute, sublicense, and/or sell copies of the Software, and to
-permit persons to whom the Software is furnished to do so, subject to
-the following conditions:
-
-The above copyright notice and this permission notice shall be
-included in all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
-EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
-IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
-CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
-TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
-SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
=========================================
-END OF progress@2.0.3 AND INFORMATION
+END OF side-channel-map@1.0.1 AND INFORMATION
-%% proxy-from-env@1.1.0 NOTICES AND INFORMATION BEGIN HERE
+%% side-channel-weakmap@1.0.2 NOTICES AND INFORMATION BEGIN HERE
=========================================
-The MIT License
+MIT License
-Copyright (C) 2016-2018 Rob Wu
+Copyright (c) 2019 Jordan Harband
-Permission is hereby granted, free of charge, to any person obtaining a copy of
-this software and associated documentation files (the "Software"), to deal in
-the Software without restriction, including without limitation the rights to
-use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
-of the Software, and to permit persons to whom the Software is furnished to do
-so, subject to the following conditions:
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
-FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
-COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
-IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
-CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
=========================================
-END OF proxy-from-env@1.1.0 AND INFORMATION
+END OF side-channel-weakmap@1.0.2 AND INFORMATION
-%% pump@3.0.0 NOTICES AND INFORMATION BEGIN HERE
+%% side-channel@1.1.0 NOTICES AND INFORMATION BEGIN HERE
=========================================
-The MIT License (MIT)
+MIT License
-Copyright (c) 2014 Mathias Buus
+Copyright (c) 2019 Jordan Harband
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -920,44 +3089,18 @@ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
-The above copyright notice and this permission notice shall be included in
-all copies or substantial portions of the Software.
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-THE SOFTWARE.
-=========================================
-END OF pump@3.0.0 AND INFORMATION
-
-%% retry@0.12.0 NOTICES AND INFORMATION BEGIN HERE
-=========================================
-Copyright (c) 2011:
-Tim Koschützki (tim@debuggable.com)
-Felix Geisendörfer (felix@debuggable.com)
-
- Permission is hereby granted, free of charge, to any person obtaining a copy
- of this software and associated documentation files (the "Software"), to deal
- in the Software without restriction, including without limitation the rights
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- copies of the Software, and to permit persons to whom the Software is
- furnished to do so, subject to the following conditions:
-
- The above copyright notice and this permission notice shall be included in
- all copies or substantial portions of the Software.
-
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- THE SOFTWARE.
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
=========================================
-END OF retry@0.12.0 AND INFORMATION
+END OF side-channel@1.1.0 AND INFORMATION
%% signal-exit@3.0.7 NOTICES AND INFORMATION BEGIN HERE
=========================================
@@ -1005,7 +3148,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
=========================================
END OF smart-buffer@4.2.0 AND INFORMATION
-%% socks-proxy-agent@8.0.4 NOTICES AND INFORMATION BEGIN HERE
+%% socks-proxy-agent@8.0.5 NOTICES AND INFORMATION BEGIN HERE
=========================================
(The MIT License)
@@ -1030,7 +3173,7 @@ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
=========================================
-END OF socks-proxy-agent@8.0.4 AND INFORMATION
+END OF socks-proxy-agent@8.0.5 AND INFORMATION
%% socks@2.8.3 NOTICES AND INFORMATION BEGIN HERE
=========================================
@@ -1086,11 +3229,12 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
=========================================
END OF sprintf-js@1.1.3 AND INFORMATION
-%% stack-utils@2.0.5 NOTICES AND INFORMATION BEGIN HERE
+%% statuses@2.0.2 NOTICES AND INFORMATION BEGIN HERE
=========================================
The MIT License (MIT)
-Copyright (c) Isaac Z. Schlueter , James Talmage (github.com/jamestalmage), and Contributors
+Copyright (c) 2014 Jonathan Ong
+Copyright (c) 2016 Douglas Christopher Wilson
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -1110,7 +3254,135 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
=========================================
-END OF stack-utils@2.0.5 AND INFORMATION
+END OF statuses@2.0.2 AND INFORMATION
+
+%% toidentifier@1.0.1 NOTICES AND INFORMATION BEGIN HERE
+=========================================
+MIT License
+
+Copyright (c) 2016 Douglas Christopher Wilson
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+=========================================
+END OF toidentifier@1.0.1 AND INFORMATION
+
+%% type-is@2.0.1 NOTICES AND INFORMATION BEGIN HERE
+=========================================
+(The MIT License)
+
+Copyright (c) 2014 Jonathan Ong
+Copyright (c) 2014-2015 Douglas Christopher Wilson
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+=========================================
+END OF type-is@2.0.1 AND INFORMATION
+
+%% unpipe@1.0.0 NOTICES AND INFORMATION BEGIN HERE
+=========================================
+(The MIT License)
+
+Copyright (c) 2015 Douglas Christopher Wilson
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+=========================================
+END OF unpipe@1.0.0 AND INFORMATION
+
+%% vary@1.1.2 NOTICES AND INFORMATION BEGIN HERE
+=========================================
+(The MIT License)
+
+Copyright (c) 2014-2017 Douglas Christopher Wilson
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+=========================================
+END OF vary@1.1.2 AND INFORMATION
+
+%% which@2.0.2 NOTICES AND INFORMATION BEGIN HERE
+=========================================
+The ISC License
+
+Copyright (c) Isaac Z. Schlueter and Contributors
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
+IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+=========================================
+END OF which@2.0.2 AND INFORMATION
%% wrappy@1.0.2 NOTICES AND INFORMATION BEGIN HERE
=========================================
@@ -1157,7 +3429,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
=========================================
END OF ws@8.17.1 AND INFORMATION
-%% yaml@2.6.0 NOTICES AND INFORMATION BEGIN HERE
+%% yaml@2.8.3 NOTICES AND INFORMATION BEGIN HERE
=========================================
Copyright Eemeli Aro
@@ -1173,9 +3445,9 @@ OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
THIS SOFTWARE.
=========================================
-END OF yaml@2.6.0 AND INFORMATION
+END OF yaml@2.8.3 AND INFORMATION
-%% yauzl@2.10.0 NOTICES AND INFORMATION BEGIN HERE
+%% yauzl@3.2.1 NOTICES AND INFORMATION BEGIN HERE
=========================================
The MIT License (MIT)
@@ -1199,7 +3471,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
=========================================
-END OF yauzl@2.10.0 AND INFORMATION
+END OF yauzl@3.2.1 AND INFORMATION
%% yazl@2.5.1 NOTICES AND INFORMATION BEGIN HERE
=========================================
@@ -1227,8 +3499,54 @@ SOFTWARE.
=========================================
END OF yazl@2.5.1 AND INFORMATION
+%% zod-to-json-schema@3.25.1 NOTICES AND INFORMATION BEGIN HERE
+=========================================
+ISC License
+
+Copyright (c) 2020, Stefan Terdell
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+=========================================
+END OF zod-to-json-schema@3.25.1 AND INFORMATION
+
+%% zod@4.3.6 NOTICES AND INFORMATION BEGIN HERE
+=========================================
+MIT License
+
+Copyright (c) 2025 Colin McDonnell
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+=========================================
+END OF zod@4.3.6 AND INFORMATION
+
SUMMARY BEGIN HERE
=========================================
-Total Packages: 48
+Total Packages: 133
=========================================
END OF SUMMARY
\ No newline at end of file
diff --git a/priv/static/node_modules/playwright-core/bin/PrintDeps.exe b/priv/static/node_modules/playwright-core/bin/PrintDeps.exe
deleted file mode 100644
index eb8ddf4d..00000000
Binary files a/priv/static/node_modules/playwright-core/bin/PrintDeps.exe and /dev/null differ
diff --git a/priv/static/node_modules/playwright-core/bin/README.md b/priv/static/node_modules/playwright-core/bin/README.md
deleted file mode 100644
index 2426643d..00000000
--- a/priv/static/node_modules/playwright-core/bin/README.md
+++ /dev/null
@@ -1,2 +0,0 @@
-See building instructions at [`/browser_patches/winldd/README.md`](../../../browser_patches/winldd/README.md)
-
diff --git a/priv/static/node_modules/playwright-core/bin/install_webkit_wsl.ps1 b/priv/static/node_modules/playwright-core/bin/install_webkit_wsl.ps1
new file mode 100644
index 00000000..ccaaf156
--- /dev/null
+++ b/priv/static/node_modules/playwright-core/bin/install_webkit_wsl.ps1
@@ -0,0 +1,33 @@
+$ErrorActionPreference = 'Stop'
+
+# This script sets up a WSL distribution that will be used to run WebKit.
+
+$Distribution = "playwright"
+$Username = "pwuser"
+
+$distributions = (wsl --list --quiet) -split "\r?\n"
+if ($distributions -contains $Distribution) {
+ Write-Host "WSL distribution '$Distribution' already exists. Skipping installation."
+} else {
+ Write-Host "Installing new WSL distribution '$Distribution'..."
+ $VhdSize = "10GB"
+ wsl --install -d Ubuntu-24.04 --name $Distribution --no-launch --vhd-size $VhdSize
+ wsl -d $Distribution -u root adduser --gecos GECOS --disabled-password $Username
+}
+
+$pwshDirname = (Resolve-Path -Path $PSScriptRoot).Path;
+$playwrightCoreRoot = Resolve-Path (Join-Path $pwshDirname "..")
+
+$initScript = @"
+if [ ! -f "/home/$Username/node/bin/node" ]; then
+ mkdir -p /home/$Username/node
+ curl -fsSL https://nodejs.org/dist/v22.17.0/node-v22.17.0-linux-x64.tar.xz -o /home/$Username/node/node-v22.17.0-linux-x64.tar.xz
+ tar -xJf /home/$Username/node/node-v22.17.0-linux-x64.tar.xz -C /home/$Username/node --strip-components=1
+ sudo -u $Username echo 'export PATH=/home/$Username/node/bin:\`$PATH' >> /home/$Username/.profile
+fi
+/home/$Username/node/bin/node cli.js install-deps webkit
+sudo -u $Username PLAYWRIGHT_SKIP_BROWSER_GC=1 /home/$Username/node/bin/node cli.js install webkit
+"@ -replace "\r\n", "`n"
+
+wsl -d $Distribution --cd $playwrightCoreRoot -u root -- bash -c "$initScript"
+Write-Host "Done!"
\ No newline at end of file
diff --git a/priv/static/node_modules/playwright-core/bin/reinstall_chrome_beta_mac.sh b/priv/static/node_modules/playwright-core/bin/reinstall_chrome_beta_mac.sh
index b6e1990c..617e3b5e 100755
--- a/priv/static/node_modules/playwright-core/bin/reinstall_chrome_beta_mac.sh
+++ b/priv/static/node_modules/playwright-core/bin/reinstall_chrome_beta_mac.sh
@@ -4,7 +4,7 @@ set -x
rm -rf "/Applications/Google Chrome Beta.app"
cd /tmp
-curl -o ./googlechromebeta.dmg -k https://dl.google.com/chrome/mac/universal/beta/googlechromebeta.dmg
+curl --retry 3 -o ./googlechromebeta.dmg https://dl.google.com/chrome/mac/universal/beta/googlechromebeta.dmg
hdiutil attach -nobrowse -quiet -noautofsck -noautoopen -mountpoint /Volumes/googlechromebeta.dmg ./googlechromebeta.dmg
cp -pR "/Volumes/googlechromebeta.dmg/Google Chrome Beta.app" /Applications
hdiutil detach /Volumes/googlechromebeta.dmg
diff --git a/priv/static/node_modules/playwright-core/bin/reinstall_chrome_stable_mac.sh b/priv/static/node_modules/playwright-core/bin/reinstall_chrome_stable_mac.sh
index 91d826c0..6aa650a5 100755
--- a/priv/static/node_modules/playwright-core/bin/reinstall_chrome_stable_mac.sh
+++ b/priv/static/node_modules/playwright-core/bin/reinstall_chrome_stable_mac.sh
@@ -4,7 +4,7 @@ set -x
rm -rf "/Applications/Google Chrome.app"
cd /tmp
-curl -o ./googlechrome.dmg -k https://dl.google.com/chrome/mac/universal/stable/GGRO/googlechrome.dmg
+curl --retry 3 -o ./googlechrome.dmg https://dl.google.com/chrome/mac/universal/stable/GGRO/googlechrome.dmg
hdiutil attach -nobrowse -quiet -noautofsck -noautoopen -mountpoint /Volumes/googlechrome.dmg ./googlechrome.dmg
cp -pR "/Volumes/googlechrome.dmg/Google Chrome.app" /Applications
hdiutil detach /Volumes/googlechrome.dmg
diff --git a/priv/static/node_modules/playwright-core/bin/reinstall_msedge_beta_linux.sh b/priv/static/node_modules/playwright-core/bin/reinstall_msedge_beta_linux.sh
index ececd05a..a1531a95 100755
--- a/priv/static/node_modules/playwright-core/bin/reinstall_msedge_beta_linux.sh
+++ b/priv/static/node_modules/playwright-core/bin/reinstall_msedge_beta_linux.sh
@@ -32,6 +32,12 @@ if ! command -v curl >/dev/null; then
apt-get install -y curl
fi
+# GnuPG is not preinstalled in slim images
+if ! command -v gpg >/dev/null; then
+ apt-get update
+ apt-get install -y gpg
+fi
+
# 3. Add the GPG key, the apt repo, update the apt cache, and install the package
curl https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > /tmp/microsoft.gpg
install -o root -g root -m 644 /tmp/microsoft.gpg /etc/apt/trusted.gpg.d/
diff --git a/priv/static/node_modules/playwright-core/bin/reinstall_msedge_beta_mac.sh b/priv/static/node_modules/playwright-core/bin/reinstall_msedge_beta_mac.sh
index 69c06024..72ec3e4e 100755
--- a/priv/static/node_modules/playwright-core/bin/reinstall_msedge_beta_mac.sh
+++ b/priv/static/node_modules/playwright-core/bin/reinstall_msedge_beta_mac.sh
@@ -3,7 +3,7 @@ set -e
set -x
cd /tmp
-curl -o ./msedge_beta.pkg -k "$1"
+curl --retry 3 -o ./msedge_beta.pkg "$1"
# Note: there's no way to uninstall previously installed MSEdge.
# However, running PKG again seems to update installation.
sudo installer -pkg /tmp/msedge_beta.pkg -target /
diff --git a/priv/static/node_modules/playwright-core/bin/reinstall_msedge_dev_linux.sh b/priv/static/node_modules/playwright-core/bin/reinstall_msedge_dev_linux.sh
index 6ab84c31..7fde34e5 100755
--- a/priv/static/node_modules/playwright-core/bin/reinstall_msedge_dev_linux.sh
+++ b/priv/static/node_modules/playwright-core/bin/reinstall_msedge_dev_linux.sh
@@ -32,6 +32,12 @@ if ! command -v curl >/dev/null; then
apt-get install -y curl
fi
+# GnuPG is not preinstalled in slim images
+if ! command -v gpg >/dev/null; then
+ apt-get update
+ apt-get install -y gpg
+fi
+
# 3. Add the GPG key, the apt repo, update the apt cache, and install the package
curl https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > /tmp/microsoft.gpg
install -o root -g root -m 644 /tmp/microsoft.gpg /etc/apt/trusted.gpg.d/
diff --git a/priv/static/node_modules/playwright-core/bin/reinstall_msedge_dev_mac.sh b/priv/static/node_modules/playwright-core/bin/reinstall_msedge_dev_mac.sh
index 0ad05b0a..3376e869 100755
--- a/priv/static/node_modules/playwright-core/bin/reinstall_msedge_dev_mac.sh
+++ b/priv/static/node_modules/playwright-core/bin/reinstall_msedge_dev_mac.sh
@@ -3,7 +3,7 @@ set -e
set -x
cd /tmp
-curl -o ./msedge_dev.pkg -k "$1"
+curl --retry 3 -o ./msedge_dev.pkg "$1"
# Note: there's no way to uninstall previously installed MSEdge.
# However, running PKG again seems to update installation.
sudo installer -pkg /tmp/msedge_dev.pkg -target /
diff --git a/priv/static/node_modules/playwright-core/bin/reinstall_msedge_stable_linux.sh b/priv/static/node_modules/playwright-core/bin/reinstall_msedge_stable_linux.sh
index e66f85bb..4acb1dbf 100755
--- a/priv/static/node_modules/playwright-core/bin/reinstall_msedge_stable_linux.sh
+++ b/priv/static/node_modules/playwright-core/bin/reinstall_msedge_stable_linux.sh
@@ -32,6 +32,12 @@ if ! command -v curl >/dev/null; then
apt-get install -y curl
fi
+# GnuPG is not preinstalled in slim images
+if ! command -v gpg >/dev/null; then
+ apt-get update
+ apt-get install -y gpg
+fi
+
# 3. Add the GPG key, the apt repo, update the apt cache, and install the package
curl https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > /tmp/microsoft.gpg
install -o root -g root -m 644 /tmp/microsoft.gpg /etc/apt/trusted.gpg.d/
diff --git a/priv/static/node_modules/playwright-core/bin/reinstall_msedge_stable_mac.sh b/priv/static/node_modules/playwright-core/bin/reinstall_msedge_stable_mac.sh
index b82cfb37..afcd2f53 100755
--- a/priv/static/node_modules/playwright-core/bin/reinstall_msedge_stable_mac.sh
+++ b/priv/static/node_modules/playwright-core/bin/reinstall_msedge_stable_mac.sh
@@ -3,7 +3,7 @@ set -e
set -x
cd /tmp
-curl -o ./msedge_stable.pkg -k "$1"
+curl --retry 3 -o ./msedge_stable.pkg "$1"
# Note: there's no way to uninstall previously installed MSEdge.
# However, running PKG again seems to update installation.
sudo installer -pkg /tmp/msedge_stable.pkg -target /
diff --git a/priv/static/node_modules/playwright-core/browsers.json b/priv/static/node_modules/playwright-core/browsers.json
index 81f1d3af..aff31024 100644
--- a/priv/static/node_modules/playwright-core/browsers.json
+++ b/priv/static/node_modules/playwright-core/browsers.json
@@ -3,59 +3,75 @@
"browsers": [
{
"name": "chromium",
- "revision": "1148",
+ "revision": "1217",
"installByDefault": true,
- "browserVersion": "131.0.6778.33"
+ "browserVersion": "147.0.7727.15",
+ "title": "Chrome for Testing"
},
{
"name": "chromium-headless-shell",
- "revision": "1148",
+ "revision": "1217",
"installByDefault": true,
- "browserVersion": "131.0.6778.33"
+ "browserVersion": "147.0.7727.15",
+ "title": "Chrome Headless Shell"
},
{
"name": "chromium-tip-of-tree",
- "revision": "1277",
+ "revision": "1417",
"installByDefault": false,
- "browserVersion": "132.0.6834.0"
+ "browserVersion": "148.0.7755.0",
+ "title": "Chrome Canary for Testing"
+ },
+ {
+ "name": "chromium-tip-of-tree-headless-shell",
+ "revision": "1417",
+ "installByDefault": false,
+ "browserVersion": "148.0.7755.0",
+ "title": "Chrome Canary Headless Shell"
},
{
"name": "firefox",
- "revision": "1466",
+ "revision": "1511",
"installByDefault": true,
- "browserVersion": "132.0"
+ "browserVersion": "148.0.2",
+ "title": "Firefox"
},
{
"name": "firefox-beta",
- "revision": "1465",
+ "revision": "1505",
"installByDefault": false,
- "browserVersion": "132.0b8"
+ "browserVersion": "148.0b9",
+ "title": "Firefox Beta"
},
{
"name": "webkit",
- "revision": "2104",
+ "revision": "2272",
"installByDefault": true,
"revisionOverrides": {
- "mac10.14": "1446",
- "mac10.15": "1616",
- "mac11": "1816",
- "mac11-arm64": "1816",
- "mac12": "2009",
- "mac12-arm64": "2009",
+ "mac14": "2251",
+ "mac14-arm64": "2251",
+ "debian11-x64": "2105",
+ "debian11-arm64": "2105",
"ubuntu20.04-x64": "2092",
"ubuntu20.04-arm64": "2092"
},
- "browserVersion": "18.2"
+ "browserVersion": "26.4",
+ "title": "WebKit"
},
{
"name": "ffmpeg",
- "revision": "1010",
+ "revision": "1011",
"installByDefault": true,
"revisionOverrides": {
"mac12": "1010",
"mac12-arm64": "1010"
}
},
+ {
+ "name": "winldd",
+ "revision": "1007",
+ "installByDefault": false
+ },
{
"name": "android",
"revision": "1001",
diff --git a/priv/static/node_modules/playwright-core/index.js b/priv/static/node_modules/playwright-core/index.js
index 3d246e9f..d4991d0e 100644
--- a/priv/static/node_modules/playwright-core/index.js
+++ b/priv/static/node_modules/playwright-core/index.js
@@ -13,13 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-const minimumMajorNodeVersion = 14;
+const minimumMajorNodeVersion = 18;
const currentNodeVersion = process.versions.node;
const semver = currentNodeVersion.split('.');
const [major] = [+semver[0]];
if (major < minimumMajorNodeVersion) {
- // eslint-disable-next-line no-console
console.error(
'You are running Node.js ' +
currentNodeVersion +
diff --git a/priv/static/node_modules/playwright-core/lib/androidServerImpl.js b/priv/static/node_modules/playwright-core/lib/androidServerImpl.js
index 72556cb6..568548b7 100644
--- a/priv/static/node_modules/playwright-core/lib/androidServerImpl.js
+++ b/priv/static/node_modules/playwright-core/lib/androidServerImpl.js
@@ -1,69 +1,65 @@
"use strict";
-
-Object.defineProperty(exports, "__esModule", {
- value: true
+var __defProp = Object.defineProperty;
+var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
+var __getOwnPropNames = Object.getOwnPropertyNames;
+var __hasOwnProp = Object.prototype.hasOwnProperty;
+var __export = (target, all) => {
+ for (var name in all)
+ __defProp(target, name, { get: all[name], enumerable: true });
+};
+var __copyProps = (to, from, except, desc) => {
+ if (from && typeof from === "object" || typeof from === "function") {
+ for (let key of __getOwnPropNames(from))
+ if (!__hasOwnProp.call(to, key) && key !== except)
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
+ }
+ return to;
+};
+var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
+var androidServerImpl_exports = {};
+__export(androidServerImpl_exports, {
+ AndroidServerLauncherImpl: () => AndroidServerLauncherImpl
});
-exports.AndroidServerLauncherImpl = void 0;
-var _utilsBundle = require("./utilsBundle");
-var _utils = require("./utils");
-var _playwright = require("./server/playwright");
-var _playwrightServer = require("./remote/playwrightServer");
-/**
- * Copyright (c) Microsoft Corporation.
- *
- * Licensed under the Apache License, Version 2.0 (the 'License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
+module.exports = __toCommonJS(androidServerImpl_exports);
+var import_playwrightServer = require("./remote/playwrightServer");
+var import_playwright = require("./server/playwright");
+var import_crypto = require("./server/utils/crypto");
+var import_utilsBundle = require("./utilsBundle");
+var import_progress = require("./server/progress");
class AndroidServerLauncherImpl {
async launchServer(options = {}) {
- const playwright = (0, _playwright.createPlaywright)({
- sdkLanguage: 'javascript',
- isServer: true
- });
- // 1. Pre-connect to the device
- let devices = await playwright.android.devices({
+ const playwright = (0, import_playwright.createPlaywright)({ sdkLanguage: "javascript", isServer: true });
+ const controller = new import_progress.ProgressController();
+ let devices = await controller.run((progress) => playwright.android.devices(progress, {
host: options.adbHost,
port: options.adbPort,
omitDriverInstall: options.omitDriverInstall
- });
- if (devices.length === 0) throw new Error('No devices found');
+ }));
+ if (devices.length === 0)
+ throw new Error("No devices found");
if (options.deviceSerialNumber) {
- devices = devices.filter(d => d.serial === options.deviceSerialNumber);
- if (devices.length === 0) throw new Error(`No device with serial number '${options.deviceSerialNumber}' not found`);
+ devices = devices.filter((d) => d.serial === options.deviceSerialNumber);
+ if (devices.length === 0)
+ throw new Error(`No device with serial number '${options.deviceSerialNumber}' was found`);
}
- if (devices.length > 1) throw new Error(`More than one device found. Please specify deviceSerialNumber`);
+ if (devices.length > 1)
+ throw new Error(`More than one device found. Please specify deviceSerialNumber`);
const device = devices[0];
- const path = options.wsPath ? options.wsPath.startsWith('/') ? options.wsPath : `/${options.wsPath}` : `/${(0, _utils.createGuid)()}`;
-
- // 2. Start the server
- const server = new _playwrightServer.PlaywrightServer({
- mode: 'launchServer',
- path,
- maxConnections: 1,
- preLaunchedAndroidDevice: device
- });
+ const path = options.wsPath ? options.wsPath.startsWith("/") ? options.wsPath : `/${options.wsPath}` : `/${(0, import_crypto.createGuid)()}`;
+ const server = new import_playwrightServer.PlaywrightServer({ mode: "launchServer", path, maxConnections: 1, preLaunchedAndroidDevice: device });
const wsEndpoint = await server.listen(options.port, options.host);
-
- // 3. Return the BrowserServer interface
- const browserServer = new _utilsBundle.ws.EventEmitter();
+ const browserServer = new import_utilsBundle.ws.EventEmitter();
browserServer.wsEndpoint = () => wsEndpoint;
browserServer.close = () => device.close();
browserServer.kill = () => device.close();
- device.on('close', () => {
+ device.on("close", () => {
server.close();
- browserServer.emit('close');
+ browserServer.emit("close");
});
return browserServer;
}
}
-exports.AndroidServerLauncherImpl = AndroidServerLauncherImpl;
\ No newline at end of file
+// Annotate the CommonJS export names for ESM import in node:
+0 && (module.exports = {
+ AndroidServerLauncherImpl
+});
diff --git a/priv/static/node_modules/playwright-core/lib/bootstrap.js b/priv/static/node_modules/playwright-core/lib/bootstrap.js
new file mode 100644
index 00000000..f00db609
--- /dev/null
+++ b/priv/static/node_modules/playwright-core/lib/bootstrap.js
@@ -0,0 +1,77 @@
+"use strict";
+if (process.env.PW_INSTRUMENT_MODULES) {
+ const Module = require("module");
+ const originalLoad = Module._load;
+ const root = { name: "", selfMs: 0, totalMs: 0, childrenMs: 0, children: [] };
+ let current = root;
+ const stack = [];
+ Module._load = function(request, _parent, _isMain) {
+ const node = { name: request, selfMs: 0, totalMs: 0, childrenMs: 0, children: [] };
+ current.children.push(node);
+ stack.push(current);
+ current = node;
+ const start = performance.now();
+ let result;
+ try {
+ result = originalLoad.apply(this, arguments);
+ } catch (e) {
+ current = stack.pop();
+ current.children.pop();
+ throw e;
+ }
+ const duration = performance.now() - start;
+ node.totalMs = duration;
+ node.selfMs = Math.max(0, duration - node.childrenMs);
+ current = stack.pop();
+ current.childrenMs += duration;
+ return result;
+ };
+ process.on("exit", () => {
+ function printTree(node, prefix, isLast, lines2, depth) {
+ if (node.totalMs < 1 && depth > 0)
+ return;
+ const connector = depth === 0 ? "" : isLast ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 ";
+ const time = `${node.totalMs.toFixed(1).padStart(8)}ms`;
+ const self = node.children.length ? ` (self: ${node.selfMs.toFixed(1)}ms)` : "";
+ lines2.push(`${time} ${prefix}${connector}${node.name}${self}`);
+ const childPrefix = prefix + (depth === 0 ? "" : isLast ? " " : "\u2502 ");
+ const sorted2 = node.children.slice().sort((a, b) => b.totalMs - a.totalMs);
+ for (let i = 0; i < sorted2.length; i++)
+ printTree(sorted2[i], childPrefix, i === sorted2.length - 1, lines2, depth + 1);
+ }
+ let totalModules = 0;
+ function count(n) {
+ totalModules++;
+ n.children.forEach(count);
+ }
+ root.children.forEach(count);
+ const lines = [];
+ const sorted = root.children.slice().sort((a, b) => b.totalMs - a.totalMs);
+ for (let i = 0; i < sorted.length; i++)
+ printTree(sorted[i], "", i === sorted.length - 1, lines, 0);
+ const totalMs = root.children.reduce((s, c) => s + c.totalMs, 0);
+ process.stderr.write(`
+--- Module load tree: ${totalModules} modules, ${totalMs.toFixed(0)}ms total ---
+` + lines.join("\n") + "\n");
+ const flat = /* @__PURE__ */ new Map();
+ function gather(n) {
+ const existing = flat.get(n.name);
+ if (existing) {
+ existing.selfMs += n.selfMs;
+ existing.totalMs += n.totalMs;
+ existing.count++;
+ } else {
+ flat.set(n.name, { selfMs: n.selfMs, totalMs: n.totalMs, count: 1 });
+ }
+ n.children.forEach(gather);
+ }
+ root.children.forEach(gather);
+ const top50 = [...flat.entries()].sort((a, b) => b[1].selfMs - a[1].selfMs).slice(0, 50);
+ const flatLines = top50.map(
+ ([mod, { selfMs, totalMs: totalMs2, count: count2 }]) => `${selfMs.toFixed(1).padStart(8)}ms self ${totalMs2.toFixed(1).padStart(8)}ms total (x${String(count2).padStart(3)}) ${mod}`
+ );
+ process.stderr.write(`
+--- Top 50 modules by self time ---
+` + flatLines.join("\n") + "\n");
+ });
+}
diff --git a/priv/static/node_modules/playwright-core/lib/browserServerImpl.js b/priv/static/node_modules/playwright-core/lib/browserServerImpl.js
index f6101fe3..ac2b25d8 100644
--- a/priv/static/node_modules/playwright-core/lib/browserServerImpl.js
+++ b/priv/static/node_modules/playwright-core/lib/browserServerImpl.js
@@ -1,74 +1,91 @@
"use strict";
-
-Object.defineProperty(exports, "__esModule", {
- value: true
+var __create = Object.create;
+var __defProp = Object.defineProperty;
+var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
+var __getOwnPropNames = Object.getOwnPropertyNames;
+var __getProtoOf = Object.getPrototypeOf;
+var __hasOwnProp = Object.prototype.hasOwnProperty;
+var __export = (target, all) => {
+ for (var name in all)
+ __defProp(target, name, { get: all[name], enumerable: true });
+};
+var __copyProps = (to, from, except, desc) => {
+ if (from && typeof from === "object" || typeof from === "function") {
+ for (let key of __getOwnPropNames(from))
+ if (!__hasOwnProp.call(to, key) && key !== except)
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
+ }
+ return to;
+};
+var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
+ // If the importer is in node compatibility mode or this is not an ESM
+ // file that has been converted to a CommonJS file using a Babel-
+ // compatible transform (i.e. "__esModule" has not been set), then set
+ // "default" to the CommonJS "module.exports" for node compatibility.
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
+ mod
+));
+var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
+var browserServerImpl_exports = {};
+__export(browserServerImpl_exports, {
+ BrowserServerLauncherImpl: () => BrowserServerLauncherImpl
});
-exports.BrowserServerLauncherImpl = void 0;
-var _utilsBundle = require("./utilsBundle");
-var _clientHelper = require("./client/clientHelper");
-var _utils = require("./utils");
-var _instrumentation = require("./server/instrumentation");
-var _playwright = require("./server/playwright");
-var _playwrightServer = require("./remote/playwrightServer");
-var _helper = require("./server/helper");
-var _stackTrace = require("./utils/stackTrace");
-var _socksProxy = require("./common/socksProxy");
-/**
- * Copyright (c) Microsoft Corporation.
- *
- * Licensed under the Apache License, Version 2.0 (the 'License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
+module.exports = __toCommonJS(browserServerImpl_exports);
+var import_playwrightServer = require("./remote/playwrightServer");
+var import_helper = require("./server/helper");
+var import_playwright = require("./server/playwright");
+var import_crypto = require("./server/utils/crypto");
+var import_debug = require("./server/utils/debug");
+var import_stackTrace = require("./utils/isomorphic/stackTrace");
+var import_time = require("./utils/isomorphic/time");
+var import_utilsBundle = require("./utilsBundle");
+var validatorPrimitives = __toESM(require("./protocol/validatorPrimitives"));
+var import_progress = require("./server/progress");
class BrowserServerLauncherImpl {
constructor(browserName) {
- this._browserName = void 0;
this._browserName = browserName;
}
async launchServer(options = {}) {
- const playwright = (0, _playwright.createPlaywright)({
- sdkLanguage: 'javascript',
- isServer: true
- });
- // TODO: enable socks proxy once ipv6 is supported.
- const socksProxy = false ? new _socksProxy.SocksProxy() : undefined;
- playwright.options.socksProxyPort = await (socksProxy === null || socksProxy === void 0 ? void 0 : socksProxy.listen(0));
-
- // 1. Pre-launch the browser
- const metadata = (0, _instrumentation.serverSideCallMetadata)();
- const browser = await playwright[this._browserName].launch(metadata, {
+ const playwright = (0, import_playwright.createPlaywright)({ sdkLanguage: "javascript", isServer: true });
+ const metadata = { id: "", startTime: 0, endTime: 0, type: "Internal", method: "", params: {}, log: [], internal: true };
+ const validatorContext = {
+ tChannelImpl: (names, arg, path2) => {
+ throw new validatorPrimitives.ValidationError(`${path2}: channels are not expected in launchServer`);
+ },
+ binary: "buffer",
+ isUnderTest: import_debug.isUnderTest
+ };
+ let launchOptions = {
...options,
- ignoreDefaultArgs: Array.isArray(options.ignoreDefaultArgs) ? options.ignoreDefaultArgs : undefined,
+ ignoreDefaultArgs: Array.isArray(options.ignoreDefaultArgs) ? options.ignoreDefaultArgs : void 0,
ignoreAllDefaultArgs: !!options.ignoreDefaultArgs && !Array.isArray(options.ignoreDefaultArgs),
- env: options.env ? (0, _clientHelper.envObjectToArray)(options.env) : undefined
- }, toProtocolLogger(options.logger)).catch(e => {
- const log = _helper.helper.formatBrowserLogs(metadata.log);
- (0, _stackTrace.rewriteErrorMessage)(e, `${e.message} Failed to launch browser.${log}`);
+ env: options.env ? envObjectToArray(options.env) : void 0,
+ timeout: options.timeout ?? import_time.DEFAULT_PLAYWRIGHT_LAUNCH_TIMEOUT
+ };
+ let browser;
+ try {
+ const controller = new import_progress.ProgressController(metadata);
+ browser = await controller.run(async (progress) => {
+ if (options._userDataDir !== void 0) {
+ const validator = validatorPrimitives.scheme["BrowserTypeLaunchPersistentContextParams"];
+ launchOptions = validator({ ...launchOptions, userDataDir: options._userDataDir }, "", validatorContext);
+ const context = await playwright[this._browserName].launchPersistentContext(progress, options._userDataDir, launchOptions);
+ return context._browser;
+ } else {
+ const validator = validatorPrimitives.scheme["BrowserTypeLaunchParams"];
+ launchOptions = validator(launchOptions, "", validatorContext);
+ return await playwright[this._browserName].launch(progress, launchOptions, toProtocolLogger(options.logger));
+ }
+ });
+ } catch (e) {
+ const log = import_helper.helper.formatBrowserLogs(metadata.log);
+ (0, import_stackTrace.rewriteErrorMessage)(e, `${e.message} Failed to launch browser.${log}`);
throw e;
- });
- const path = options.wsPath ? options.wsPath.startsWith('/') ? options.wsPath : `/${options.wsPath}` : `/${(0, _utils.createGuid)()}`;
-
- // 2. Start the server
- const server = new _playwrightServer.PlaywrightServer({
- mode: 'launchServer',
- path,
- maxConnections: Infinity,
- preLaunchedBrowser: browser,
- preLaunchedSocksProxy: socksProxy
- });
+ }
+ const path = options.wsPath ? options.wsPath.startsWith("/") ? options.wsPath : `/${options.wsPath}` : `/${(0, import_crypto.createGuid)()}`;
+ const server = new import_playwrightServer.PlaywrightServer({ mode: options._sharedBrowser ? "launchServerShared" : "launchServer", path, maxConnections: Infinity, preLaunchedBrowser: browser });
const wsEndpoint = await server.listen(options.port, options.host);
-
- // 3. Return the BrowserServer interface
- const browserServer = new _utilsBundle.ws.EventEmitter();
+ const browserServer = new import_utilsBundle.ws.EventEmitter();
browserServer.process = () => browser.options.browserProcess.process;
browserServer.wsEndpoint = () => wsEndpoint;
browserServer.close = () => browser.options.browserProcess.close();
@@ -77,16 +94,27 @@ class BrowserServerLauncherImpl {
browserServer._disconnectForTest = () => server.close();
browserServer._userDataDirForTest = browser._userDataDirForTest;
browser.options.browserProcess.onclose = (exitCode, signal) => {
- socksProxy === null || socksProxy === void 0 || socksProxy.close().catch(() => {});
server.close();
- browserServer.emit('close', exitCode, signal);
+ browserServer.emit("close", exitCode, signal);
};
return browserServer;
}
}
-exports.BrowserServerLauncherImpl = BrowserServerLauncherImpl;
function toProtocolLogger(logger) {
return logger ? (direction, message) => {
- if (logger.isEnabled('protocol', 'verbose')) logger.log('protocol', 'verbose', (direction === 'send' ? 'SEND ► ' : '◀ RECV ') + JSON.stringify(message), [], {});
- } : undefined;
-}
\ No newline at end of file
+ if (logger.isEnabled("protocol", "verbose"))
+ logger.log("protocol", "verbose", (direction === "send" ? "SEND \u25BA " : "\u25C0 RECV ") + JSON.stringify(message), [], {});
+ } : void 0;
+}
+function envObjectToArray(env) {
+ const result = [];
+ for (const name in env) {
+ if (!Object.is(env[name], void 0))
+ result.push({ name, value: String(env[name]) });
+ }
+ return result;
+}
+// Annotate the CommonJS export names for ESM import in node:
+0 && (module.exports = {
+ BrowserServerLauncherImpl
+});
diff --git a/priv/static/node_modules/playwright-core/lib/cli/browserActions.js b/priv/static/node_modules/playwright-core/lib/cli/browserActions.js
new file mode 100644
index 00000000..2a00914f
--- /dev/null
+++ b/priv/static/node_modules/playwright-core/lib/cli/browserActions.js
@@ -0,0 +1,308 @@
+"use strict";
+var __create = Object.create;
+var __defProp = Object.defineProperty;
+var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
+var __getOwnPropNames = Object.getOwnPropertyNames;
+var __getProtoOf = Object.getPrototypeOf;
+var __hasOwnProp = Object.prototype.hasOwnProperty;
+var __export = (target, all) => {
+ for (var name in all)
+ __defProp(target, name, { get: all[name], enumerable: true });
+};
+var __copyProps = (to, from, except, desc) => {
+ if (from && typeof from === "object" || typeof from === "function") {
+ for (let key of __getOwnPropNames(from))
+ if (!__hasOwnProp.call(to, key) && key !== except)
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
+ }
+ return to;
+};
+var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
+ // If the importer is in node compatibility mode or this is not an ESM
+ // file that has been converted to a CommonJS file using a Babel-
+ // compatible transform (i.e. "__esModule" has not been set), then set
+ // "default" to the CommonJS "module.exports" for node compatibility.
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
+ mod
+));
+var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
+var browserActions_exports = {};
+__export(browserActions_exports, {
+ codegen: () => codegen,
+ open: () => open,
+ pdf: () => pdf,
+ screenshot: () => screenshot
+});
+module.exports = __toCommonJS(browserActions_exports);
+var import_fs = __toESM(require("fs"));
+var import_os = __toESM(require("os"));
+var import_path = __toESM(require("path"));
+var playwright = __toESM(require("../.."));
+var import_utils = require("../utils");
+var import_utilsBundle = require("../utilsBundle");
+async function launchContext(options, extraOptions) {
+ validateOptions(options);
+ const browserType = lookupBrowserType(options);
+ const launchOptions = extraOptions;
+ if (options.channel)
+ launchOptions.channel = options.channel;
+ launchOptions.handleSIGINT = false;
+ const contextOptions = (
+ // Copy the device descriptor since we have to compare and modify the options.
+ options.device ? { ...playwright.devices[options.device] } : {}
+ );
+ if (!extraOptions.headless)
+ contextOptions.deviceScaleFactor = import_os.default.platform() === "darwin" ? 2 : 1;
+ if (browserType.name() === "webkit" && process.platform === "linux") {
+ delete contextOptions.hasTouch;
+ delete contextOptions.isMobile;
+ }
+ if (contextOptions.isMobile && browserType.name() === "firefox")
+ contextOptions.isMobile = void 0;
+ if (options.blockServiceWorkers)
+ contextOptions.serviceWorkers = "block";
+ if (options.proxyServer) {
+ launchOptions.proxy = {
+ server: options.proxyServer
+ };
+ if (options.proxyBypass)
+ launchOptions.proxy.bypass = options.proxyBypass;
+ }
+ if (options.viewportSize) {
+ try {
+ const [width, height] = options.viewportSize.split(",").map((n) => +n);
+ if (isNaN(width) || isNaN(height))
+ throw new Error("bad values");
+ contextOptions.viewport = { width, height };
+ } catch (e) {
+ throw new Error('Invalid viewport size format: use "width,height", for example --viewport-size="800,600"');
+ }
+ }
+ if (options.geolocation) {
+ try {
+ const [latitude, longitude] = options.geolocation.split(",").map((n) => parseFloat(n.trim()));
+ contextOptions.geolocation = {
+ latitude,
+ longitude
+ };
+ } catch (e) {
+ throw new Error('Invalid geolocation format, should be "lat,long". For example --geolocation="37.819722,-122.478611"');
+ }
+ contextOptions.permissions = ["geolocation"];
+ }
+ if (options.userAgent)
+ contextOptions.userAgent = options.userAgent;
+ if (options.lang)
+ contextOptions.locale = options.lang;
+ if (options.colorScheme)
+ contextOptions.colorScheme = options.colorScheme;
+ if (options.timezone)
+ contextOptions.timezoneId = options.timezone;
+ if (options.loadStorage)
+ contextOptions.storageState = options.loadStorage;
+ if (options.ignoreHttpsErrors)
+ contextOptions.ignoreHTTPSErrors = true;
+ if (options.saveHar) {
+ contextOptions.recordHar = { path: import_path.default.resolve(process.cwd(), options.saveHar), mode: "minimal" };
+ if (options.saveHarGlob)
+ contextOptions.recordHar.urlFilter = options.saveHarGlob;
+ contextOptions.serviceWorkers = "block";
+ }
+ let browser;
+ let context;
+ if (options.userDataDir) {
+ context = await browserType.launchPersistentContext(options.userDataDir, { ...launchOptions, ...contextOptions });
+ browser = context.browser();
+ } else {
+ browser = await browserType.launch(launchOptions);
+ context = await browser.newContext(contextOptions);
+ }
+ let closingBrowser = false;
+ async function closeBrowser() {
+ if (closingBrowser)
+ return;
+ closingBrowser = true;
+ if (options.saveStorage)
+ await context.storageState({ path: options.saveStorage }).catch((e) => null);
+ if (options.saveHar)
+ await context.close();
+ await browser.close();
+ }
+ context.on("page", (page) => {
+ page.on("dialog", () => {
+ });
+ page.on("close", () => {
+ const hasPage = browser.contexts().some((context2) => context2.pages().length > 0);
+ if (hasPage)
+ return;
+ closeBrowser().catch(() => {
+ });
+ });
+ });
+ process.on("SIGINT", async () => {
+ await closeBrowser();
+ (0, import_utils.gracefullyProcessExitDoNotHang)(130);
+ });
+ const timeout = options.timeout ? parseInt(options.timeout, 10) : 0;
+ context.setDefaultTimeout(timeout);
+ context.setDefaultNavigationTimeout(timeout);
+ delete launchOptions.headless;
+ delete launchOptions.executablePath;
+ delete launchOptions.handleSIGINT;
+ delete contextOptions.deviceScaleFactor;
+ return { browser, browserName: browserType.name(), context, contextOptions, launchOptions, closeBrowser };
+}
+async function openPage(context, url) {
+ let page = context.pages()[0];
+ if (!page)
+ page = await context.newPage();
+ if (url) {
+ if (import_fs.default.existsSync(url))
+ url = "file://" + import_path.default.resolve(url);
+ else if (!url.startsWith("http") && !url.startsWith("file://") && !url.startsWith("about:") && !url.startsWith("data:"))
+ url = "http://" + url;
+ await page.goto(url);
+ }
+ return page;
+}
+async function open(options, url) {
+ const { context } = await launchContext(options, { headless: !!process.env.PWTEST_CLI_HEADLESS, executablePath: process.env.PWTEST_CLI_EXECUTABLE_PATH });
+ await context._exposeConsoleApi();
+ await openPage(context, url);
+}
+async function codegen(options, url) {
+ const { target: language, output: outputFile, testIdAttribute: testIdAttributeName } = options;
+ const tracesDir = import_path.default.join(import_os.default.tmpdir(), `playwright-recorder-trace-${Date.now()}`);
+ const { context, browser, launchOptions, contextOptions, closeBrowser } = await launchContext(options, {
+ headless: !!process.env.PWTEST_CLI_HEADLESS,
+ executablePath: process.env.PWTEST_CLI_EXECUTABLE_PATH,
+ tracesDir
+ });
+ const donePromise = new import_utils.ManualPromise();
+ maybeSetupTestHooks(browser, closeBrowser, donePromise);
+ import_utilsBundle.dotenv.config({ path: "playwright.env" });
+ await context._enableRecorder({
+ language,
+ launchOptions,
+ contextOptions,
+ device: options.device,
+ saveStorage: options.saveStorage,
+ mode: "recording",
+ testIdAttributeName,
+ outputFile: outputFile ? import_path.default.resolve(outputFile) : void 0,
+ handleSIGINT: false
+ });
+ await openPage(context, url);
+ donePromise.resolve();
+}
+async function maybeSetupTestHooks(browser, closeBrowser, donePromise) {
+ if (!process.env.PWTEST_CLI_IS_UNDER_TEST)
+ return;
+ const logs = [];
+ require("playwright-core/lib/utilsBundle").debug.log = (...args) => {
+ const line = require("util").format(...args) + "\n";
+ logs.push(line);
+ process.stderr.write(line);
+ };
+ browser.on("disconnected", () => {
+ const hasCrashLine = logs.some((line) => line.includes("process did exit:") && !line.includes("process did exit: exitCode=0, signal=null"));
+ if (hasCrashLine) {
+ process.stderr.write("Detected browser crash.\n");
+ (0, import_utils.gracefullyProcessExitDoNotHang)(1);
+ }
+ });
+ const close = async () => {
+ await donePromise;
+ await closeBrowser();
+ };
+ if (process.env.PWTEST_CLI_EXIT_AFTER_TIMEOUT) {
+ setTimeout(close, +process.env.PWTEST_CLI_EXIT_AFTER_TIMEOUT);
+ return;
+ }
+ let stdin = "";
+ process.stdin.on("data", (data) => {
+ stdin += data.toString();
+ if (stdin.startsWith("exit")) {
+ process.stdin.destroy();
+ close();
+ }
+ });
+}
+async function waitForPage(page, captureOptions) {
+ if (captureOptions.waitForSelector) {
+ console.log(`Waiting for selector ${captureOptions.waitForSelector}...`);
+ await page.waitForSelector(captureOptions.waitForSelector);
+ }
+ if (captureOptions.waitForTimeout) {
+ console.log(`Waiting for timeout ${captureOptions.waitForTimeout}...`);
+ await page.waitForTimeout(parseInt(captureOptions.waitForTimeout, 10));
+ }
+}
+async function screenshot(options, captureOptions, url, path2) {
+ const { context } = await launchContext(options, { headless: true });
+ console.log("Navigating to " + url);
+ const page = await openPage(context, url);
+ await waitForPage(page, captureOptions);
+ console.log("Capturing screenshot into " + path2);
+ await page.screenshot({ path: path2, fullPage: !!captureOptions.fullPage });
+ await page.close();
+}
+async function pdf(options, captureOptions, url, path2) {
+ if (options.browser !== "chromium")
+ throw new Error("PDF creation is only working with Chromium");
+ const { context } = await launchContext({ ...options, browser: "chromium" }, { headless: true });
+ console.log("Navigating to " + url);
+ const page = await openPage(context, url);
+ await waitForPage(page, captureOptions);
+ console.log("Saving as pdf into " + path2);
+ await page.pdf({ path: path2, format: captureOptions.paperFormat });
+ await page.close();
+}
+function lookupBrowserType(options) {
+ let name = options.browser;
+ if (options.device) {
+ const device = playwright.devices[options.device];
+ name = device.defaultBrowserType;
+ }
+ let browserType;
+ switch (name) {
+ case "chromium":
+ browserType = playwright.chromium;
+ break;
+ case "webkit":
+ browserType = playwright.webkit;
+ break;
+ case "firefox":
+ browserType = playwright.firefox;
+ break;
+ case "cr":
+ browserType = playwright.chromium;
+ break;
+ case "wk":
+ browserType = playwright.webkit;
+ break;
+ case "ff":
+ browserType = playwright.firefox;
+ break;
+ }
+ if (browserType)
+ return browserType;
+ import_utilsBundle.program.help();
+}
+function validateOptions(options) {
+ if (options.device && !(options.device in playwright.devices)) {
+ const lines = [`Device descriptor not found: '${options.device}', available devices are:`];
+ for (const name in playwright.devices)
+ lines.push(` "${name}"`);
+ throw new Error(lines.join("\n"));
+ }
+ if (options.colorScheme && !["light", "dark"].includes(options.colorScheme))
+ throw new Error('Invalid color scheme, should be one of "light", "dark"');
+}
+// Annotate the CommonJS export names for ESM import in node:
+0 && (module.exports = {
+ codegen,
+ open,
+ pdf,
+ screenshot
+});
diff --git a/priv/static/node_modules/playwright-core/lib/cli/driver.js b/priv/static/node_modules/playwright-core/lib/cli/driver.js
index ddbf3e5d..c96c3f63 100644
--- a/priv/static/node_modules/playwright-core/lib/cli/driver.js
+++ b/priv/static/node_modules/playwright-core/lib/cli/driver.js
@@ -1,95 +1,98 @@
"use strict";
-
-Object.defineProperty(exports, "__esModule", {
- value: true
+var __create = Object.create;
+var __defProp = Object.defineProperty;
+var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
+var __getOwnPropNames = Object.getOwnPropertyNames;
+var __getProtoOf = Object.getPrototypeOf;
+var __hasOwnProp = Object.prototype.hasOwnProperty;
+var __export = (target, all) => {
+ for (var name in all)
+ __defProp(target, name, { get: all[name], enumerable: true });
+};
+var __copyProps = (to, from, except, desc) => {
+ if (from && typeof from === "object" || typeof from === "function") {
+ for (let key of __getOwnPropNames(from))
+ if (!__hasOwnProp.call(to, key) && key !== except)
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
+ }
+ return to;
+};
+var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
+ // If the importer is in node compatibility mode or this is not an ESM
+ // file that has been converted to a CommonJS file using a Babel-
+ // compatible transform (i.e. "__esModule" has not been set), then set
+ // "default" to the CommonJS "module.exports" for node compatibility.
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
+ mod
+));
+var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
+var driver_exports = {};
+__export(driver_exports, {
+ launchBrowserServer: () => launchBrowserServer,
+ printApiJson: () => printApiJson,
+ runDriver: () => runDriver,
+ runServer: () => runServer
});
-exports.launchBrowserServer = launchBrowserServer;
-exports.printApiJson = printApiJson;
-exports.runDriver = runDriver;
-exports.runServer = runServer;
-var _fs = _interopRequireDefault(require("fs"));
-var playwright = _interopRequireWildcard(require("../.."));
-var _server = require("../server");
-var _transport = require("../protocol/transport");
-var _playwrightServer = require("../remote/playwrightServer");
-var _processLauncher = require("../utils/processLauncher");
-function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
-function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
-function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
-/**
- * Copyright (c) Microsoft Corporation.
- *
- * Licensed under the Apache License, Version 2.0 (the 'License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-/* eslint-disable no-console */
-
+module.exports = __toCommonJS(driver_exports);
+var import_fs = __toESM(require("fs"));
+var playwright = __toESM(require("../.."));
+var import_pipeTransport = require("../server/utils/pipeTransport");
+var import_playwrightServer = require("../remote/playwrightServer");
+var import_server = require("../server");
+var import_processLauncher = require("../server/utils/processLauncher");
function printApiJson() {
- // Note: this file is generated by build-playwright-driver.sh
- console.log(JSON.stringify(require('../../api.json')));
+ console.log(JSON.stringify(require("../../api.json")));
}
function runDriver() {
- const dispatcherConnection = new _server.DispatcherConnection();
- new _server.RootDispatcher(dispatcherConnection, async (rootScope, {
- sdkLanguage
- }) => {
- const playwright = (0, _server.createPlaywright)({
- sdkLanguage
- });
- return new _server.PlaywrightDispatcher(rootScope, playwright);
+ const dispatcherConnection = new import_server.DispatcherConnection();
+ new import_server.RootDispatcher(dispatcherConnection, async (rootScope, { sdkLanguage }) => {
+ const playwright2 = (0, import_server.createPlaywright)({ sdkLanguage });
+ return new import_server.PlaywrightDispatcher(rootScope, playwright2);
});
- const transport = new _transport.PipeTransport(process.stdout, process.stdin);
- transport.onmessage = message => dispatcherConnection.dispatch(JSON.parse(message));
- // Certain Language Binding JSON parsers (e.g. .NET) do not like strings with lone surrogates.
- const isJavaScriptLanguageBinding = !process.env.PW_LANG_NAME || process.env.PW_LANG_NAME === 'javascript';
+ const transport = new import_pipeTransport.PipeTransport(process.stdout, process.stdin);
+ transport.onmessage = (message) => dispatcherConnection.dispatch(JSON.parse(message));
+ const isJavaScriptLanguageBinding = !process.env.PW_LANG_NAME || process.env.PW_LANG_NAME === "javascript";
const replacer = !isJavaScriptLanguageBinding && String.prototype.toWellFormed ? (key, value) => {
- if (typeof value === 'string') return value.toWellFormed();
+ if (typeof value === "string")
+ return value.toWellFormed();
return value;
- } : undefined;
- dispatcherConnection.onmessage = message => transport.send(JSON.stringify(message, replacer));
+ } : void 0;
+ dispatcherConnection.onmessage = (message) => transport.send(JSON.stringify(message, replacer));
transport.onclose = () => {
- // Drop any messages during shutdown on the floor.
- dispatcherConnection.onmessage = () => {};
- (0, _processLauncher.gracefullyProcessExitDoNotHang)(0);
+ dispatcherConnection.onmessage = () => {
+ };
+ (0, import_processLauncher.gracefullyProcessExitDoNotHang)(0);
};
- // Ignore the SIGINT signal in the driver process so the parent can gracefully close the connection.
- // We still will destruct everything (close browsers and exit) when the transport pipe closes.
- process.on('SIGINT', () => {
- // Keep the process running.
+ process.on("SIGINT", () => {
});
}
async function runServer(options) {
const {
port,
host,
- path = '/',
+ path = "/",
maxConnections = Infinity,
- extension
+ extension,
+ artifactsDir
} = options;
- const server = new _playwrightServer.PlaywrightServer({
- mode: extension ? 'extension' : 'default',
- path,
- maxConnections
- });
+ const server = new import_playwrightServer.PlaywrightServer({ mode: extension ? "extension" : "default", path, maxConnections, artifactsDir });
const wsEndpoint = await server.listen(port, host);
- process.on('exit', () => server.close().catch(console.error));
- console.log('Listening on ' + wsEndpoint);
- process.stdin.on('close', () => (0, _processLauncher.gracefullyProcessExitDoNotHang)(0));
+ process.on("exit", () => server.close().catch(console.error));
+ console.log("Listening on " + wsEndpoint);
+ process.stdin.on("close", () => (0, import_processLauncher.gracefullyProcessExitDoNotHang)(0));
}
async function launchBrowserServer(browserName, configFile) {
let options = {};
- if (configFile) options = JSON.parse(_fs.default.readFileSync(configFile).toString());
+ if (configFile)
+ options = JSON.parse(import_fs.default.readFileSync(configFile).toString());
const browserType = playwright[browserName];
const server = await browserType.launchServer(options);
console.log(server.wsEndpoint());
-}
\ No newline at end of file
+}
+// Annotate the CommonJS export names for ESM import in node:
+0 && (module.exports = {
+ launchBrowserServer,
+ printApiJson,
+ runDriver,
+ runServer
+});
diff --git a/priv/static/node_modules/playwright-core/lib/cli/installActions.js b/priv/static/node_modules/playwright-core/lib/cli/installActions.js
new file mode 100644
index 00000000..eddcf4ad
--- /dev/null
+++ b/priv/static/node_modules/playwright-core/lib/cli/installActions.js
@@ -0,0 +1,171 @@
+"use strict";
+var __create = Object.create;
+var __defProp = Object.defineProperty;
+var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
+var __getOwnPropNames = Object.getOwnPropertyNames;
+var __getProtoOf = Object.getPrototypeOf;
+var __hasOwnProp = Object.prototype.hasOwnProperty;
+var __export = (target, all) => {
+ for (var name in all)
+ __defProp(target, name, { get: all[name], enumerable: true });
+};
+var __copyProps = (to, from, except, desc) => {
+ if (from && typeof from === "object" || typeof from === "function") {
+ for (let key of __getOwnPropNames(from))
+ if (!__hasOwnProp.call(to, key) && key !== except)
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
+ }
+ return to;
+};
+var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
+ // If the importer is in node compatibility mode or this is not an ESM
+ // file that has been converted to a CommonJS file using a Babel-
+ // compatible transform (i.e. "__esModule" has not been set), then set
+ // "default" to the CommonJS "module.exports" for node compatibility.
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
+ mod
+));
+var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
+var installActions_exports = {};
+__export(installActions_exports, {
+ installBrowsers: () => installBrowsers,
+ installDeps: () => installDeps,
+ markDockerImage: () => markDockerImage,
+ registry: () => import_server.registry,
+ uninstallBrowsers: () => uninstallBrowsers
+});
+module.exports = __toCommonJS(installActions_exports);
+var import_path = __toESM(require("path"));
+var import_server = require("../server");
+var import_utils = require("../utils");
+var import_utils2 = require("../utils");
+var import_ascii = require("../server/utils/ascii");
+function printInstalledBrowsers(browsers) {
+ const browserPaths = /* @__PURE__ */ new Set();
+ for (const browser of browsers)
+ browserPaths.add(browser.browserPath);
+ console.log(` Browsers:`);
+ for (const browserPath of [...browserPaths].sort())
+ console.log(` ${browserPath}`);
+ console.log(` References:`);
+ const references = /* @__PURE__ */ new Set();
+ for (const browser of browsers)
+ references.add(browser.referenceDir);
+ for (const reference of [...references].sort())
+ console.log(` ${reference}`);
+}
+function printGroupedByPlaywrightVersion(browsers) {
+ const dirToVersion = /* @__PURE__ */ new Map();
+ for (const browser of browsers) {
+ if (dirToVersion.has(browser.referenceDir))
+ continue;
+ const packageJSON = require(import_path.default.join(browser.referenceDir, "package.json"));
+ const version = packageJSON.version;
+ dirToVersion.set(browser.referenceDir, version);
+ }
+ const groupedByPlaywrightMinorVersion = /* @__PURE__ */ new Map();
+ for (const browser of browsers) {
+ const version = dirToVersion.get(browser.referenceDir);
+ let entries = groupedByPlaywrightMinorVersion.get(version);
+ if (!entries) {
+ entries = [];
+ groupedByPlaywrightMinorVersion.set(version, entries);
+ }
+ entries.push(browser);
+ }
+ const sortedVersions = [...groupedByPlaywrightMinorVersion.keys()].sort((a, b) => {
+ const aComponents = a.split(".");
+ const bComponents = b.split(".");
+ const aMajor = parseInt(aComponents[0], 10);
+ const bMajor = parseInt(bComponents[0], 10);
+ if (aMajor !== bMajor)
+ return aMajor - bMajor;
+ const aMinor = parseInt(aComponents[1], 10);
+ const bMinor = parseInt(bComponents[1], 10);
+ if (aMinor !== bMinor)
+ return aMinor - bMinor;
+ return aComponents.slice(2).join(".").localeCompare(bComponents.slice(2).join("."));
+ });
+ for (const version of sortedVersions) {
+ console.log(`
+Playwright version: ${version}`);
+ printInstalledBrowsers(groupedByPlaywrightMinorVersion.get(version));
+ }
+}
+async function markDockerImage(dockerImageNameTemplate) {
+ (0, import_utils2.assert)(dockerImageNameTemplate, "dockerImageNameTemplate is required");
+ await (0, import_server.writeDockerVersion)(dockerImageNameTemplate);
+}
+async function installBrowsers(args, options) {
+ if ((0, import_utils.isLikelyNpxGlobal)()) {
+ console.error((0, import_ascii.wrapInASCIIBox)([
+ `WARNING: It looks like you are running 'npx playwright install' without first`,
+ `installing your project's dependencies.`,
+ ``,
+ `To avoid unexpected behavior, please install your dependencies first, and`,
+ `then run Playwright's install command:`,
+ ``,
+ ` npm install`,
+ ` npx playwright install`,
+ ``,
+ `If your project does not yet depend on Playwright, first install the`,
+ `applicable npm package (most commonly @playwright/test), and`,
+ `then run Playwright's install command to download the browsers:`,
+ ``,
+ ` npm install @playwright/test`,
+ ` npx playwright install`,
+ ``
+ ].join("\n"), 1));
+ }
+ if (options.shell === false && options.onlyShell)
+ throw new Error(`Only one of --no-shell and --only-shell can be specified`);
+ const shell = options.shell === false ? "no" : options.onlyShell ? "only" : void 0;
+ const executables = import_server.registry.resolveBrowsers(args, { shell });
+ if (options.withDeps)
+ await import_server.registry.installDeps(executables, !!options.dryRun);
+ if (options.dryRun && options.list)
+ throw new Error(`Only one of --dry-run and --list can be specified`);
+ if (options.dryRun) {
+ for (const executable of executables) {
+ console.log(import_server.registry.calculateDownloadTitle(executable));
+ console.log(` Install location: ${executable.directory ?? ""}`);
+ if (executable.downloadURLs?.length) {
+ const [url, ...fallbacks] = executable.downloadURLs;
+ console.log(` Download url: ${url}`);
+ for (let i = 0; i < fallbacks.length; ++i)
+ console.log(` Download fallback ${i + 1}: ${fallbacks[i]}`);
+ }
+ console.log(``);
+ }
+ } else if (options.list) {
+ const browsers = await import_server.registry.listInstalledBrowsers();
+ printGroupedByPlaywrightVersion(browsers);
+ } else {
+ await import_server.registry.install(executables, { force: options.force });
+ await import_server.registry.validateHostRequirementsForExecutablesIfNeeded(executables, process.env.PW_LANG_NAME || "javascript").catch((e) => {
+ e.name = "Playwright Host validation warning";
+ console.error(e);
+ });
+ }
+}
+async function uninstallBrowsers(options) {
+ delete process.env.PLAYWRIGHT_SKIP_BROWSER_GC;
+ await import_server.registry.uninstall(!!options.all).then(({ numberOfBrowsersLeft }) => {
+ if (!options.all && numberOfBrowsersLeft > 0) {
+ console.log("Successfully uninstalled Playwright browsers for the current Playwright installation.");
+ console.log(`There are still ${numberOfBrowsersLeft} browsers left, used by other Playwright installations.
+To uninstall Playwright browsers for all installations, re-run with --all flag.`);
+ }
+ });
+}
+async function installDeps(args, options) {
+ await import_server.registry.installDeps(import_server.registry.resolveBrowsers(args, {}), !!options.dryRun);
+}
+// Annotate the CommonJS export names for ESM import in node:
+0 && (module.exports = {
+ installBrowsers,
+ installDeps,
+ markDockerImage,
+ registry,
+ uninstallBrowsers
+});
diff --git a/priv/static/node_modules/playwright-core/lib/cli/program.js b/priv/static/node_modules/playwright-core/lib/cli/program.js
index ce7bd998..39b59dd5 100644
--- a/priv/static/node_modules/playwright-core/lib/cli/program.js
+++ b/priv/static/node_modules/playwright-core/lib/cli/program.js
@@ -1,607 +1,225 @@
"use strict";
-
-Object.defineProperty(exports, "__esModule", {
- value: true
-});
-Object.defineProperty(exports, "program", {
- enumerable: true,
- get: function () {
- return _utilsBundle.program;
- }
+var __defProp = Object.defineProperty;
+var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
+var __getOwnPropNames = Object.getOwnPropertyNames;
+var __hasOwnProp = Object.prototype.hasOwnProperty;
+var __export = (target, all) => {
+ for (var name in all)
+ __defProp(target, name, { get: all[name], enumerable: true });
+};
+var __copyProps = (to, from, except, desc) => {
+ if (from && typeof from === "object" || typeof from === "function") {
+ for (let key of __getOwnPropNames(from))
+ if (!__hasOwnProp.call(to, key) && key !== except)
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
+ }
+ return to;
+};
+var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
+var program_exports = {};
+__export(program_exports, {
+ program: () => import_utilsBundle2.program
});
-var _fs = _interopRequireDefault(require("fs"));
-var _os = _interopRequireDefault(require("os"));
-var _path = _interopRequireDefault(require("path"));
-var _utilsBundle = require("../utilsBundle");
-var _driver = require("./driver");
-var _traceViewer = require("../server/trace/viewer/traceViewer");
-var playwright = _interopRequireWildcard(require("../.."));
-var _child_process = require("child_process");
-var _utils = require("../utils");
-var _server = require("../server");
-var _errors = require("../client/errors");
-function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
-function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
-function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
-/**
- * Copyright (c) Microsoft Corporation.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-/* eslint-disable no-console */
-
-const packageJSON = require('../../package.json');
-_utilsBundle.program.version('Version ' + (process.env.PW_CLI_DISPLAY_VERSION || packageJSON.version)).name(buildBasePlaywrightCLICommand(process.env.PW_LANG_NAME));
-_utilsBundle.program.command('mark-docker-image [dockerImageNameTemplate]', {
- hidden: true
-}).description('mark docker image').allowUnknownOption(true).action(function (dockerImageNameTemplate) {
- (0, _utils.assert)(dockerImageNameTemplate, 'dockerImageNameTemplate is required');
- (0, _server.writeDockerVersion)(dockerImageNameTemplate).catch(logErrorAndExit);
+module.exports = __toCommonJS(program_exports);
+var import_bootstrap = require("../bootstrap");
+var import_utils = require("../utils");
+var import_traceCli = require("../tools/trace/traceCli");
+var import_utilsBundle = require("../utilsBundle");
+var import_utilsBundle2 = require("../utilsBundle");
+const packageJSON = require("../../package.json");
+import_utilsBundle.program.version("Version " + (process.env.PW_CLI_DISPLAY_VERSION || packageJSON.version)).name(buildBasePlaywrightCLICommand(process.env.PW_LANG_NAME));
+import_utilsBundle.program.command("mark-docker-image [dockerImageNameTemplate]", { hidden: true }).description("mark docker image").allowUnknownOption(true).action(async function(dockerImageNameTemplate) {
+ const { markDockerImage } = require("./installActions");
+ markDockerImage(dockerImageNameTemplate).catch(logErrorAndExit);
});
-commandWithOpenOptions('open [url]', 'open page in browser specified via -b, --browser', []).action(function (url, options) {
- open(options, url, codegenId()).catch(logErrorAndExit);
-}).addHelpText('afterAll', `
+commandWithOpenOptions("open [url]", "open page in browser specified via -b, --browser", []).action(async function(url, options) {
+ const { open } = require("./browserActions");
+ open(options, url).catch(logErrorAndExit);
+}).addHelpText("afterAll", `
Examples:
$ open
$ open -b webkit https://example.com`);
-commandWithOpenOptions('codegen [url]', 'open page and generate code for user actions', [['-o, --output ', 'saves the generated script to a file'], ['--target ', `language to generate, one of javascript, playwright-test, python, python-async, python-pytest, csharp, csharp-mstest, csharp-nunit, java, java-junit`, codegenId()], ['--save-trace ', 'record a trace for the session and save it to a file'], ['--test-id-attribute ', 'use the specified attribute to generate data test ID selectors']]).action(function (url, options) {
- codegen(options, url).catch(logErrorAndExit);
-}).addHelpText('afterAll', `
+commandWithOpenOptions(
+ "codegen [url]",
+ "open page and generate code for user actions",
+ [
+ ["-o, --output ", "saves the generated script to a file"],
+ ["--target ", `language to generate, one of javascript, playwright-test, python, python-async, python-pytest, csharp, csharp-mstest, csharp-nunit, java, java-junit`, codegenId()],
+ ["--test-id-attribute ", "use the specified attribute to generate data test ID selectors"]
+ ]
+).action(async function(url, options) {
+ const { codegen } = require("./browserActions");
+ await codegen(options, url);
+}).addHelpText("afterAll", `
Examples:
$ codegen
$ codegen --target=python
$ codegen -b webkit https://example.com`);
-_utilsBundle.program.command('debug [args...]', {
- hidden: true
-}).description('run command in debug mode: disable timeout, open inspector').allowUnknownOption(true).action(function (app, options) {
- (0, _child_process.spawn)(app, options, {
- env: {
- ...process.env,
- PWDEBUG: '1'
- },
- stdio: 'inherit'
- });
-}).addHelpText('afterAll', `
-Examples:
-
- $ debug node test.js
- $ debug npm run test`);
-function suggestedBrowsersToInstall() {
- return _server.registry.executables().filter(e => e.installType !== 'none' && e.type !== 'tool').map(e => e.name).join(', ');
-}
-function defaultBrowsersToInstall(options) {
- let executables = _server.registry.defaultExecutables();
- if (options.noShell) executables = executables.filter(e => e.name !== 'chromium-headless-shell');
- if (options.onlyShell) executables = executables.filter(e => e.name !== 'chromium');
- return executables;
-}
-function checkBrowsersToInstall(args, options) {
- if (options.noShell && options.onlyShell) throw new Error(`Only one of --no-shell and --only-shell can be specified`);
- const faultyArguments = [];
- const executables = [];
- const handleArgument = arg => {
- const executable = _server.registry.findExecutable(arg);
- if (!executable || executable.installType === 'none') faultyArguments.push(arg);else executables.push(executable);
- if ((executable === null || executable === void 0 ? void 0 : executable.browserName) === 'chromium') executables.push(_server.registry.findExecutable('ffmpeg'));
- };
- for (const arg of args) {
- if (arg === 'chromium') {
- if (!options.onlyShell) handleArgument('chromium');
- if (!options.noShell) handleArgument('chromium-headless-shell');
- } else {
- handleArgument(arg);
- }
- }
- if (faultyArguments.length) throw new Error(`Invalid installation targets: ${faultyArguments.map(name => `'${name}'`).join(', ')}. Expecting one of: ${suggestedBrowsersToInstall()}`);
- return executables;
-}
-_utilsBundle.program.command('install [browser...]').description('ensure browsers necessary for this version of Playwright are installed').option('--with-deps', 'install system dependencies for browsers').option('--dry-run', 'do not execute installation, only print information').option('--force', 'force reinstall of stable browser channels').option('--only-shell', 'only install headless shell when installing chromium').option('--no-shell', 'do not install chromium headless shell').action(async function (args, options) {
- // For '--no-shell' option, commander sets `shell: false` instead.
- if (options.shell === false) options.noShell = true;
- if ((0, _utils.isLikelyNpxGlobal)()) {
- console.error((0, _utils.wrapInASCIIBox)([`WARNING: It looks like you are running 'npx playwright install' without first`, `installing your project's dependencies.`, ``, `To avoid unexpected behavior, please install your dependencies first, and`, `then run Playwright's install command:`, ``, ` npm install`, ` npx playwright install`, ``, `If your project does not yet depend on Playwright, first install the`, `applicable npm package (most commonly @playwright/test), and`, `then run Playwright's install command to download the browsers:`, ``, ` npm install @playwright/test`, ` npx playwright install`, ``].join('\n'), 1));
- }
+import_utilsBundle.program.command("install [browser...]").description("ensure browsers necessary for this version of Playwright are installed").option("--with-deps", "install system dependencies for browsers").option("--dry-run", "do not execute installation, only print information").option("--list", "prints list of browsers from all playwright installations").option("--force", "force reinstall of already installed browsers").option("--only-shell", "only install headless shell when installing chromium").option("--no-shell", "do not install chromium headless shell").action(async function(args, options) {
try {
- const hasNoArguments = !args.length;
- const executables = hasNoArguments ? defaultBrowsersToInstall(options) : checkBrowsersToInstall(args, options);
- if (options.withDeps) await _server.registry.installDeps(executables, !!options.dryRun);
- if (options.dryRun) {
- for (const executable of executables) {
- var _executable$directory, _executable$downloadU;
- const version = executable.browserVersion ? `version ` + executable.browserVersion : '';
- console.log(`browser: ${executable.name}${version ? ' ' + version : ''}`);
- console.log(` Install location: ${(_executable$directory = executable.directory) !== null && _executable$directory !== void 0 ? _executable$directory : ''}`);
- if ((_executable$downloadU = executable.downloadURLs) !== null && _executable$downloadU !== void 0 && _executable$downloadU.length) {
- const [url, ...fallbacks] = executable.downloadURLs;
- console.log(` Download url: ${url}`);
- for (let i = 0; i < fallbacks.length; ++i) console.log(` Download fallback ${i + 1}: ${fallbacks[i]}`);
- }
- console.log(``);
- }
- } else {
- const forceReinstall = hasNoArguments ? false : !!options.force;
- await _server.registry.install(executables, forceReinstall);
- await _server.registry.validateHostRequirementsForExecutablesIfNeeded(executables, process.env.PW_LANG_NAME || 'javascript').catch(e => {
- e.name = 'Playwright Host validation warning';
- console.error(e);
- });
- }
+ const { installBrowsers } = require("./installActions");
+ await installBrowsers(args, options);
} catch (e) {
- console.log(`Failed to install browsers\n${e}`);
- (0, _utils.gracefullyProcessExitDoNotHang)(1);
+ console.log(`Failed to install browsers
+${e}`);
+ (0, import_utils.gracefullyProcessExitDoNotHang)(1);
}
-}).addHelpText('afterAll', `
+}).addHelpText("afterAll", `
Examples:
- $ install
Install default browsers.
- $ install chrome firefox
- Install custom browsers, supports ${suggestedBrowsersToInstall()}.`);
-_utilsBundle.program.command('uninstall').description('Removes browsers used by this installation of Playwright from the system (chromium, firefox, webkit, ffmpeg). This does not include branded channels.').option('--all', 'Removes all browsers used by any Playwright installation from the system.').action(async options => {
- delete process.env.PLAYWRIGHT_SKIP_BROWSER_GC;
- await _server.registry.uninstall(!!options.all).then(({
- numberOfBrowsersLeft
- }) => {
- if (!options.all && numberOfBrowsersLeft > 0) {
- console.log('Successfully uninstalled Playwright browsers for the current Playwright installation.');
- console.log(`There are still ${numberOfBrowsersLeft} browsers left, used by other Playwright installations.\nTo uninstall Playwright browsers for all installations, re-run with --all flag.`);
- }
- }).catch(logErrorAndExit);
+ Install custom browsers, supports chromium, firefox, webkit, chromium-headless-shell.`);
+import_utilsBundle.program.command("uninstall").description("Removes browsers used by this installation of Playwright from the system (chromium, firefox, webkit, ffmpeg). This does not include branded channels.").option("--all", "Removes all browsers used by any Playwright installation from the system.").action(async (options) => {
+ const { uninstallBrowsers } = require("./installActions");
+ uninstallBrowsers(options).catch(logErrorAndExit);
});
-_utilsBundle.program.command('install-deps [browser...]').description('install dependencies necessary to run browsers (will ask for sudo permissions)').option('--dry-run', 'Do not execute installation commands, only print them').action(async function (args, options) {
+import_utilsBundle.program.command("install-deps [browser...]").description("install dependencies necessary to run browsers (will ask for sudo permissions)").option("--dry-run", "Do not execute installation commands, only print them").action(async function(args, options) {
try {
- if (!args.length) await _server.registry.installDeps(defaultBrowsersToInstall({}), !!options.dryRun);else await _server.registry.installDeps(checkBrowsersToInstall(args, {}), !!options.dryRun);
+ const { installDeps } = require("./installActions");
+ await installDeps(args, options);
} catch (e) {
- console.log(`Failed to install browser dependencies\n${e}`);
- (0, _utils.gracefullyProcessExitDoNotHang)(1);
+ console.log(`Failed to install browser dependencies
+${e}`);
+ (0, import_utils.gracefullyProcessExitDoNotHang)(1);
}
-}).addHelpText('afterAll', `
+}).addHelpText("afterAll", `
Examples:
- $ install-deps
Install dependencies for default browsers.
- $ install-deps chrome firefox
- Install dependencies for specific browsers, supports ${suggestedBrowsersToInstall()}.`);
-const browsers = [{
- alias: 'cr',
- name: 'Chromium',
- type: 'chromium'
-}, {
- alias: 'ff',
- name: 'Firefox',
- type: 'firefox'
-}, {
- alias: 'wk',
- name: 'WebKit',
- type: 'webkit'
-}];
-for (const {
- alias,
- name,
- type
-} of browsers) {
- commandWithOpenOptions(`${alias} [url]`, `open page in ${name}`, []).action(function (url, options) {
- open({
- ...options,
- browser: type
- }, url, options.target).catch(logErrorAndExit);
- }).addHelpText('afterAll', `
+ Install dependencies for specific browsers, supports chromium, firefox, webkit, chromium-headless-shell.`);
+const browsers = [
+ { alias: "cr", name: "Chromium", type: "chromium" },
+ { alias: "ff", name: "Firefox", type: "firefox" },
+ { alias: "wk", name: "WebKit", type: "webkit" }
+];
+for (const { alias, name, type } of browsers) {
+ commandWithOpenOptions(`${alias} [url]`, `open page in ${name}`, []).action(async function(url, options) {
+ const { open } = require("./browserActions");
+ open({ ...options, browser: type }, url).catch(logErrorAndExit);
+ }).addHelpText("afterAll", `
Examples:
$ ${alias} https://example.com`);
}
-commandWithOpenOptions('screenshot ', 'capture a page screenshot', [['--wait-for-selector ', 'wait for selector before taking a screenshot'], ['--wait-for-timeout ', 'wait for timeout in milliseconds before taking a screenshot'], ['--full-page', 'whether to take a full page screenshot (entire scrollable area)']]).action(function (url, filename, command) {
+commandWithOpenOptions(
+ "screenshot ",
+ "capture a page screenshot",
+ [
+ ["--wait-for-selector ", "wait for selector before taking a screenshot"],
+ ["--wait-for-timeout ", "wait for timeout in milliseconds before taking a screenshot"],
+ ["--full-page", "whether to take a full page screenshot (entire scrollable area)"]
+ ]
+).action(async function(url, filename, command) {
+ const { screenshot } = require("./browserActions");
screenshot(command, command, url, filename).catch(logErrorAndExit);
-}).addHelpText('afterAll', `
+}).addHelpText("afterAll", `
Examples:
$ screenshot -b webkit https://example.com example.png`);
-commandWithOpenOptions('pdf ', 'save page as pdf', [['--wait-for-selector ', 'wait for given selector before saving as pdf'], ['--wait-for-timeout ', 'wait for given timeout in milliseconds before saving as pdf']]).action(function (url, filename, options) {
+commandWithOpenOptions(
+ "pdf ",
+ "save page as pdf",
+ [
+ ["--paper-format ", "paper format: Letter, Legal, Tabloid, Ledger, A0, A1, A2, A3, A4, A5, A6"],
+ ["--wait-for-selector ", "wait for given selector before saving as pdf"],
+ ["--wait-for-timeout ", "wait for given timeout in milliseconds before saving as pdf"]
+ ]
+).action(async function(url, filename, options) {
+ const { pdf } = require("./browserActions");
pdf(options, options, url, filename).catch(logErrorAndExit);
-}).addHelpText('afterAll', `
+}).addHelpText("afterAll", `
Examples:
$ pdf https://example.com example.pdf`);
-_utilsBundle.program.command('run-driver', {
- hidden: true
-}).action(function (options) {
- (0, _driver.runDriver)();
+import_utilsBundle.program.command("run-driver", { hidden: true }).action(async function(options) {
+ const { runDriver } = require("./driver");
+ runDriver();
});
-_utilsBundle.program.command('run-server', {
- hidden: true
-}).option('--port ', 'Server port').option('--host ', 'Server host').option('--path ', 'Endpoint Path', '/').option('--max-clients ', 'Maximum clients').option('--mode ', 'Server mode, either "default" or "extension"').action(function (options) {
- (0, _driver.runServer)({
- port: options.port ? +options.port : undefined,
+import_utilsBundle.program.command("run-server", { hidden: true }).option("--port ", "Server port").option("--host ", "Server host").option("--path ", "Endpoint Path", "/").option("--max-clients ", "Maximum clients").option("--mode ", 'Server mode, either "default" or "extension"').option("--artifacts-dir ", "Artifacts directory").action(async function(options) {
+ const { runServer } = require("./driver");
+ runServer({
+ port: options.port ? +options.port : void 0,
host: options.host,
path: options.path,
maxConnections: options.maxClients ? +options.maxClients : Infinity,
- extension: options.mode === 'extension' || !!process.env.PW_EXTENSION_MODE
+ extension: options.mode === "extension" || !!process.env.PW_EXTENSION_MODE,
+ artifactsDir: options.artifactsDir
}).catch(logErrorAndExit);
});
-_utilsBundle.program.command('print-api-json', {
- hidden: true
-}).action(function (options) {
- (0, _driver.printApiJson)();
+import_utilsBundle.program.command("print-api-json", { hidden: true }).action(async function(options) {
+ const { printApiJson } = require("./driver");
+ printApiJson();
});
-_utilsBundle.program.command('launch-server', {
- hidden: true
-}).requiredOption('--browser ', 'Browser name, one of "chromium", "firefox" or "webkit"').option('--config ', 'JSON file with launchServer options').action(function (options) {
- (0, _driver.launchBrowserServer)(options.browser, options.config);
+import_utilsBundle.program.command("launch-server", { hidden: true }).requiredOption("--browser ", 'Browser name, one of "chromium", "firefox" or "webkit"').option("--config ", "JSON file with launchServer options").action(async function(options) {
+ const { launchBrowserServer } = require("./driver");
+ launchBrowserServer(options.browser, options.config);
});
-_utilsBundle.program.command('show-trace [trace...]').option('-b, --browser ', 'browser to use, one of cr, chromium, ff, firefox, wk, webkit', 'chromium').option('-h, --host ', 'Host to serve trace on; specifying this option opens trace in a browser tab').option('-p, --port ', 'Port to serve trace on, 0 for any free port; specifying this option opens trace in a browser tab').option('--stdin', 'Accept trace URLs over stdin to update the viewer').description('show trace viewer').action(function (traces, options) {
- if (options.browser === 'cr') options.browser = 'chromium';
- if (options.browser === 'ff') options.browser = 'firefox';
- if (options.browser === 'wk') options.browser = 'webkit';
+import_utilsBundle.program.command("show-trace [trace]").option("-b, --browser ", "browser to use, one of cr, chromium, ff, firefox, wk, webkit", "chromium").option("-h, --host ", "Host to serve trace on; specifying this option opens trace in a browser tab").option("-p, --port ", "Port to serve trace on, 0 for any free port; specifying this option opens trace in a browser tab").option("--stdin", "Accept trace URLs over stdin to update the viewer").description("show trace viewer").action(async function(trace, options) {
+ if (options.browser === "cr")
+ options.browser = "chromium";
+ if (options.browser === "ff")
+ options.browser = "firefox";
+ if (options.browser === "wk")
+ options.browser = "webkit";
const openOptions = {
host: options.host,
port: +options.port,
isServer: !!options.stdin
};
- if (options.port !== undefined || options.host !== undefined) (0, _traceViewer.runTraceInBrowser)(traces, openOptions).catch(logErrorAndExit);else (0, _traceViewer.runTraceViewerApp)(traces, options.browser, openOptions, true).catch(logErrorAndExit);
-}).addHelpText('afterAll', `
+ const { runTraceInBrowser, runTraceViewerApp } = require("../server/trace/viewer/traceViewer");
+ if (options.port !== void 0 || options.host !== void 0)
+ runTraceInBrowser(trace, openOptions).catch(logErrorAndExit);
+ else
+ runTraceViewerApp(trace, options.browser, openOptions).catch(logErrorAndExit);
+}).addHelpText("afterAll", `
Examples:
+ $ show-trace
$ show-trace https://example.com/trace.zip`);
-async function launchContext(options, extraOptions) {
- validateOptions(options);
- const browserType = lookupBrowserType(options);
- const launchOptions = extraOptions;
- if (options.channel) launchOptions.channel = options.channel;
- launchOptions.handleSIGINT = false;
- const contextOptions =
- // Copy the device descriptor since we have to compare and modify the options.
- options.device ? {
- ...playwright.devices[options.device]
- } : {};
-
- // In headful mode, use host device scale factor for things to look nice.
- // In headless, keep things the way it works in Playwright by default.
- // Assume high-dpi on MacOS. TODO: this is not perfect.
- if (!extraOptions.headless) contextOptions.deviceScaleFactor = _os.default.platform() === 'darwin' ? 2 : 1;
-
- // Work around the WebKit GTK scrolling issue.
- if (browserType.name() === 'webkit' && process.platform === 'linux') {
- delete contextOptions.hasTouch;
- delete contextOptions.isMobile;
- }
- if (contextOptions.isMobile && browserType.name() === 'firefox') contextOptions.isMobile = undefined;
- if (options.blockServiceWorkers) contextOptions.serviceWorkers = 'block';
-
- // Proxy
-
- if (options.proxyServer) {
- launchOptions.proxy = {
- server: options.proxyServer
- };
- if (options.proxyBypass) launchOptions.proxy.bypass = options.proxyBypass;
- }
- const browser = await browserType.launch(launchOptions);
- if (process.env.PWTEST_CLI_IS_UNDER_TEST) {
- process._didSetSourcesForTest = text => {
- process.stdout.write('\n-------------8<-------------\n');
- process.stdout.write(text);
- process.stdout.write('\n-------------8<-------------\n');
- const autoExitCondition = process.env.PWTEST_CLI_AUTO_EXIT_WHEN;
- if (autoExitCondition && text.includes(autoExitCondition)) closeBrowser();
- };
- // Make sure we exit abnormally when browser crashes.
- const logs = [];
- require('playwright-core/lib/utilsBundle').debug.log = (...args) => {
- const line = require('util').format(...args) + '\n';
- logs.push(line);
- process.stderr.write(line);
- };
- browser.on('disconnected', () => {
- const hasCrashLine = logs.some(line => line.includes('process did exit:') && !line.includes('process did exit: exitCode=0, signal=null'));
- if (hasCrashLine) {
- process.stderr.write('Detected browser crash.\n');
- (0, _utils.gracefullyProcessExitDoNotHang)(1);
- }
- });
- }
-
- // Viewport size
- if (options.viewportSize) {
- try {
- const [width, height] = options.viewportSize.split(',').map(n => parseInt(n, 10));
- contextOptions.viewport = {
- width,
- height
- };
- } catch (e) {
- throw new Error('Invalid viewport size format: use "width, height", for example --viewport-size=800,600');
- }
- }
-
- // Geolocation
-
- if (options.geolocation) {
- try {
- const [latitude, longitude] = options.geolocation.split(',').map(n => parseFloat(n.trim()));
- contextOptions.geolocation = {
- latitude,
- longitude
- };
- } catch (e) {
- throw new Error('Invalid geolocation format, should be "lat,long". For example --geolocation="37.819722,-122.478611"');
- }
- contextOptions.permissions = ['geolocation'];
- }
-
- // User agent
-
- if (options.userAgent) contextOptions.userAgent = options.userAgent;
-
- // Lang
-
- if (options.lang) contextOptions.locale = options.lang;
-
- // Color scheme
-
- if (options.colorScheme) contextOptions.colorScheme = options.colorScheme;
-
- // Timezone
-
- if (options.timezone) contextOptions.timezoneId = options.timezone;
-
- // Storage
-
- if (options.loadStorage) contextOptions.storageState = options.loadStorage;
- if (options.ignoreHttpsErrors) contextOptions.ignoreHTTPSErrors = true;
-
- // HAR
-
- if (options.saveHar) {
- contextOptions.recordHar = {
- path: _path.default.resolve(process.cwd(), options.saveHar),
- mode: 'minimal'
- };
- if (options.saveHarGlob) contextOptions.recordHar.urlFilter = options.saveHarGlob;
- contextOptions.serviceWorkers = 'block';
- }
-
- // Close app when the last window closes.
-
- const context = await browser.newContext(contextOptions);
- let closingBrowser = false;
- async function closeBrowser() {
- // We can come here multiple times. For example, saving storage creates
- // a temporary page and we call closeBrowser again when that page closes.
- if (closingBrowser) return;
- closingBrowser = true;
- if (options.saveTrace) await context.tracing.stop({
- path: options.saveTrace
- });
- if (options.saveStorage) await context.storageState({
- path: options.saveStorage
- }).catch(e => null);
- if (options.saveHar) await context.close();
- await browser.close();
- }
- context.on('page', page => {
- page.on('dialog', () => {}); // Prevent dialogs from being automatically dismissed.
- page.on('close', () => {
- const hasPage = browser.contexts().some(context => context.pages().length > 0);
- if (hasPage) return;
- // Avoid the error when the last page is closed because the browser has been closed.
- closeBrowser().catch(() => {});
- });
- });
- process.on('SIGINT', async () => {
- await closeBrowser();
- (0, _utils.gracefullyProcessExitDoNotHang)(130);
- });
- const timeout = options.timeout ? parseInt(options.timeout, 10) : 0;
- context.setDefaultTimeout(timeout);
- context.setDefaultNavigationTimeout(timeout);
- if (options.saveTrace) await context.tracing.start({
- screenshots: true,
- snapshots: true
- });
-
- // Omit options that we add automatically for presentation purpose.
- delete launchOptions.headless;
- delete launchOptions.executablePath;
- delete launchOptions.handleSIGINT;
- delete contextOptions.deviceScaleFactor;
- return {
- browser,
- browserName: browserType.name(),
- context,
- contextOptions,
- launchOptions
- };
-}
-async function openPage(context, url) {
- const page = await context.newPage();
- if (url) {
- if (_fs.default.existsSync(url)) url = 'file://' + _path.default.resolve(url);else if (!url.startsWith('http') && !url.startsWith('file://') && !url.startsWith('about:') && !url.startsWith('data:')) url = 'http://' + url;
- await page.goto(url).catch(error => {
- if (process.env.PWTEST_CLI_AUTO_EXIT_WHEN && (0, _errors.isTargetClosedError)(error)) {
- // Tests with PWTEST_CLI_AUTO_EXIT_WHEN might close page too fast, resulting
- // in a stray navigation aborted error. We should ignore it.
- } else {
- throw error;
- }
- });
- }
- return page;
-}
-async function open(options, url, language) {
- const {
- context,
- launchOptions,
- contextOptions
- } = await launchContext(options, {
- headless: !!process.env.PWTEST_CLI_HEADLESS,
- executablePath: process.env.PWTEST_CLI_EXECUTABLE_PATH
- });
- await context._enableRecorder({
- language,
- launchOptions,
- contextOptions,
- device: options.device,
- saveStorage: options.saveStorage,
- handleSIGINT: false
- });
- await openPage(context, url);
-}
-async function codegen(options, url) {
- const {
- target: language,
- output: outputFile,
- testIdAttribute: testIdAttributeName
- } = options;
- const tracesDir = _path.default.join(_os.default.tmpdir(), `playwright-recorder-trace-${Date.now()}`);
- const {
- context,
- launchOptions,
- contextOptions
- } = await launchContext(options, {
- headless: !!process.env.PWTEST_CLI_HEADLESS,
- executablePath: process.env.PWTEST_CLI_EXECUTABLE_PATH,
- tracesDir
- });
- _utilsBundle.dotenv.config({
- path: 'playwright.env'
- });
- await context._enableRecorder({
- language,
- launchOptions,
- contextOptions,
- device: options.device,
- saveStorage: options.saveStorage,
- mode: 'recording',
- codegenMode: process.env.PW_RECORDER_IS_TRACE_VIEWER ? 'trace-events' : 'actions',
- testIdAttributeName,
- outputFile: outputFile ? _path.default.resolve(outputFile) : undefined,
- handleSIGINT: false
- });
- await openPage(context, url);
-}
-async function waitForPage(page, captureOptions) {
- if (captureOptions.waitForSelector) {
- console.log(`Waiting for selector ${captureOptions.waitForSelector}...`);
- await page.waitForSelector(captureOptions.waitForSelector);
- }
- if (captureOptions.waitForTimeout) {
- console.log(`Waiting for timeout ${captureOptions.waitForTimeout}...`);
- await page.waitForTimeout(parseInt(captureOptions.waitForTimeout, 10));
- }
-}
-async function screenshot(options, captureOptions, url, path) {
- const {
- context
- } = await launchContext(options, {
- headless: true
- });
- console.log('Navigating to ' + url);
- const page = await openPage(context, url);
- await waitForPage(page, captureOptions);
- console.log('Capturing screenshot into ' + path);
- await page.screenshot({
- path,
- fullPage: !!captureOptions.fullPage
- });
- // launchContext takes care of closing the browser.
- await page.close();
-}
-async function pdf(options, captureOptions, url, path) {
- if (options.browser !== 'chromium') throw new Error('PDF creation is only working with Chromium');
- const {
- context
- } = await launchContext({
- ...options,
- browser: 'chromium'
- }, {
- headless: true
- });
- console.log('Navigating to ' + url);
- const page = await openPage(context, url);
- await waitForPage(page, captureOptions);
- console.log('Saving as pdf into ' + path);
- await page.pdf({
- path
- });
- // launchContext takes care of closing the browser.
- await page.close();
-}
-function lookupBrowserType(options) {
- let name = options.browser;
- if (options.device) {
- const device = playwright.devices[options.device];
- name = device.defaultBrowserType;
- }
- let browserType;
- switch (name) {
- case 'chromium':
- browserType = playwright.chromium;
- break;
- case 'webkit':
- browserType = playwright.webkit;
- break;
- case 'firefox':
- browserType = playwright.firefox;
- break;
- case 'cr':
- browserType = playwright.chromium;
- break;
- case 'wk':
- browserType = playwright.webkit;
- break;
- case 'ff':
- browserType = playwright.firefox;
- break;
- }
- if (browserType) return browserType;
- _utilsBundle.program.help();
-}
-function validateOptions(options) {
- if (options.device && !(options.device in playwright.devices)) {
- const lines = [`Device descriptor not found: '${options.device}', available devices are:`];
- for (const name in playwright.devices) lines.push(` "${name}"`);
- throw new Error(lines.join('\n'));
- }
- if (options.colorScheme && !['light', 'dark'].includes(options.colorScheme)) throw new Error('Invalid color scheme, should be one of "light", "dark"');
-}
+(0, import_traceCli.addTraceCommands)(import_utilsBundle.program, logErrorAndExit);
+import_utilsBundle.program.command("cli", { hidden: true }).allowExcessArguments(true).allowUnknownOption(true).action(async (options) => {
+ const { program: cliProgram } = require("../tools/cli-client/program");
+ process.argv.splice(process.argv.indexOf("cli"), 1);
+ cliProgram().catch(logErrorAndExit);
+});
function logErrorAndExit(e) {
- if (process.env.PWDEBUGIMPL) console.error(e);else console.error(e.name + ': ' + e.message);
- (0, _utils.gracefullyProcessExitDoNotHang)(1);
+ if (process.env.PWDEBUGIMPL)
+ console.error(e);
+ else
+ console.error(e.name + ": " + e.message);
+ (0, import_utils.gracefullyProcessExitDoNotHang)(1);
}
function codegenId() {
- return process.env.PW_LANG_NAME || 'playwright-test';
+ return process.env.PW_LANG_NAME || "playwright-test";
}
function commandWithOpenOptions(command, description, options) {
- let result = _utilsBundle.program.command(command).description(description);
- for (const option of options) result = result.option(option[0], ...option.slice(1));
- return result.option('-b, --browser ', 'browser to use, one of cr, chromium, ff, firefox, wk, webkit', 'chromium').option('--block-service-workers', 'block service workers').option('--channel ', 'Chromium distribution channel, "chrome", "chrome-beta", "msedge-dev", etc').option('--color-scheme ', 'emulate preferred color scheme, "light" or "dark"').option('--device ', 'emulate device, for example "iPhone 11"').option('--geolocation ', 'specify geolocation coordinates, for example "37.819722,-122.478611"').option('--ignore-https-errors', 'ignore https errors').option('--load-storage ', 'load context storage state from the file, previously saved with --save-storage').option('--lang ', 'specify language / locale, for example "en-GB"').option('--proxy-server ', 'specify proxy server, for example "http://myproxy:3128" or "socks5://myproxy:8080"').option('--proxy-bypass ', 'comma-separated domains to bypass proxy, for example ".com,chromium.org,.domain.com"').option('--save-har ', 'save HAR file with all network activity at the end').option('--save-har-glob ', 'filter entries in the HAR by matching url against this glob pattern').option('--save-storage ', 'save context storage state at the end, for later use with --load-storage').option('--timezone