# Copyright (c) Microsoft Corporation. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import asyncio from typing import Any, List from urllib.parse import urlparse import pytest from playwright.async_api import ( Browser, BrowserContext, Error, JSHandle, Page, Playwright, ) from tests.server import Server from tests.utils import TARGET_CLOSED_ERROR_MESSAGE from .utils import Utils async def test_page_event_should_create_new_context(browser: Browser) -> None: assert len(browser.contexts) == 0 context = await browser.new_context() assert len(browser.contexts) == 1 assert context in browser.contexts await context.close() assert len(browser.contexts) == 0 assert context.browser == browser async def test_window_open_should_use_parent_tab_context(browser: Browser, server: Server) -> None: context = await browser.new_context() page = await context.new_page() await page.goto(server.EMPTY_PAGE) async with page.expect_popup() as page_info: await page.evaluate("url => window.open(url)", server.EMPTY_PAGE) popup = await page_info.value assert popup.context == context await context.close() async def test_page_event_should_isolate_localStorage_and_cookies( browser: Browser, server: Server ) -> None: # Create two incognito contexts. context1 = await browser.new_context() context2 = await browser.new_context() assert len(context1.pages) == 0 assert len(context2.pages) == 0 # Create a page in first incognito context. page1 = await context1.new_page() await page1.goto(server.EMPTY_PAGE) await page1.evaluate( """() => { localStorage.setItem('name', 'page1') document.cookie = 'name=page1' }""" ) assert len(context1.pages) == 1 assert len(context2.pages) == 0 # Create a page in second incognito context. page2 = await context2.new_page() await page2.goto(server.EMPTY_PAGE) await page2.evaluate( """() => { localStorage.setItem('name', 'page2') document.cookie = 'name=page2' }""" ) assert len(context1.pages) == 1 assert len(context2.pages) == 1 assert context1.pages[0] == page1 assert context2.pages[0] == page2 # Make sure pages don't share localstorage or cookies. assert await page1.evaluate("localStorage.getItem('name')") == "page1" assert await page1.evaluate("document.cookie") == "name=page1" assert await page2.evaluate("localStorage.getItem('name')") == "page2" assert await page2.evaluate("document.cookie") == "name=page2" # Cleanup contexts. await asyncio.gather(context1.close(), context2.close()) assert browser.contexts == [] @pytest.mark.skip(reason="Not supported by Camoufox (WIP)") async def test_page_event_should_propagate_default_viewport_to_the_page( browser: Browser, utils: Utils ) -> None: context = await browser.new_context(viewport={"width": 456, "height": 789}) page = await context.new_page() await utils.verify_viewport(page, 456, 789) await context.close() async def test_page_event_should_respect_device_scale_factor(browser: Browser) -> None: context = await browser.new_context(device_scale_factor=3) page = await context.new_page() assert await page.evaluate("window.devicePixelRatio") == 3 await context.close() async def test_page_event_should_not_allow_device_scale_factor_with_null_viewport( browser: Browser, ) -> None: with pytest.raises(Error) as exc_info: await browser.new_context(no_viewport=True, device_scale_factor=1) assert ( exc_info.value.message == 'Browser.new_context: "deviceScaleFactor" option is not supported with null "viewport"' ) async def test_page_event_should_not_allow_is_mobile_with_null_viewport( browser: Browser, ) -> None: with pytest.raises(Error) as exc_info: await browser.new_context(no_viewport=True, is_mobile=True) assert ( exc_info.value.message == 'Browser.new_context: "isMobile" option is not supported with null "viewport"' ) async def test_close_should_work_for_empty_context(browser: Browser) -> None: context = await browser.new_context() await context.close() async def test_close_should_abort_wait_for_event(browser: Browser) -> None: context = await browser.new_context() with pytest.raises(Error) as exc_info: async with context.expect_page(): await context.close() assert TARGET_CLOSED_ERROR_MESSAGE in exc_info.value.message async def test_close_should_be_callable_twice(browser: Browser) -> None: context = await browser.new_context() await asyncio.gather( context.close(), context.close(), ) await context.close() @pytest.mark.skip(reason="Not supported by Camoufox") async def test_user_agent_should_work(browser: Browser, server: Server) -> None: async def baseline() -> None: context = await browser.new_context() page = await context.new_page() assert "Mozilla" in await page.evaluate("navigator.userAgent") await context.close() await baseline() async def override() -> None: context = await browser.new_context(user_agent="foobar") page = await context.new_page() [request, _] = await asyncio.gather( server.wait_for_request("/empty.html"), page.goto(server.EMPTY_PAGE), ) assert request.getHeader("user-agent") == "foobar" await context.close() await override() async def test_user_agent_should_work_for_subframes( browser: Browser, server: Server, utils: Utils ) -> None: context = await browser.new_context(user_agent="foobar") page = await context.new_page() [request, _] = await asyncio.gather( server.wait_for_request("/empty.html"), utils.attach_frame(page, "frame1", server.EMPTY_PAGE), ) assert request.getHeader("user-agent") == "foobar" await context.close() @pytest.mark.skip(reason="Not supported by Camoufox") async def test_user_agent_should_emulate_device_user_agent( playwright: Playwright, browser: Browser, server: Server ) -> None: context = await browser.new_context(user_agent=playwright.devices["iPhone 6"]["user_agent"]) page = await context.new_page() await page.goto(server.PREFIX + "/mobile.html") assert "iPhone" in await page.evaluate("navigator.userAgent") await context.close() async def test_user_agent_should_make_a_copy_of_default_options( browser: Browser, server: Server ) -> None: options: Any = {"user_agent": "foobar"} context = await browser.new_context(**options) options["user_agent"] = "wrong" page = await context.new_page() [request, _] = await asyncio.gather( server.wait_for_request("/empty.html"), page.goto(server.EMPTY_PAGE), ) assert request.getHeader("user-agent") == "foobar" await context.close() @pytest.mark.skip(reason="Not supported by Camoufox") async def test_page_event_should_bypass_csp_meta_tag(browser: Browser, server: Server) -> None: async def baseline() -> None: context = await browser.new_context() page = await context.new_page() await page.goto(server.PREFIX + "/csp.html") try: await page.add_script_tag(content="window.__injected = 42;") except Error: pass assert await page.evaluate("window.__injected") is None await context.close() await baseline() # By-pass CSP and try one more time. async def override() -> None: context = await browser.new_context(bypass_csp=True) page = await context.new_page() await page.goto(server.PREFIX + "/csp.html") await page.add_script_tag(content="window.__injected = 42;") assert await page.evaluate("() => window.__injected") == 42 await context.close() await override() @pytest.mark.skip(reason="Not supported by Camoufox") async def test_page_event_should_bypass_csp_header(browser: Browser, server: Server) -> None: # Make sure CSP prohibits add_script_tag. server.set_csp("/empty.html", 'default-src "self"') async def baseline() -> None: context = await browser.new_context() page = await context.new_page() await page.goto(server.EMPTY_PAGE) try: await page.add_script_tag(content="window.__injected = 42;") except Error: pass assert await page.evaluate("() => window.__injected") is None await context.close() await baseline() # By-pass CSP and try one more time. async def override() -> None: context = await browser.new_context(bypass_csp=True) page = await context.new_page() await page.goto(server.EMPTY_PAGE) await page.add_script_tag(content="window.__injected = 42;") assert await page.evaluate("window.__injected") == 42 await context.close() await override() @pytest.mark.skip(reason="Not supported by Camoufox") async def test_page_event_should_bypass_after_cross_process_navigation( browser: Browser, server: Server ) -> None: context = await browser.new_context(bypass_csp=True) page = await context.new_page() await page.goto(server.PREFIX + "/csp.html") await page.add_script_tag(content="window.__injected = 42;") assert await page.evaluate("window.__injected") == 42 await page.goto(server.CROSS_PROCESS_PREFIX + "/csp.html") await page.add_script_tag(content="window.__injected = 42;") assert await page.evaluate("window.__injected") == 42 await context.close() @pytest.mark.skip(reason="Not supported by Camoufox") async def test_page_event_should_bypass_csp_in_iframes_as_well( browser: Browser, server: Server, utils: Utils ) -> None: async def baseline() -> None: # Make sure CSP prohibits add_script_tag in an iframe. context = await browser.new_context() page = await context.new_page() await page.goto(server.EMPTY_PAGE) frame = await utils.attach_frame(page, "frame1", server.PREFIX + "/csp.html") try: await frame.add_script_tag(content="window.__injected = 42;") except Error: pass assert await frame.evaluate("window.__injected") is None await context.close() await baseline() # By-pass CSP and try one more time. async def override() -> None: context = await browser.new_context(bypass_csp=True) page = await context.new_page() await page.goto(server.EMPTY_PAGE) frame = await utils.attach_frame(page, "frame1", server.PREFIX + "/csp.html") try: await frame.add_script_tag(content="window.__injected = 42;") except Error: pass assert await frame.evaluate("window.__injected") == 42 await context.close() await override() @pytest.mark.skip(reason="Not supported by Camoufox") async def test_csp_should_work(browser: Browser, is_webkit: bool) -> None: async def baseline() -> None: context = await browser.new_context(java_script_enabled=False) page = await context.new_page() await page.goto('data:text/html, ') with pytest.raises(Error) as exc_info: await page.evaluate("something") if is_webkit: assert "Can't find variable: something" in exc_info.value.message else: assert "something is not defined" in exc_info.value.message await context.close() await baseline() async def override() -> None: context = await browser.new_context() page = await context.new_page() await page.goto('data:text/html, ') assert await page.evaluate("something") == "forbidden" await context.close() await override() async def test_csp_should_be_able_to_navigate_after_disabling_javascript( browser: Browser, server: Server ) -> None: context = await browser.new_context(java_script_enabled=False) page = await context.new_page() await page.goto(server.EMPTY_PAGE) await context.close() async def test_pages_should_return_all_of_the_pages( context: BrowserContext, server: Server ) -> None: page = await context.new_page() second = await context.new_page() all_pages = context.pages assert len(all_pages) == 2 assert page in all_pages assert second in all_pages async def test_pages_should_close_all_belonging_pages_once_closing_context( context: BrowserContext, ) -> None: await context.new_page() assert len(context.pages) == 1 await context.close() assert context.pages == [] async def test_expose_binding_should_work(context: BrowserContext) -> None: binding_source = [] def binding(source: Any, a: int, b: int) -> int: binding_source.append(source) return a + b await context.expose_binding("add", lambda source, a, b: binding(source, a, b)) page = await context.new_page() result = await page.evaluate("add(5, 6)") assert binding_source[0]["context"] == context assert binding_source[0]["page"] == page assert binding_source[0]["frame"] == page.main_frame assert result == 11 async def test_expose_function_should_work(context: BrowserContext) -> None: await context.expose_function("add", lambda a, b: a + b) page = await context.new_page() await page.expose_function("mul", lambda a, b: a * b) await context.expose_function("sub", lambda a, b: a - b) result = await page.evaluate( """async function() { return { mul: await mul(9, 4), add: await add(9, 4), sub: await sub(9, 4) } }""" ) assert result == {"mul": 36, "add": 13, "sub": 5} async def test_expose_function_should_throw_for_duplicate_registrations( context: BrowserContext, server: Server ) -> None: await context.expose_function("foo", lambda: None) await context.expose_function("bar", lambda: None) with pytest.raises(Error) as exc_info: await context.expose_function("foo", lambda: None) assert exc_info.value.message == 'Function "foo" has been already registered' page = await context.new_page() with pytest.raises(Error) as exc_info: await page.expose_function("foo", lambda: None) assert ( exc_info.value.message == 'Function "foo" has been already registered in the browser context' ) await page.expose_function("baz", lambda: None) with pytest.raises(Error) as exc_info: await context.expose_function("baz", lambda: None) assert ( exc_info.value.message == 'Function "baz" has been already registered in one of the pages' ) @pytest.mark.skip(reason="Not supported by Camoufox") async def test_expose_function_should_be_callable_from_inside_add_init_script( context: BrowserContext, server: Server ) -> None: args = [] await context.expose_function("woof", lambda arg: args.append(arg)) await context.add_init_script("woof('context')") page = await context.new_page() await page.evaluate("undefined") assert args == ["context"] args = [] await page.add_init_script("woof('page')") await page.reload() assert args == ["context", "page"] async def test_expose_bindinghandle_should_work(context: BrowserContext) -> None: targets: List[JSHandle] = [] def logme(t: JSHandle) -> int: targets.append(t) return 17 page = await context.new_page() await page.expose_binding("logme", lambda source, t: logme(t), handle=True) result = await page.evaluate("logme({ foo: 42 })") assert (await targets[0].evaluate("x => x.foo")) == 42 assert result == 17 async def test_auth_should_fail_without_credentials( context: BrowserContext, server: Server ) -> None: server.set_auth("/empty.html", "user", "pass") page = await context.new_page() response = await page.goto(server.EMPTY_PAGE) assert response assert response.status == 401 async def test_auth_should_work_with_correct_credentials(browser: Browser, server: Server) -> None: server.set_auth("/empty.html", "user", "pass") context = await browser.new_context(http_credentials={"username": "user", "password": "pass"}) page = await context.new_page() response = await page.goto(server.EMPTY_PAGE) assert response assert response.status == 200 await context.close() async def test_auth_should_fail_with_wrong_credentials(browser: Browser, server: Server) -> None: server.set_auth("/empty.html", "user", "pass") context = await browser.new_context(http_credentials={"username": "foo", "password": "bar"}) page = await context.new_page() response = await page.goto(server.EMPTY_PAGE) assert response assert response.status == 401 await context.close() async def test_auth_should_return_resource_body(browser: Browser, server: Server) -> None: server.set_auth("/playground.html", "user", "pass") context = await browser.new_context(http_credentials={"username": "user", "password": "pass"}) page = await context.new_page() response = await page.goto(server.PREFIX + "/playground.html") assert response assert response.status == 200 assert await page.title() == "Playground" assert "Playground" in await response.text() await context.close() async def test_should_work_with_correct_credentials_and_matching_origin( browser: Browser, server: Server ) -> None: server.set_auth("/empty.html", "user", "pass") context = await browser.new_context( http_credentials={ "username": "user", "password": "pass", "origin": server.PREFIX, } ) page = await context.new_page() response = await page.goto(server.EMPTY_PAGE) assert response assert response.status == 200 await context.close() async def test_should_work_with_correct_credentials_and_matching_origin_case_insensitive( browser: Browser, server: Server ) -> None: server.set_auth("/empty.html", "user", "pass") context = await browser.new_context( http_credentials={ "username": "user", "password": "pass", "origin": server.PREFIX.upper(), } ) page = await context.new_page() response = await page.goto(server.EMPTY_PAGE) assert response assert response.status == 200 await context.close() async def test_should_fail_with_correct_credentials_and_mismatching_scheme( browser: Browser, server: Server ) -> None: server.set_auth("/empty.html", "user", "pass") context = await browser.new_context( http_credentials={ "username": "user", "password": "pass", "origin": server.PREFIX.replace("http://", "https://"), } ) page = await context.new_page() response = await page.goto(server.EMPTY_PAGE) assert response assert response.status == 401 await context.close() async def test_should_fail_with_correct_credentials_and_mismatching_hostname( browser: Browser, server: Server ) -> None: server.set_auth("/empty.html", "user", "pass") hostname = urlparse(server.PREFIX).hostname assert hostname origin = server.PREFIX.replace(hostname, "mismatching-hostname") context = await browser.new_context( http_credentials={"username": "user", "password": "pass", "origin": origin} ) page = await context.new_page() response = await page.goto(server.EMPTY_PAGE) assert response assert response.status == 401 await context.close() async def test_should_fail_with_correct_credentials_and_mismatching_port( browser: Browser, server: Server ) -> None: server.set_auth("/empty.html", "user", "pass") origin = server.PREFIX.replace(str(server.PORT), str(server.PORT + 1)) context = await browser.new_context( http_credentials={"username": "user", "password": "pass", "origin": origin} ) page = await context.new_page() response = await page.goto(server.EMPTY_PAGE) assert response assert response.status == 401 await context.close() async def test_offline_should_work_with_initial_option( browser: Browser, server: Server, browser_name: str, ) -> None: context = await browser.new_context(offline=True) page = await context.new_page() frame_navigated_task = asyncio.create_task(page.wait_for_event("framenavigated")) with pytest.raises(Error) as exc_info: await page.goto(server.EMPTY_PAGE) if browser_name == "firefox": await frame_navigated_task assert exc_info.value await context.set_offline(False) response = await page.goto(server.EMPTY_PAGE) assert response assert response.status == 200 await context.close() async def test_offline_should_emulate_navigator_online( context: BrowserContext, server: Server ) -> None: page = await context.new_page() assert await page.evaluate("window.navigator.onLine") await context.set_offline(True) assert await page.evaluate("window.navigator.onLine") is False await context.set_offline(False) assert await page.evaluate("window.navigator.onLine") async def test_page_event_should_have_url(context: BrowserContext, server: Server) -> None: page = await context.new_page() async with context.expect_page() as other_page_info: await page.evaluate("url => window.open(url)", server.EMPTY_PAGE) other_page = await other_page_info.value assert other_page.url == server.EMPTY_PAGE async def test_page_event_should_have_url_after_domcontentloaded( context: BrowserContext, server: Server ) -> None: page = await context.new_page() async with context.expect_page() as other_page_info: await page.evaluate("url => window.open(url)", server.EMPTY_PAGE) other_page = await other_page_info.value await other_page.wait_for_load_state("domcontentloaded") assert other_page.url == server.EMPTY_PAGE async def test_page_event_should_have_about_blank_url_with_domcontentloaded( context: BrowserContext, server: Server ) -> None: page = await context.new_page() async with context.expect_page() as other_page_info: await page.evaluate("url => window.open(url)", "about:blank") other_page = await other_page_info.value await other_page.wait_for_load_state("domcontentloaded") assert other_page.url == "about:blank" async def test_page_event_should_have_about_blank_for_empty_url_with_domcontentloaded( context: BrowserContext, server: Server ) -> None: page = await context.new_page() async with context.expect_page() as other_page_info: await page.evaluate("window.open()") other_page = await other_page_info.value await other_page.wait_for_load_state("domcontentloaded") assert other_page.url == "about:blank" async def test_page_event_should_report_when_a_new_page_is_created_and_closed( context: BrowserContext, server: Server ) -> None: page = await context.new_page() async with context.expect_page() as page_info: await page.evaluate("url => window.open(url)", server.CROSS_PROCESS_PREFIX + "/empty.html") other_page = await page_info.value # The url is about:blank in FF when 'page' event is fired. assert server.CROSS_PROCESS_PREFIX in other_page.url assert await other_page.evaluate("['Hello', 'world'].join(' ')") == "Hello world" assert await other_page.query_selector("body") all_pages = context.pages assert page in all_pages assert other_page in all_pages close_event_received = [] other_page.once("close", lambda _: close_event_received.append(True)) await other_page.close() assert close_event_received == [True] all_pages = context.pages assert page in all_pages assert other_page not in all_pages async def test_page_event_should_report_initialized_pages( context: BrowserContext, server: Server ) -> None: async with context.expect_page() as page_info: await context.new_page() new_page = await page_info.value assert new_page.url == "about:blank" async with context.expect_page() as popup_info: await new_page.evaluate("window.open('about:blank')") popup = await popup_info.value assert popup.url == "about:blank" async def test_page_event_should_have_an_opener(context: BrowserContext, server: Server) -> None: page = await context.new_page() await page.goto(server.EMPTY_PAGE) async with context.expect_page() as page_info: await page.goto(server.PREFIX + "/popup/window-open.html") popup = await page_info.value assert popup.url == server.PREFIX + "/popup/popup.html" assert await popup.opener() == page assert await page.opener() is None async def test_page_event_should_fire_page_lifecycle_events( context: BrowserContext, server: Server ) -> None: events: List[str] = [] def handle_page(page: Page) -> None: events.append("CREATED: " + page.url) page.on("close", lambda _: events.append("DESTROYED: " + page.url)) context.on("page", handle_page) page = await context.new_page() await page.goto(server.EMPTY_PAGE) await page.close() assert events == ["CREATED: about:blank", f"DESTROYED: {server.EMPTY_PAGE}"] @pytest.mark.skip_browser("webkit") async def test_page_event_should_work_with_shift_clicking( context: BrowserContext, server: Server ) -> None: # WebKit: Shift+Click does not open a new window. page = await context.new_page() await page.goto(server.EMPTY_PAGE) await page.set_content('yo') async with context.expect_page() as page_info: await page.click("a", modifiers=["Shift"]) popup = await page_info.value assert await popup.opener() is None @pytest.mark.only_browser("chromium") async def test_page_event_should_work_with_ctrl_clicking( context: BrowserContext, server: Server ) -> None: # Firefox: reports an opener in this case. # WebKit: Ctrl+Click does not open a new tab. page = await context.new_page() await page.goto(server.EMPTY_PAGE) await page.set_content('yo') async with context.expect_page() as popup_info: await page.click("a", modifiers=["ControlOrMeta"]) popup = await popup_info.value assert await popup.opener() is None async def test_strict_selectors_on_context(browser: Browser, server: Server) -> None: context = await browser.new_context(strict_selectors=True) page = await context.new_page() await page.goto(server.EMPTY_PAGE) await page.set_content( """ """ ) with pytest.raises(Error): await page.text_content("button") with pytest.raises(Error): await page.query_selector("button") await context.close() @pytest.mark.skip_browser("webkit") # https://bugs.webkit.org/show_bug.cgi?id=225281 async def test_should_support_forced_colors(browser: Browser) -> None: context = await browser.new_context(forced_colors="active") page = await context.new_page() assert await page.evaluate("matchMedia('(forced-colors: active)').matches") assert not await page.evaluate("matchMedia('(forced-colors: none)').matches")