Skip to content

Commit 8c5cb54

Browse files
authored
tests: added headful tests (#139)
1 parent 38ab7fb commit 8c5cb54

File tree

8 files changed

+257
-0
lines changed

8 files changed

+257
-0
lines changed

.github/workflows/ci.yml

+4
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,11 @@ jobs:
7676
- name: Install
7777
run: python -m playwright install
7878
- name: Test
79+
if: matrix.os != 'ubuntu-latest'
7980
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
81+
- name: Test
82+
if: matrix.os == 'ubuntu-latest'
83+
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
8084
- name: Coveralls
8185
run: coveralls
8286
env:

playwright/async_api.py

+4
Original file line numberDiff line numberDiff line change
@@ -5716,6 +5716,7 @@ async def launchPersistentContext(
57165716
hasTouch: bool = None,
57175717
colorScheme: Literal["light", "dark", "no-preference"] = None,
57185718
acceptDownloads: bool = None,
5719+
chromiumSandbox: bool = None,
57195720
) -> "BrowserContext":
57205721
"""BrowserType.launchPersistentContext
57215722
@@ -5784,6 +5785,8 @@ async def launchPersistentContext(
57845785
Emulates `'prefers-colors-scheme'` media feature, supported values are `'light'`, `'dark'`, `'no-preference'`. See page.emulateMedia(options) for more details. Defaults to '`light`'.
57855786
acceptDownloads : Optional[bool]
57865787
Whether to automatically download all the attachments. Defaults to `false` where all the downloads are canceled.
5788+
chromiumSandbox : Optional[bool]
5789+
Enable Chromium sandboxing. Defaults to `true`.
57875790
57885791
Returns
57895792
-------
@@ -5823,6 +5826,7 @@ async def launchPersistentContext(
58235826
hasTouch=hasTouch,
58245827
colorScheme=colorScheme,
58255828
acceptDownloads=acceptDownloads,
5829+
chromiumSandbox=chromiumSandbox,
58265830
)
58275831
)
58285832

playwright/browser_context.py

+2
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,8 @@ async def newPage(self) -> Page:
109109
async def cookies(self, urls: Union[str, List[str]] = None) -> List[Cookie]:
110110
if urls is None:
111111
urls = []
112+
if not isinstance(urls, list):
113+
urls = [urls]
112114
return await self._channel.send("cookies", dict(urls=urls))
113115

114116
async def addCookies(self, cookies: List[Cookie]) -> None:

playwright/browser_type.py

+3
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
from pathlib import Path
1516
from typing import Dict, List
1617

1718
from playwright.browser import Browser
@@ -119,7 +120,9 @@ async def launchPersistentContext(
119120
hasTouch: bool = None,
120121
colorScheme: ColorScheme = None,
121122
acceptDownloads: bool = None,
123+
chromiumSandbox: bool = None,
122124
) -> BrowserContext:
125+
userDataDir = str(Path(userDataDir))
123126
try:
124127
return from_channel(
125128
await self._channel.send(

playwright/sync_api.py

+4
Original file line numberDiff line numberDiff line change
@@ -5952,6 +5952,7 @@ def launchPersistentContext(
59525952
hasTouch: bool = None,
59535953
colorScheme: Literal["light", "dark", "no-preference"] = None,
59545954
acceptDownloads: bool = None,
5955+
chromiumSandbox: bool = None,
59555956
) -> "BrowserContext":
59565957
"""BrowserType.launchPersistentContext
59575958
@@ -6020,6 +6021,8 @@ def launchPersistentContext(
60206021
Emulates `'prefers-colors-scheme'` media feature, supported values are `'light'`, `'dark'`, `'no-preference'`. See page.emulateMedia(options) for more details. Defaults to '`light`'.
60216022
acceptDownloads : Optional[bool]
60226023
Whether to automatically download all the attachments. Defaults to `false` where all the downloads are canceled.
6024+
chromiumSandbox : Optional[bool]
6025+
Enable Chromium sandboxing. Defaults to `true`.
60236026
60246027
Returns
60256028
-------
@@ -6060,6 +6063,7 @@ def launchPersistentContext(
60606063
hasTouch=hasTouch,
60616064
colorScheme=colorScheme,
60626065
acceptDownloads=acceptDownloads,
6066+
chromiumSandbox=chromiumSandbox,
60636067
)
60646068
)
60656069
)

