From e80863204201a2806df578cdd5265d43ac1ea305 Mon Sep 17 00:00:00 2001 From: Corey Innis Date: Tue, 4 Jun 2024 20:23:51 -0700 Subject: [PATCH 01/50] API: `BrowserContext.add_cookies/2` and `clear_cookies/1` return `self()` Co-authored-by: Corey Innis --- lib/playwright/browser_context.ex | 19 +++++++++++-------- lib/playwright/sdk/channel_owner.ex | 2 +- test/api/browser_context/add_cookies_test.exs | 6 ++++++ .../browser_context/clear_cookies_test.exs | 5 +++++ 4 files changed, 23 insertions(+), 9 deletions(-) diff --git a/lib/playwright/browser_context.ex b/lib/playwright/browser_context.ex index 4324ef5b..b1e2bdb6 100644 --- a/lib/playwright/browser_context.ex +++ b/lib/playwright/browser_context.ex @@ -168,6 +168,9 @@ defmodule Playwright.BrowserContext do @property :owner_page @property :routes + @typedoc "An explicit shorthand for the BrowserContext.t() subject." + @type self :: t() + @typedoc "Recognized cookie fields" @type cookie :: %{ name: String.t(), @@ -229,11 +232,11 @@ defmodule Playwright.BrowserContext do ## Returns - - `:ok` + - `self()` ## Example - :ok = BrowserContext.add_cookies(context, [cookie_1, cookie_2]) + context = BrowserContext.add_cookies(context, [cookie_1, cookie_2]) ## Cookie fields @@ -249,11 +252,11 @@ defmodule Playwright.BrowserContext do | `:secure` | `boolean()` | *(optional)* | | `:sameSite` | `binary()` | *(optional)* one of "Strict", "Lax", "None" | """ - @spec add_cookies(t(), [cookie]) :: :ok + @spec add_cookies(t(), [cookie]) :: self() def add_cookies(context, cookies) - def add_cookies(%BrowserContext{session: session} = context, cookies) do - Channel.post(session, {:guid, context.guid}, :add_cookies, %{cookies: cookies}) + def add_cookies(%BrowserContext{} = context, cookies) do + post!(context, :add_cookies, %{cookies: cookies}) end @doc """ @@ -329,9 +332,9 @@ defmodule Playwright.BrowserContext do @doc """ Clears `Playwright.BrowserContext` cookies. """ - @spec clear_cookies(t()) :: :ok - def clear_cookies(%BrowserContext{session: session} = context) do - Channel.post(session, {:guid, context.guid}, :clear_cookies) + @spec clear_cookies(t()) :: self() + def clear_cookies(%BrowserContext{} = context) do + post!(context, :clear_cookies) end @spec clear_permissions(t()) :: :ok diff --git a/lib/playwright/sdk/channel_owner.ex b/lib/playwright/sdk/channel_owner.ex index 924da9a3..8735249f 100644 --- a/lib/playwright/sdk/channel_owner.ex +++ b/lib/playwright/sdk/channel_owner.ex @@ -97,7 +97,7 @@ defmodule Playwright.SDK.ChannelOwner do {:ok, event.target} end - defp post!(owner, action, params) do + defp post!(owner, action, params \\ %{}) do case Channel.post(owner.session, {:guid, owner.guid}, action, params) do # simple "success": send "self" {:ok, %{id: _}} -> diff --git a/test/api/browser_context/add_cookies_test.exs b/test/api/browser_context/add_cookies_test.exs index 437bba91..56c73b54 100644 --- a/test/api/browser_context/add_cookies_test.exs +++ b/test/api/browser_context/add_cookies_test.exs @@ -3,6 +3,12 @@ defmodule Playwright.BrowserContext.AddCookiesTest do alias Playwright.{BrowserContext, Page} describe "BrowserContext.add_cookies/2" do + test "returns 'self'", %{assets: assets, page: page} do + context = Page.owned_context(page) + cookies = [%{url: assets.empty, name: "password", value: "123456"}] + assert %BrowserContext{} = BrowserContext.add_cookies(context, cookies) + end + test "adds cookies, readable by Page", %{assets: assets, page: page} do context = Page.owned_context(page) page |> Page.goto(assets.empty) diff --git a/test/api/browser_context/clear_cookies_test.exs b/test/api/browser_context/clear_cookies_test.exs index f9ba94b3..fb1bff60 100644 --- a/test/api/browser_context/clear_cookies_test.exs +++ b/test/api/browser_context/clear_cookies_test.exs @@ -3,6 +3,11 @@ defmodule Playwright.BrowserContext.ClearCookiesTest do alias Playwright.{BrowserContext, Page} describe "BrowserContext.clear_cookies/1" do + test "returns 'self'", %{page: page} do + context = Page.owned_context(page) + assert %BrowserContext{} = BrowserContext.clear_cookies(context) + end + test "clears cookies for the context", %{assets: assets, page: page} do context = Page.owned_context(page) page |> Page.goto(assets.empty) From 694ee22538b6365ea476c6810fdafdd904a17908 Mon Sep 17 00:00:00 2001 From: Corey Innis Date: Tue, 4 Jun 2024 21:00:48 -0700 Subject: [PATCH 02/50] API: `BrowserContext.on/3` returns `subject()` This change also includes: - Renaming the `self()` type as `subject()`, to avoid confusion with Elixir's native `self()`. - Addition of `ChannelOwner.bind!/3`, a helper for calls to `Channel.bind/4`. - Some minor test tweaks for further clarity. Co-authored-by: Ryan Spore --- lib/playwright/browser_context.ex | 14 +++++++------- lib/playwright/page.ex | 3 +++ lib/playwright/sdk/channel_owner.ex | 6 ++++++ test/api/browser_context/network_test.exs | 11 +++++++++-- test/api/page_test.exs | 12 ++++++------ 5 files changed, 31 insertions(+), 15 deletions(-) diff --git a/lib/playwright/browser_context.ex b/lib/playwright/browser_context.ex index b1e2bdb6..edadab88 100644 --- a/lib/playwright/browser_context.ex +++ b/lib/playwright/browser_context.ex @@ -169,7 +169,7 @@ defmodule Playwright.BrowserContext do @property :routes @typedoc "An explicit shorthand for the BrowserContext.t() subject." - @type self :: t() + @type subject :: t() @typedoc "Recognized cookie fields" @type cookie :: %{ @@ -232,7 +232,7 @@ defmodule Playwright.BrowserContext do ## Returns - - `self()` + - `subject()` ## Example @@ -252,7 +252,7 @@ defmodule Playwright.BrowserContext do | `:secure` | `boolean()` | *(optional)* | | `:sameSite` | `binary()` | *(optional)* one of "Strict", "Lax", "None" | """ - @spec add_cookies(t(), [cookie]) :: self() + @spec add_cookies(t(), [cookie]) :: subject() def add_cookies(context, cookies) def add_cookies(%BrowserContext{} = context, cookies) do @@ -332,7 +332,7 @@ defmodule Playwright.BrowserContext do @doc """ Clears `Playwright.BrowserContext` cookies. """ - @spec clear_cookies(t()) :: self() + @spec clear_cookies(t()) :: subject() def clear_cookies(%BrowserContext{} = context) do post!(context, :clear_cookies) end @@ -519,9 +519,9 @@ defmodule Playwright.BrowserContext do @doc """ Register a (non-blocking) callback/handler for various types of events. """ - @spec on(t(), event(), function()) :: :ok - def on(%BrowserContext{session: session} = context, event, callback) do - Channel.bind(session, {:guid, context.guid}, event, callback) + @spec on(t(), event(), function()) :: subject() + def on(%BrowserContext{} = context, event, callback) do + bind!(context, event, callback) end @doc """ diff --git a/lib/playwright/page.ex b/lib/playwright/page.ex index 0d27158a..58b989c3 100644 --- a/lib/playwright/page.ex +++ b/lib/playwright/page.ex @@ -58,6 +58,9 @@ defmodule Playwright.Page do # @property :touchscreen # --- + @typedoc "An explicit shorthand for the Page.t() subject." + @type subject :: t() + @type dimensions :: map() @type expression :: binary() @type function_or_options :: fun() | options() | nil diff --git a/lib/playwright/sdk/channel_owner.ex b/lib/playwright/sdk/channel_owner.ex index 8735249f..95b19663 100644 --- a/lib/playwright/sdk/channel_owner.ex +++ b/lib/playwright/sdk/channel_owner.ex @@ -97,6 +97,12 @@ defmodule Playwright.SDK.ChannelOwner do {:ok, event.target} end + defp bind!(owner, event, callback) do + returning(owner, fn -> + Channel.bind(owner.session, {:guid, owner.guid}, event, callback) + end) + end + defp post!(owner, action, params \\ %{}) do case Channel.post(owner.session, {:guid, owner.guid}, action, params) do # simple "success": send "self" diff --git a/test/api/browser_context/network_test.exs b/test/api/browser_context/network_test.exs index b4f72abc..67156a02 100644 --- a/test/api/browser_context/network_test.exs +++ b/test/api/browser_context/network_test.exs @@ -2,16 +2,23 @@ defmodule Playwright.BrowserContext.NetworkTest do use Playwright.TestCase, async: true alias Playwright.{Browser, BrowserContext, Page} + describe "BrowserContext.on/3" do + test "returns 'self'", %{browser: browser} do + context = Browser.new_context(browser) + assert %BrowserContext{} = BrowserContext.on(context, :foo, fn -> nil end) + end + end + describe "BrowserContext network events" do @tag without: [:page] test "on :request", %{assets: assets, browser: browser} do - this = self() + test_pid = self() context = Browser.new_context(browser) page = BrowserContext.new_page(context) BrowserContext.on(context, "request", fn %{params: %{request: request}} -> - send(this, request.url) + send(test_pid, request.url) end) page |> Page.goto(assets.prefix <> "/empty.html") diff --git a/test/api/page_test.exs b/test/api/page_test.exs index d539f4dc..be73ee3a 100644 --- a/test/api/page_test.exs +++ b/test/api/page_test.exs @@ -80,11 +80,11 @@ defmodule Playwright.PageTest do @tag exclude: [:page] test "on :close (atom)", %{browser: browser} do page = Browser.new_page(browser) - this = self() + test_pid = self() guid = page.guid Page.on(page, :close, fn event -> - send(this, event) + send(test_pid, event) end) Page.close(page) @@ -94,12 +94,12 @@ defmodule Playwright.PageTest do @tag exclude: [:page] test "on 'close' (string)", %{browser: browser} do page = Browser.new_page(browser) - this = self() + test_pid = self() guid = page.guid Page.on(page, "close", fn event -> assert Page.is_closed(event.target) - send(this, event) + send(test_pid, event) end) Page.close(page) @@ -109,13 +109,13 @@ defmodule Playwright.PageTest do # NOTE: this is really about *any* `on` event handling @tag exclude: [:page] test "on 'close' of one Page does not affect another", %{browser: browser} do - this = self() + test_pid = self() %{guid: guid_one} = page_one = Browser.new_page(browser) %{guid: guid_two} = page_two = Browser.new_page(browser) Page.on(page_one, "close", fn %{target: target} -> - send(this, target.guid) + send(test_pid, target.guid) end) Page.close(page_one) From 80e4939ddbdc61efb726fe76948f90cde1d0c2b4 Mon Sep 17 00:00:00 2001 From: Corey Innis Date: Tue, 4 Jun 2024 21:14:00 -0700 Subject: [PATCH 03/50] API: `BrowserContext.grant_permissions/3` returns `subject()` Co-authored-by: Corey Innis --- lib/playwright/browser_context.ex | 6 +++--- lib/playwright/sdk/channel_owner.ex | 9 +++++++++ test/api/browser_context/permissions_test.exs | 5 +++++ 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/lib/playwright/browser_context.ex b/lib/playwright/browser_context.ex index edadab88..d4886544 100644 --- a/lib/playwright/browser_context.ex +++ b/lib/playwright/browser_context.ex @@ -479,10 +479,10 @@ defmodule Playwright.BrowserContext do end) end - @spec grant_permissions(t(), [String.t()], options()) :: :ok | {:error, Channel.Error.t()} - def grant_permissions(%BrowserContext{session: session} = context, permissions, options \\ %{}) do + @spec grant_permissions(t(), [String.t()], options()) :: subject() | {:error, Channel.Error.t()} + def grant_permissions(%BrowserContext{} = context, permissions, options \\ %{}) do params = Map.merge(%{permissions: permissions}, options) - Channel.post(session, {:guid, context.guid}, :grant_permissions, params) + post!(context, :grant_permissions, params) end @spec new_cdp_session(t(), Frame.t() | Page.t()) :: Playwright.CDPSession.t() diff --git a/lib/playwright/sdk/channel_owner.ex b/lib/playwright/sdk/channel_owner.ex index 95b19663..8cc3eff4 100644 --- a/lib/playwright/sdk/channel_owner.ex +++ b/lib/playwright/sdk/channel_owner.ex @@ -108,9 +108,18 @@ defmodule Playwright.SDK.ChannelOwner do # simple "success": send "self" {:ok, %{id: _}} -> Channel.find(owner.session, {:guid, owner.guid}) + + # acceptable (API call) errors + {:error, %Channel.Error{} = error} -> + {:error, error} end end + defp returning(owner, task) do + task.() + Channel.find(owner.session, {:guid, owner.guid}) + end + defp returning(%{session: session} = subject, task) do task.() Channel.find(session, {:guid, subject.guid}) diff --git a/test/api/browser_context/permissions_test.exs b/test/api/browser_context/permissions_test.exs index e34c46a9..30871331 100644 --- a/test/api/browser_context/permissions_test.exs +++ b/test/api/browser_context/permissions_test.exs @@ -10,6 +10,11 @@ defmodule Playwright.BrowserContext.PermissionsTest do end describe "BrowserContext.grant_permissions/3" do + test "returns 'self'", %{assets: assets, browser: browser} do + context = Browser.new_context(browser) + assert %BrowserContext{} = BrowserContext.grant_permissions(context, [], %{origin: assets.empty}) + end + test "denies permission when not listed", %{assets: assets, page: page} do context = Page.context(page) page |> Page.goto(assets.empty) From ecad4712f34aa5eff3d7f9e836d95726f6b1ef82 Mon Sep 17 00:00:00 2001 From: Corey Innis Date: Tue, 4 Jun 2024 21:17:32 -0700 Subject: [PATCH 04/50] API: `APIRequestContext` prepare docs/specs Co-authored-by: Ryan Spore --- lib/playwright/api_request_context.ex | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/lib/playwright/api_request_context.ex b/lib/playwright/api_request_context.ex index 66692506..f2fb8b7b 100644 --- a/lib/playwright/api_request_context.ex +++ b/lib/playwright/api_request_context.ex @@ -1,7 +1,8 @@ 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. + 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. Use this at caution as has not been tested. @@ -10,23 +11,26 @@ defmodule Playwright.APIRequestContext do use Playwright.SDK.ChannelOwner alias Playwright.APIRequestContext + @typedoc "An explicit shorthand for the APIRequestContext.t() subject." + @type subject :: t() + @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(:post_data) => any(), + optional(:json_data) => any(), + optional(:form_data) => any(), + optional(:multipart_data) => any(), optional(:timeout) => non_neg_integer(), - optional(:failOnStatusCode) => boolean(), - optional(:ignoreHTTPSErrors) => boolean() + optional(:fail_on_status_code) => boolean(), + optional(:ignore_HTTPS_errors) => boolean() } # @spec delete(t(), binary(), options()) :: APIResponse.t() # def delete(context, url, options \\ %{}) - # @spec dispose(t()) :: :ok + # @spec dispose(t()) :: subject() # def dispose(api_request_context) # @spec fetch(t(), binary() | Request.t(), options()) :: APIResponse.t() From e6c556b2c168441273f23895c316acf815438176 Mon Sep 17 00:00:00 2001 From: Corey Innis Date: Tue, 4 Jun 2024 21:18:48 -0700 Subject: [PATCH 05/50] API: `BrowserContext.clear_permissions/1` returns `subject()` Co-authored-by: Corey Innis --- lib/playwright/browser_context.ex | 6 +++--- test/api/browser_context/permissions_test.exs | 5 +++++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/playwright/browser_context.ex b/lib/playwright/browser_context.ex index d4886544..7b580275 100644 --- a/lib/playwright/browser_context.ex +++ b/lib/playwright/browser_context.ex @@ -337,9 +337,9 @@ defmodule Playwright.BrowserContext do post!(context, :clear_cookies) end - @spec clear_permissions(t()) :: :ok - def clear_permissions(%BrowserContext{session: session} = context) do - Channel.post(session, {:guid, context.guid}, :clear_permissions) + @spec clear_permissions(t()) :: subject() + def clear_permissions(%BrowserContext{} = context) do + post!(context, :clear_permissions) end @doc """ diff --git a/test/api/browser_context/permissions_test.exs b/test/api/browser_context/permissions_test.exs index 30871331..888ccd0a 100644 --- a/test/api/browser_context/permissions_test.exs +++ b/test/api/browser_context/permissions_test.exs @@ -89,6 +89,11 @@ defmodule Playwright.BrowserContext.PermissionsTest do end describe "BrowserContext.clear_permissions/1" do + test "returns 'self'", %{browser: browser} do + context = Browser.new_context(browser) + assert %BrowserContext{} = BrowserContext.clear_permissions(context) + end + test "clears previously granted permissions", %{assets: assets, page: page} do context = Page.context(page) page |> Page.goto(assets.empty) From 7e0008bc4ccd111a4399fe46387548a8b620926e Mon Sep 17 00:00:00 2001 From: Corey Innis Date: Tue, 4 Jun 2024 21:41:03 -0700 Subject: [PATCH 06/50] API: `CDPSession.*` return `subject()` Co-authored-by: Ryan Spore --- lib/playwright/cdp_session.ex | 15 ++++++++++++--- lib/playwright/sdk/channel_owner.ex | 4 ++++ test/api/browser_context/add_cookies_test.exs | 2 +- test/api/browser_context/clear_cookies_test.exs | 2 +- test/api/browser_context/network_test.exs | 2 +- test/api/browser_context/permissions_test.exs | 4 ++-- test/api/chromium/cdp_session_test.exs | 12 ++++++++++++ test/api/click_test.exs | 10 ++++++++++ 8 files changed, 43 insertions(+), 8 deletions(-) diff --git a/lib/playwright/cdp_session.ex b/lib/playwright/cdp_session.ex index 2ef20ae1..9904fa5d 100644 --- a/lib/playwright/cdp_session.ex +++ b/lib/playwright/cdp_session.ex @@ -6,6 +6,9 @@ defmodule Playwright.CDPSession do @property :bindings + @typedoc "An explicit shorthand for the CDPSession.t() subject." + @type subject :: t() + @typedoc "Supported events" @type event :: binary() @@ -27,15 +30,21 @@ defmodule Playwright.CDPSession do # API # --------------------------------------------------------------------------- - @spec detach(t()) :: :ok | {:error, term()} + @spec detach(t()) :: subject() | {:error, term()} def detach(%CDPSession{session: session} = cdp_session) do - Channel.post(session, {:guid, cdp_session.guid}, :detach) + case Channel.post(session, {:guid, cdp_session.guid}, :detach) do + {:ok, _} -> + cdp_session + + {:error, %Channel.Error{} = error} -> + {:error, error} + end end @doc """ Register a (non-blocking) callback/handler for various types of events. """ - @spec on(t(), event(), function()) :: CDPSession.t() + @spec on(t(), event(), function()) :: subject() def on(%CDPSession{bindings: bindings, session: session} = cdp_session, event, callback) do scoped = Map.get(bindings, event, []) bindings = Map.put(bindings, event, [callback | scoped]) diff --git a/lib/playwright/sdk/channel_owner.ex b/lib/playwright/sdk/channel_owner.ex index 8cc3eff4..f8116f5a 100644 --- a/lib/playwright/sdk/channel_owner.ex +++ b/lib/playwright/sdk/channel_owner.ex @@ -103,6 +103,10 @@ defmodule Playwright.SDK.ChannelOwner do end) end + # NOTE: the `Channel.find` herein is causing some slowdowns. + # e.g., for `CDPSession.detach/1` (which change is currently reverted). + # it *could* be that switching come calls to some sort of :cast, instead + # of :call, would solve problems. defp post!(owner, action, params \\ %{}) do case Channel.post(owner.session, {:guid, owner.guid}, action, params) do # simple "success": send "self" diff --git a/test/api/browser_context/add_cookies_test.exs b/test/api/browser_context/add_cookies_test.exs index 56c73b54..f50f8b7e 100644 --- a/test/api/browser_context/add_cookies_test.exs +++ b/test/api/browser_context/add_cookies_test.exs @@ -3,7 +3,7 @@ defmodule Playwright.BrowserContext.AddCookiesTest do alias Playwright.{BrowserContext, Page} describe "BrowserContext.add_cookies/2" do - test "returns 'self'", %{assets: assets, page: page} do + test "returns 'subject'", %{assets: assets, page: page} do context = Page.owned_context(page) cookies = [%{url: assets.empty, name: "password", value: "123456"}] assert %BrowserContext{} = BrowserContext.add_cookies(context, cookies) diff --git a/test/api/browser_context/clear_cookies_test.exs b/test/api/browser_context/clear_cookies_test.exs index fb1bff60..06d3c0d3 100644 --- a/test/api/browser_context/clear_cookies_test.exs +++ b/test/api/browser_context/clear_cookies_test.exs @@ -3,7 +3,7 @@ defmodule Playwright.BrowserContext.ClearCookiesTest do alias Playwright.{BrowserContext, Page} describe "BrowserContext.clear_cookies/1" do - test "returns 'self'", %{page: page} do + test "returns 'subject'", %{page: page} do context = Page.owned_context(page) assert %BrowserContext{} = BrowserContext.clear_cookies(context) end diff --git a/test/api/browser_context/network_test.exs b/test/api/browser_context/network_test.exs index 67156a02..75f3384a 100644 --- a/test/api/browser_context/network_test.exs +++ b/test/api/browser_context/network_test.exs @@ -3,7 +3,7 @@ defmodule Playwright.BrowserContext.NetworkTest do alias Playwright.{Browser, BrowserContext, Page} describe "BrowserContext.on/3" do - test "returns 'self'", %{browser: browser} do + test "returns 'subject'", %{browser: browser} do context = Browser.new_context(browser) assert %BrowserContext{} = BrowserContext.on(context, :foo, fn -> nil end) end diff --git a/test/api/browser_context/permissions_test.exs b/test/api/browser_context/permissions_test.exs index 888ccd0a..dd5869d4 100644 --- a/test/api/browser_context/permissions_test.exs +++ b/test/api/browser_context/permissions_test.exs @@ -10,7 +10,7 @@ defmodule Playwright.BrowserContext.PermissionsTest do end describe "BrowserContext.grant_permissions/3" do - test "returns 'self'", %{assets: assets, browser: browser} do + test "returns 'subject'", %{assets: assets, browser: browser} do context = Browser.new_context(browser) assert %BrowserContext{} = BrowserContext.grant_permissions(context, [], %{origin: assets.empty}) end @@ -89,7 +89,7 @@ defmodule Playwright.BrowserContext.PermissionsTest do end describe "BrowserContext.clear_permissions/1" do - test "returns 'self'", %{browser: browser} do + test "returns 'subject'", %{browser: browser} do context = Browser.new_context(browser) assert %BrowserContext{} = BrowserContext.clear_permissions(context) end diff --git a/test/api/chromium/cdp_session_test.exs b/test/api/chromium/cdp_session_test.exs index aadf979d..3601e6c5 100644 --- a/test/api/chromium/cdp_session_test.exs +++ b/test/api/chromium/cdp_session_test.exs @@ -33,6 +33,12 @@ defmodule Playwright.Chromium.CDPSessionTest do end describe "CDPSession.on/3" do + test "returns 'subject'", %{page: page} do + context = Page.owned_context(page) + session = BrowserContext.new_cdp_session(context, page) + assert %CDPSession{} = CDPSession.on(session, "event", fn -> nil end) + end + test "handling Runtime console events`", %{page: page} do pid = self() context = Page.context(page) @@ -71,6 +77,12 @@ defmodule Playwright.Chromium.CDPSessionTest do end describe "CDPSession.detach/1" do + test "returns 'subject'", %{page: page} do + context = Page.owned_context(page) + session = BrowserContext.new_cdp_session(context, page) + assert %CDPSession{} = CDPSession.detach(session) + end + test "detaches the session", %{page: page} do context = Page.context(page) session = BrowserContext.new_cdp_session(context, page) diff --git a/test/api/click_test.exs b/test/api/click_test.exs index cb3aea29..86d841ac 100644 --- a/test/api/click_test.exs +++ b/test/api/click_test.exs @@ -14,6 +14,11 @@ defmodule Playwright.ClickTest do end describe "Page.click/3" do + test "returns 'subject'", %{assets: assets, page: page} do + Page.goto(page, assets.prefix <> "/input/button.html") + assert %Page{} = Page.click(page, "button") + end + test "with a button", %{assets: assets, page: page} do Page.goto(page, assets.prefix <> "/input/button.html") Page.click(page, "button") @@ -22,6 +27,11 @@ defmodule Playwright.ClickTest do end describe "Page.dblclick/2, mimicking Python tests" do + test "returns 'subject'", %{assets: assets, page: page} do + Page.goto(page, assets.prefix <> "/input/button.html") + assert %Page{} = Page.dblclick(page, "button") + end + test "test_locators.py: `test_double_click_the_button`", %{assets: assets, page: page} do Page.goto(page, assets.prefix <> "/input/button.html") From 38cef8ef7730f8a8ec80f3378297a59932364065 Mon Sep 17 00:00:00 2001 From: Corey Innis Date: Tue, 4 Jun 2024 21:52:58 -0700 Subject: [PATCH 07/50] API: `Page.add_init_script/2` returns `subject()` Co-authored-by: Corey Innis --- lib/playwright/page.ex | 14 +++----------- test/api/add_init_script_test.exs | 16 ++++++++++------ 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/lib/playwright/page.ex b/lib/playwright/page.ex index 58b989c3..86fe3499 100644 --- a/lib/playwright/page.ex +++ b/lib/playwright/page.ex @@ -134,17 +134,9 @@ defmodule Playwright.Page do > `Playwright.BrowserContext.add_init_script/2` and > `Playwright.Page.add_init_script/2` is not defined. """ - @spec add_init_script(t(), binary() | map()) :: :ok - def add_init_script(%Page{session: session} = page, script) when is_binary(script) do - params = %{source: script} - - case Channel.post(session, {:guid, page.guid}, :add_init_script, params) do - {:ok, _} -> - :ok - - {:error, error} -> - {:error, error} - end + @spec add_init_script(t(), binary() | map()) :: subject() + def add_init_script(%Page{} = page, script) when is_binary(script) do + post!(page, :add_init_script, %{source: script}) end def add_init_script(%Page{} = page, %{path: path} = script) when is_map(script) do diff --git a/test/api/add_init_script_test.exs b/test/api/add_init_script_test.exs index ed8893f9..a2b7057c 100644 --- a/test/api/add_init_script_test.exs +++ b/test/api/add_init_script_test.exs @@ -3,8 +3,12 @@ defmodule Playwright.AddInitScriptTest do alias Playwright.{Browser, BrowserContext, Page} describe "Page.add_init_script/2" do + test "returns 'subject'", %{page: page} do + assert %Page{} = Page.add_init_script(page, "window.injected = 123") + end + test "evaluates before anything else on the page", %{page: page} do - :ok = Page.add_init_script(page, "window.injected = 123") + page = Page.add_init_script(page, "window.injected = 123") nil = Page.goto(page, "data:text/html,") assert Page.evaluate(page, "window.result") == 123 @@ -12,15 +16,15 @@ defmodule Playwright.AddInitScriptTest do test "providing `param: script` as a file path", %{page: page} do fixture = "test/support/fixtures/injectedfile.js" - :ok = Page.add_init_script(page, %{path: fixture}) + page = Page.add_init_script(page, %{path: fixture}) nil = Page.goto(page, "data:text/html,") assert Page.evaluate(page, "window.result") == 123 end test "support for multiple scripts", %{page: page} do - :ok = Page.add_init_script(page, "window.script1 = 'one'") - :ok = Page.add_init_script(page, "window.script2 = 'two'") + page = Page.add_init_script(page, "window.script1 = 'one'") + page = Page.add_init_script(page, "window.script2 = 'two'") nil = Page.goto(page, "data:text/html,

some content

") assert Page.evaluate(page, "window.script1") == "one" @@ -35,7 +39,7 @@ defmodule Playwright.AddInitScriptTest do page = BrowserContext.new_page(context) :ok = BrowserContext.add_init_script(context, "window.temp = 123") - :ok = Page.add_init_script(page, "window.injected = window.temp") + page = Page.add_init_script(page, "window.injected = window.temp") nil = Page.goto(page, "data:text/html,") assert Page.evaluate(page, "window.result") == 123 @@ -57,7 +61,7 @@ defmodule Playwright.AddInitScriptTest do context = Page.owned_context(page) :ok = BrowserContext.add_init_script(context, "window.temp = 123") - :ok = Page.add_init_script(page, "window.injected = window.temp") + page = Page.add_init_script(page, "window.injected = window.temp") nil = Page.goto(page, "data:text/html,") assert Page.evaluate(page, "window.result") == 123 From 30ef1c348c476fea12ae9ebc70322550ae3581d4 Mon Sep 17 00:00:00 2001 From: Corey Innis Date: Tue, 4 Jun 2024 21:57:36 -0700 Subject: [PATCH 08/50] API: `BrowserContext.add_init_script/2` returns `subject()` Co-authored-by: Ryan Spore --- lib/playwright/browser_context.ex | 14 +++----------- test/api/add_init_script_test.exs | 11 ++++++++--- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/lib/playwright/browser_context.ex b/lib/playwright/browser_context.ex index 7b580275..6b0b3802 100644 --- a/lib/playwright/browser_context.ex +++ b/lib/playwright/browser_context.ex @@ -302,17 +302,9 @@ defmodule Playwright.BrowserContext do > `Playwright.BrowserContext.add_init_script/2` and > `Playwright.Page.add_init_script/2` is not defined. """ - @spec add_init_script(t(), binary() | map()) :: :ok - def add_init_script(%BrowserContext{session: session} = context, script) when is_binary(script) do - params = %{source: script} - - case Channel.post(session, {:guid, context.guid}, :add_init_script, params) do - {:ok, _} -> - :ok - - {:error, error} -> - {:error, error} - end + @spec add_init_script(t(), binary() | map()) :: subject() + def add_init_script(%BrowserContext{} = context, script) when is_binary(script) do + post!(context, :add_init_script, %{source: script}) end def add_init_script(%BrowserContext{} = context, %{path: path} = script) when is_map(script) do diff --git a/test/api/add_init_script_test.exs b/test/api/add_init_script_test.exs index a2b7057c..f1012538 100644 --- a/test/api/add_init_script_test.exs +++ b/test/api/add_init_script_test.exs @@ -33,12 +33,17 @@ defmodule Playwright.AddInitScriptTest do end describe "BrowserContext.add_init_script/2" do + test "returns 'subject'", %{browser: browser} do + context = Browser.new_context(browser) + assert %BrowserContext{} = BrowserContext.add_init_script(context, "window.injected = 123") + end + @tag exclude: [:page] test "combined with `Page.add_init_script/2`", %{browser: browser} do context = Browser.new_context(browser) page = BrowserContext.new_page(context) - :ok = BrowserContext.add_init_script(context, "window.temp = 123") + BrowserContext.add_init_script(context, "window.temp = 123") page = Page.add_init_script(page, "window.injected = window.temp") nil = Page.goto(page, "data:text/html,") @@ -51,7 +56,7 @@ defmodule Playwright.AddInitScriptTest do fixture = "test/support/fixtures/injectedfile.js" page = BrowserContext.new_page(context) - :ok = BrowserContext.add_init_script(context, %{path: fixture}) + BrowserContext.add_init_script(context, %{path: fixture}) nil = Page.goto(page, "data:text/html,") assert Page.evaluate(page, "window.result") == 123 @@ -60,7 +65,7 @@ defmodule Playwright.AddInitScriptTest do test "adding to the BrowserContext for an already created Page", %{page: page} do context = Page.owned_context(page) - :ok = BrowserContext.add_init_script(context, "window.temp = 123") + BrowserContext.add_init_script(context, "window.temp = 123") page = Page.add_init_script(page, "window.injected = window.temp") nil = Page.goto(page, "data:text/html,") From 40848a3adea20466102618293ba9440541e14c25 Mon Sep 17 00:00:00 2001 From: Corey Innis Date: Tue, 4 Jun 2024 22:13:48 -0700 Subject: [PATCH 09/50] API: `BrowserContext...` functions return `subject()` Co-authored-by: Corey Innis --- lib/playwright/browser_context.ex | 10 +++++----- test/api/browser_context_test.exs | 11 +++++++++++ 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/lib/playwright/browser_context.ex b/lib/playwright/browser_context.ex index 6b0b3802..f5000e21 100644 --- a/lib/playwright/browser_context.ex +++ b/lib/playwright/browser_context.ex @@ -528,7 +528,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(), function(), map()) :: subject() def route(context, pattern, handler, options \\ %{}) def route(%BrowserContext{session: session} = context, pattern, handler, _options) do @@ -540,7 +540,7 @@ defmodule Playwright.BrowserContext do patterns = Helpers.RouteHandler.prepare(routes) Channel.patch(session, {:guid, context.guid}, %{routes: routes}) - Channel.post(session, {:guid, context.guid}, :set_network_interception_patterns, %{patterns: patterns}) + post!(context, :set_network_interception_patterns, %{patterns: patterns}) end) end @@ -576,9 +576,9 @@ defmodule Playwright.BrowserContext do # --- - @spec set_offline(t(), boolean()) :: :ok - def set_offline(%BrowserContext{session: session} = context, offline) do - Channel.post(session, {:guid, context.guid}, :set_offline, %{offline: offline}) + @spec set_offline(t(), boolean()) :: subject() + def set_offline(%BrowserContext{} = context, offline) do + post!(context, :set_offline, %{offline: offline}) end # --- diff --git a/test/api/browser_context_test.exs b/test/api/browser_context_test.exs index c383ab0a..217fbd53 100644 --- a/test/api/browser_context_test.exs +++ b/test/api/browser_context_test.exs @@ -125,6 +125,11 @@ defmodule Playwright.BrowserContextTest do end describe "BrowserContext.route/4" do + test "returns 'subject'", %{page: page} do + context = Page.context(page) + assert %BrowserContext{} = BrowserContext.route(context, "**/*", fn -> nil end) + end + test "intercepts requests w/ a glob-style matcher", %{assets: assets, page: page} do pid = self() context = Page.context(page) @@ -245,6 +250,12 @@ defmodule Playwright.BrowserContextTest do end describe "BrowserContext.set_offline/2" do + test "returns 'subject'", %{page: page} do + context = Page.context(page) + assert %BrowserContext{} = BrowserContext.set_offline(context, false) + assert %BrowserContext{} = BrowserContext.set_offline(context, true) + end + @tag without: [:page] test "using initial option", %{assets: assets, browser: browser} do context = Browser.new_context(browser, %{offline: true}) From 6c1e854958007f06e412a540bfe589c0856de5ac Mon Sep 17 00:00:00 2001 From: Corey Innis Date: Tue, 4 Jun 2024 22:30:10 -0700 Subject: [PATCH 10/50] API: convert various functions to return `subject()` Co-authored-by: Ryan Spore --- lib/playwright/element_handle.ex | 15 ++++++--------- lib/playwright/frame.ex | 21 +++++++++------------ lib/playwright/page.ex | 12 ++++++++---- test/api/click_test.exs | 7 ++++--- test/api/element_handle_test.exs | 13 ++++++++++--- 5 files changed, 37 insertions(+), 31 deletions(-) diff --git a/lib/playwright/element_handle.ex b/lib/playwright/element_handle.ex index dd01580d..9ab5f9a8 100644 --- a/lib/playwright/element_handle.ex +++ b/lib/playwright/element_handle.ex @@ -58,6 +58,9 @@ defmodule Playwright.ElementHandle do @property :preview + @typedoc "An explicit shorthand for the ElementHandle.t() subject." + @type subject :: t() + @typedoc "A map/struct providing call options" @type options :: map() @@ -139,15 +142,9 @@ defmodule Playwright.ElementHandle do this function raises a `TimeoutError`. Passing zero (`0`) for timeout disables this. """ - @spec click(t(), options()) :: :ok - def click(%ElementHandle{session: session} = handle, options \\ %{}) do - case Channel.post(session, {:guid, handle.guid}, :click, options) do - {:ok, _} -> - :ok - - {:error, error} -> - {:error, error} - end + @spec click(t(), options()) :: subject() + def click(%ElementHandle{} = handle, options \\ %{}) do + post!(handle, :click, options) end # --- diff --git a/lib/playwright/frame.ex b/lib/playwright/frame.ex index e635054c..c5de27be 100644 --- a/lib/playwright/frame.ex +++ b/lib/playwright/frame.ex @@ -21,6 +21,9 @@ defmodule Playwright.Frame do @property :load_states @property :url + @typedoc "An explicit shorthand for the Frame.t() subject." + @type subject :: t() + @type evaluation_argument :: any() @type expression :: binary() @type options :: map() @@ -102,10 +105,10 @@ defmodule Playwright.Frame do `option: timeout`, `/click/3` raises a `TimeoutError`. Passing zero for `option: timeout` disables this. """ - @spec click(t(), binary(), options()) :: :ok - def click(owner, selector, options \\ %{}) + @spec click(t(), binary(), options()) :: subject() + def click(frame, selector, options \\ %{}) - def click(%Frame{session: session} = frame, selector, options) do + def click(%Frame{} = frame, selector, options) do params = Map.merge( %{ @@ -116,13 +119,7 @@ defmodule Playwright.Frame do options ) - case Channel.post(session, {:guid, frame.guid}, :click, params) do - {:ok, _} -> - :ok - - {:error, error} -> - {:error, error} - end + post!(frame, :click, params) end # --- @@ -167,7 +164,7 @@ defmodule Playwright.Frame do ## Returns - - `:ok` + - `subject()` ## Arguments @@ -184,7 +181,7 @@ defmodule Playwright.Frame do | `:timeout` | option | `number()` | Maximum time in milliseconds. Pass `0` to disable timeout. The default value can be changed by using the `Playwright.BrowserContext.set_default_timeout/2` or `Playwright.Page.set_default_timeout/2` functions. `(default: 30 seconds)` | | `:trial` | option | `boolean()` | When set, this call only performs the actionability checks and skips the action. Useful to wait until the element is ready for the action without performing it. `(default: false)` | """ - @spec dblclick(Frame.t(), binary(), options()) :: :ok + @spec dblclick(Frame.t(), binary(), options()) :: subject() def dblclick(%Frame{session: session} = frame, selector, options \\ %{}) do params = Map.merge( diff --git a/lib/playwright/page.ex b/lib/playwright/page.ex index 86fe3499..49df74d5 100644 --- a/lib/playwright/page.ex +++ b/lib/playwright/page.ex @@ -159,9 +159,11 @@ defmodule Playwright.Page do # --- - @spec click(t(), binary(), options()) :: :ok + @spec click(t(), binary(), options()) :: subject() def click(%Page{} = page, selector, options \\ %{}) do - main_frame(page) |> Frame.click(selector, options) + returning(page, fn -> + main_frame(page) |> Frame.click(selector, options) + end) end @doc """ @@ -246,11 +248,13 @@ defmodule Playwright.Page do @doc """ A shortcut for the main frame's `Playwright.Frame.dblclick/3`. """ - @spec dblclick(t(), binary(), options()) :: :ok + @spec dblclick(t(), binary(), options()) :: subject() def dblclick(page, selector, options \\ %{}) def dblclick(%Page{} = page, selector, options) do - main_frame(page) |> Frame.dblclick(selector, options) + returning(page, fn -> + main_frame(page) |> Frame.dblclick(selector, options) + end) end @doc """ diff --git a/test/api/click_test.exs b/test/api/click_test.exs index 86d841ac..61915744 100644 --- a/test/api/click_test.exs +++ b/test/api/click_test.exs @@ -6,9 +6,10 @@ defmodule Playwright.ClickTest do test "with a button inside an iframe", %{assets: assets, page: page} do :ok = Page.set_content(page, "
spacer
") frame = attach_frame(page, "button-test", assets.prefix <> "/input/button.html") - %ElementHandle{} = button = Frame.query_selector(frame, "button") - assert ElementHandle.click(button) == :ok + Frame.query_selector(frame, "button") + |> ElementHandle.click() + assert Frame.evaluate(frame, "window.result") == "Clicked" end end @@ -45,7 +46,7 @@ defmodule Playwright.ClickTest do } """) - assert Page.dblclick(page, "button") == :ok + page = Page.dblclick(page, "button") assert Page.evaluate(page, "window['double']") == true assert Page.evaluate(page, "window['result']") == "Clicked" end diff --git a/test/api/element_handle_test.exs b/test/api/element_handle_test.exs index e8c46d73..9f1e7a02 100644 --- a/test/api/element_handle_test.exs +++ b/test/api/element_handle_test.exs @@ -21,11 +21,18 @@ defmodule Playwright.ElementHandleTest do end describe "ElementHandle.click/1" do - test "", %{assets: assets, page: page} do + test "returns 'subject'", %{assets: assets, page: page} do Page.goto(page, assets.prefix <> "/input/button.html") - button = Page.query_selector(page, "button") - assert ElementHandle.click(button) == :ok + assert %ElementHandle{} = ElementHandle.click(button) + end + + test "...", %{assets: assets, page: page} do + Page.goto(page, assets.prefix <> "/input/button.html") + + Page.query_selector(page, "button") + |> ElementHandle.click() + assert Page.evaluate(page, "function () { return window['result']; }") == "Clicked" end end From a399d334762f6fb84cadd351c963f008a8286513 Mon Sep 17 00:00:00 2001 From: Ryan Spore Date: Mon, 12 Aug 2024 13:57:02 -0700 Subject: [PATCH 11/50] Switch to referring to subject type as t() Co-authored-by: Corey Innis --- lib/playwright/api_request_context.ex | 5 +---- lib/playwright/browser_context.ex | 21 +++++++++------------ lib/playwright/cdp_session.ex | 7 ++----- lib/playwright/element_handle.ex | 5 +---- lib/playwright/frame.ex | 9 +++------ lib/playwright/page.ex | 9 +++------ 6 files changed, 19 insertions(+), 37 deletions(-) diff --git a/lib/playwright/api_request_context.ex b/lib/playwright/api_request_context.ex index f2fb8b7b..75f21728 100644 --- a/lib/playwright/api_request_context.ex +++ b/lib/playwright/api_request_context.ex @@ -11,9 +11,6 @@ defmodule Playwright.APIRequestContext do use Playwright.SDK.ChannelOwner alias Playwright.APIRequestContext - @typedoc "An explicit shorthand for the APIRequestContext.t() subject." - @type subject :: t() - @type fetch_options() :: %{ optional(:params) => any(), optional(:method) => binary(), @@ -30,7 +27,7 @@ defmodule Playwright.APIRequestContext do # @spec delete(t(), binary(), options()) :: APIResponse.t() # def delete(context, url, options \\ %{}) - # @spec dispose(t()) :: subject() + # @spec dispose(t()) :: t() # def dispose(api_request_context) # @spec fetch(t(), binary() | Request.t(), options()) :: APIResponse.t() diff --git a/lib/playwright/browser_context.ex b/lib/playwright/browser_context.ex index f5000e21..52697799 100644 --- a/lib/playwright/browser_context.ex +++ b/lib/playwright/browser_context.ex @@ -168,9 +168,6 @@ defmodule Playwright.BrowserContext do @property :owner_page @property :routes - @typedoc "An explicit shorthand for the BrowserContext.t() subject." - @type subject :: t() - @typedoc "Recognized cookie fields" @type cookie :: %{ name: String.t(), @@ -232,7 +229,7 @@ defmodule Playwright.BrowserContext do ## Returns - - `subject()` + - `t()` ## Example @@ -252,7 +249,7 @@ defmodule Playwright.BrowserContext do | `:secure` | `boolean()` | *(optional)* | | `:sameSite` | `binary()` | *(optional)* one of "Strict", "Lax", "None" | """ - @spec add_cookies(t(), [cookie]) :: subject() + @spec add_cookies(t(), [cookie]) :: t() def add_cookies(context, cookies) def add_cookies(%BrowserContext{} = context, cookies) do @@ -302,7 +299,7 @@ defmodule Playwright.BrowserContext do > `Playwright.BrowserContext.add_init_script/2` and > `Playwright.Page.add_init_script/2` is not defined. """ - @spec add_init_script(t(), binary() | map()) :: subject() + @spec add_init_script(t(), binary() | map()) :: t() def add_init_script(%BrowserContext{} = context, script) when is_binary(script) do post!(context, :add_init_script, %{source: script}) end @@ -324,12 +321,12 @@ defmodule Playwright.BrowserContext do @doc """ Clears `Playwright.BrowserContext` cookies. """ - @spec clear_cookies(t()) :: subject() + @spec clear_cookies(t()) :: t() def clear_cookies(%BrowserContext{} = context) do post!(context, :clear_cookies) end - @spec clear_permissions(t()) :: subject() + @spec clear_permissions(t()) :: t() def clear_permissions(%BrowserContext{} = context) do post!(context, :clear_permissions) end @@ -471,7 +468,7 @@ defmodule Playwright.BrowserContext do end) end - @spec grant_permissions(t(), [String.t()], options()) :: subject() | {:error, Channel.Error.t()} + @spec grant_permissions(t(), [String.t()], options()) :: t() | {:error, Channel.Error.t()} def grant_permissions(%BrowserContext{} = context, permissions, options \\ %{}) do params = Map.merge(%{permissions: permissions}, options) post!(context, :grant_permissions, params) @@ -511,7 +508,7 @@ defmodule Playwright.BrowserContext do @doc """ Register a (non-blocking) callback/handler for various types of events. """ - @spec on(t(), event(), function()) :: subject() + @spec on(t(), event(), function()) :: t() def on(%BrowserContext{} = context, event, callback) do bind!(context, event, callback) end @@ -528,7 +525,7 @@ defmodule Playwright.BrowserContext do Channel.list(context.session, {:guid, context.guid}, "Page") end - @spec route(t(), binary(), function(), map()) :: subject() + @spec route(t(), binary(), function(), map()) :: t() def route(context, pattern, handler, options \\ %{}) def route(%BrowserContext{session: session} = context, pattern, handler, _options) do @@ -576,7 +573,7 @@ defmodule Playwright.BrowserContext do # --- - @spec set_offline(t(), boolean()) :: subject() + @spec set_offline(t(), boolean()) :: t() def set_offline(%BrowserContext{} = context, offline) do post!(context, :set_offline, %{offline: offline}) end diff --git a/lib/playwright/cdp_session.ex b/lib/playwright/cdp_session.ex index 9904fa5d..ca2efc12 100644 --- a/lib/playwright/cdp_session.ex +++ b/lib/playwright/cdp_session.ex @@ -6,9 +6,6 @@ defmodule Playwright.CDPSession do @property :bindings - @typedoc "An explicit shorthand for the CDPSession.t() subject." - @type subject :: t() - @typedoc "Supported events" @type event :: binary() @@ -30,7 +27,7 @@ defmodule Playwright.CDPSession do # API # --------------------------------------------------------------------------- - @spec detach(t()) :: subject() | {:error, term()} + @spec detach(t()) :: t() | {:error, term()} def detach(%CDPSession{session: session} = cdp_session) do case Channel.post(session, {:guid, cdp_session.guid}, :detach) do {:ok, _} -> @@ -44,7 +41,7 @@ defmodule Playwright.CDPSession do @doc """ Register a (non-blocking) callback/handler for various types of events. """ - @spec on(t(), event(), function()) :: subject() + @spec on(t(), event(), function()) :: t() def on(%CDPSession{bindings: bindings, session: session} = cdp_session, event, callback) do scoped = Map.get(bindings, event, []) bindings = Map.put(bindings, event, [callback | scoped]) diff --git a/lib/playwright/element_handle.ex b/lib/playwright/element_handle.ex index 9ab5f9a8..3d55a7dc 100644 --- a/lib/playwright/element_handle.ex +++ b/lib/playwright/element_handle.ex @@ -58,9 +58,6 @@ defmodule Playwright.ElementHandle do @property :preview - @typedoc "An explicit shorthand for the ElementHandle.t() subject." - @type subject :: t() - @typedoc "A map/struct providing call options" @type options :: map() @@ -142,7 +139,7 @@ defmodule Playwright.ElementHandle do this function raises a `TimeoutError`. Passing zero (`0`) for timeout disables this. """ - @spec click(t(), options()) :: subject() + @spec click(t(), options()) :: t() def click(%ElementHandle{} = handle, options \\ %{}) do post!(handle, :click, options) end diff --git a/lib/playwright/frame.ex b/lib/playwright/frame.ex index c5de27be..c69a3301 100644 --- a/lib/playwright/frame.ex +++ b/lib/playwright/frame.ex @@ -21,9 +21,6 @@ defmodule Playwright.Frame do @property :load_states @property :url - @typedoc "An explicit shorthand for the Frame.t() subject." - @type subject :: t() - @type evaluation_argument :: any() @type expression :: binary() @type options :: map() @@ -105,7 +102,7 @@ defmodule Playwright.Frame do `option: timeout`, `/click/3` raises a `TimeoutError`. Passing zero for `option: timeout` disables this. """ - @spec click(t(), binary(), options()) :: subject() + @spec click(t(), binary(), options()) :: t() def click(frame, selector, options \\ %{}) def click(%Frame{} = frame, selector, options) do @@ -164,7 +161,7 @@ defmodule Playwright.Frame do ## Returns - - `subject()` + - `t()` ## Arguments @@ -181,7 +178,7 @@ defmodule Playwright.Frame do | `:timeout` | option | `number()` | Maximum time in milliseconds. Pass `0` to disable timeout. The default value can be changed by using the `Playwright.BrowserContext.set_default_timeout/2` or `Playwright.Page.set_default_timeout/2` functions. `(default: 30 seconds)` | | `:trial` | option | `boolean()` | When set, this call only performs the actionability checks and skips the action. Useful to wait until the element is ready for the action without performing it. `(default: false)` | """ - @spec dblclick(Frame.t(), binary(), options()) :: subject() + @spec dblclick(Frame.t(), binary(), options()) :: t() def dblclick(%Frame{session: session} = frame, selector, options \\ %{}) do params = Map.merge( diff --git a/lib/playwright/page.ex b/lib/playwright/page.ex index 49df74d5..49d127f6 100644 --- a/lib/playwright/page.ex +++ b/lib/playwright/page.ex @@ -58,9 +58,6 @@ defmodule Playwright.Page do # @property :touchscreen # --- - @typedoc "An explicit shorthand for the Page.t() subject." - @type subject :: t() - @type dimensions :: map() @type expression :: binary() @type function_or_options :: fun() | options() | nil @@ -134,7 +131,7 @@ defmodule Playwright.Page do > `Playwright.BrowserContext.add_init_script/2` and > `Playwright.Page.add_init_script/2` is not defined. """ - @spec add_init_script(t(), binary() | map()) :: subject() + @spec add_init_script(t(), binary() | map()) :: t() def add_init_script(%Page{} = page, script) when is_binary(script) do post!(page, :add_init_script, %{source: script}) end @@ -159,7 +156,7 @@ defmodule Playwright.Page do # --- - @spec click(t(), binary(), options()) :: subject() + @spec click(t(), binary(), options()) :: t() def click(%Page{} = page, selector, options \\ %{}) do returning(page, fn -> main_frame(page) |> Frame.click(selector, options) @@ -248,7 +245,7 @@ defmodule Playwright.Page do @doc """ A shortcut for the main frame's `Playwright.Frame.dblclick/3`. """ - @spec dblclick(t(), binary(), options()) :: subject() + @spec dblclick(t(), binary(), options()) :: t() def dblclick(page, selector, options \\ %{}) def dblclick(%Page{} = page, selector, options) do From 6ed3ba6fe54d09e7481ef23722b58fb09531ac4a Mon Sep 17 00:00:00 2001 From: Corey Innis Date: Mon, 12 Aug 2024 15:06:29 -0700 Subject: [PATCH 12/50] Split Error types into API and SDK Co-authored-by: Ryan Spore --- lib/playwright/{sdk/channel => api}/error.ex | 15 +++++------- lib/playwright/api/errors/web_error.ex | 0 lib/playwright/browser_context.ex | 2 +- lib/playwright/cdp_session.ex | 2 +- lib/playwright/frame.ex | 2 +- lib/playwright/locator.ex | 25 ++++++++++++-------- lib/playwright/response.ex | 2 +- lib/playwright/sdk/channel.ex | 4 ++-- lib/playwright/sdk/channel/catalog.ex | 3 +-- lib/playwright/sdk/channel/response.ex | 2 +- lib/playwright/sdk/channel_owner.ex | 2 +- lib/playwright/sdk/error.ex | 22 +++++++++++++++++ lib/playwright/sdk/helpers/error_handling.ex | 4 ++-- test/api/locator_test.exs | 6 ++--- test/api/navigation_test.exs | 2 +- test/api/page/expect_test.exs | 14 +++++------ test/api/page_test.exs | 8 +++---- test/sdk/channel/catalog_test.exs | 5 ++-- 18 files changed, 72 insertions(+), 48 deletions(-) rename lib/playwright/{sdk/channel => api}/error.ex (65%) delete mode 100644 lib/playwright/api/errors/web_error.ex create mode 100644 lib/playwright/sdk/error.ex diff --git a/lib/playwright/sdk/channel/error.ex b/lib/playwright/api/error.ex similarity index 65% rename from lib/playwright/sdk/channel/error.ex rename to lib/playwright/api/error.ex index ef368818..8f6bf72f 100644 --- a/lib/playwright/sdk/channel/error.ex +++ b/lib/playwright/api/error.ex @@ -1,8 +1,7 @@ -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] @@ -13,17 +12,15 @@ defmodule Playwright.SDK.Channel.Error do } 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", + def new(%{error: %{message: message} = error}, _catalog) do + 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/browser_context.ex b/lib/playwright/browser_context.ex index 52697799..48678a68 100644 --- a/lib/playwright/browser_context.ex +++ b/lib/playwright/browser_context.ex @@ -468,7 +468,7 @@ defmodule Playwright.BrowserContext do end) end - @spec grant_permissions(t(), [String.t()], options()) :: t() | {:error, Channel.Error.t()} + @spec grant_permissions(t(), [String.t()], options()) :: t() | {:error, Playwright.API.Error.t()} def grant_permissions(%BrowserContext{} = context, permissions, options \\ %{}) do params = Map.merge(%{permissions: permissions}, options) post!(context, :grant_permissions, params) diff --git a/lib/playwright/cdp_session.ex b/lib/playwright/cdp_session.ex index ca2efc12..6094d923 100644 --- a/lib/playwright/cdp_session.ex +++ b/lib/playwright/cdp_session.ex @@ -33,7 +33,7 @@ defmodule Playwright.CDPSession do {:ok, _} -> cdp_session - {:error, %Channel.Error{} = error} -> + {:error, %Playwright.API.Error{} = error} -> {:error, error} end end diff --git a/lib/playwright/frame.ex b/lib/playwright/frame.ex index c69a3301..2a0a52bd 100644 --- a/lib/playwright/frame.ex +++ b/lib/playwright/frame.ex @@ -927,7 +927,7 @@ defmodule Playwright.Frame do FIXME: the following is NOT TRUE... Returns `nil` if waiting for a hidden or detached element. """ - @spec wait_for_selector(t(), binary(), map()) :: ElementHandle.t() | {:error, Channel.Error.t()} + @spec wait_for_selector(t(), binary(), map()) :: ElementHandle.t() | {:error, Playwright.API.Error.t()} def wait_for_selector(%Frame{session: session} = frame, selector, options \\ %{}) do Channel.post(session, {:guid, frame.guid}, :wait_for_selector, Map.merge(%{selector: selector}, options)) end diff --git a/lib/playwright/locator.ex b/lib/playwright/locator.ex index bc7170e0..f534de4d 100644 --- a/lib/playwright/locator.ex +++ b/lib/playwright/locator.ex @@ -262,7 +262,7 @@ defmodule Playwright.Locator do If the element is detached from the DOM at any moment during the action, this method throws. When all steps combined have not finished during the specified timeout, this method throws a - `Playwright.SDK.Channel.Error.t()`. Passing `0` timeout disables this. + `Playwright.API.Error.t()`. Passing `0` timeout disables this. ## Returns @@ -282,10 +282,10 @@ defmodule Playwright.Locator do | `: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)` | | `:trial` | option | `boolean()` | When set, this call only performs the actionability checks and skips the action. Useful to wait until the element is ready for the action without performing it. `(default: false)` | """ - @spec click(t(), options_click()) :: :ok + @spec click(t(), options_click()) :: t() def click(%Locator{} = locator, options \\ %{}) do options = Map.merge(options, %{strict: true}) - Frame.click(locator.frame, locator.selector, options) + returning(locator, fn -> Frame.click(locator.frame, locator.selector, options) end) end # @spec content_frame(Locator.t()) :: FrameLocator.t() @@ -420,7 +420,7 @@ defmodule Playwright.Locator do ## Returns - `Playwright.ElementHandle.t()` - - `{:error, Playwright.SDK.Channel.Error.t()}` + - `{:error, Playwright.API.Error.t()}` ## Arguments @@ -429,7 +429,7 @@ defmodule Playwright.Locator do | `:timeout` | option | `number()` | Maximum time in milliseconds. Pass `0` to disable timeout. The default value can be changed by using the `Playwright.BrowserContext.set_default_timeout/2` or `Playwright.Page.set_default_timeout/2` functions. `(default: 30 seconds)` | """ @doc deprecated: "Discouraged: Prefer using Locators and web assertions over ElementHandles because latter are inherently racy." - @spec element_handle(t(), options()) :: ElementHandle.t() | {:error, Channel.Error.t()} + @spec element_handle(t(), options()) :: ElementHandle.t() | {:error, Playwright.API.Error.t()} def element_handle(%Locator{} = locator, options \\ %{}) do options = Map.merge(%{strict: true, state: "attached"}, options) @@ -527,7 +527,7 @@ defmodule Playwright.Locator do ## Returns - `Playwright.ElementHandle.t()` - - `{:error, Playwright.SDK.Channel.Error.t()}` + - `{:error, Playwright.API.Error.t()}` ## Arguments @@ -537,7 +537,7 @@ defmodule Playwright.Locator do | `arg` | param | `any()` | Argument to pass to `expression` `(optional)` | | `:timeout` | option | `number()` | Maximum time in milliseconds. Pass `0` to disable timeout. The default value can be changed by using the `Playwright.BrowserContext.set_default_timeout/2` or `Playwright.Page.set_default_timeout/2` functions. `(default: 30 seconds)` | """ - @spec evaluate_handle(t(), binary(), any(), options()) :: ElementHandle.t() | {:error, Channel.Error.t()} + @spec evaluate_handle(t(), binary(), any(), options()) :: ElementHandle.t() | {:error, Playwright.API.Error.t()} def evaluate_handle(locator, expression, arg \\ nil, options \\ %{}) # NOTE: need to do all of the map-like things before a plain `map()`, @@ -1322,7 +1322,7 @@ defmodule Playwright.Locator do # const orderSent = page.locator('#order-sent'); # await orderSent.waitFor(); - @spec wait_for(t(), options()) :: t() | {:error, Channel.Error.t()} + @spec wait_for(t(), options()) :: t() | {:error, Playwright.API.Error.t()} def wait_for(%Locator{} = locator, options \\ %{}) do case Frame.wait_for_selector(locator.frame, locator.selector, options) do {:error, _} = error -> @@ -1337,8 +1337,13 @@ defmodule Playwright.Locator do # --------------------------------------------------------------------------- defp returning(subject, task) do - task.() - subject + case task.() do + {:error, _} = error -> + error + + _ -> + subject + end end defp with_element(%Locator{frame: frame} = locator, options, task) do diff --git a/lib/playwright/response.ex b/lib/playwright/response.ex index 964a27c8..1a11c556 100644 --- a/lib/playwright/response.ex +++ b/lib/playwright/response.ex @@ -62,7 +62,7 @@ defmodule Playwright.Response do end @spec ok({t(), t()}) :: boolean() - def ok({:error, %Playwright.SDK.Channel.Error{}}) do + def ok({:error, %Playwright.SDK.Error{}}) do false end diff --git a/lib/playwright/sdk/channel.ex b/lib/playwright/sdk/channel.ex index 5c168a61..91b2b6b8 100644 --- a/lib/playwright/sdk/channel.ex +++ b/lib/playwright/sdk/channel.ex @@ -1,7 +1,7 @@ defmodule Playwright.SDK.Channel do @moduledoc false import Playwright.SDK.Helpers.ErrorHandling - alias Playwright.SDK.Channel.{Catalog, Connection, Error, Event, Message, Response, Session} + alias Playwright.SDK.Channel.{Catalog, Connection, Event, Message, Response, Session} # API # --------------------------------------------------------------------------- @@ -135,7 +135,7 @@ defmodule Playwright.SDK.Channel do item end - defp reply(%Error{} = error, from) do + defp reply(%Playwright.API.Error{} = error, from) do Task.start_link(fn -> GenServer.reply(from, {:error, error}) end) diff --git a/lib/playwright/sdk/channel/catalog.ex b/lib/playwright/sdk/channel/catalog.ex index 7b02432c..3c3cb19a 100644 --- a/lib/playwright/sdk/channel/catalog.ex +++ b/lib/playwright/sdk/channel/catalog.ex @@ -7,7 +7,6 @@ defmodule Playwright.SDK.Channel.Catalog do """ 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, Playwright.API.Error.t()} 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..9022436f 100644 --- a/lib/playwright/sdk/channel/response.ex +++ b/lib/playwright/sdk/channel/response.ex @@ -38,7 +38,7 @@ defmodule Playwright.SDK.Channel.Response do end def recv(_session, %{error: error, id: _}) do - Channel.Error.new(error, nil) + Playwright.API.Error.new(error, nil) end def recv(session, %{id: _} = message) do diff --git a/lib/playwright/sdk/channel_owner.ex b/lib/playwright/sdk/channel_owner.ex index f8116f5a..c3ef2cc3 100644 --- a/lib/playwright/sdk/channel_owner.ex +++ b/lib/playwright/sdk/channel_owner.ex @@ -114,7 +114,7 @@ defmodule Playwright.SDK.ChannelOwner do Channel.find(owner.session, {:guid, owner.guid}) # acceptable (API call) errors - {:error, %Channel.Error{} = error} -> + {:error, %Playwright.API.Error{} = error} -> {:error, error} end end diff --git a/lib/playwright/sdk/error.ex b/lib/playwright/sdk/error.ex new file mode 100644 index 00000000..e8675dee --- /dev/null +++ b/lib/playwright/sdk/error.ex @@ -0,0 +1,22 @@ +defmodule Playwright.SDK.Error do + @moduledoc false + # `Error` represents an error message received from the Playwright server + # that is in response to a `Message` previously sent. + + @enforce_keys [:type, :message] + defstruct [:type, :message] + + @type t() :: %__MODULE__{ + type: String.t(), + message: String.t() + } + + # TODO: determine why we get here... + # DONE: see comment at error_handling.ex:9. + def new(type, message) do + %__MODULE__{ + type: type, + message: message + } + end +end diff --git a/lib/playwright/sdk/helpers/error_handling.ex b/lib/playwright/sdk/helpers/error_handling.ex index b8300122..bb3d1fa9 100644 --- a/lib/playwright/sdk/helpers/error_handling.ex +++ b/lib/playwright/sdk/helpers/error_handling.ex @@ -1,6 +1,6 @@ defmodule Playwright.SDK.Helpers.ErrorHandling do @moduledoc false - alias Playwright.SDK.Channel.Error + alias Playwright.SDK.Error def with_timeout(options, action) when is_map(options) and is_function(action) do timeout = options |> Map.get(:timeout, 30_000) @@ -15,7 +15,7 @@ defmodule Playwright.SDK.Helpers.ErrorHandling do action.(timeout + 100) catch :exit, {:timeout, _} = _reason -> - {:error, Error.new(%{error: %{message: "Timeout #{inspect(timeout)}ms exceeded."}}, nil)} + {:error, Error.new("TimeoutError", "Timeout #{inspect(timeout)}ms exceeded waiting for reply from Playwright Server.")} end end end diff --git a/test/api/locator_test.exs b/test/api/locator_test.exs index a24e9a2a..480fd6ac 100644 --- a/test/api/locator_test.exs +++ b/test/api/locator_test.exs @@ -2,7 +2,7 @@ defmodule Playwright.LocatorTest do use Playwright.TestCase, async: true alias Playwright.{ElementHandle, Locator, Page} - alias Playwright.SDK.Channel.Error + alias Playwright.API.Error describe "Locator.all/1" do test "returns a list of Locators, addressing each respective element", %{page: page} do @@ -112,11 +112,11 @@ defmodule Playwright.LocatorTest do [options: options] end - test "returns :ok on a successful click", %{options: options, page: page} do + test "returns 'subject' on a successful click", %{options: options, page: page} do frame = Page.main_frame(page) locator = Locator.new(frame, "a#link") - assert :ok = Locator.click(locator, options) + assert ^locator = Locator.click(locator, options) end test "returns a timeout error when unable to click", %{options: options, page: page} do diff --git a/test/api/navigation_test.exs b/test/api/navigation_test.exs index c157fb3d..96b7c668 100644 --- a/test/api/navigation_test.exs +++ b/test/api/navigation_test.exs @@ -1,7 +1,7 @@ defmodule Playwright.NavigationTest do use Playwright.TestCase, async: true alias Playwright.{Page, Response} - alias Playwright.SDK.Channel.Error + alias Playwright.API.Error describe "Page.goto/2" do test "works (and updates the page's URL)", %{assets: assets, page: page} do diff --git a/test/api/page/expect_test.exs b/test/api/page/expect_test.exs index c8a3405b..8337f9b6 100644 --- a/test/api/page/expect_test.exs +++ b/test/api/page/expect_test.exs @@ -1,7 +1,7 @@ defmodule Playwright.Page.NetworkTest do use Playwright.TestCase, async: true alias Playwright.{BrowserContext, Page, Response} - alias Playwright.SDK.Channel.{Error, Event} + alias Playwright.SDK.Channel.Event describe "Page.expect_event/3 without a 'trigger" do test "w/ an event", %{assets: assets, page: page} do @@ -35,12 +35,12 @@ defmodule Playwright.Page.NetworkTest do end test "w/ an event and a timeout", %{page: page} do - {:error, %Error{message: message}} = + {:error, %Playwright.SDK.Error{message: message}} = Page.expect_event(page, :request_finished, %{ timeout: 500 }) - assert message == "Timeout 500ms exceeded." + assert "Timeout 500ms exceeded" <> _ = message end test "w/ an event, a (truthy) predicate, and a timeout", %{assets: assets, page: page} do @@ -60,7 +60,7 @@ defmodule Playwright.Page.NetworkTest do test "w/ an event, a (falsy) predicate, and (incidentally) a timeout", %{assets: assets, page: page} do Task.start(fn -> Page.goto(page, assets.empty) end) - {:error, %Error{message: message}} = + {:error, %Playwright.SDK.Error{message: message}} = Page.expect_event(page, :request_finished, %{ predicate: fn _, _ -> false @@ -68,7 +68,7 @@ defmodule Playwright.Page.NetworkTest do timeout: 500 }) - assert message == "Timeout 500ms exceeded." + assert "Timeout 500ms exceeded" <> _ = message end end @@ -107,7 +107,7 @@ defmodule Playwright.Page.NetworkTest do end test "w/ an event and a (falsy) predicate", %{assets: assets, page: page} do - {:error, %Error{message: message}} = + {:error, %Playwright.SDK.Error{message: message}} = Page.expect_event( page, :request_finished, @@ -122,7 +122,7 @@ defmodule Playwright.Page.NetworkTest do end ) - assert message == "Timeout 500ms exceeded." + assert "Timeout 500ms exceeded" <> _ = message end test "w/ an event and a timeout", %{assets: assets, page: page} do diff --git a/test/api/page_test.exs b/test/api/page_test.exs index be73ee3a..47e6a92c 100644 --- a/test/api/page_test.exs +++ b/test/api/page_test.exs @@ -2,7 +2,7 @@ defmodule Playwright.PageTest do use Playwright.TestCase, async: true alias Playwright.{Browser, ElementHandle, Frame, Locator, Page, Request, Response, Route} alias Playwright.SDK.Channel - alias Playwright.SDK.Channel.{Error, Event} + alias Playwright.SDK.Channel.Event describe "Page.drag_and_drop/4" do test "returns 'subject'", %{assets: assets, page: page} do @@ -276,7 +276,7 @@ defmodule Playwright.PageTest do test "with a single option given mismatched attributes, returns a timeout", %{assets: assets, page: page} do page |> Page.goto(assets.prefix <> "/input/select.html") - assert {:error, %Error{message: "Timeout 500ms exceeded."}} = + assert {:error, %Playwright.API.Error{message: "Timeout 500ms exceeded."}} = Page.select_option(page, "select", %{value: "green", label: "Brown"}, %{timeout: 500}) end @@ -356,7 +356,7 @@ defmodule Playwright.PageTest do page |> Page.close() - assert {:error, %Error{message: "Timeout 100ms exceeded."}} = + assert {:error, %Playwright.SDK.Error{message: "Timeout 100ms exceeded" <> _}} = Channel.find(page.session, {:guid, page.guid}, %{timeout: 100}) end end @@ -398,7 +398,7 @@ defmodule Playwright.PageTest do assert page |> Page.get_attribute("div#outer", "name") == "value" assert page |> Page.get_attribute("div#outer", "foo") == nil - assert({:error, %Error{message: "Timeout 500ms exceeded."}} = Page.get_attribute(page, "glorp", "foo", %{timeout: 500})) + assert({:error, %Playwright.API.Error{message: "Timeout 500ms exceeded."}} = Page.get_attribute(page, "glorp", "foo", %{timeout: 500})) end end diff --git a/test/sdk/channel/catalog_test.exs b/test/sdk/channel/catalog_test.exs index e549c108..2283e759 100644 --- a/test/sdk/channel/catalog_test.exs +++ b/test/sdk/channel/catalog_test.exs @@ -1,6 +1,7 @@ defmodule Plawyeright.Channel.CatalogTest do use ExUnit.Case, async: true - alias Playwright.SDK.Channel.{Catalog, Error} + alias Playwright.SDK.Error + alias Playwright.SDK.Channel.Catalog setup do %{ @@ -23,7 +24,7 @@ defmodule Plawyeright.Channel.CatalogTest do end test "returns an Error when there is no match within the timeout period", %{catalog: catalog} do - assert {:error, %Error{message: "Timeout 50ms exceeded."}} = Catalog.get(catalog, "Missing", %{timeout: 50}) + assert {:error, %Error{message: "Timeout 50ms exceeded" <> _}} = Catalog.get(catalog, "Missing", %{timeout: 50}) end end From b2224421330060ad98055723ef7b4090d8f3d9f4 Mon Sep 17 00:00:00 2001 From: Ryan Spore Date: Mon, 12 Aug 2024 15:08:59 -0700 Subject: [PATCH 13/50] remove redundant head on ChannelOwner.returning/2 Co-authored-by: Corey Innis --- lib/playwright/sdk/channel_owner.ex | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lib/playwright/sdk/channel_owner.ex b/lib/playwright/sdk/channel_owner.ex index c3ef2cc3..d8703a88 100644 --- a/lib/playwright/sdk/channel_owner.ex +++ b/lib/playwright/sdk/channel_owner.ex @@ -119,11 +119,6 @@ defmodule Playwright.SDK.ChannelOwner do end end - defp returning(owner, task) do - task.() - Channel.find(owner.session, {:guid, owner.guid}) - end - defp returning(%{session: session} = subject, task) do task.() Channel.find(session, {:guid, subject.guid}) From 33b6b159db12e63a5d3398347af4b29abccead76 Mon Sep 17 00:00:00 2001 From: Corey Innis Date: Mon, 12 Aug 2024 15:12:09 -0700 Subject: [PATCH 14/50] Delete unused UnitTest CaseTemplate Co-authored-by: Ryan Spore --- test/support/unit_test.ex | 41 --------------------------------------- 1 file changed, 41 deletions(-) delete mode 100644 test/support/unit_test.ex diff --git a/test/support/unit_test.ex b/test/support/unit_test.ex deleted file mode 100644 index 151db9d1..00000000 --- a/test/support/unit_test.ex +++ /dev/null @@ -1,41 +0,0 @@ -defmodule Playwright.UnitTest do - @moduledoc """ - `UnitTest` is a helper module intended for use by the tests *of* Playwright. - """ - use ExUnit.CaseTemplate - - using do - quote do - defp pass(true) do - assert true - end - - defp pass(false) do - assert false - end - - defp pass(:pass) do - assert true - end - - defp pass(:fail) do - assert false - end - - defp pass({:ok, _}) do - assert true - end - - defp pass({:error, _}) do - assert false - end - - require Logger - - defp pass(other) do - Logger.warning("pass/1 not implemented for: #{inspect(other)}") - assert true - end - end - end -end From e477a3aa12036e5cb5ecc5994bcaef4144c3d54d Mon Sep 17 00:00:00 2001 From: Corey Innis Date: Tue, 4 Jun 2024 20:23:51 -0700 Subject: [PATCH 15/50] API: `BrowserContext.add_cookies/2` and `clear_cookies/1` return `self()` --- lib/playwright/browser_context.ex | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/playwright/browser_context.ex b/lib/playwright/browser_context.ex index 48678a68..55e79659 100644 --- a/lib/playwright/browser_context.ex +++ b/lib/playwright/browser_context.ex @@ -168,6 +168,9 @@ defmodule Playwright.BrowserContext do @property :owner_page @property :routes + @typedoc "An explicit shorthand for the BrowserContext.t() subject." + @type self :: t() + @typedoc "Recognized cookie fields" @type cookie :: %{ name: String.t(), From 7b9c6b46f8a6f0e2fcf1a8e246b11c3f11422e0f Mon Sep 17 00:00:00 2001 From: Corey Innis Date: Tue, 4 Jun 2024 21:00:48 -0700 Subject: [PATCH 16/50] API: `BrowserContext.on/3` returns `subject()` This change also includes: - Renaming the `self()` type as `subject()`, to avoid confusion with Elixir's native `self()`. - Addition of `ChannelOwner.bind!/3`, a helper for calls to `Channel.bind/4`. - Some minor test tweaks for further clarity. --- lib/playwright/browser_context.ex | 2 +- lib/playwright/page.ex | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/playwright/browser_context.ex b/lib/playwright/browser_context.ex index 55e79659..a6d81585 100644 --- a/lib/playwright/browser_context.ex +++ b/lib/playwright/browser_context.ex @@ -169,7 +169,7 @@ defmodule Playwright.BrowserContext do @property :routes @typedoc "An explicit shorthand for the BrowserContext.t() subject." - @type self :: t() + @type subject :: t() @typedoc "Recognized cookie fields" @type cookie :: %{ diff --git a/lib/playwright/page.ex b/lib/playwright/page.ex index 49d127f6..47eb5d4b 100644 --- a/lib/playwright/page.ex +++ b/lib/playwright/page.ex @@ -58,6 +58,9 @@ defmodule Playwright.Page do # @property :touchscreen # --- + @typedoc "An explicit shorthand for the Page.t() subject." + @type subject :: t() + @type dimensions :: map() @type expression :: binary() @type function_or_options :: fun() | options() | nil From 958cb06211c48785268cc049266abfc0ba9c6255 Mon Sep 17 00:00:00 2001 From: Corey Innis Date: Tue, 4 Jun 2024 21:41:03 -0700 Subject: [PATCH 17/50] API: `CDPSession.*` return `subject()` --- lib/playwright/cdp_session.ex | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/playwright/cdp_session.ex b/lib/playwright/cdp_session.ex index 6094d923..55db2448 100644 --- a/lib/playwright/cdp_session.ex +++ b/lib/playwright/cdp_session.ex @@ -6,6 +6,9 @@ defmodule Playwright.CDPSession do @property :bindings + @typedoc "An explicit shorthand for the CDPSession.t() subject." + @type subject :: t() + @typedoc "Supported events" @type event :: binary() From 80ca654ed4bdf929eed07416857a87ca574be85c Mon Sep 17 00:00:00 2001 From: Corey Innis Date: Tue, 4 Jun 2024 21:42:51 -0700 Subject: [PATCH 18/50] API: remove `@deprecated` clause; it's too noisy --- lib/playwright/page/accessibility.ex | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/playwright/page/accessibility.ex b/lib/playwright/page/accessibility.ex index dd8a2236..0fef59e7 100644 --- a/lib/playwright/page/accessibility.ex +++ b/lib/playwright/page/accessibility.ex @@ -116,8 +116,10 @@ defmodule Playwright.Page.Accessibility do |> (&(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." + @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 From 2aad2cefce4b034d8a454165c33a4e26bc97b06f Mon Sep 17 00:00:00 2001 From: Corey Innis Date: Tue, 4 Jun 2024 22:13:48 -0700 Subject: [PATCH 19/50] API: `BrowserContext...` functions return `subject()` --- lib/playwright/browser_context.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/playwright/browser_context.ex b/lib/playwright/browser_context.ex index a6d81585..8e6501e6 100644 --- a/lib/playwright/browser_context.ex +++ b/lib/playwright/browser_context.ex @@ -447,7 +447,7 @@ defmodule Playwright.BrowserContext do page: %Playwright.Page{} } - See `Playwright.Page.expose_binding/4` for a similar, page-scoped version. + See `Playwright.Page.expose_binding/4` for a similar, Page-scoped version. """ @spec expose_binding(BrowserContext.t(), String.t(), function(), options()) :: BrowserContext.t() def expose_binding(%BrowserContext{session: session} = context, name, callback, options \\ %{}) do From 19b9dd193329287470427fd1df338c57b2ee3f99 Mon Sep 17 00:00:00 2001 From: Corey Innis Date: Thu, 26 Sep 2024 14:08:04 -0700 Subject: [PATCH 20/50] Remove all `@type subject :: t()` We've switched to the more idiomatic `t()` for function specs. --- lib/playwright/browser_context.ex | 3 --- lib/playwright/cdp_session.ex | 3 --- lib/playwright/page.ex | 3 --- 3 files changed, 9 deletions(-) diff --git a/lib/playwright/browser_context.ex b/lib/playwright/browser_context.ex index 8e6501e6..ef4bf478 100644 --- a/lib/playwright/browser_context.ex +++ b/lib/playwright/browser_context.ex @@ -168,9 +168,6 @@ defmodule Playwright.BrowserContext do @property :owner_page @property :routes - @typedoc "An explicit shorthand for the BrowserContext.t() subject." - @type subject :: t() - @typedoc "Recognized cookie fields" @type cookie :: %{ name: String.t(), diff --git a/lib/playwright/cdp_session.ex b/lib/playwright/cdp_session.ex index 55db2448..6094d923 100644 --- a/lib/playwright/cdp_session.ex +++ b/lib/playwright/cdp_session.ex @@ -6,9 +6,6 @@ defmodule Playwright.CDPSession do @property :bindings - @typedoc "An explicit shorthand for the CDPSession.t() subject." - @type subject :: t() - @typedoc "Supported events" @type event :: binary() diff --git a/lib/playwright/page.ex b/lib/playwright/page.ex index 47eb5d4b..49d127f6 100644 --- a/lib/playwright/page.ex +++ b/lib/playwright/page.ex @@ -58,9 +58,6 @@ defmodule Playwright.Page do # @property :touchscreen # --- - @typedoc "An explicit shorthand for the Page.t() subject." - @type subject :: t() - @type dimensions :: map() @type expression :: binary() @type function_or_options :: fun() | options() | nil From 26fed1817de591a947971919f9ba15c59479c62c Mon Sep 17 00:00:00 2001 From: Corey Innis Date: Sun, 29 Sep 2024 23:02:46 -0400 Subject: [PATCH 21/50] SDK: refactor `ChannelOwner.post!/3` -> `Channel.post/3` The `ChannelOwner.post!/3` implementation was problematic in (at least) the following ways: - It resulted in a somewhat inheritance-like implementation and usage, w/out any need for that; - It did not handle well differences between "posts" that should result in updates to the "subject" resource vs. retrieval of related resources; - It did note handle well cases in which the "subject" **should not** be refreshed. These shortcomings required multiple work-arounds and code verbosity. The introduction of `Channel.post/3` feels much cleaner, more manageable, and idiomatic. --- lib/playwright.ex | 15 +- lib/playwright/api_request_context.ex | 28 +- lib/playwright/binding_call.ex | 8 +- lib/playwright/browser.ex | 9 +- lib/playwright/browser_context.ex | 44 +- lib/playwright/browser_type.ex | 2 +- lib/playwright/cdp_session.ex | 16 +- lib/playwright/element_handle.ex | 55 +-- lib/playwright/frame.ex | 422 +++++++++----------- lib/playwright/js_handle.ex | 37 +- lib/playwright/locator.ex | 35 +- lib/playwright/page.ex | 44 +- lib/playwright/page/accessibility.ex | 4 +- lib/playwright/page/keyboard.ex | 10 +- lib/playwright/response.ex | 4 +- lib/playwright/route.ex | 5 +- lib/playwright/sdk/channel.ex | 48 ++- lib/playwright/sdk/channel_owner.ex | 27 +- lib/playwright/sdk/helpers/serialization.ex | 7 + test/api/chromium/cdp_session_test.exs | 8 +- test/api/click_test.exs | 2 +- test/api/element_handle_test.exs | 2 +- test/api/locator_test.exs | 173 ++++++-- 23 files changed, 558 insertions(+), 447 deletions(-) diff --git a/lib/playwright.ex b/lib/playwright.ex index 6b780f4d..90578ba0 100644 --- a/lib/playwright.ex +++ b/lib/playwright.ex @@ -18,6 +18,8 @@ defmodule Playwright do """ use Playwright.SDK.ChannelOwner + alias Playwright + alias Playwright.SDK.Channel alias Playwright.SDK.Config @property :chromium @@ -85,16 +87,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/api_request_context.ex b/lib/playwright/api_request_context.ex index 75f21728..3ca0a10f 100644 --- a/lib/playwright/api_request_context.ex +++ b/lib/playwright/api_request_context.ex @@ -9,8 +9,12 @@ defmodule Playwright.APIRequestContext do """ use Playwright.SDK.ChannelOwner + alias Playwright.SDK.Channel alias Playwright.APIRequestContext + # types + # ---------------------------------------------------------------------------- + @type fetch_options() :: %{ optional(:params) => any(), optional(:method) => binary(), @@ -24,6 +28,9 @@ defmodule Playwright.APIRequestContext do optional(:ignore_HTTPS_errors) => boolean() } + # API + # ---------------------------------------------------------------------------- + # @spec delete(t(), binary(), options()) :: APIResponse.t() # def delete(context, url, options \\ %{}) @@ -43,19 +50,8 @@ defmodule Playwright.APIRequestContext do # 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 - ) - ) + def post(%APIRequestContext{} = context, url, options \\ %{}) do + Channel.post({context, :fetch}, %{url: url, method: "POST"}, options) end # @spec put(t(), binary(), options()) :: APIResponse.t() @@ -66,9 +62,7 @@ defmodule Playwright.APIRequestContext do # 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 - }) + def body(%APIRequestContext{} = context, response) do + Channel.post({context, :fetch_response_body}, %{fetchUid: response.fetchUid}) end 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..1c813377 100644 --- a/lib/playwright/browser.ex +++ b/lib/playwright/browser.ex @@ -67,8 +67,7 @@ defmodule Playwright.Browser do def close(%Browser{session: session} = browser) do case Channel.find(session, {:guid, browser.guid}, %{timeout: 10}) do %Browser{} -> - Channel.post(session, {:guid, browser.guid}, :close) - :ok + Channel.close(browser) {:error, _} -> :ok @@ -124,14 +123,14 @@ defmodule Playwright.Browser do ## Arguments - | key/name | type | | description | + | key/name | type | | description | | ------------------ | ------ | ----------- | ----------- | | `accept_downloads` | option | `boolean()` | Whether to automatically download all the attachments. If false, all the downloads are canceled. `(default: false)` | | `...` | option | `...` | ... | """ @spec new_context(t(), options()) :: BrowserContext.t() - def new_context(%Browser{guid: guid} = browser, options \\ %{}) do - Channel.post(browser.session, {:guid, guid}, :new_context, prepare(options)) + def new_context(%Browser{} = browser, options \\ %{}) do + Channel.post({browser, :new_context}, prepare(options)) end @doc """ diff --git a/lib/playwright/browser_context.ex b/lib/playwright/browser_context.ex index ef4bf478..cf747df7 100644 --- a/lib/playwright/browser_context.ex +++ b/lib/playwright/browser_context.ex @@ -253,7 +253,7 @@ defmodule Playwright.BrowserContext do def add_cookies(context, cookies) def add_cookies(%BrowserContext{} = context, cookies) do - post!(context, :add_cookies, %{cookies: cookies}) + Channel.post({context, :add_cookies}, %{cookies: cookies}) end @doc """ @@ -301,7 +301,7 @@ defmodule Playwright.BrowserContext do """ @spec add_init_script(t(), binary() | map()) :: t() def add_init_script(%BrowserContext{} = context, script) when is_binary(script) do - post!(context, :add_init_script, %{source: script}) + Channel.post({context, :add_init_script}, %{source: script}) end def add_init_script(%BrowserContext{} = context, %{path: path} = script) when is_map(script) do @@ -323,12 +323,12 @@ defmodule Playwright.BrowserContext do """ @spec clear_cookies(t()) :: t() def clear_cookies(%BrowserContext{} = context) do - post!(context, :clear_cookies) + Channel.post({context, :clear_cookies}) end @spec clear_permissions(t()) :: t() def clear_permissions(%BrowserContext{} = context) do - post!(context, :clear_permissions) + Channel.post({context, :clear_permissions}) end @doc """ @@ -339,13 +339,12 @@ defmodule Playwright.BrowserContext do > - The default browser context cannot be closed. """ @spec close(t()) :: :ok - def close(%BrowserContext{session: session} = context) do + def close(%BrowserContext{} = context) do # A call to `close` will remove the item from the catalog. `Catalog.find` # here ensures that we do not `post` a 2nd `close`. - case Channel.find(session, {:guid, context.guid}, %{timeout: 10}) do + case Channel.find(context.session, {:guid, context.guid}, %{timeout: 10}) do %BrowserContext{} -> - Channel.post(session, {:guid, context.guid}, :close) - :ok + Channel.close(context) {:error, _} -> :ok @@ -364,13 +363,13 @@ defmodule Playwright.BrowserContext do ## Arguments - | key/name | type | | description | + | key/name | type | | description | | ---------- | ----- | -------------------------- | ----------- | | `urls` | param | `binary()` or `[binary()]` | List of URLs. `(optional)` | """ @spec cookies(t(), url | [url]) :: [cookie] - def cookies(%BrowserContext{session: session} = context, urls \\ []) do - Channel.post(session, {:guid, context.guid}, :cookies, %{urls: urls}) + def cookies(%BrowserContext{} = context, urls \\ []) do + Channel.post({context, :cookies}, %{urls: urls}) end @doc """ @@ -449,7 +448,7 @@ defmodule Playwright.BrowserContext do @spec expose_binding(BrowserContext.t(), String.t(), function(), options()) :: BrowserContext.t() def expose_binding(%BrowserContext{session: session} = context, name, callback, options \\ %{}) do Channel.patch(session, {:guid, context.guid}, %{bindings: Map.merge(context.bindings, %{name => callback})}) - post!(context, :expose_binding, Map.merge(%{name: name, needs_handle: false}, options)) + Channel.post({context, :expose_binding}, Map.merge(%{name: name, needs_handle: false}, options)) end @doc """ @@ -471,18 +470,18 @@ defmodule Playwright.BrowserContext do @spec grant_permissions(t(), [String.t()], options()) :: t() | {:error, Playwright.API.Error.t()} def grant_permissions(%BrowserContext{} = context, permissions, options \\ %{}) do params = Map.merge(%{permissions: permissions}, options) - post!(context, :grant_permissions, params) + Channel.post({context, :grant_permissions}, params) end @spec new_cdp_session(t(), Frame.t() | Page.t()) :: Playwright.CDPSession.t() def new_cdp_session(context, owner) - def new_cdp_session(%BrowserContext{session: session} = context, %Frame{} = frame) do - Channel.post(session, {:guid, context.guid}, "newCDPSession", %{frame: %{guid: frame.guid}}) + def new_cdp_session(%BrowserContext{} = context, %Frame{} = frame) do + Channel.post({context, "newCDPSession"}, %{frame: %{guid: frame.guid}}) end - def new_cdp_session(%BrowserContext{session: session} = context, %Page{} = page) do - Channel.post(session, {:guid, context.guid}, "newCDPSession", %{page: %{guid: page.guid}}) + def new_cdp_session(%BrowserContext{} = context, %Page{} = page) do + Channel.post({context, "newCDPSession"}, %{page: %{guid: page.guid}}) end @doc """ @@ -495,10 +494,10 @@ defmodule Playwright.BrowserContext do @spec new_page(t()) :: Page.t() def new_page(context) - def new_page(%BrowserContext{session: session} = context) do + def new_page(%BrowserContext{} = context) do case context.owner_page do nil -> - Channel.post(session, {:guid, context.guid}, :new_page) + Channel.post({context, :new_page}) %Playwright.Page{} -> raise(RuntimeError, message: "Please use Playwright.Browser.new_context/1") @@ -537,7 +536,7 @@ defmodule Playwright.BrowserContext do patterns = Helpers.RouteHandler.prepare(routes) Channel.patch(session, {:guid, context.guid}, %{routes: routes}) - post!(context, :set_network_interception_patterns, %{patterns: patterns}) + Channel.post({context, :set_network_interception_patterns}, %{patterns: patterns}) end) end @@ -575,7 +574,7 @@ defmodule Playwright.BrowserContext do @spec set_offline(t(), boolean()) :: t() def set_offline(%BrowserContext{} = context, offline) do - post!(context, :set_offline, %{offline: offline}) + Channel.post({context, :set_offline}, %{offline: offline}) end # --- @@ -585,7 +584,7 @@ defmodule Playwright.BrowserContext do # --- - @spec unroute(t(), binary(), function() | nil) :: :ok + @spec unroute(t(), binary(), function() | nil) :: t() def unroute(%BrowserContext{session: session} = context, pattern, callback \\ nil) do with_latest(context, fn context -> remaining = @@ -594,7 +593,6 @@ defmodule Playwright.BrowserContext do end) Channel.patch(session, {:guid, context.guid}, %{routes: remaining}) - :ok end) end diff --git a/lib/playwright/browser_type.ex b/lib/playwright/browser_type.ex index 899658fa..60900432 100644 --- a/lib/playwright/browser_type.ex +++ b/lib/playwright/browser_type.ex @@ -163,7 +163,7 @@ defmodule Playwright.BrowserType do # ---------------------------------------------------------------------------- defp browser(%BrowserType{} = browser_type) do - Channel.post(browser_type.session, {:guid, browser_type.guid}, :launch, Config.launch_options()) + Channel.post({browser_type, :launch}, Config.launch_options()) end defp chromium(session) do diff --git a/lib/playwright/cdp_session.ex b/lib/playwright/cdp_session.ex index 6094d923..7994171f 100644 --- a/lib/playwright/cdp_session.ex +++ b/lib/playwright/cdp_session.ex @@ -27,15 +27,9 @@ defmodule Playwright.CDPSession do # API # --------------------------------------------------------------------------- - @spec detach(t()) :: t() | {:error, term()} - def detach(%CDPSession{session: session} = cdp_session) do - case Channel.post(session, {:guid, cdp_session.guid}, :detach) do - {:ok, _} -> - cdp_session - - {:error, %Playwright.API.Error{} = error} -> - {:error, error} - end + @spec detach(t()) :: t() | {:error, Playwright.API.Error.t()} + def detach(%CDPSession{} = cdp_session) do + Channel.post({cdp_session, :detach}, %{refresh: false}) end @doc """ @@ -49,8 +43,8 @@ defmodule Playwright.CDPSession do end @spec send(t(), binary(), options()) :: map() - def send(%CDPSession{session: session} = cdp_session, method, params \\ %{}) do - Channel.post(session, {:guid, cdp_session.guid}, :send, %{method: method, params: params}) + def send(%CDPSession{} = cdp_session, method, params \\ %{}) do + Channel.post({cdp_session, :send}, %{method: method, params: params}) end # private diff --git a/lib/playwright/element_handle.ex b/lib/playwright/element_handle.ex index 3d55a7dc..eaeb07cd 100644 --- a/lib/playwright/element_handle.ex +++ b/lib/playwright/element_handle.ex @@ -54,6 +54,7 @@ defmodule Playwright.ElementHandle do use Playwright.SDK.ChannelOwner alias Playwright.{ElementHandle, Frame, JSHandle} + alias Playwright.API.Error alias Playwright.SDK.{Channel, ChannelOwner} @property :preview @@ -89,8 +90,8 @@ defmodule Playwright.ElementHandle do # --------------------------------------------------------------------------- @spec bounding_box(ElementHandle.t()) :: map() | nil - def bounding_box(%ElementHandle{session: session} = handle) do - Channel.post(session, {:guid, handle.guid}, :bounding_box) + def bounding_box(%ElementHandle{} = handle) do + Channel.post({handle, :bounding_box}) end @doc """ @@ -98,8 +99,8 @@ defmodule Playwright.ElementHandle do or `nil` otherwise. """ @spec content_frame(t()) :: Frame.t() | nil - def content_frame(%ElementHandle{session: session} = handle) do - Channel.post(session, {:guid, handle.guid}, :content_frame) + def content_frame(%ElementHandle{} = handle) do + Channel.post({handle, :content_frame}) end # @spec owner_frame(t()) :: Frame.t() | nil @@ -141,7 +142,7 @@ defmodule Playwright.ElementHandle do """ @spec click(t(), options()) :: t() def click(%ElementHandle{} = handle, options \\ %{}) do - post!(handle, :click, options) + Channel.post({handle, :click}, options) end # --- @@ -169,8 +170,8 @@ defmodule Playwright.ElementHandle do Returns the value of an element's attribute. """ @spec get_attribute(t(), binary()) :: binary() | nil - def get_attribute(%ElementHandle{session: session} = handle, name) do - Channel.post(session, {:guid, handle.guid}, :get_attribute, %{name: name}) + def get_attribute(%ElementHandle{} = handle, name) do + Channel.post({handle, :get_attribute}, %{name: name}) end # --- @@ -212,21 +213,9 @@ defmodule Playwright.ElementHandle do # def is_hidden(handle) # ⚠️ DISCOURAGED - @spec is_visible(t()) :: boolean() - def is_visible(%ElementHandle{session: session} = handle) do - case Channel.post(session, {:guid, handle.guid}, :is_visible) do - false -> - false - - true -> - true - - {:ok, value} -> - value - - {:error, error} -> - {:error, error} - end + @spec is_visible(t()) :: boolean() | {:error, Error.t()} + def is_visible(%ElementHandle{} = handle) do + Channel.post({handle, :is_visible}) end # --- @@ -248,8 +237,8 @@ defmodule Playwright.ElementHandle do @spec query_selector(t(), binary()) :: ElementHandle.t() | nil def query_selector(handle, selector) - def query_selector(%ElementHandle{session: session} = handle, selector) do - Channel.post(session, {:guid, handle.guid}, :query_selector, %{selector: selector}) + def query_selector(%ElementHandle{} = handle, selector) do + Channel.post({handle, :query_selector}, %{selector: selector}) end defdelegate q(handle, selector), to: __MODULE__, as: :query_selector @@ -264,16 +253,16 @@ defmodule Playwright.ElementHandle do # ⚠️ DISCOURAGED @spec screenshot(ElementHandle.t(), options()) :: binary() - def screenshot(%ElementHandle{session: session} = handle, options \\ %{}) do + def screenshot(%ElementHandle{} = handle, options \\ %{}) do case Map.pop(options, :path) do {nil, params} -> - encoded = Channel.post(session, {:guid, handle.guid}, :screenshot, params) + encoded = Channel.post({handle, :screenshot}, params) Base.decode64!(encoded) {path, params} -> [_, filetype] = String.split(path, ".") - encoded = Channel.post(session, {:guid, handle.guid}, :screenshot, Map.put(params, :type, filetype)) + encoded = Channel.post({handle, :screenshot}, Map.put(params, :type, filetype)) decoded = Base.decode64!(encoded) File.write!(path, decoded) decoded @@ -282,8 +271,8 @@ defmodule Playwright.ElementHandle do # ⚠️ DISCOURAGED @spec scroll_into_view(ElementHandle.t(), options()) :: :ok - def scroll_into_view(%ElementHandle{session: session} = handle, options \\ %{}) do - Channel.post(session, {:guid, handle.guid}, :scroll_into_view_if_needed, options) + def scroll_into_view(%ElementHandle{} = handle, options \\ %{}) do + Channel.post({handle, :scroll_into_view_if_needed}, options) end # ⚠️ DISCOURAGED @@ -292,8 +281,8 @@ defmodule Playwright.ElementHandle do # ⚠️ DISCOURAGED @spec select_text(ElementHandle.t(), options()) :: :ok - def select_text(%ElementHandle{session: session} = handle, options \\ %{}) do - Channel.post(session, {:guid, handle.guid}, :select_text, options) + def select_text(%ElementHandle{} = handle, options \\ %{}) do + Channel.post({handle, :select_text}, options) end # ⚠️ DISCOURAGED @@ -317,8 +306,8 @@ defmodule Playwright.ElementHandle do @spec text_content(t()) :: binary() | nil def text_content(handle) - def text_content(%ElementHandle{session: session} = handle) do - Channel.post(session, {:guid, handle.guid}, :text_content) + def text_content(%ElementHandle{} = handle) do + Channel.post({handle, :text_content}) end # --- diff --git a/lib/playwright/frame.ex b/lib/playwright/frame.ex index 2a0a52bd..aa5a984e 100644 --- a/lib/playwright/frame.ex +++ b/lib/playwright/frame.ex @@ -16,6 +16,7 @@ defmodule Playwright.Frame do """ use Playwright.SDK.ChannelOwner alias Playwright.{ElementHandle, Frame, Locator, Response} + alias Playwright.API.Error alias Playwright.SDK.{ChannelOwner, Helpers} @property :load_states @@ -31,8 +32,8 @@ defmodule Playwright.Frame do # --------------------------------------------------------------------------- @impl ChannelOwner - def init(%Frame{session: session} = frame, _initializer) do - Channel.bind(session, {:guid, frame.guid}, :loadstate, fn %{params: params} = event -> + def init(%Frame{} = frame, _initializer) do + Channel.bind(frame.session, {:guid, frame.guid}, :loadstate, fn %{params: params} = event -> target = event.target case params do @@ -44,7 +45,7 @@ defmodule Playwright.Frame do end end) - Channel.bind(session, {:guid, frame.guid}, :navigated, fn event -> + Channel.bind(frame.session, {:guid, frame.guid}, :navigated, fn event -> {:patch, %{event.target | url: event.params.url}} end) @@ -56,30 +57,22 @@ defmodule Playwright.Frame do # --- - # @spec add_script_tag(Frame.t(), options()) :: ElementHandle.t() + # @spec add_script_tag(Frame.t(), options()) :: ElementHandle.t() | {:error, Error.t()} # def add_script_tag(frame, options \\ %{}) - # @spec add_style_tag(Frame.t(), options()) :: ElementHandle.t() + # @spec add_style_tag(Frame.t(), options()) :: ElementHandle.t() | {:error, Error.t()} # def add_style_tag(frame, options \\ %{}) # --- - @spec check(t(), binary(), options()) :: :ok - def check(%Frame{session: session} = frame, selector, options \\ %{}) do - params = Map.merge(%{selector: selector}, options) - - case Channel.post(session, {:guid, frame.guid}, :check, params) do - {:ok, _} -> - :ok - - {:error, error} -> - {:error, error} - end + @spec check(t(), binary(), options()) :: t() | {:error, Error.t()} + def check(%Frame{} = frame, selector, options \\ %{}) do + Channel.post({frame, :check}, %{selector: selector}, options) end # --- - # @spec child_frames(Frame.t()) :: [Frame.t()] + # @spec child_frames(Frame.t()) :: [Frame.t()] | {:error, Error.t()} # def child_frames(frame) # --- @@ -102,34 +95,26 @@ defmodule Playwright.Frame do `option: timeout`, `/click/3` raises a `TimeoutError`. Passing zero for `option: timeout` disables this. """ - @spec click(t(), binary(), options()) :: t() + @spec click(t(), binary(), options()) :: t() | {:error, Error.t()} def click(frame, selector, options \\ %{}) def click(%Frame{} = frame, selector, options) do - params = - Map.merge( - %{ - selector: selector, - timeout: 30_000, - wait_until: "load" - }, - options - ) - - post!(frame, :click, params) + Channel.post( + {frame, :click}, + %{ + selector: selector, + timeout: 30_000, + wait_until: "load" + }, + options + ) end # --- - @spec content(Frame.t()) :: binary() | {:error, term()} - def content(%Frame{session: session} = frame) do - case Channel.post(session, {:guid, frame.guid}, :content) do - {:error, error} -> - {:error, error} - - content -> - content - end + @spec content(Frame.t()) :: binary() | {:error, Error.t()} + def content(%Frame{} = frame) do + Channel.post({frame, :content}) end # --- @@ -162,10 +147,11 @@ defmodule Playwright.Frame do ## Returns - `t()` + - `{:error, Error.t()}` ## Arguments - | key/name | type | | description | + | key/name | type | | description | | ---------------- | ------ | --------------------------------- | ----------- | | `selector` | param | `binary()` | A selector to search for an element. If there are multiple elements satisfying the selector, the first will be used. See "working with selectors (guide)" for more details. | | `:button` | option | `:left`, `:right` or `:middle` | `(default: :left)` | @@ -178,23 +164,9 @@ defmodule Playwright.Frame do | `:timeout` | option | `number()` | Maximum time in milliseconds. Pass `0` to disable timeout. The default value can be changed by using the `Playwright.BrowserContext.set_default_timeout/2` or `Playwright.Page.set_default_timeout/2` functions. `(default: 30 seconds)` | | `:trial` | option | `boolean()` | When set, this call only performs the actionability checks and skips the action. Useful to wait until the element is ready for the action without performing it. `(default: false)` | """ - @spec dblclick(Frame.t(), binary(), options()) :: t() - def dblclick(%Frame{session: session} = frame, selector, options \\ %{}) do - params = - Map.merge( - %{ - selector: selector - }, - options - ) - - case Channel.post(session, {:guid, frame.guid}, :dblclick, params) do - {:ok, _} -> - :ok - - {:error, error} -> - {:error, error} - end + @spec dblclick(Frame.t(), binary(), options()) :: t() | {:error, Error.t()} + def dblclick(%Frame{} = frame, selector, options \\ %{}) do + Channel.post({frame, :dblclick}, %{selector: selector}, options) end @doc """ @@ -234,11 +206,12 @@ defmodule Playwright.Frame do ## Returns - - `:ok` + - `t()` + - `{:error, Error.t()}` ## Arguments - | key/name | type | | description | + | key/name | type | | description | | ---------------- | ------ | ----------------------- | ----------- | | `selector` | param | `binary()` | A selector to search for an element. If there are multiple elements satisfying the selector, the first will be used. See "working with selectors (guide)" for more details. | | `type` | param | `atom()` or `binary()` | DOM event type: `:click`, `:dragstart`, etc. | @@ -246,7 +219,7 @@ defmodule Playwright.Frame do | `:strict` | option | `boolean()` | When true, the call requires selector to resolve to a single element. If given selector resolves to more then one element, the call throws an exception. | | `:timeout` | option | `number()` | Maximum time in milliseconds. Pass `0` to disable timeout. The default value can be changed by using the `Playwright.BrowserContext.set_default_timeout/2` or `Playwright.Page.set_default_timeout/2` functions. `(default: 30 seconds)` | """ - @spec dispatch_event(Frame.t(), binary(), binary(), evaluation_argument(), options()) :: :ok + @spec dispatch_event(Frame.t(), binary(), binary(), evaluation_argument(), options()) :: t() | {:error, Error.t()} def dispatch_event(frame, selector, type, event_init \\ nil, options \\ %{}) def dispatch_event(%Frame{} = frame, selector, type, options, _) @@ -254,29 +227,29 @@ defmodule Playwright.Frame do dispatch_event(frame, selector, type, nil, options) end - def dispatch_event(%Frame{session: session} = frame, selector, type, event_init, options) do - params = - Map.merge(options, %{ + def dispatch_event(%Frame{} = frame, selector, type, event_init, options) do + Channel.post( + {frame, :dispatch_event}, + %{ selector: selector, type: type, - event_init: Helpers.Serialization.serialize(event_init) - }) - - Channel.post(session, {:guid, frame.guid}, :dispatch_event, params) + event_init: serialize(event_init) + }, + options + ) end # --- - @spec drag_and_drop(Frame.t(), binary(), binary(), options()) :: Frame.t() + @spec drag_and_drop(Frame.t(), binary(), binary(), options()) :: t() | {:error, Error.t()} def drag_and_drop(frame, source, target, options \\ %{}) do - params = Map.merge(%{source: source, target: target}, options) - post!(frame, :drag_and_drop, params) + Channel.post({frame, :drag_and_drop}, %{source: source, target: target}, options) end - # @spec eval_on_selector(Frame.t(), binary(), expression(), any(), options()) :: :ok + # @spec eval_on_selector(Frame.t(), binary(), expression(), any(), options()) :: ... # def eval_on_selector(frame, selector, expression, arg \\ nil, options \\ %{}) - # @spec eval_on_selector_all(Frame.t(), binary(), expression(), any(), options()) :: :ok + # @spec eval_on_selector_all(Frame.t(), binary(), expression(), any(), options()) :: ... # def eval_on_selector_all(frame, selector, expression, arg \\ nil, options \\ %{}) # --- @@ -286,12 +259,12 @@ defmodule Playwright.Frame do !!! """ - @spec eval_on_selector(Frame.t(), binary(), binary(), term(), map()) :: term() + @spec eval_on_selector(Frame.t(), binary(), binary(), term(), map()) :: term() | {:error, Error.t()} def eval_on_selector(frame, selector, expression, arg \\ nil, options \\ %{}) - def eval_on_selector(%Frame{session: session} = frame, selector, expression, arg, _options) do + def eval_on_selector(%Frame{} = frame, selector, expression, arg, _options) do parse_result(fn -> - Channel.post(session, {:guid, frame.guid}, :eval_on_selector, %{ + Channel.post({frame, :eval_on_selector}, %{ selector: selector, expression: expression, arg: serialize(arg) @@ -299,13 +272,16 @@ defmodule Playwright.Frame do end) end - def eval_on_selector_all(%Frame{session: session} = frame, selector, expression, arg \\ nil) do + def eval_on_selector_all(%Frame{} = frame, selector, expression, arg \\ nil) do parse_result(fn -> - Channel.post(session, {:guid, frame.guid}, :eval_on_selector_all, %{ - selector: selector, - expression: expression, - arg: Helpers.Serialization.serialize(arg) - }) + Channel.post( + {frame, :eval_on_selector_all}, + %{ + selector: selector, + expression: expression, + arg: serialize(arg) + } + ) end) end @@ -313,16 +289,19 @@ defmodule Playwright.Frame do Returns the return value of `expression`. !!! """ - @spec evaluate(t(), expression(), any()) :: :ok + @spec evaluate(t(), expression(), any()) :: term() | {:error, Error.t()} def evaluate(owner, expression, arg \\ nil) - def evaluate(%Frame{session: session} = frame, expression, arg) do + def evaluate(%Frame{} = frame, expression, arg) do parse_result(fn -> - Channel.post(session, {:guid, frame.guid}, :evaluate_expression, %{ - expression: expression, - is_function: Helpers.Expression.function?(expression), - arg: serialize(arg) - }) + Channel.post( + {frame, :evaluate_expression}, + %{ + expression: expression, + is_function: Helpers.Expression.function?(expression), + arg: serialize(arg) + } + ) end) end @@ -330,18 +309,22 @@ defmodule Playwright.Frame do Returns the return value of `expression` as a `Playwright.JSHandle`. !!! """ - @spec evaluate_handle(t(), expression(), any()) :: serializable() - def evaluate_handle(%Frame{session: session} = frame, expression, arg \\ nil) do - Channel.post(session, {:guid, frame.guid}, :evaluate_expression_handle, %{ - expression: expression, - is_function: Helpers.Expression.function?(expression), - arg: Helpers.Serialization.serialize(arg) - }) + @spec evaluate_handle(t(), expression(), any()) :: serializable() | {:error, Error.t()} + def evaluate_handle(%Frame{} = frame, expression, arg \\ nil) do + Channel.post( + {frame, :evaluate_expression_handle}, + %{ + expression: expression, + is_function: Helpers.Expression.function?(expression), + arg: serialize(arg) + }, + %{refresh: false} + ) end # --- - # @spec expect_navigation(Frame.t(), function(), options()) :: Playwright.Response.t() | nil + # @spec expect_navigation(Frame.t(), function(), options()) :: ... # def expect_navigation(frame, trigger, options \\ %{}) # --- @@ -365,11 +348,12 @@ defmodule Playwright.Frame do ## Returns - - `:ok` + - `t()` + - `{:error, Error.t()}` ## Arguments - | key/name | type | | description | + | key/name | type | | description | | ---------------- | ------ | --------------------------------- | ----------- | | `selector` | param | `binary()` | A selector to search for an element. If there are multiple elements satisfying the selector, the first will be used. See "working with selectors (guide)" for more details. | | `value` | param | `binary()` | Value to fill for the ``, `