diff --git a/lib/phoenix_test/playwright/config.ex b/lib/phoenix_test/playwright/config.ex index ced80b2..3d791e8 100644 --- a/lib/phoenix_test/playwright/config.ex +++ b/lib/phoenix_test/playwright/config.ex @@ -12,6 +12,15 @@ browser_opts = [ type: {:in, browsers}, type_doc: "`#{Enum.map_join(browsers, " | ", &":#{&1}")}`" ], + browser_launch_opts: [ + default: [], + type: :keyword_list, + doc: """ + Additional arguments passed to Playwright [browserType.launch](https://playwright.dev/docs/api/class-browsertype#browser-type-launch). + E.g. `[args: ["--use-fake-ui-for-media-stream", "--use-fake-device-for-media-stream"]]`. + Can't be used with remote browser (ws_endpoint). + """ + ], browser_launch_timeout: [ default: to_timeout(second: 4), type: :non_neg_integer @@ -68,6 +77,7 @@ schema_opts = [ E.g. `[http_credentials: %{username: "a", password: "b"}]`. """ ], + browser_launch_opts: browser_opts[:browser_launch_opts], browser_launch_timeout: browser_opts[:browser_launch_timeout], browser_page_opts: [ default: [], @@ -172,9 +182,9 @@ schema_opts = [ schema = NimbleOptions.new!(schema_opts) -setup_all_keys = ~w(browser_pool browser browser_launch_timeout executable_path headless slow_mo)a +setup_all_keys = ~w(browser_pool browser browser_launch_opts browser_launch_timeout executable_path headless slow_mo)a setup_keys = ~w(accept_dialogs ecto_sandbox_stop_owner_delay screenshot trace browser_context_opts browser_page_opts)a -merge_global_into_browser_pool_keys = ~w(browser browser_launch_timeout headless slow_mo)a +merge_global_into_browser_pool_keys = ~w(browser browser_launch_opts browser_launch_timeout headless slow_mo)a defmodule PhoenixTest.Playwright.Config do @moduledoc """ diff --git a/lib/phoenix_test/playwright/internal/browser.ex b/lib/phoenix_test/playwright/internal/browser.ex index 6a9eaa3..afda16e 100644 --- a/lib/phoenix_test/playwright/internal/browser.ex +++ b/lib/phoenix_test/playwright/internal/browser.ex @@ -7,7 +7,9 @@ defmodule PhoenixTest.Playwright.Browser do def launch_browser!(config) do {launch_timeout, opts} = Keyword.pop!(config, :browser_launch_timeout) {browser, opts} = Keyword.pop!(opts, :browser) + {launch_opts, opts} = Keyword.pop!(opts, :browser_launch_opts) opts = opts |> Keyword.put(:timeout, launch_timeout) |> Keyword.delete(:browser_pool) + opts = Keyword.merge(opts, launch_opts) case PlaywrightEx.launch_browser(browser, opts) do {:ok, browser} -> diff --git a/test/phoenix_test/playwright/browser_launch_opts_test.exs b/test/phoenix_test/playwright/browser_launch_opts_test.exs new file mode 100644 index 0000000..807184b --- /dev/null +++ b/test/phoenix_test/playwright/browser_launch_opts_test.exs @@ -0,0 +1,62 @@ +defmodule PhoenixTest.Playwright.BrowserLaunchOptsTest do + @moduledoc """ + Tests that browser_launch_opts are passed through to Playwright. + + These tests verify that browser launch flags actually affect browser behavior + by testing getUserMedia with and without fake media device flags. + """ + + use PhoenixTest.Playwright.Case, + async: true, + browser_pool: false, + browser_launch_opts: [ + args: [ + "--use-fake-ui-for-media-stream", + "--use-fake-device-for-media-stream" + ] + ] + + @moduletag skip: !!Application.compile_env!(:phoenix_test, :playwright)[:ws_endpoint] + + test "getUserMedia succeeds with fake media device flags", %{conn: conn} do + conn + |> visit("/pw/live") + |> assert_has("h1") + |> evaluate( + """ + navigator.mediaDevices.getUserMedia({ audio: true }) + .then(() => "success") + .catch(e => "error: " + e.name) + """, + &assert(&1 == "success") + ) + end +end + +defmodule PhoenixTest.Playwright.BrowserLaunchOptsWithoutFlagsTest do + @moduledoc """ + Tests that getUserMedia fails WITHOUT the fake media device flags. + This proves the flags in BrowserLaunchOptsTest actually have an effect. + """ + + use PhoenixTest.Playwright.Case, + async: true, + browser_pool: false + + @moduletag skip: !!Application.compile_env!(:phoenix_test, :playwright)[:ws_endpoint] + + test "getUserMedia fails without fake media device flags", %{conn: conn} do + conn + |> visit("/pw/live") + |> assert_has("h1") + |> evaluate( + """ + navigator.mediaDevices.getUserMedia({ audio: true }) + .then(() => "success") + .catch(e => "error: " + e.name) + """, + # Without fake device flags, getUserMedia should fail in headless mode + &assert(&1 =~ "error:") + ) + end +end