# 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 Optional, cast import pytest from playwright.async_api import Browser, ElementHandle, Error, FloatRect, Page from tests.server import Server from .utils import Utils async def test_bounding_box(page: Page, server: Server) -> None: await page.set_viewport_size({"width": 500, "height": 500}) await page.goto(server.PREFIX + "/grid.html") element_handle = await page.query_selector(".box:nth-of-type(13)") assert element_handle box = await element_handle.bounding_box() assert box == {"x": 100, "y": 50, "width": 50, "height": 50} async def test_bounding_box_handle_nested_frames(page: Page, server: Server) -> None: await page.set_viewport_size({"width": 500, "height": 500}) await page.goto(server.PREFIX + "/frames/nested-frames.html") nested_frame = page.frame(name="dos") assert nested_frame element_handle = await nested_frame.query_selector("div") assert element_handle box = await element_handle.bounding_box() assert box == {"x": 24, "y": 224, "width": 268, "height": 18} async def test_bounding_box_return_null_for_invisible_elements(page: Page, server: Server) -> None: await page.set_content('
hi
') element = await page.query_selector("div") assert element assert await element.bounding_box() is None async def test_bounding_box_force_a_layout(page: Page, server: Server) -> None: await page.set_viewport_size({"width": 500, "height": 500}) await page.set_content('
hello
') element_handle = await page.query_selector("div") assert element_handle await page.evaluate('element => element.style.height = "200px"', element_handle) box = await element_handle.bounding_box() assert box == {"x": 8, "y": 8, "width": 100, "height": 200} async def test_bounding_box_with_SVG_nodes(page: Page, server: Server) -> None: await page.set_content( """ """ ) element = await page.query_selector("#therect") assert element pw_bounding_box = await element.bounding_box() web_bounding_box = await page.evaluate( """e => { rect = e.getBoundingClientRect() return {x: rect.x, y: rect.y, width: rect.width, height: rect.height} }""", element, ) assert pw_bounding_box == web_bounding_box @pytest.mark.skip_browser("firefox") async def test_bounding_box_with_page_scale(browser: Browser, server: Server) -> None: context = await browser.new_context(viewport={"width": 400, "height": 400}, is_mobile=True) page = await context.new_page() await page.goto(server.PREFIX + "/input/button.html") button = await page.query_selector("button") assert button await button.evaluate( """button => { document.body.style.margin = '0' button.style.borderWidth = '0' button.style.width = '200px' button.style.height = '20px' button.style.marginLeft = '17px' button.style.marginTop = '23px' }""" ) box = await button.bounding_box() assert box assert round(box["x"] * 100) == 17 * 100 assert round(box["y"] * 100) == 23 * 100 assert round(box["width"] * 100) == 200 * 100 assert round(box["height"] * 100) == 20 * 100 await context.close() async def test_bounding_box_when_inline_box_child_is_outside_of_viewport( page: Page, ) -> None: await page.set_content( """ woofdoggo """ ) handle = await page.query_selector("span") assert handle box = await handle.bounding_box() web_bounding_box = await handle.evaluate( """e => { rect = e.getBoundingClientRect(); return {x: rect.x, y: rect.y, width: rect.width, height: rect.height}; }""" ) def roundbox(b: Optional[FloatRect]) -> FloatRect: assert b return { "x": round(b["x"] * 100), "y": round(b["y"] * 100), "width": round(b["width"] * 100), "height": round(b["height"] * 100), } assert roundbox(box) == roundbox(web_bounding_box) async def test_content_frame(page: Page, server: Server, utils: Utils) -> None: await page.goto(server.EMPTY_PAGE) await utils.attach_frame(page, "frame1", server.EMPTY_PAGE) element_handle = await page.query_selector("#frame1") assert element_handle frame = await element_handle.content_frame() assert frame == page.frames[1] async def test_content_frame_for_non_iframes(page: Page, server: Server, utils: Utils) -> None: await page.goto(server.EMPTY_PAGE) await utils.attach_frame(page, "frame1", server.EMPTY_PAGE) frame = page.frames[1] element_handle = cast(ElementHandle, await frame.evaluate_handle("document.body")) assert await element_handle.content_frame() is None async def test_content_frame_for_document_element(page: Page, server: Server, utils: Utils) -> None: await page.goto(server.EMPTY_PAGE) await utils.attach_frame(page, "frame1", server.EMPTY_PAGE) frame = page.frames[1] element_handle = cast(ElementHandle, await frame.evaluate_handle("document.documentElement")) assert await element_handle.content_frame() is None async def test_owner_frame(page: Page, server: Server, utils: Utils) -> None: await page.goto(server.EMPTY_PAGE) await utils.attach_frame(page, "frame1", server.EMPTY_PAGE) frame = page.frames[1] element_handle = cast(ElementHandle, await frame.evaluate_handle("document.body")) assert await element_handle.owner_frame() == frame async def test_owner_frame_for_cross_process_iframes( page: Page, server: Server, utils: Utils ) -> None: await page.goto(server.EMPTY_PAGE) await utils.attach_frame(page, "frame1", server.CROSS_PROCESS_PREFIX + "/empty.html") frame = page.frames[1] element_handle = cast(ElementHandle, await frame.evaluate_handle("document.body")) assert await element_handle.owner_frame() == frame async def test_owner_frame_for_document(page: Page, server: Server, utils: Utils) -> None: await page.goto(server.EMPTY_PAGE) await utils.attach_frame(page, "frame1", server.EMPTY_PAGE) frame = page.frames[1] element_handle = cast(ElementHandle, await frame.evaluate_handle("document")) assert await element_handle.owner_frame() == frame async def test_owner_frame_for_iframe_elements(page: Page, server: Server, utils: Utils) -> None: await page.goto(server.EMPTY_PAGE) await utils.attach_frame(page, "frame1", server.EMPTY_PAGE) frame = page.main_frame element_handle = cast( ElementHandle, await frame.evaluate_handle('document.querySelector("#frame1")') ) assert await element_handle.owner_frame() == frame async def test_owner_frame_for_cross_frame_evaluations( page: Page, server: Server, utils: Utils ) -> None: await page.goto(server.EMPTY_PAGE) await utils.attach_frame(page, "frame1", server.EMPTY_PAGE) frame = page.main_frame element_handle = cast( ElementHandle, await frame.evaluate_handle( 'document.querySelector("#frame1").contentWindow.document.body' ), ) assert await element_handle.owner_frame() == frame.child_frames[0] async def test_owner_frame_for_detached_elements(page: Page, server: Server) -> None: await page.goto(server.EMPTY_PAGE) div_handle = cast( ElementHandle, await page.evaluate_handle( """() => { div = document.createElement('div'); document.body.appendChild(div); return div; }""" ), ) assert div_handle assert await div_handle.owner_frame() == page.main_frame await page.evaluate( """() => { div = document.querySelector('div') document.body.removeChild(div) }""" ) assert await div_handle.owner_frame() == page.main_frame async def test_owner_frame_for_adopted_elements(page: Page, server: Server) -> None: await page.goto(server.EMPTY_PAGE) async with page.expect_popup() as popup_info: await page.evaluate("url => window.__popup = window.open(url)", server.EMPTY_PAGE) popup = await popup_info.value div_handle = cast( ElementHandle, await page.evaluate_handle( """() => { div = document.createElement('div'); document.body.appendChild(div); return div; }""" ), ) assert div_handle assert await div_handle.owner_frame() == page.main_frame await popup.wait_for_load_state("domcontentloaded") await page.evaluate( """() => { div = document.querySelector('div'); window.__popup.document.body.appendChild(div); }""" ) assert await div_handle.owner_frame() == popup.main_frame async def test_click(page: Page, server: Server) -> None: await page.goto(server.PREFIX + "/input/button.html") button = await page.query_selector("button") assert button await button.click() assert await page.evaluate("result") == "Clicked" async def test_click_with_node_removed(page: Page, server: Server) -> None: await page.goto(server.PREFIX + "/input/button.html") await page.evaluate('delete window["Node"]') button = await page.query_selector("button") assert button await button.click() assert await page.evaluate("result") == "Clicked" async def test_click_for_shadow_dom_v1(page: Page, server: Server) -> None: await page.goto(server.PREFIX + "/shadow.html") button_handle = cast(ElementHandle, await page.evaluate_handle("button")) await button_handle.click() assert await page.evaluate("clicked") async def test_click_for_TextNodes(page: Page, server: Server) -> None: await page.goto(server.PREFIX + "/input/button.html") buttonTextNode = cast( ElementHandle, await page.evaluate_handle('document.querySelector("button").firstChild'), ) await buttonTextNode.click() assert await page.evaluate("result") == "Clicked" async def test_click_throw_for_detached_nodes(page: Page, server: Server) -> None: await page.goto(server.PREFIX + "/input/button.html") button = await page.query_selector("button") assert button await page.evaluate("button => button.remove()", button) with pytest.raises(Error) as exc_info: await button.click() assert "Element is not attached to the DOM" in exc_info.value.message async def test_click_throw_for_hidden_nodes_with_force(page: Page, server: Server) -> None: await page.goto(server.PREFIX + "/input/button.html") button = await page.query_selector("button") assert button await page.evaluate('button => button.style.display = "none"', button) with pytest.raises(Error) as exc_info: await button.click(force=True) assert "Element is not visible" in exc_info.value.message async def test_click_throw_for_recursively_hidden_nodes_with_force( page: Page, server: Server ) -> None: await page.goto(server.PREFIX + "/input/button.html") button = await page.query_selector("button") assert button await page.evaluate('button => button.parentElement.style.display = "none"', button) with pytest.raises(Error) as exc_info: await button.click(force=True) assert "Element is not visible" in exc_info.value.message async def test_click_throw_for__br__elements_with_force(page: Page, server: Server) -> None: await page.set_content("hello
goodbye") br = await page.query_selector("br") assert br with pytest.raises(Error) as exc_info: await br.click(force=True) assert "Element is outside of the viewport" in exc_info.value.message async def test_double_click_the_button(page: Page, server: Server) -> None: await page.goto(server.PREFIX + "/input/button.html") await page.evaluate( """() => { window.double = false; button = document.querySelector('button'); button.addEventListener('dblclick', event => { window.double = true; }); }""" ) button = await page.query_selector("button") assert button await button.dblclick() assert await page.evaluate("double") assert await page.evaluate("result") == "Clicked" async def test_hover(page: Page, server: Server) -> None: await page.goto(server.PREFIX + "/input/scrollable.html") button = await page.query_selector("#button-6") assert button await button.hover() assert await page.evaluate('document.querySelector("button:hover").id') == "button-6" async def test_hover_when_node_is_removed(page: Page, server: Server) -> None: await page.goto(server.PREFIX + "/input/scrollable.html") await page.evaluate('delete window["Node"]') button = await page.query_selector("#button-6") assert button await button.hover() assert await page.evaluate('document.querySelector("button:hover").id') == "button-6" async def test_scroll(page: Page, server: Server) -> None: await page.goto(server.PREFIX + "/offscreenbuttons.html") for i in range(11): button = await page.query_selector(f"#btn{i}") assert button before = await button.evaluate( """button => { return button.getBoundingClientRect().right - window.innerWidth }""" ) assert before == 10 * i await button.scroll_into_view_if_needed() after = await button.evaluate( """button => { return button.getBoundingClientRect().right - window.innerWidth }""" ) assert after <= 0 await page.evaluate("() => window.scrollTo(0, 0)") async def test_scroll_should_throw_for_detached_element(page: Page, server: Server) -> None: await page.set_content("
Hello
") div = await page.query_selector("div") assert div await div.evaluate("div => div.remove()") with pytest.raises(Error) as exc_info: await div.scroll_into_view_if_needed() assert "Element is not attached to the DOM" in exc_info.value.message async def waiting_helper(page: Page, after: str) -> None: div = await page.query_selector("div") assert div done = [] async def scroll() -> None: done.append(False) await div.scroll_into_view_if_needed() done.append(True) promise = asyncio.create_task(scroll()) await asyncio.sleep(0) # execute scheduled tasks, but don't await them await page.evaluate("() => new Promise(f => setTimeout(f, 1000))") assert done == [False] await div.evaluate(after) await promise assert done == [False, True] async def test_should_wait_for_display_none_to_become_visible(page: Page) -> None: await page.set_content('
Hello
') await waiting_helper(page, 'div => div.style.display = "block"') async def test_should_work_for_visibility_hidden_element(page: Page) -> None: await page.set_content('
Hello
') div = await page.query_selector("div") assert div await div.scroll_into_view_if_needed() async def test_should_work_for_zero_sized_element(page: Page) -> None: await page.set_content('
Hello
') div = await page.query_selector("div") assert div await div.scroll_into_view_if_needed() async def test_should_wait_for_nested_display_none_to_become_visible( page: Page, ) -> None: await page.set_content('
Hello
') await waiting_helper(page, 'div => div.parentElement.style.display = "block"') async def test_should_timeout_waiting_for_visible(page: Page) -> None: await page.set_content('
Hello
') div = await page.query_selector("div") assert div with pytest.raises(Error) as exc_info: await div.scroll_into_view_if_needed(timeout=3000) assert "element is not visible" in exc_info.value.message assert "retrying scroll into view action" in exc_info.value.message async def test_fill_input(page: Page, server: Server) -> None: await page.goto(server.PREFIX + "/input/textarea.html") handle = await page.query_selector("input") assert handle await handle.fill("some value") assert await page.evaluate("result") == "some value" async def test_fill_input_when_Node_is_removed(page: Page, server: Server) -> None: await page.goto(server.PREFIX + "/input/textarea.html") await page.evaluate('delete window["Node"]') handle = await page.query_selector("input") assert handle await handle.fill("some value") assert await page.evaluate("result") == "some value" async def test_select_textarea( page: Page, server: Server, is_firefox: bool, is_webkit: bool ) -> None: await page.goto(server.PREFIX + "/input/textarea.html") textarea = await page.query_selector("textarea") assert textarea await textarea.evaluate('textarea => textarea.value = "some value"') await textarea.select_text() if is_firefox or is_webkit: assert await textarea.evaluate("el => el.selectionStart") == 0 assert await textarea.evaluate("el => el.selectionEnd") == 10 else: assert await page.evaluate("() => window.getSelection().toString()") == "some value" async def test_select_input(page: Page, server: Server, is_firefox: bool, is_webkit: bool) -> None: await page.goto(server.PREFIX + "/input/textarea.html") input = await page.query_selector("input") assert input await input.evaluate('input => input.value = "some value"') await input.select_text() if is_firefox or is_webkit: assert await input.evaluate("el => el.selectionStart") == 0 assert await input.evaluate("el => el.selectionEnd") == 10 else: assert await page.evaluate("() => window.getSelection().toString()") == "some value" async def test_select_text_select_plain_div(page: Page, server: Server) -> None: await page.goto(server.PREFIX + "/input/textarea.html") div = await page.query_selector("div.plain") assert div await div.select_text() assert await page.evaluate("() => window.getSelection().toString()") == "Plain div" async def test_select_text_timeout_waiting_for_invisible_element( page: Page, server: Server ) -> None: await page.goto(server.PREFIX + "/input/textarea.html") textarea = await page.query_selector("textarea") assert textarea await textarea.evaluate('e => e.style.display = "none"') with pytest.raises(Error) as exc_info: await textarea.select_text(timeout=3000) assert "element is not visible" in exc_info.value.message async def test_select_text_wait_for_visible(page: Page, server: Server) -> None: await page.goto(server.PREFIX + "/input/textarea.html") textarea = await page.query_selector("textarea") assert textarea await textarea.evaluate('textarea => textarea.value = "some value"') await textarea.evaluate('e => e.style.display = "none"') done = [] async def select_text() -> None: done.append(False) await textarea.select_text(timeout=3000) done.append(True) promise = asyncio.create_task(select_text()) await asyncio.sleep(0) # execute scheduled tasks, but don't await them await page.evaluate("() => new Promise(f => setTimeout(f, 1000))") await textarea.evaluate('e => e.style.display = "block"') await promise assert done == [False, True] async def test_a_nice_preview(page: Page, server: Server) -> None: await page.goto(f"{server.PREFIX}/dom.html") outer = await page.query_selector("#outer") inner = await page.query_selector("#inner") assert inner check = await page.query_selector("#check") text = await inner.evaluate_handle("e => e.firstChild") await page.evaluate("1") # Give them a chance to calculate the preview. assert str(outer) == 'JSHandle@
' assert str(inner) == 'JSHandle@
Text,↵more text
' assert str(text) == "JSHandle@#text=Text,↵more text" assert str(check) == 'JSHandle@' async def test_get_attribute(page: Page, server: Server) -> None: await page.goto(f"{server.PREFIX}/dom.html") handle = await page.query_selector("#outer") assert handle assert await handle.get_attribute("name") == "value" assert await page.get_attribute("#outer", "name") == "value" async def test_inner_html(page: Page, server: Server) -> None: await page.goto(f"{server.PREFIX}/dom.html") handle = await page.query_selector("#outer") assert handle assert await handle.inner_html() == '
Text,\nmore text
' assert await page.inner_html("#outer") == '
Text,\nmore text
' async def test_inner_text(page: Page, server: Server) -> None: await page.goto(f"{server.PREFIX}/dom.html") handle = await page.query_selector("#inner") assert handle assert await handle.inner_text() == "Text, more text" assert await page.inner_text("#inner") == "Text, more text" async def test_inner_text_should_throw(page: Page, server: Server) -> None: await page.set_content("text") with pytest.raises(Error) as exc_info1: await page.inner_text("svg") assert " Node is not an HTMLElement" in exc_info1.value.message handle = await page.query_selector("svg") assert handle with pytest.raises(Error) as exc_info2: await handle.inner_text() assert " Node is not an HTMLElement" in exc_info2.value.message async def test_text_content(page: Page, server: Server) -> None: await page.goto(f"{server.PREFIX}/dom.html") handle = await page.query_selector("#inner") assert handle assert await handle.text_content() == "Text,\nmore text" assert await page.text_content("#inner") == "Text,\nmore text" async def test_check_the_box(page: Page) -> None: await page.set_content('') input = await page.query_selector("input") assert input await input.check() assert await page.evaluate("checkbox.checked") async def test_uncheck_the_box(page: Page) -> None: await page.set_content('') input = await page.query_selector("input") assert input await input.uncheck() assert await page.evaluate("checkbox.checked") is False async def test_select_single_option(page: Page, server: Server) -> None: await page.goto(server.PREFIX + "/input/select.html") select = await page.query_selector("select") assert select await select.select_option(value="blue") assert await page.evaluate("result.onInput") == ["blue"] assert await page.evaluate("result.onChange") == ["blue"] async def test_focus_a_button(page: Page, server: Server) -> None: await page.goto(server.PREFIX + "/input/button.html") button = await page.query_selector("button") assert button assert await button.evaluate("button => document.activeElement === button") is False await button.focus() assert await button.evaluate("button => document.activeElement === button") async def test_is_visible_and_is_hidden_should_work(page: Page) -> None: await page.set_content("
Hi
") div = await page.query_selector("div") assert div assert await div.is_visible() assert await div.is_hidden() is False assert await page.is_visible("div") assert await page.is_hidden("div") is False span = await page.query_selector("span") assert span assert await span.is_visible() is False assert await span.is_hidden() assert await page.is_visible("span") is False assert await page.is_hidden("span") async def test_is_enabled_and_is_disabled_should_work(page: Page) -> None: await page.set_content( """
div
""" ) div = await page.query_selector("div") assert div assert await div.is_enabled() assert await div.is_disabled() is False assert await page.is_enabled("div") assert await page.is_disabled("div") is False button1 = await page.query_selector(":text('button1')") assert button1 assert await button1.is_enabled() is False assert await button1.is_disabled() assert await page.is_enabled(":text('button1')") is False assert await page.is_disabled(":text('button1')") button2 = await page.query_selector(":text('button2')") assert button2 assert await button2.is_enabled() assert await button2.is_disabled() is False assert await page.is_enabled(":text('button2')") assert await page.is_disabled(":text('button2')") is False async def test_is_editable_should_work(page: Page) -> None: await page.set_content("") await page.eval_on_selector("textarea", "t => t.readOnly = true") input1 = await page.query_selector("#input1") assert input1 assert await input1.is_editable() is False assert await page.is_editable("#input1") is False input2 = await page.query_selector("#input2") assert input2 assert await input2.is_editable() assert await page.is_editable("#input2") textarea = await page.query_selector("textarea") assert textarea assert await textarea.is_editable() is False assert await page.is_editable("textarea") is False async def test_is_checked_should_work(page: Page) -> None: await page.set_content('
Not a checkbox
') handle = await page.query_selector("input") assert handle assert await handle.is_checked() assert await page.is_checked("input") await handle.evaluate("input => input.checked = false") assert await handle.is_checked() is False assert await page.is_checked("input") is False with pytest.raises(Error) as exc_info: await page.is_checked("div") assert "Not a checkbox or radio button" in exc_info.value.message async def test_input_value(page: Page, server: Server) -> None: await page.goto(server.PREFIX + "/input/textarea.html") element = await page.query_selector("input") assert element await element.fill("my-text-content") assert await element.input_value() == "my-text-content" await element.fill("") assert await element.input_value() == "" async def test_set_checked(page: Page) -> None: await page.set_content("``") input = await page.query_selector("input") assert input await input.set_checked(True) assert await page.evaluate("checkbox.checked") await input.set_checked(False) assert await page.evaluate("checkbox.checked") is False async def test_should_allow_disposing_twice(page: Page) -> None: await page.set_content("
39
") element = await page.query_selector("section") assert element await element.dispose() await element.dispose()