scripts/update_api.sh

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#!/bin/bash
2+
3+
function update_api {
4+
echo "Generating $1"
5+
file_name="$1"
6+
generate_script="$2"
7+
git checkout HEAD -- "$file_name"
8+
9+
python "$generate_script" > .x
10+
11+
mv .x "$file_name"
12+
pre-commit run --files $file_name
13+
}
14+
15+
update_api "playwright/sync_api.py" "scripts/generate_sync_api.py"
16+
update_api "playwright/async_api.py" "scripts/generate_async_api.py"
17+
18+
echo "Regenerated APIs"

tests/async/test_headful.py

+195
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
# Copyright (c) Microsoft Corporation.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License")
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http:#www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
16+
import asyncio
17+
18+
import pytest
19+
20+
21+
async def test_should_have_default_url_when_launching_browser(
22+
browser_type, launch_arguments, tmpdir
23+
):
24+
browser_context = await browser_type.launchPersistentContext(
25+
tmpdir, **{**launch_arguments, "headless": False}
26+
)
27+
urls = [page.url for page in browser_context.pages]
28+
assert urls == ["about:blank"]
29+
await browser_context.close()
30+
31+
32+
async def test_headless_should_be_able_to_read_cookies_written_by_headful(
33+
browser_type, launch_arguments, server, tmpdir, is_chromium, is_win
34+
):
35+
if is_chromium and is_win:
36+
pytest.skip("see https://github.com/microsoft/playwright/issues/717")
37+
# Write a cookie in headful chrome
38+
headful_context = await browser_type.launchPersistentContext(
39+
tmpdir, **{**launch_arguments, "headless": False}
40+
)
41+
headful_page = await headful_context.newPage()
42+
await headful_page.goto(server.EMPTY_PAGE)
43+
await headful_page.evaluate(
44+
"""() => document.cookie = 'foo=true; expires=Fri, 31 Dec 9999 23:59:59 GMT'"""
45+
)
46+
await headful_context.close()
47+
# Read the cookie from headless chrome
48+
headless_context = await browser_type.launchPersistentContext(
49+
tmpdir, **{**launch_arguments, "headless": True}
50+
)
51+
headless_page = await headless_context.newPage()
52+
await headless_page.goto(server.EMPTY_PAGE)
53+
cookie = await headless_page.evaluate("() => document.cookie")
54+
await headless_context.close()
55+
# This might throw. See https://github.com/GoogleChrome/puppeteer/issues/2778
56+
assert cookie == "foo=true"
57+
58+
59+
async def test_should_close_browser_with_beforeunload_page(
60+
browser_type, launch_arguments, server, tmpdir
61+
):
62+
browser_context = await browser_type.launchPersistentContext(
63+
tmpdir, **{**launch_arguments, "headless": False}
64+
)
65+
page = await browser_context.newPage()
66+
await page.goto(server.PREFIX + "/beforeunload.html")
67+
# We have to interact with a page so that 'beforeunload' handlers
68+
# fire.
69+
await page.click("body")
70+
await browser_context.close()
71+
72+
73+
async def test_should_not_crash_when_creating_second_context(
74+
browser_type, launch_arguments, server
75+
):
76+
browser = await browser_type.launch(**{**launch_arguments, "headless": False})
77+
browser_context = await browser.newContext()
78+
await browser_context.newPage()
79+
await browser_context.close()
80+
browser_context = await browser.newContext()
81+
await browser_context.newPage()
82+
await browser_context.close()
83+
await browser.close()
84+
85+
86+
async def test_should_click_background_tab(browser_type, launch_arguments, server):
87+
browser = await browser_type.launch(**{**launch_arguments, "headless": False})
88+
page = await browser.newPage()
89+
await page.setContent(
90+
'<button>Hello</button><a target=_blank href="${server.EMPTY_PAGE}">empty.html</a>'
91+
)
92+
await page.click("a")
93+
await page.click("button")
94+
await browser.close()
95+
96+
97+
async def test_should_close_browser_after_context_menu_was_triggered(
98+
browser_type, launch_arguments, server
99+
):
100+
browser = await browser_type.launch(**{**launch_arguments, "headless": False})
101+
page = await browser.newPage()
102+
await page.goto(server.PREFIX + "/grid.html")
103+
await page.click("body", button="right")
104+
await browser.close()
105+
106+
107+
async def test_should_not_block_third_party_cookies(
108+
browser_type, launch_arguments, server, is_chromium, is_firefox
109+
):
110+
browser = await browser_type.launch(**{**launch_arguments, "headless": False})
111+
page = await browser.newPage()
112+
await page.goto(server.EMPTY_PAGE)
113+
await page.evaluate(
114+
"""src => {
115+
let fulfill;
116+
const promise = new Promise(x => fulfill = x);
117+
const iframe = document.createElement('iframe');
118+
document.body.appendChild(iframe);
119+
iframe.onload = fulfill;
120+
iframe.src = src;
121+
return promise;
122+
}""",
123+
server.CROSS_PROCESS_PREFIX + "/grid.html",
124+
)
125+
document_cookie = await page.frames[1].evaluate(
126+
"""() => {
127+
document.cookie = 'username=John Doe';
128+
return document.cookie;
129+
}"""
130+
)
131+
132+
await page.waitForTimeout(2000)
133+
allowsThirdParty = is_chromium or is_firefox
134+
assert document_cookie == ("username=John Doe" if allowsThirdParty else "")
135+
cookies = await page.context.cookies(server.CROSS_PROCESS_PREFIX + "/grid.html")
136+
if allowsThirdParty:
137+
assert cookies == [
138+
{
139+
"domain": "127.0.0.1",
140+
"expires": -1,
141+
"httpOnly": False,
142+
"name": "username",
143+
"path": "/",
144+
"sameSite": "None",
145+
"secure": False,
146+
"value": "John Doe",
147+
}
148+
]
149+
else:
150+
assert cookies == []
151+
152+
await browser.close()
153+
154+
155+
@pytest.mark.skip_browser("webkit")
156+
async def test_should_not_override_viewport_size_when_passed_null(
157+
browser_type, launch_arguments, server
158+
):
159+
# Our WebKit embedder does not respect window features.
160+
browser = await browser_type.launch(**{**launch_arguments, "headless": False})
161+
context = await browser.newContext(viewport=0)
162+
page = await context.newPage()
163+
await page.goto(server.EMPTY_PAGE)
164+
[popup, _] = await asyncio.gather(
165+
page.waitForEvent("popup"),
166+
page.evaluate(
167+
"""() => {
168+
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');
169+
win.resizeTo(500, 450);
170+
}"""
171+
),
172+
)
173+
await popup.waitForLoadState()
174+
await popup.waitForFunction(
175+
"""() => window.outerWidth === 500 && window.outerHeight === 450"""
176+
)
177+
await context.close()
178+
await browser.close()
179+
180+
181+
async def test_page_bring_to_front_should_work(browser_type, launch_arguments):
182+
browser = await browser_type.launch(**{**launch_arguments, "headless": False})
183+
page1 = await browser.newPage()
184+
await page1.setContent("Page1")
185+
page2 = await browser.newPage()
186+
await page2.setContent("Page2")
187+
188+
await page1.bringToFront()
189+
assert await page1.evaluate("document.visibilityState") == "visible"
190+
assert await page2.evaluate("document.visibilityState") == "visible"
191+
192+
await page2.bringToFront()
193+
assert await page1.evaluate("document.visibilityState") == "visible"
194+
assert await page2.evaluate("document.visibilityState") == "visible"
195+
await browser.close()

