Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,11 @@ jobs:
- name: Install
run: python -m playwright install
- name: Test
if: matrix.os != 'ubuntu-latest'
run: pytest -vv --browser=${{ matrix.browser }} --junitxml=junit/test-results-${{ matrix.os }}-${{ matrix.python-version }}-${{ matrix.browser }}.xml --cov=playwright --cov=scripts --cov-report xml --timeout 30
- name: Test
if: matrix.os == 'ubuntu-latest'
run: xvfb-run pytest -vv --browser=${{ matrix.browser }} --junitxml=junit/test-results-${{ matrix.os }}-${{ matrix.python-version }}-${{ matrix.browser }}.xml --cov=playwright --cov=scripts --cov-report xml --timeout 30
- name: Coveralls
run: coveralls
env:
Expand Down
4 changes: 4 additions & 0 deletions playwright/async_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -5716,6 +5716,7 @@ async def launchPersistentContext(
hasTouch: bool = None,
colorScheme: Literal["light", "dark", "no-preference"] = None,
acceptDownloads: bool = None,
chromiumSandbox: bool = None,
) -> "BrowserContext":
"""BrowserType.launchPersistentContext

Expand Down Expand Up @@ -5784,6 +5785,8 @@ async def launchPersistentContext(
Emulates `'prefers-colors-scheme'` media feature, supported values are `'light'`, `'dark'`, `'no-preference'`. See page.emulateMedia(options) for more details. Defaults to '`light`'.
acceptDownloads : Optional[bool]
Whether to automatically download all the attachments. Defaults to `false` where all the downloads are canceled.
chromiumSandbox : Optional[bool]
Enable Chromium sandboxing. Defaults to `true`.

Returns
-------
Expand Down Expand Up @@ -5823,6 +5826,7 @@ async def launchPersistentContext(
hasTouch=hasTouch,
colorScheme=colorScheme,
acceptDownloads=acceptDownloads,
chromiumSandbox=chromiumSandbox,
)
)

Expand Down
2 changes: 2 additions & 0 deletions playwright/browser_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@ async def newPage(self) -> Page:
async def cookies(self, urls: Union[str, List[str]] = None) -> List[Cookie]:
if urls is None:
urls = []
if not isinstance(urls, list):
urls = [urls]
return await self._channel.send("cookies", dict(urls=urls))

async def addCookies(self, cookies: List[Cookie]) -> None:
Expand Down
3 changes: 3 additions & 0 deletions playwright/browser_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.

from pathlib import Path
from typing import Dict, List

from playwright.browser import Browser
Expand Down Expand Up @@ -119,7 +120,9 @@ async def launchPersistentContext(
hasTouch: bool = None,
colorScheme: ColorScheme = None,
acceptDownloads: bool = None,
chromiumSandbox: bool = None,
) -> BrowserContext:
userDataDir = str(Path(userDataDir))
try:
return from_channel(
await self._channel.send(
Expand Down
4 changes: 4 additions & 0 deletions playwright/sync_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -5952,6 +5952,7 @@ def launchPersistentContext(
hasTouch: bool = None,
colorScheme: Literal["light", "dark", "no-preference"] = None,
acceptDownloads: bool = None,
chromiumSandbox: bool = None,
) -> "BrowserContext":
"""BrowserType.launchPersistentContext

Expand Down Expand Up @@ -6020,6 +6021,8 @@ def launchPersistentContext(
Emulates `'prefers-colors-scheme'` media feature, supported values are `'light'`, `'dark'`, `'no-preference'`. See page.emulateMedia(options) for more details. Defaults to '`light`'.
acceptDownloads : Optional[bool]
Whether to automatically download all the attachments. Defaults to `false` where all the downloads are canceled.
chromiumSandbox : Optional[bool]
Enable Chromium sandboxing. Defaults to `true`.

Returns
-------
Expand Down Expand Up @@ -6060,6 +6063,7 @@ def launchPersistentContext(
hasTouch=hasTouch,
colorScheme=colorScheme,
acceptDownloads=acceptDownloads,
chromiumSandbox=chromiumSandbox,
)
)
)
Expand Down
18 changes: 18 additions & 0 deletions scripts/update_api.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#!/bin/bash

