defmodule Playwright.BrowserType do @moduledoc """ `Playwright.BrowserType` provides functions to launch a specific browser instance or connect to an existing one. The following is a typical example of using Playwright to drive automation: alias Playwright.{Browser, BrowserType, Page} {session, browser} = BrowserType.launch(:chromium) page = Browser.new_page(browser) Page.goto(page, "https://example.com") # other actions... Browser.close(browser) ## Example Open a new chromium via the CLI driver: {session, browser} = Playwright.BrowserType.launch() Connect to a running playwright instance: {session, browser} = Playwright.BrowserType.connect("ws://localhost:3000/playwright") """ use Playwright.ChannelOwner alias Playwright.{BrowserType, Config, Transport} alias Playwright.Channel.Session @typedoc "The web client type used for `launch/1` and `connect/2` functions." @type client :: :chromium | :firefox | :webkit @typedoc "Options for `connect/2`" @type connect_options :: map() @typedoc "A map/struct providing call options" @type options :: map() @typedoc "A string URL" @type url :: String.t() @typedoc "A websocket endpoint (URL)" @type ws_endpoint :: url() @doc """ Attaches Playwright to an existing browser instance over a websocket. ## Returns - `{session, %Playwright.Browser{}}` ## Arguments | key/name | type | | description | | ------------- | ------ | --------------------------- | ----------- | | `ws_endpoint` | param | `BrowserType.ws_endpoint()` | A browser websocket endpoint to connect to. | | `:headers` | option | `map()` | Additional HTTP headers to be sent with websocket connect request | | `:slow_mow` | option | `integer()` | Slows down Playwright operations by the specified amount of milliseconds. Useful so that you can see what is going on. `(default: 0)` | | `:logger` | option | | Logger sink for Playwright logging | | `:timeout` | option | `integer()` | Maximum time in milliseconds to wait for the connection to be established. Pass `0` to disable timeout. `(default: 30_000 (30 seconds))` | """ @spec connect(ws_endpoint(), connect_options()) :: {pid(), Playwright.Browser.t()} def connect(ws_endpoint, options \\ %{}) def connect(ws_endpoint, _options) do with {:ok, session} <- new_session(Transport.WebSocket, [ws_endpoint]), %{guid: guid} <- launched_browser(session), browser <- Channel.find(session, {:guid, guid}) do {session, browser} else {:error, error} -> {:error, {"Error connecting to #{inspect(ws_endpoint)}", error}} error -> {:error, {"Error connecting to #{inspect(ws_endpoint)}", error}} end end # --- # @spec connect_over_cdp(BrowserType.t(), url(), options()) :: Playwright.Browser.t() # def connect_over_cdp(browser_type, endpoint_url, options \\ %{}) # @spec executable_path(BrowserType.t()) :: String.t() # def executable_path(browser_type) # --- @doc """ Launches a new browser instance via the Playwright driver CLI. ## Example # Use `:ignore_default_args` option to filter out `--mute-audio` from # default arguments: browser = Playwright.launch(:chromium, %{ignore_default_args = ["--mute-audio"]}) ## Returns - `{session, %Playwright.Browser{}}` ## Arguments ... (many) ## NOTE > **Chromium-only** Playwright can also be used to control the Google Chrome > or Microsoft Edge browsers, but it works best with the version of Chromium > it is bundled with. There is no guarantee it will work with any other > version. Use `:executable_path` option with extreme caution. > > If Google Chrome (rather than Chromium) is preferred, a > [Chrome Canary](https://www.google.com/chrome/browser/canary.html) or > [Dev Channel](https://www.chromium.org/getting-involved/dev-channel) build > is suggested. > > Stock browsers like Google Chrome and Microsoft Edge are suitable for tests > that require proprietary media codecs for video playback. > See [this article](https://www.howtogeek.com/202825/what%E2%80%99s-the-difference-between-chromium-and-chrome/) > for other differences between Chromium and Chrome. > [This article](https://chromium.googlesource.com/chromium/src/+/lkgr/docs/chromium_browser_vs_google_chrome.md) > describes some differences for Linux users. """ @spec launch(client() | nil, any()) :: {pid(), Playwright.Browser.t()} def launch(client \\ nil, options \\ %{}) def launch(nil, options) do launch(:chromium, options) end def launch(client, options) when is_atom(client) and client in [:chromium] do {:ok, session} = new_session(Transport.Driver, options) {session, chromium(session)} end def launch(client, _options) when is_atom(client) and client in [:firefox, :webkit] do raise RuntimeError, message: "not yet implemented: #{inspect(client)}" end def launch(options, _) do launch(nil, options) end # --- # @spec launch_persistent_context(BrowserType.t(), String.t(), options()) :: Playwright.BrowserContext.t() # def launch_persistent_context(browser_type, user_data_dir, options \\ %{}) # @spec launch_server(BrowserType.t(), options()) :: Playwright.BrowserServer.t() # def launch_server(browser_type, options \\ %{}) # @spec name(BrowserType.t()) :: client() # def name(browser_type) # --- # private # ---------------------------------------------------------------------------- defp browser(%BrowserType{} = browser_type) do Channel.post(browser_type.session, {:guid, browser_type.guid}, :launch, Config.launch_options()) end defp chromium(session) do case Channel.find(session, {:guid, "Playwright"}) do %Playwright{} = playwright -> %{guid: guid} = playwright.chromium Channel.find(session, {:guid, guid}) |> browser() other -> raise("expected chromium to return a `Playwright`, received: #{inspect(other)}") end end defp new_session(transport, args) do DynamicSupervisor.start_child( Session.Supervisor, {Session, {transport, args}} ) end defp launched_browser(session) do playwright = Channel.find(session, {:guid, "Playwright"}) playwright.initializer.preLaunchedBrowser end end