tests/server.py

+27
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,21 @@
1414

1515
import abc
1616
import asyncio
17+
import contextlib
1718
import gzip
1819
import mimetypes
1920
import socket
2021
import threading
2122
from contextlib import closing
2223
from http import HTTPStatus
2324

25+
import greenlet
2426
from OpenSSL import crypto
2527
from twisted.internet import reactor, ssl
2628
from twisted.web import http
2729

2830
from playwright.path_utils import get_file_dirname
31+
from playwright.sync_base import dispatcher_fiber
2932

3033
_dirname = get_file_dirname()
3134

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

142+
@contextlib.contextmanager
143+
def expect_request(self, path):
144+
future = asyncio.create_task(self.wait_for_request(path))
145+
146+
class CallbackValue:
147+
def __init__(self) -> None:
148+
self._value = None
149+
150+
@property
151+
def value(self):
152+
return self._value
153+
154+
g_self = greenlet.getcurrent()
155+
cb_wrapper = CallbackValue()
156+
157+
def done_cb(task):
158+
cb_wrapper._value = future.result()
159+
g_self.switch()
160+
161+
future.add_done_callback(done_cb)
162+
yield cb_wrapper
163+
while not future.done():
164+
dispatcher_fiber.switch()
165+
139166
def set_auth(self, path: str, username: str, password: str):
140167
self.auth[path] = (username, password)
141168

0 commit comments

Comments
 (0)