function update_api {
echo "Generating $1"
file_name="$1"
generate_script="$2"
git checkout HEAD -- "$file_name"

python "$generate_script" > .x

mv .x "$file_name"
pre-commit run --files $file_name
}

update_api "playwright/sync_api.py" "scripts/generate_sync_api.py"
update_api "playwright/async_api.py" "scripts/generate_async_api.py"

echo "Regenerated APIs"
195 changes: 195 additions & 0 deletions tests/async/test_headful.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
# 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

import pytest


async def test_should_have_default_url_when_launching_browser(
browser_type, launch_arguments, tmpdir
):
browser_context = await browser_type.launchPersistentContext(
tmpdir, **{**launch_arguments, "headless": False}
)
urls = [page.url for page in browser_context.pages]
assert urls == ["about:blank"]
await browser_context.close()


async def test_headless_should_be_able_to_read_cookies_written_by_headful(
browser_type, launch_arguments, server, tmpdir, is_chromium, is_win
):
if is_chromium and is_win:
pytest.skip("see https://github.com/microsoft/playwright/issues/717")
# Write a cookie in headful chrome
headful_context = await browser_type.launchPersistentContext(
tmpdir, **{**launch_arguments, "headless": False}
)
headful_page = await headful_context.newPage()
await headful_page.goto(server.EMPTY_PAGE)
await headful_page.evaluate(
"""() => document.cookie = 'foo=true; expires=Fri, 31 Dec 9999 23:59:59 GMT'"""
)
await headful_context.close()
# Read the cookie from headless chrome
headless_context = await browser_type.launchPersistentContext(
tmpdir, **{**launch_arguments, "headless": True}
)
headless_page = await headless_context.newPage()
await headless_page.goto(server.EMPTY_PAGE)
cookie = await headless_page.evaluate("() => document.cookie")
await headless_context.close()
# This might throw. See https://github.com/GoogleChrome/puppeteer/issues/2778
assert cookie == "foo=true"


async def test_should_close_browser_with_beforeunload_page(
browser_type, launch_arguments, server, tmpdir
):
browser_context = await browser_type.launchPersistentContext(
tmpdir, **{**launch_arguments, "headless": False}
)
page = await browser_context.newPage()
await page.goto(server.PREFIX + "/beforeunload.html")
# We have to interact with a page so that 'beforeunload' handlers
# fire.
await page.click("body")
await browser_context.close()


async def test_should_not_crash_when_creating_second_context(
browser_type, launch_arguments, server
):
browser = await browser_type.launch(**{**launch_arguments, "headless": False})
browser_context = await browser.newContext()
await browser_context.newPage()
await browser_context.close()
browser_context = await browser.newContext()
await browser_context.newPage()
await browser_context.close()
await browser.close()


async def test_should_click_background_tab(browser_type, launch_arguments, server):
browser = await browser_type.launch(**{**launch_arguments, "headless": False})
page = await browser.newPage()
await page.setContent(
'<button>Hello</button><a target=_blank href="${server.EMPTY_PAGE}">empty.html</a>'
)
await page.click("a")
await page.click("button")
await browser.close()


async def test_should_close_browser_after_context_menu_was_triggered(
browser_type, launch_arguments, server
):
browser = await browser_type.launch(**{**launch_arguments, "headless": False})
page = await browser.newPage()
await page.goto(server.PREFIX + "/grid.html")
await page.click("body", button="right")
await browser.close()


