diff --git a/lib/playwright.ex b/lib/playwright.ex
index 6b780f4d..02088f80 100644
--- a/lib/playwright.ex
+++ b/lib/playwright.ex
@@ -8,8 +8,8 @@ defmodule Playwright do
alias Playwright.API.{Browser, Page, Response}
- {:ok, browser} = Playwright.launch(:chromium)
- {:ok, page} = Browser.new_page(browser)
+ {:ok, session, browser} = Playwright.launch(:chromium)
+ {:ok, page} = Browser.new_page(browser)
{:ok, response} = Page.goto(browser, "http://example.com")
assert Response.ok(response)
@@ -18,6 +18,9 @@ defmodule Playwright do
"""
use Playwright.SDK.ChannelOwner
+ alias Playwright
+ alias Playwright.APIRequest
+ alias Playwright.SDK.Channel
alias Playwright.SDK.Config
@property :chromium
@@ -33,6 +36,9 @@ defmodule Playwright do
# @typedoc "Options for `launch`."
# @type launch_options :: Playwright.SDK.Config.launch_options()
+ # API
+ # ---------------------------------------------------------------------------
+
@doc """
Initiates an instance of `Playwright.Browser` use the WebSocket transport.
@@ -55,7 +61,7 @@ defmodule Playwright do
options = Map.merge(Config.connect_options(), options)
{:ok, session} = new_session(Playwright.SDK.Transport.WebSocket, options)
{:ok, browser} = new_browser(session, client, options)
- {:ok, browser}
+ {:ok, session, browser}
end
@doc """
@@ -67,7 +73,7 @@ defmodule Playwright do
## Arguments
- | key/name | typ | | description |
+ | key/name | type | | description |
| ----------| ----- | ----------- | ----------- |
| `client` | param | `client()` | The type of client (browser) to launch. |
| `options` | param | `options()` | `Playwright.SDK.Config.launch_options()` |
@@ -77,7 +83,11 @@ defmodule Playwright do
options = Map.merge(Config.launch_options(), options)
{:ok, session} = new_session(Playwright.SDK.Transport.Driver, options)
{:ok, browser} = new_browser(session, client, options)
- {:ok, browser}
+ {:ok, session, browser}
+ end
+
+ def request(session) do
+ APIRequest.new(session)
end
# private
@@ -85,16 +95,21 @@ defmodule Playwright do
defp new_browser(session, client, options)
when is_atom(client) and client in [:chromium, :firefox, :webkit] do
- with play <- Playwright.SDK.Channel.find(session, {:guid, "Playwright"}),
+ with play <- Channel.find(session, {:guid, "Playwright"}),
guid <- Map.get(play, client)[:guid] do
- {:ok, Playwright.SDK.Channel.post(session, {:guid, guid}, :launch, options)}
+ playwright = %Playwright{
+ guid: guid,
+ session: session
+ }
+
+ {:ok, Channel.post({playwright, :launch}, options)}
end
end
defp new_session(transport, args) do
DynamicSupervisor.start_child(
- Playwright.SDK.Channel.Session.Supervisor,
- {Playwright.SDK.Channel.Session, {transport, args}}
+ Channel.Session.Supervisor,
+ {Channel.Session, {transport, args}}
)
end
end
diff --git a/lib/playwright/sdk/channel/error.ex b/lib/playwright/api/error.ex
similarity index 56%
rename from lib/playwright/sdk/channel/error.ex
rename to lib/playwright/api/error.ex
index ef368818..908de13b 100644
--- a/lib/playwright/sdk/channel/error.ex
+++ b/lib/playwright/api/error.ex
@@ -1,29 +1,37 @@
-defmodule Playwright.SDK.Channel.Error do
+defmodule Playwright.API.Error do
@moduledoc false
# `Error` represents an error message received from the Playwright server
# that is in response to a `Message` previously sent.
- alias Playwright.SDK.Channel
@enforce_keys [:type, :message]
- defstruct [:type, :message]
+ defstruct [:type, :message, :wrapped]
@type t() :: %__MODULE__{
type: String.t(),
- message: String.t()
+ message: String.t(),
+ wrapped: any()
}
+ def new(%{error: %{name: name, message: message, wrapped: original} = _error}, _catalog) do
+ %__MODULE__{
+ type: name,
+ message: String.split(message, "\n") |> List.first(),
+ wrapped: original
+ }
+ end
+
def new(%{error: %{name: name, message: message} = _error}, _catalog) do
- %Channel.Error{
+ %__MODULE__{
type: name,
message: String.split(message, "\n") |> List.first()
}
end
- # TODO: determine why we get here...
- # DONE: see comment at error_handling.ex:9.
def new(%{error: %{message: message} = _error}, _catalog) do
- %Channel.Error{
- type: "NotImplementedError",
+ # dbg(error)
+
+ %__MODULE__{
+ type: "UnknownError",
message: String.split(message, "\n") |> List.first()
}
end
diff --git a/lib/playwright/api/errors/web_error.ex b/lib/playwright/api/errors/web_error.ex
deleted file mode 100644
index e69de29b..00000000
diff --git a/lib/playwright/api_request.ex b/lib/playwright/api_request.ex
index f743231d..2dffe467 100644
--- a/lib/playwright/api_request.ex
+++ b/lib/playwright/api_request.ex
@@ -1,7 +1,296 @@
defmodule Playwright.APIRequest do
- @moduledoc false
- use Playwright.SDK.ChannelOwner
+ @moduledoc """
+ `Playwright.APIRequest` exposes an API to be used for the web API testing.
- # @spec new_context(t(), options()) :: APIRequestContext.t()
- # def new_context(api_request, options \\ %{})
+ The module is used for creating `Playwright.APIRequestContext` instances,
+ which in turn may be used for sending web requests. An instance of this
+ modeule may be obtained via `Playwright.request/1`.
+
+ For more usage details, see `Playwright.APIRequestContext`.
+ """
+
+ use Playwright.SDK.Pipeline
+ alias Playwright.API.Error
+ alias Playwright.APIRequest
+ alias Playwright.APIRequestContext
+ alias Playwright.SDK.Channel
+
+ # structs & types
+ # ----------------------------------------------------------------------------
+
+ @enforce_keys [:guid, :session]
+ defstruct [:guid, :session]
+
+ @typedoc """
+ `#{String.replace_prefix(inspect(__MODULE__), "Elixir.", "")}`
+ """
+ @type t() :: %__MODULE__{
+ guid: binary(),
+ session: pid()
+ }
+
+ @typedoc "Options for calls to `new_context/1`"
+ @type options :: %{
+ optional(:base_url) => String.t(),
+ optional(:client_certificates) => [client_certificate()],
+ optional(:extra_http_headers) => http_headers(),
+ optional(:http_credentials) => http_credentials(),
+ optional(:ignore_https_errors) => boolean(),
+ optional(:proxy) => proxy_settings(),
+ optional(:storage_state) => storage_state() | Path.t() | String.t(),
+ optional(:timeout) => float(),
+ optional(:user_agent) => String.t()
+ }
+
+ @typedoc """
+ A client TLS certificate to be used in requests.
+ """
+ @type client_certificate :: %{
+ required(:origin) => String.t(),
+ optional(:cert_path) => Path.t() | String.t(),
+ optional(:key_path) => Path.t() | String.t(),
+ optional(:pfx_path) => Path.t() | String.t(),
+ optional(:passphrase) => String.t()
+ }
+
+ @typedoc "A `map` containing additional HTTP headers to be sent with every request."
+ @type http_headers :: %{required(String.t()) => String.t()}
+
+ @typedoc "HTTP authetication credentials."
+ @type http_credentials :: %{
+ required(:username) => String.t(),
+ required(:password) => String.t(),
+ optional(:origin) => String.t(),
+ optional(:send) => :always | :unauthorized
+ }
+
+ @typedoc "Network proxy settings."
+ @type proxy_settings :: %{
+ required(:server) => String.t(),
+ optional(:bypass) => String.t(),
+ optional(:username) => String.t(),
+ optional(:password) => String.t()
+ }
+
+ @typedoc "Storage state settings."
+ @type storage_state :: %{
+ required(:cookies) => [cookie()],
+ required(:origins) => [
+ %{
+ required(:origin) => String.t(),
+ required(:local_storage) => [local_storage()]
+ }
+ ]
+ }
+
+ @typedoc "An HTTP cookie."
+ @type cookie :: %{
+ optional(:name) => String.t(),
+ optional(:value) => String.t(),
+ required(:domain) => String.t(),
+ required(:path) => String.t(),
+ optional(:expires) => float(),
+ optional(:http_only) => boolean(),
+ optional(:secure) => boolean(),
+ # same_site: "Lax" | "None" | "Strict"
+ optional(:same_site) => String.t()
+ }
+
+ @typedoc "Local storage settings."
+ @type local_storage :: %{
+ required(:name) => String.t(),
+ required(:value) => String.t()
+ }
+
+ # API
+ # ----------------------------------------------------------------------------
+
+ @doc """
+ Returns a new `Playwright.APIRequest`.
+
+ See also `Playwright.request/1` which is a more likely entry-point.
+
+ ## Usage
+
+ request = APIRequest.new(session)
+
+ ## Arguments
+
+ | name | description |
+ | --------- | -------------------------------------------- |
+ | `session` | The `pid` for the current Playwright session |
+
+ ## Returns
+
+ - `Playwright.APIRequest.t()`
+ """
+ @spec new(pid()) :: t()
+ def new(session) do
+ %__MODULE__{
+ guid: "Playwright",
+ session: session
+ }
+ end
+
+ @doc """
+ Creates a new instance of `Playwright.APIRequestContext`.
+
+ ## Usage
+
+ request = Playwright.request(session)
+
+ APIRequest.new_context(request)
+ APIRequest.new_context(request, options)
+
+ APIRequest.new_context!(request)
+ APIRequest.new_context!(request, options)
+
+ ## Arguments
+
+ | name | | description |
+ | --------- | ---------- | -------------------------- |
+ | `request` | | The "subject" `APIRequest` |
+ | `options` | (optional) | `APIRequest.options()` |
+
+ ## Options
+
+
⋯
+
+ ### Option: `:base_url`
+
+ Functions such as `Playwright.APIRequestContext.get/3` take the base URL into
+ consideration by using the [`URL()`](https://developer.mozilla.org/en-US/docs/Web/API/URL/URL)
+ constructor for building the corresponding require URL.
+
+ #### Examples
+
+ - With `base_url: http://localhost:3000`, sending a request to `/bar.html`
+ results in `http://localhost:3000/bar.html`.
+ - With `base_url: http://localhost:3000/foo/`, sending a request to `/bar.html`
+ results in `http://localhost:3000/foo/bar.html`.
+ - With `base_url: http://localhost:3000/foo` (without the trailing slash),
+ navigating to `./bar.html` results in `http://localhost:3000/bar.html`.
+
+
⋯
+
+ ### Option: `:client_certificates`
+
+ A list of client certificates to be used. Each certificate instance must have
+ both `:cert_path` and `:key_path` or a single `:pfx_path` to load the client
+ certificate. Optionally, the `:passphrase` property should be provided if the
+ certficiate is encrypted. The `:origin` property should be provided with an
+ exact match to the request origin for which the certificate is valid.
+
+ TLS client authentication allows the server to request a client certificate
+ and verify it.
+
+ > #### NOTE {: .info}
+ >
+ > Using client certificates in combination with proxy servers is not supported.
+
+ > #### NOTE {: .info}
+ >
+ > When using WebKit on macOS, accessing `localhost` will not pick up client
+ > certificates. As a work-around: replace `localhost` with `local.playwright`.
+
+ #### Details
+
+ | name | | description |
+ | ------------- | ---------- | --------------------------------- |
+ | `:origin` | | Exact origin that the certificate is valid for. Origin includes https protocol, a hostname and optionally a port. |
+ | `:cert_path` | (optional) | Path to the file with the certificate in PEM format. |
+ | `:key_path` | (optional) | Path to the file with the private key in PEM format. |
+ | `:pfx_path` | (optional) | Path to the PFX or PKCS12 encoded private key and certificate chain. |
+ | `:passphrase` | (optional) | Passphrase for the private key (PEM or PFX). |
+
+
⋯
+
+ ### Option: `:extra_http_headers`
+
+ A `map` containing additional HTTP headers to be sent with every request.
+
+
⋯
+
+ ### Option: `:http_credentials`
+
+ Credentials for [HTTP authentication](https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication).
+ If no `:origin` is specified, the `:username` and `:password` are sent to any
+ servers upon unauthorized responses.
+
+ #### Details
+
+ | name | | description |
+ | ----------- | ---------- | ----------- |
+ | `:username` | | |
+ | `:password` | | |
+ | `:origin` | (optional) | Restrain sending http credentials on specific origin (`scheme://host:port`). |
+ | `:send` | (optional) | This option only applies to the requests sent from corresponding `APIRequestContext` and does not affect requests sent from the browser. `:always` - `Authorization` header with basic authentication credentials will be sent with the each API request. `:unauthorized`- the credentials are only sent when 401 (Unauthorized) response with `WWW-Authenticate` header is received. Defaults to `:unauthorized`. |
+
+
⋯
+
+ ### Option: `:ignore_https_errors`
+
+ Whether to ignore HTTPS errors when sending network requests. Defaults to
+ `false`.
+
+
⋯
+
+ ### Option: `:proxy`
+
+ Network proxy settings.
+
+ | name | | description |
+ | ----------- | ---------- | ----------- |
+ | `:server` | | Proxy to be used for all requests. HTTP and SOCKS proxies are supported, for example `http://myproxy.com:3128` or `socks5://myproxy.com:3128`. Short form `myproxy.com:3128` is considered an HTTP proxy. |
+ | `:bypass` | (optional) | Optional comma-separated domains to bypass proxy, for example `".com, chromium.org, .domain.com"`. |
+ | `:username` | (optional) | Optional username to use if HTTP proxy requires authentication. |
+ | `:password` | (optional) | Optional password to use if HTTP proxy requires authentication. |
+
+
⋯
+
+ ### Option: `:storage_state`
+
+ Populates context with given storage state.
+
+ This option can be used to initialize context with logged-in information
+ obtained via, either, a path to the file with saved storage, or the value
+ returned by one of `BrowserContext.storage_state/2` or
+ `APIRequestContext.storage_state/2`.
+
+ | name | | description |
+ | ----------- | ---------- | ----------- |
+ | `:cookies` | | `[APIRequest.cookie()]` |
+ | `:origins` | | `[APIRequest.origin()]` |
+
+
⋯
+
+ ### Option: `:timeout`
+
+ Maximum time in milliseconds to wait for the response. Defaults to `30_000`
+ (30 seconds). Pass `0` to disable the timeout.
+
+
⋯
+
+ ### Option: `:user_agent`
+
+ Specific user agent to use in this context.
+
+ ## Returns
+
+ - `Playwright.APIRequestContext.t()`
+ - `{:error, Playwright.API.Error.t()}`
+ """
+ @pipe {:new_context, [:request]}
+ @pipe {:new_context, [:request, :options]}
+ @spec new_context(t(), options()) :: APIRequestContext.t() | {:error, Error.t()}
+ def new_context(request, options \\ %{})
+
+ def new_context(%APIRequest{} = request, %{storage_state: storage} = options) when is_binary(storage) do
+ storage = Jason.decode!(File.read!(storage))
+ new_context(request, Map.merge(options, %{storage_state: storage}))
+ end
+
+ def new_context(%APIRequest{} = request, options) do
+ Channel.post({request, :new_request}, options)
+ end
end
diff --git a/lib/playwright/api_request_context.ex b/lib/playwright/api_request_context.ex
index 66692506..0205ade8 100644
--- a/lib/playwright/api_request_context.ex
+++ b/lib/playwright/api_request_context.ex
@@ -1,73 +1,538 @@
defmodule Playwright.APIRequestContext do
@moduledoc """
- This API is used for the Web API testing. You can use it to trigger API endpoints, configure micro-services,
- prepare environment or the server to your e2e test.
+ `Playwright.APIRequestContext` is useful for testing of web APIs.
- Use this at caution as has not been tested.
+ The module may be used to trigger API endpoints, configure micro-services,
+ prepare environment or services in end-to-end (e2e) tests.
+ Each `Playwright.BrowserContext` (browser context) has an associated
+ `Playwright.APIRequestContext` (API context) instance that shares cookie
+ storage with the browser context and can be accessed via
+ `Playwright.BrowserContext.request/1` or `Playwright.Page.request/1`.
+ It is also possible to create a new `Playwright.APIRequestContext` instance
+ via `Playwright.APIRequest.newContext/2`.
+
+ ## Cookie management
+
+ An `Playwright.APIRequestContext` returned by `Playwright.BrowserContext.request/1`
+ or `Playwright.Page.request/1` shares cookie storage with the corresponding
+ `Playwright.BrowserContext`. Each API request will have a cookie HTTP header
+ populated with the values from the browser context. If the API response
+ contains a `Set-Cookie` header, it will automatically update `Playwright.BrowserContext`
+ cookies and requests made from the page will pick up the changes. This means
+ that if you authenticate using this API, your e2e test will be authenticated.
+
+ If you want API requests to not interfere with the browser cookies, create a
+ new `Playwright.APIRequestContext` via `Playwright.APIRequest.new_context/1`.
+ Such API contexts will have isolated cookie storage.
+
+ ## Shared options
+
+ The following options are available for all forms of request:
+
+ | name | | description |
+ | ---------------------- | ---------- | --------------------------------- |
+ | `:data` | (optional) | Sets post data of the request. If the `:data` parameter is a `serializable()`, it will be serialized as a JSON string and the `content-type` HTTP header will be set to `application/json`, if not explicitly set. Otherwise the `content-type` header will be set to `application/octet-stream` if not explicitly set. |
+ | `:fail_on_status_code` | (optional) | Whether to raise an error for response codes other than `2xx` and `3xx`. By default, a `Playwright.APIResponse` is returned for all status codes. |
+ | `:form` | (optional) | Provides content that will be serialized as an HTML form using `application/x-www-form-urlencoded` encoding and sent as the request body. If this parameter is specified, the `content-type` HTTP header will be set to `application/x-www-form-urlencoded` unless explicitly provided. |
+ | `:headers` | (optional) | Set HTTP headers. These headers will apply to the fetched request as well as any redirects initiated by it. |
+ | `:ignore_https_errors` | (optional) | Whether to ignore HTTPS errors when sending network requests. Defaults to `false`. |
+ | `:max_redirects` | (optional) | Maximum number of request redirects that will be followed automatically. An error will be thrown if the number is exceeded. Defaults to `20`. Pass `0` to not follow redirects. |
+ | `:max_retries` | (optional) | Maximum number of times network errors should be retried. Currently only `ECONNRESET` error is retried. Does not retry based on HTTP response codes. An error will be thrown if the limit is exceeded. Defaults to `0` - no retries. |
+ | `:method` | (optional) | If set changes the fetch method (e.g. [`PUT`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PUT) or [`POST`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST)). If not specified, [`GET`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/GET) method is used. |
+ | `:multipart` | (optional) | Provides content that will be serialized as an HTML form using `multipart/form-data` encoding and sent as the request body. If this parameter is specified, the `content-type` header will be set to `multipart/form-data` unless explicitly provided. File values can be passed either as a `Multipart.t()` or as file-like object containing file name, mime-type and content. |
+ | `:params` | (optional) | Query parameters to be sent with the URL. |
+ | `:timeout` | (optional) | Request timeout in milliseconds. Defaults to `30_000` (30 seconds). Pass `0` to disable timeout. |
+
+ When constructing a `:multipart` parameter as a `form()`, the following fields
+ should be defined:application
+
+ - `:name` - File name (`String.t()`)
+ - `:mime_type` - File type (`String.t()`)
+ - `:buffer` - File content (`binary()`)
"""
use Playwright.SDK.ChannelOwner
alias Playwright.APIRequestContext
+ alias Playwright.APIResponse
+ alias Playwright.Request
+ alias Playwright.API.Error
+ alias Playwright.SDK.Channel
+
+ # structs & types
+ # ----------------------------------------------------------------------------
+
+ @typedoc "Options for the various request types."
+ @type options :: %{
+ # TODO: support the equivalent of TypeScript's `Buffer`
+ optional(:data) => serializable() | String.t(),
+ optional(:fail_on_status_code) => boolean(),
+ optional(:form) => form(),
+ optional(:headers) => http_headers(),
+ optional(:ignore_https_errors) => boolean(),
+ optional(:max_redirects) => number(),
+ optional(:max_retries) => number(),
+ optional(:method) => String.t(),
+ # TODO: support the equivalent of TypeScript's `ReadStream`
+ optional(:multipart) => Multipart.t() | form(),
+ optional(:params) => form() | String.t(),
+ optional(:timeout) => float()
+ }
+
+ @typedoc "A data structure for form content."
+ @type form :: %{
+ required(String.t()) => binary() | boolean() | float() | String.t()
+ }
+
+ @typedoc "A `map` containing additional HTTP headers to be sent with every request."
+ @type http_headers :: %{required(String.t()) => String.t()}
+
+ @typedoc "Data serializable as JSON."
+ @type serializable :: list() | map()
+
+ @typedoc "Storage state settings."
+ @type storage_state :: %{
+ required(:cookies) => [cookie()],
+ required(:origins) => [
+ %{
+ required(:origin) => String.t(),
+ required(:local_storage) => [local_storage()]
+ }
+ ]
+ }
+
+ @typedoc "An HTTP cookie."
+ @type cookie :: %{
+ optional(:name) => String.t(),
+ optional(:value) => String.t(),
+ required(:domain) => String.t(),
+ required(:path) => String.t(),
+ optional(:expires) => float(),
+ optional(:http_only) => boolean(),
+ optional(:secure) => boolean(),
+ # same_site: "Lax" | "None" | "Strict"
+ optional(:same_site) => String.t()
+ }
+
+ @typedoc "Local storage settings."
+ @type local_storage :: %{
+ required(:name) => String.t(),
+ required(:value) => String.t()
+ }
+
+ @typedoc "Options for `dispose/2`."
+ @type opts_dispose :: %{
+ optional(:reason) => String.t()
+ }
+
+ @typedoc "Options for `storage_state/2`."
+ @type opts_storage :: %{
+ optional(:path) => String.t()
+ }
+
+ # API
+ # ----------------------------------------------------------------------------
+
+ @doc """
+ Sends an HTTP(S) [`DELETE`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/DELETE)
+ request and returns its response.
+
+ Function invocation will populate request cookies from the context, and update
+ context cookies from the response. Calls automatically follow redirects.
+
+ ## Usage
+
+ request = Playwright.request(session)
+ context = APIRequest.new_context(request)
+
+ APIRequest.delete(context, "https://example.com/api/books")
+
+ ## Arguments
+
+ | name | | description |
+ | ---------------- | ---------- | --------------------------------- |
+ | `context` | | The "subject" `APIRequestContext` |
+ | `url` | | Target URL |
+ | `options` | (optional) | `APIRequestContext.options()` |
+
+ ## Options
+
+ See "Shared options" above.
+
+ ## Returns
+
+ - `Playwright.APIResponse.t()`
+ - `{:error, Error.t()}`
+ """
+ @pipe {:delete, [:context, :url]}
+ @pipe {:delete, [:context, :url, :options]}
+ @spec delete(t(), binary(), options()) :: t() | {:error, Error.t()}
+ def delete(context, url, options \\ %{})
+
+ def delete(%APIRequestContext{} = context, url, options) do
+ fetch(context, url, Map.merge(options, %{method: "DELETE"}))
+ end
+
+ @doc """
+ Disposes of resources related to this `Playwright.APIRequestContext`.
+
+ All responses returned by `Playwright.APIRequestContext.fetch/3` and similar
+ are stored in memory in order to support later, cached calls to
+ `Playwright.APIResponse.body/1`, etc. `dispose/1` discards all associated
+ resources. Subsequent calls to any function on disposed `APIRequestContext`
+ will result in errors.
+
+ ## Arguments
+
+ | name | | description |
+ | ---------------- | ---------- | --------------------------------- |
+ | `context` | | The "subject" `APIRequestContext` |
+ | `options` | (optional) | Options (see below) |
+
+ ## Options
+
+ | name | | description |
+ | -------- | ---------- | --------------------------------- |
+ | `reason` | (optional) | The reason to be reported to any operations interrupted by the context disposal. |
+
+ ## Returns
+
+ - `:ok`
+ - `{:error, %Error{}}`
+ """
+ @pipe {:dispose, [:context]}
+ @pipe {:dispose, [:context, :options]}
+ @spec dispose(t(), opts_dispose()) :: :ok | {:error, Error.t()}
+ def dispose(context, options \\ %{})
+
+ def dispose(%APIRequestContext{} = context, options) do
+ case Channel.post({context, "dispose"}, options, %{refresh: false}) do
+ {:error, %Playwright.API.Error{} = error} ->
+ {:error, error}
+
+ _ ->
+ :ok
+ end
+ end
+
+ # ---
+
+ @doc """
+ Sends an HTTP(S) request and returns the response (`Playwright.APIResponse`).
+
+ Function invocation will populate request cookies from the context, and update
+ context cookies from the response.
+
+ ## Usage
- @type fetch_options() :: %{
- optional(:params) => any(),
- optional(:method) => binary(),
- optional(:headers) => any(),
- optional(:postData) => any(),
- optional(:jsonData) => any(),
- optional(:formData) => any(),
- optional(:multipartData) => any(),
- optional(:timeout) => non_neg_integer(),
- optional(:failOnStatusCode) => boolean(),
- optional(:ignoreHTTPSErrors) => boolean()
+ JSON objects may be passed directly to the request:
+
+ request = Playwright.request(session)
+ context = APIRequest.new_context(request)
+
+ APIRequest.fetch(context, "https://example.com/api/books", %{
+ method: "POST",
+ data: %{
+ author: "Jane Doe",
+ title: "Book Title"
}
+ })
+
+ A common way to send file(s) in the body of a request is to upload them as
+ form fields with `multipart/form-data` encoding.
+ Use [`FormData`](https://developer.mozilla.org/en-US/docs/Web/API/FormData) to
+ construct the request body and pass that to the request via the `multipart`
+ parameter:
+
+ data = Multipart.new()
+ |> Multipart.add_field("author", "Jane Doe")
+ |> Multipart.add_field("title", "Book Title")
+ |> Multipart.add_file("path/to/manuscript.md", name: "manuscript.md")
+
+ APIRequest.fetch(context, "https://example.com/api/books", %{
+ method: "POST",
+ multipart: data
+ })
+
+ ## Arguments
+
+ | name | | description |
+ | ---------------- | ---------- | --------------------------------- |
+ | `context` | | The "subject" `APIRequestContext` |
+ | `url` | | Target URL |
+ | `options` | (optional) | `APIRequestContext.options()` |
+
+ ## Options
+
+ See "Shared options" above.
- # @spec delete(t(), binary(), options()) :: APIResponse.t()
- # def delete(context, url, options \\ %{})
-
- # @spec dispose(t()) :: :ok
- # def dispose(api_request_context)
-
- # @spec fetch(t(), binary() | Request.t(), options()) :: APIResponse.t()
- # def fetch(context, url_or_request, options \\ %{})
-
- # @spec get(t(), binary(), options()) :: APIResponse.t()
- # def get(context, url, options \\ %{})
-
- # @spec head(t(), binary(), options()) :: APIResponse.t()
- # def head(context, url, options \\ %{})
-
- # @spec patch(t(), binary(), options()) :: APIResponse.t()
- # def patch(context, url, options \\ %{})
-
- @spec post(t(), binary(), fetch_options()) :: Playwright.APIResponse.t()
- def post(%APIRequestContext{session: session} = context, url, options \\ %{}) do
- Channel.post(
- session,
- {:guid, context.guid},
- :fetch,
- Map.merge(
- %{
- url: url,
- method: "POST"
- },
- options
- )
- )
+ ## Returns
+
+ - `Playwright.APIResponse.t()`
+ - `{:error, Error.t()}`
+ """
+ @pipe {:fetch, [:context, :url_or_request]}
+ @pipe {:fetch, [:context, :url_or_request, :options]}
+ @spec fetch(t(), binary() | Request.t(), options()) :: APIResponse.t() | {:error, Error.t()}
+ def fetch(context, url_or_request, options \\ %{})
+
+ def fetch(%APIRequestContext{} = context, url, options) when is_binary(url) do
+ case Channel.post({context, :fetch}, %{url: url, method: "GET"}, options) do
+ {:error, _} = error ->
+ error
+
+ response ->
+ APIResponse.new(Map.merge(response, %{context: context}))
+ end
end
- # @spec put(t(), binary(), options()) :: APIResponse.t()
- # def put(context, url, options \\ %{})
+ @doc """
+ Sends an HTTP(S) [`GET`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/GET)
+ request and returns its response.
+
+ Function invocation will populate request cookies from the context, and update
+ context cookies from the response. Calls automatically follow redirects.
+
+ ## Usage
+
+ request = Playwright.request(session)
+ context = APIRequest.new_context(request)
+
+ APIRequest.get(context, "https://example.com/api/books", %{
+ params: %{isbn: "1234", page: "23"}
+ })
+
+ ## Arguments
+
+ | name | | description |
+ | ---------------- | ---------- | --------------------------------- |
+ | `context` | | The "subject" `APIRequestContext` |
+ | `url` | | Target URL |
+ | `options` | (optional) | `APIRequestContext.options()` |
+
+ ## Options
+
+ See "Shared options" above.
+
+ ## Returns
+
+ - `Playwright.APIResponse.t()`
+ - `{:error, Error.t()}`
+ """
+ @pipe {:get, [:context, :url]}
+ @pipe {:get, [:context, :url, :options]}
+ @spec get(t(), binary(), options()) :: t() | {:error, Error.t()}
+ def get(context, url, options \\ %{})
+
+ def get(%APIRequestContext{} = context, url, options) do
+ fetch(context, url, Map.merge(options, %{method: "GET"}))
+ end
+
+ @doc """
+ Sends an HTTP(S) [`HEAD`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/HEAD)
+ request and returns its response.
+
+ Function invocation will populate request cookies from the context, and update
+ context cookies from the response. Calls automatically follow redirects.
+
+ ## Usage
+
+ request = Playwright.request(session)
+ context = APIRequest.new_context(request)
+
+ APIRequest.head(context, "https://example.com/api/books")
+
+ ## Arguments
+
+ | name | | description |
+ | ---------------- | ---------- | --------------------------------- |
+ | `context` | | The "subject" `APIRequestContext` |
+ | `url` | | Target URL |
+ | `options` | (optional) | `APIRequestContext.options()` |
+
+ ## Options
+
+ See "Shared options" above.
+
+ ## Returns
+
+ - `Playwright.APIResponse.t()`
+ - `{:error, Error.t()}`
+ """
+ @pipe {:head, [:context, :url]}
+ @pipe {:head, [:context, :url, :options]}
+ @spec head(t(), binary(), options()) :: t() | {:error, Error.t()}
+ def head(context, url, options \\ %{})
+
+ def head(%APIRequestContext{} = context, url, options) do
+ fetch(context, url, Map.merge(options, %{method: "HEAD"}))
+ end
+
+ @doc """
+ Sends an HTTP(S) [`PATCH`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PATCH)
+ request and returns its response.
+
+ Function invocation will populate request cookies from the context, and update
+ context cookies from the response. Calls automatically follow redirects.
+
+ ## Usage
+
+ request = Playwright.request(session)
+ context = APIRequest.new_context(request)
+
+ APIRequest.patch(context, "https://example.com/api/books", %{
+ data: %{title: "Updated"},
+ params: %{isbn: "1234"}
+ })
+
+ ## Arguments
+
+ | name | | description |
+ | ---------------- | ---------- | --------------------------------- |
+ | `context` | | The "subject" `APIRequestContext` |
+ | `url` | | Target URL |
+ | `options` | (optional) | `APIRequestContext.options()` |
+
+ ## Options
+
+ See "Shared options" above.
+
+ ## Returns
+
+ - `Playwright.APIResponse.t()`
+ - `{:error, Error.t()}`
+ """
+ @pipe {:patch, [:context, :url]}
+ @pipe {:patch, [:context, :url, :options]}
+ @spec patch(t(), binary(), options()) :: t() | {:error, Error.t()}
+ def patch(context, url, options \\ %{})
+
+ def patch(%APIRequestContext{} = context, url, options) do
+ fetch(context, url, Map.merge(options, %{method: "PATCH"}))
+ end
+
+ @doc """
+ Sends an HTTP(S) [`POST`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST)
+ request and returns its response.
+
+ Function invocation will populate request cookies from the context, and update
+ context cookies from the response. Calls automatically follow redirects.
+
+ ## Usage
+
+ request = Playwright.request(session)
+ context = APIRequest.new_context(request)
+
+ APIRequest.post(context, "https://example.com/api/books", %{
+ data: %{title: "Updated"}
+ })
+
+ ## Arguments
+
+ | name | | description |
+ | ---------------- | ---------- | --------------------------------- |
+ | `context` | | The "subject" `APIRequestContext` |
+ | `url` | | Target URL |
+ | `options` | (optional) | `APIRequestContext.options()` |
+
+ ## Options
+
+ See "Shared options" above.
+
+ ## Returns
+
+ - `Playwright.APIResponse.t()`
+ - `{:error, Error.t()}`
+ """
+ @pipe {:post, [:context, :url]}
+ @pipe {:post, [:context, :url, :options]}
+ @spec post(t(), binary(), options()) :: t() | {:error, Error.t()}
+ def post(context, url, options \\ %{})
+
+ def post(%APIRequestContext{} = context, url, options) do
+ fetch(context, url, Map.merge(options, %{method: "POST"}))
+ end
+
+ @doc """
+ Sends an HTTP(S) [`PUT`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PUT)
+ request and returns its response.
+
+ Function invocation will populate request cookies from the context, and update
+ context cookies from the response. Calls automatically follow redirects.
+
+ ## Usage
+
+ request = Playwright.request(session)
+ context = APIRequest.new_context(request)
+
+ APIRequest.put(context, "https://example.com/api/books", %{
+ data: %{title: "Updated"}
+ })
+
+ ## Arguments
+
+ | name | | description |
+ | ---------------- | ---------- | --------------------------------- |
+ | `context` | | The "subject" `APIRequestContext` |
+ | `url` | | Target URL |
+ | `options` | (optional) | `APIRequestContext.options()` |
+
+ ## Options
+
+ See "Shared options" above.
+
+ ## Returns
+
+ - `Playwright.APIResponse.t()`
+ - `{:error, Error.t()}`
+ """
+ @pipe {:put, [:context, :url]}
+ @pipe {:put, [:context, :url, :options]}
+ @spec put(t(), binary(), options()) :: t() | {:error, Error.t()}
+ def put(context, url, options \\ %{})
+
+ def put(%APIRequestContext{} = context, url, options) do
+ fetch(context, url, Map.merge(options, %{method: "PUT"}))
+ end
+
+ @doc """
+ Returns storage state for this request context.
+
+ The storage state contains current cookies and a local storage snapshot if it
+ was passed to the initializer.
+
+ ## Arguments
+
+ | name | | description |
+ | ---------------- | ---------- | --------------------------------- |
+ | `context` | | The "subject" `APIRequestContext` |
+ | `options` | (optional) | Options (see below) |
+
+ ## Options
+
+ | name | | description |
+ | -------- | ---------- | --------------------------------- |
+ | `path` | (optional) | The file path to save the storage state. If path is a relative path, then it is resolved relative to current working directory. If no path is provided, storage state is still returned, but won't be saved to the disk. |
+
+ ## Returns
+
+ - `storage_state()`
+ - `{:error, Error.t()}`
+ """
+ @pipe {:storage_state, [:context]}
+ @pipe {:storage_state, [:context, :options]}
+ @spec storage_state(t(), opts_storage()) :: storage_state() | {:error, Error.t()}
+ def storage_state(context, options \\ %{}) do
+ {path, options} = Map.pop(options, :path)
- # @spec storage_state(t(), options()) :: StorageState.t()
- # def storage_state(context, options \\ %{})
+ case Channel.post({context, :storage_state}, options) do
+ {:error, _} = error ->
+ error
- # TODO: move to `APIResponse.body`, probably.
- @spec body(t(), Playwright.APIResponse.t()) :: any()
- def body(%APIRequestContext{session: session} = context, response) do
- Channel.post(session, {:guid, context.guid}, :fetch_response_body, %{
- fetchUid: response.fetchUid
- })
+ result ->
+ result = Map.new(result)
+ path && File.write!(path, Jason.encode!(result))
+ result
+ end
end
end
diff --git a/lib/playwright/api_response.ex b/lib/playwright/api_response.ex
index aa8518e1..a1ef62fd 100644
--- a/lib/playwright/api_response.ex
+++ b/lib/playwright/api_response.ex
@@ -1,46 +1,233 @@
defmodule Playwright.APIResponse do
- @moduledoc false
- use Playwright.SDK.ChannelOwner
+ @moduledoc """
+ `Playwright.APIResponse` represents responses returned by
+ `Playwrigh.APIRequestContext.fetch/3` and similar.
+
+ ## Usage
+
+ {:ok, session, _} = Playwright.launch()
+ request = Playwright.request(session)
+ context = APIRequest.new_context(request)
+
+ response = APIRequest.get(context, "https://example.com")
+ json = APIResponse.json!(response)
+ """
+
+ use Playwright.SDK.Pipeline
+ alias Playwright.APIRequestContext
alias Playwright.APIResponse
+ alias Playwright.API.Error
+ alias Playwright.SDK.Channel
+
+ # structs & types
+ # ----------------------------------------------------------------------------
+
+ defstruct [:context, :fetchUid, :headers, :status, :statusText, :url]
+
+ @typedoc """
+ `#{String.replace_prefix(inspect(__MODULE__), "Elixir.", "")}`
+ """
+ @type t() :: %__MODULE__{
+ context: APIRequestContext,
+ fetchUid: String.t(),
+ headers: list(%{name: String.t(), value: String.t()}),
+ status: integer(),
+ statusText: String.t(),
+ url: String.t()
+ }
+
+ @typedoc "Data serializable as JSON."
+ @type serializable() :: list() | map()
+
+ # API
+ # ----------------------------------------------------------------------------
+
+ @doc """
+ Returns a `Playwright.APIResponse` hydrated from the provided `properties`.
+
+ ## Returns
+
+ - `Playwright.APIResponse`
+ """
+ @spec new(map()) :: t()
+ def new(properties) do
+ struct(__MODULE__, properties)
+ end
+
+ @doc """
+ Returns a buffer with the response body.
+
+ ## Usage
+
+ request = Playwright.request(session) |> APIRequest.new_context()
+ response = APIRequestContext.fetch("https://example.com")
+ APIResponse.body!(response) |> IO.puts()
+
+ ## Returns
+
+ - `binary()`
+ - `{:error, %Error{type: "ResponseError"}}`
+ """
+ @pipe {:body, [:response]}
+ @spec body(t()) :: binary() | {:error, Error.t()}
+ def body(%APIResponse{} = response) do
+ case Channel.post({response.context, :fetch_response_body}, %{fetch_uid: response.fetchUid}) do
+ {:error, %Error{}} = error ->
+ error
+
+ nil ->
+ {:error, Error.new(%{error: %{name: "ResponseError", message: "Response has been disposed"}}, nil)}
+
+ result ->
+ Base.decode64!(result)
+ end
+ end
+
+ @doc """
+ Disposes the body of the response. If not called, the body will stay in memory
+ until the context closes.
+
+ ## Returns
+
+ - `:ok`
+ - `{:error, %Error{}}`
+ """
+ @pipe {:dispose, [:response]}
+ @spec dispose(t()) :: :ok | {:error, Error.t()}
+ def dispose(%APIResponse{} = response) do
+ case Channel.post({response.context, "disposeAPIResponse"}, %{fetch_uid: response.fetchUid}) do
+ {:error, %Playwright.API.Error{} = error} ->
+ {:error, error}
+
+ _ ->
+ :ok
+ end
+ end
- @property :fetchUid
- @property :headers
- @property :status
- @property :status_text
- @property :url
+ @doc """
+ Returns the value of a header.
- # @spec body(t()) :: binary() # or, equivalent of `Buffer`
- # def body(response)
- # @spec dispose(t()) :: :ok
- # def dispose(response)
+ ## Usage
- # @spec headers(t()) :: map()
- # def headers(response)
+ request = Playwright.request(session) |> APIRequest.new_context()
+ response = APIRequestContext.fetch("https://example.com")
+ APIResponse.header(response, "content-type") |> IO.puts()
- # @spec headers(t()) :: map()
- # def headers(response)
+ ## Arguments
- # @spec headers_list(APIResponse.t()) :: [map()]
- # def headers_list(response)
+ | name | | description |
+ | ---------- | ---------- | ----------------------------- |
+ | `response` | | The "subject" `APIResponse` |
+ | `name` | | The name of the HTTP header |
- # @spec json(t()) :: binary() # "serializable"; so, maybe map()?
- # def json(response)
+ ## Returns
+ - `binary()`
+ - `nil`
+ """
+ @spec header(t(), atom() | String.t()) :: binary() | nil
+ def header(response, name)
+
+ def header(%APIResponse{} = response, name) when is_atom(name) do
+ header(response, Atom.to_string(name))
+ end
+
+ def header(%APIResponse{} = response, name) when is_binary(name) do
+ case Enum.find(response.headers, fn header -> header.name == name end) do
+ nil ->
+ nil
+
+ %{value: value} ->
+ value
+ end
+ end
+
+ @doc """
+ Returns a `map(name => value)` with all the response HTTP headers associated
+ with this response.
+
+ ## Usage
+
+ request = Playwright.request(session) |> APIRequest.new_context()
+ response = APIRequestContext.fetch("https://example.com")
+ APIResponse.headers(response) |> IO.inspect()
+
+ ## Returns
+
+ - `%{String.t() => String.t()}`
+ """
+ @spec headers(t()) :: %{String.t() => String.t()}
+ def headers(%APIResponse{} = response) do
+ # Map.new([{1, 2}, {3, 4}])
+ Enum.reduce(response.headers, %{}, fn %{name: name, value: value}, headers ->
+ Map.put(headers, name, value)
+ end)
+ end
+
+ @doc """
+ Returns a deserialized version of the JSON representation of response body.
+
+ ## Usage
+
+ request = Playwright.request(session) |> APIRequest.new_context()
+ response = APIRequestContext.fetch("https://example.com")
+ APIResponse.json!(response) |> IO.inspect()
+
+ ## Returns
+
+ - `serializable()`
+ - `{:error, %Error{name: "ResponseError"}}`
+ """
+ @pipe {:json, [:response]}
+ @spec json(t()) :: serializable() | {:error, Error.t()}
+ def json(%APIResponse{} = response) do
+ case body(response) do
+ {:error, %Error{}} = error ->
+ error
+
+ result ->
+ case Jason.decode(result) do
+ {:ok, decoded} ->
+ decoded
+
+ {:error, original} ->
+ Error.new(%{error: %{name: "ResponseError", message: "Failed to decode response into JSON", original: original}}, nil)
+ end
+ end
+ end
+
+ @doc """
+ Returns a boolean indicating whether the response was successful.
+
+ Success means the response status code is within the range of `200-299`.
+
+ ## Returns
+
+ - `boolean()`
+ """
@spec ok(t()) :: boolean()
def ok(%APIResponse{} = response) do
response.status === 0 || (response.status >= 200 && response.status <= 299)
end
- # @spec status(t()) :: number()
- # def status(response)
+ @doc """
+ Returns a text representation of response body.
+
+ ## Usage
- # @spec status_text(t()) :: binary()
- # def status_text(response)
+ request = Playwright.request(session) |> APIRequest.new_context()
+ response = APIRequestContext.fetch("https://example.com")
+ APIResponse.text!(response) |> IO.puts()
- # @spec text(t()) :: binary()
- # def text(response)
+ ## Returns
- # @spec url(t()) :: binary()
- # def url(response)
+ - `binary()`
+ - `{:error, %Error{name: "ResponseError"}}`
+ """
+ @pipe {:text, [:response]}
+ @spec text(t()) :: binary() | {:error, Error.t()}
+ def text(%APIResponse{} = response) do
+ body(response)
+ end
end
diff --git a/lib/playwright/artifact.ex b/lib/playwright/artifact.ex
new file mode 100644
index 00000000..486f3da5
--- /dev/null
+++ b/lib/playwright/artifact.ex
@@ -0,0 +1,4 @@
+defmodule Playwright.Artifact do
+ @moduledoc false
+ use Playwright.SDK.ChannelOwner
+end
diff --git a/lib/playwright/binding_call.ex b/lib/playwright/binding_call.ex
index bfd86dfc..44271826 100644
--- a/lib/playwright/binding_call.ex
+++ b/lib/playwright/binding_call.ex
@@ -2,7 +2,8 @@ defmodule Playwright.BindingCall do
@moduledoc false
use Playwright.SDK.ChannelOwner
alias Playwright.BindingCall
- alias Playwright.SDK.{Channel, Helpers}
+ alias Playwright.SDK.Channel
+ alias Playwright.SDK.Helpers.Serialization
@property :args
@property :frame
@@ -19,8 +20,9 @@ defmodule Playwright.BindingCall do
page: "TBD"
}
- result = func.(source, Helpers.Serialization.deserialize(binding_call.args))
- Channel.post(session, {:guid, binding_call.guid}, :resolve, %{result: Helpers.Serialization.serialize(result)})
+ Channel.post({binding_call, :resolve}, %{
+ result: Serialization.serialize(func.(source, Serialization.deserialize(binding_call.args)))
+ })
end)
end
end
diff --git a/lib/playwright/browser.ex b/lib/playwright/browser.ex
index e741ba7a..3333526e 100644
--- a/lib/playwright/browser.ex
+++ b/lib/playwright/browser.ex
@@ -8,8 +8,7 @@ defmodule Playwright.Browser do
An example of using a `Playwright.Browser` to create a `Playwright.Page`:
alias Playwright.{Browser, Page}
-
- {:ok, browser} = Playwright.launch(:chromium)
+ {:ok, session, browser} = Playwright.launch(:chromium)
page = Browser.new_page(browser)
Page.goto(page, "https://example.com")
@@ -19,10 +18,277 @@ defmodule Playwright.Browser do
- `:name`
- `:version`
+
+ ## Shared options
+
+ The follow options are applicable to both:
+
+ `Playwright.Browser.new_context/2`
+ `Playwright.Browser.new_page/2`
+
+ | name | description |
+ | ---------------------- | --------------------------------- |
+ | `:accept_downloads` | Whether to automatically download all the attachments. Defaults to `true` where all the downloads are accepted. |
+ | `:base_url` | See details below. |
+ | `:bypass_csp` | Toggles bypassing page's Content-Security-Policy. Defaults to `false`. |
+ | `:client_certificates` | See details below. |
+ | `:color_scheme` | Emulates `"prefers-colors-scheme"` media feature, supported values are `"light"`, `"dark"`, `"no-preference"`. See `Playwright.Page.emulate_media/2` for more details. Passing `null` resets emulation to system defaults. Defaults to `"light"`. |
+ | `:device_scale_factor` | Specifies a device scale factor (can be thought of as `dpr`). Defaults to `1`. Learn more about [emulating devices with device scale factor](https://playwright.dev/docs/emulation#devices). |
+ | `:extra_http_headers` | A `map` containing additional HTTP headers to be sent with every request. |
+ | `:force_colors` | Emulates `"forced-colors"` media feature, supported values are `"active"`, `"none"`. See `Playwright.Page.emulate_media/2` for more details. Passing `null` resets emulation to system defaults. Defaults to `"none"`. |
+ | `:geolocation` | See details below. |
+ | `:has_touch` | Specifies whether the viewport supports touch events. Defaults to `false`. Learn more about [mobile emulation](https://playwright.dev/docs/emulation#devices). |
+ | `:http_credentials` | See details below. |
+ | `:ignore_https_errors` | Whether to ignore HTTPS errors when sending network requests. Defaults to `false`. |
+ | `:locale` | Specifies the user locale. For example, `en-GB`, `de-DE`, etc. Locale will affect `navigator.language` value, `Accept-Language` request header value as well as number and date formatting rules. Defaults to the system default locale. Learn more about emulation in the [emulation guide](https://playwright.dev/docs/emulation#locale--timezone). |
+ | `:logger` | Logger sink for Playwright logging. |
+ | `:offline` | Whether to emulate network being offline. Defaults to `false`. Learn more about [network emulation](https://playwright.dev/docs/emulation#offline). |
+ | `:permissions` | A list of permissions to grant to all pages in this context. See `Playwright.BrowserContext.grant_permissions/3` for more details. Defaults to `none`. |
+ | `:proxy` | See details below. |
+ | `:record_har` | See details below. |
+ | `:record_video` | See details below. |
+ | `:reduced_motion` | Emulates `"prefers-reduced-motion"` media feature, supported values are `"reduce"`, `"no-preference"`. See `Playwright.Page.emulate_media/2` for more details. Passing `null` resets emulation to system defaults. Defaults to `"no-preference"`. |
+ | `:screen` | See details below. |
+ | `:service_workers` | See details below. |
+ | `:strict_selectors` | If set to `true`, enables strict selectors mode for this context. In the strict selectors mode all operations on selectors that imply single target DOM element will throw when more than one element matches the selector. This option does not affect any `Playwright.Locator` APIs (Locators are always strict). Defaults to `false`. See `Playwright.Locator` to learn more about the strict mode. |
+ | `:timezone_id` | Changes the timezone of the context. See [ICU's metaZones.txt](ICU's metaZones.txt) for a list of supported timezone IDs. Defaults to the system timezone. |
+ | `:user_agent` | Specific user agent to use in this context. |
+ | `:video_size` | See details below. |
+ | `:videos_path` | See details below. |
+ | `:viewport` | See details below. |
+
+
⋯
+
+ ### Option: `:base_url`
+
+ When using `Page.goto/3`, `Page.route/4`, `Page.wait_for_url/3`,
+ `Page.wait_for_request/3`, or `Page.wait_for_response/3`, the base URL is
+ taken into consideration using the [`URL()`](https://developer.mozilla.org/en-US/docs/Web/API/URL/URL)
+ constructor for building the corresponding URL. Unset by default.
+
+ #### Examples
+
+ - With `base_url: http://localhost:3000`, sending a request to `/bar.html`
+ results in `http://localhost:3000/bar.html`.
+ - With `base_url: http://localhost:3000/foo/`, sending a request to `/bar.html`
+ results in `http://localhost:3000/foo/bar.html`.
+ - With `base_url: http://localhost:3000/foo` (without the trailing slash),
+ navigating to `./bar.html` results in `http://localhost:3000/bar.html`.
+
+
⋯
+
+ ### Option: `:client_certificates`
+
+ A list of client certificates to be used. Each certificate instance must have
+ both `:cert_path` and `:key_path` or a single `:pfx_path` to load the client
+ certificate. Optionally, the `:passphrase` property should be provided if the
+ certficiate is encrypted. The `:origin` property should be provided with an
+ exact match to the request origin for which the certificate is valid.
+
+ TLS client authentication allows the server to request a client certificate
+ and verify it.
+
+ > #### NOTE {: .info}
+ >
+ > Using client certificates in combination with proxy servers is not supported.
+
+ > #### NOTE {: .info}
+ >
+ > When using WebKit on macOS, accessing `localhost` will not pick up client
+ > certificates. As a work-around: replace `localhost` with `local.playwright`.
+
+ #### Details
+
+ | name | | description |
+ | ------------- | ---------- | --------------------------------- |
+ | `:origin` | | Exact origin that the certificate is valid for. Origin includes https protocol, a hostname and optionally a port. |
+ | `:cert_path` | (optional) | Path to the file with the certificate in PEM format. |
+ | `:key_path` | (optional) | Path to the file with the private key in PEM format. |
+ | `:pfx_path` | (optional) | Path to the PFX or PKCS12 encoded private key and certificate chain. |
+ | `:passphrase` | (optional) | Passphrase for the private key (PEM or PFX). |
+
+ ### Option: `:geolocation`
+
+ Browser geolocation settings.
+
+ #### Details
+
+ | name | | description |
+ | ------------- | ---------- | --------------------------------- |
+ | `:latitude` | | Latitude between `-90` and `90`. |
+ | `:longitude` | | Longitude between `-180` and `180`. |
+ | `:accuracy` | (optional) | Non-negative accuracy value. Defaults to `0`. |
+
+
⋯
+
+ ### Option: `:http_credentials`
+
+ Credentials for [HTTP authentication](https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication).
+
+ This option only applies to the requests sent from corresponding a
+ `Playwright.APIRequestContext` and does not affect requests sent from the
+ `Browser`.
+
+ #### Details
+
+ | name | | description |
+ | ----------- | ---------- | ----------- |
+ | `:username` | | |
+ | `:password` | | |
+ | `:origin` | (optional) | Restrain sending http credentials on specific origin (`scheme://host:port`). |
+ | `:send` | (optional) | This option only applies to the requests sent from corresponding `APIRequestContext` and does not affect requests sent from the browser. `:always` - `Authorization` header with basic authentication credentials will be sent with the each API request. `:unauthorized`- the credentials are only sent when 401 (Unauthorized) response with `WWW-Authenticate` header is received. Defaults to `:unauthorized`. |
+
+
⋯
+
+ ### Option: `:proxy`
+
+ Network proxy settings.
+
+ #### Details
+
+ | name | | description |
+ | ----------- | ---------- | ----------- |
+ | `:server` | | Proxy to be used for all requests. HTTP and SOCKS proxies are supported, for example `http://myproxy.com:3128` or `socks5://myproxy.com:3128`. Short form `myproxy.com:3128` is considered an HTTP proxy. |
+ | `:bypass` | (optional) | Optional comma-separated domains to bypass proxy, for example `".com, chromium.org, .domain.com"`. |
+ | `:username` | (optional) | Optional username to use if HTTP proxy requires authentication. |
+ | `:password` | (optional) | Optional password to use if HTTP proxy requires authentication. |
+
+
⋯
+
+ ### Option: `:record_har`
+
+ Enables HAR recording for all pages into `:record_har.path` file. If not
+ specified, the HAR is not recorded. Be sure to await
+ `Playwright.BrowserContext.close/1` for the HAR to be saved.
+
+ #### Details
+
+ | name | | description |
+ | --------------- | ---------- | ----------- |
+ | `:omit_content` | (optional) | Optional setting to control whether to omit request content from the HAR. Defaults to `false`. **Deprecated**; use content policy instead. |
+ | `:content` | (optional) | Optional setting to control resource content management. If `omit` is specified, content is not persisted. If `attach` is specified, resources are persisted as separate files or entries in the ZIP archive. If `embed` is specified, content is stored inline the HAR file as per HAR specification. Defaults to `attach` for `.zip` output files and to `embed` for all other file extensions. |
+ | `:path` | | Path on the filesystem to write the HAR file. If the file name ends with `.zip`, `content: 'attach'` is used by default. |
+ | `:mode` | (optional) | When set to `minimal`, only record information necessary for routing from HAR. This omits sizes, timing, page, cookies, security and other types of HAR information that are not used when replaying from HAR. Defaults to `full`. |
+ | `:url_filter` | (optional) | A glob or regex pattern to filter requests that are stored in the HAR. When a `:base_url` via the context options was provided and the passed URL is a path, it gets merged via the `new URL()` constructor. Defaults to `none`. |
+
+
⋯
+
+ ### Option: `:record_video`
+
+ Enables video recording for all pages into `:record_video.dir` directory. If
+ not specified videos are not recorded. Be sure to await
+ `Playwright.BrowserContext.close/1` for videos to be saved.
+
+ #### Details
+
+ | name | | description |
+ | --------------- | ---------- | ----------- |
+ | `:dir` | | Path to the directory for saving videos. |
+ | `:size` | (optional) | Video frame with and height: `%{width: number(), height: number()}`. |
+ | `:path` | | Path on the filesystem to write the HAR file. If the file name ends with `.zip`, `content: 'attach'` is used by default. |
+ | `:mode` | (optional) | When set to `minimal`, only record information necessary for routing from HAR. This omits sizes, timing, page, cookies, security and other types of HAR information that are not used when replaying from HAR. Defaults to `full`. |
+ | `:url_filter` | (optional) | Optional dimensions of the recorded videos. If not specified the size will be equal to `viewport` scaled down to fit into 800x800. If `viewport` is not configured explicitly the video size defaults to 800x450. Actual picture of each page will be scaled down if necessary to fit the specified size. |
+
+ - `:size`:
+ - `:width` - `number()`: Video frame width.
+ - `:height` - `number()`: Video frame height.
+
+
⋯
+
+ ### Option: `:screen`
+
+ Emulates consistent window screen size available inside web page via
+ `window.screen`. Is only used when the `viewport` is set.
+
+ #### Details
+
+ | name | | description |
+ | --------------- | ---------- | ----------- |
+ | `:width` | | Page width in pixels. |
+ | `:height` | | Page height in pixels. |
+
+
⋯
+
+ ### Option: `:service_workers`
+
+ Whether to allow sites to register Service workers. Defaults to `"allow"`.
+
+ #### Details
+
+ - `"allow"`: [Service Workers](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API) can be registered.
+ - `"block"`: Playwright will block all registration of Service Workers.
+
+
⋯
+
+ ### Option: `:storage_state`
+
+ Populates context with given storage state.
+
+ This option can be used to initialize context with logged-in information
+ obtained via, either, a path to the file with saved storage, or the value
+ returned by `BrowserContext.storage_state/2`.
+
+ Learn more about [storage state and auth](https://playwright.dev/docs/auth).
+
+ | name | | description |
+ | ----------- | ---------- | ----------- |
+ | `:cookies` | | `[Browser.cookie()]` |
+ | `:origins` | | `[Browser.origin()]` |
+
+