async def test_should_not_block_third_party_cookies(
browser_type, launch_arguments, server, is_chromium, is_firefox
):
browser = await browser_type.launch(**{**launch_arguments, "headless": False})
page = await browser.newPage()
await page.goto(server.EMPTY_PAGE)
await page.evaluate(
"""src => {
let fulfill;
const promise = new Promise(x => fulfill = x);
const iframe = document.createElement('iframe');
document.body.appendChild(iframe);
iframe.onload = fulfill;
iframe.src = src;
return promise;
}""",
server.CROSS_PROCESS_PREFIX + "/grid.html",
)
document_cookie = await page.frames[1].evaluate(
"""() => {
document.cookie = 'username=John Doe';
return document.cookie;
}"""
)

await page.waitForTimeout(2000)
allowsThirdParty = is_chromium or is_firefox
assert document_cookie == ("username=John Doe" if allowsThirdParty else "")
cookies = await page.context.cookies(server.CROSS_PROCESS_PREFIX + "/grid.html")
if allowsThirdParty:
assert cookies == [
{
"domain": "127.0.0.1",
"expires": -1,
"httpOnly": False,
"name": "username",
"path": "/",
"sameSite": "None",
"secure": False,
"value": "John Doe",
}
]
else:
assert cookies == []

await browser.close()


@pytest.mark.skip_browser("webkit")
async def test_should_not_override_viewport_size_when_passed_null(
browser_type, launch_arguments, server
):
# Our WebKit embedder does not respect window features.
browser = await browser_type.launch(**{**launch_arguments, "headless": False})
context = await browser.newContext(viewport=0)
page = await context.newPage()
await page.goto(server.EMPTY_PAGE)
[popup, _] = await asyncio.gather(
page.waitForEvent("popup"),
page.evaluate(
"""() => {
const win = window.open(window.location.href, 'Title', 'toolbar=no,location=no,directories=no,status=no,menubar=no,scrollbars=yes,resizable=yes,width=600,height=300,top=0,left=0');
win.resizeTo(500, 450);
}"""
),
)
await popup.waitForLoadState()
await popup.waitForFunction(
"""() => window.outerWidth === 500 && window.outerHeight === 450"""
)
await context.close()
await browser.close()


async def test_page_bring_to_front_should_work(browser_type, launch_arguments):
browser = await browser_type.launch(**{**launch_arguments, "headless": False})
page1 = await browser.newPage()
await page1.setContent("Page1")
page2 = await browser.newPage()
await page2.setContent("Page2")

await page1.bringToFront()
assert await page1.evaluate("document.visibilityState") == "visible"
assert await page2.evaluate("document.visibilityState") == "visible"

await page2.bringToFront()
assert await page1.evaluate("document.visibilityState") == "visible"
assert await page2.evaluate("document.visibilityState") == "visible"
await browser.close()
27 changes: 27 additions & 0 deletions tests/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,21 @@

import abc
import asyncio
import contextlib
import gzip
import mimetypes
import socket
import threading
from contextlib import closing
from http import HTTPStatus

import greenlet
from OpenSSL import crypto
from twisted.internet import reactor, ssl
from twisted.web import http

from playwright.path_utils import get_file_dirname
from playwright.sync_base import dispatcher_fiber

_dirname = get_file_dirname()

Expand Down Expand Up @@ -136,6 +139,30 @@ async def wait_for_request(self, path):
self.request_subscribers[path] = future
return await future

@contextlib.contextmanager
def expect_request(self, path):
future = asyncio.create_task(self.wait_for_request(path))

class CallbackValue:
def __init__(self) -> None:
self._value = None

@property
def value(self):
return self._value

g_self = greenlet.getcurrent()
cb_wrapper = CallbackValue()

def done_cb(task):
cb_wrapper._value = future.result()
g_self.switch()

future.add_done_callback(done_cb)
yield cb_wrapper
while not future.done():
dispatcher_fiber.switch()

def set_auth(self, path: str, username: str, password: str):
self.auth[path] = (username, password)

Expand Down