Skip to content

Commit a1e3d58

Browse files
authored
test: browser context tests, part 1 (microsoft#59)
1 parent 4ad29a7 commit a1e3d58

File tree

6 files changed

+243
-1
lines changed

6 files changed

+243
-1
lines changed

playwright/browser_context.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ def __init__(self, scope: ConnectionScope, guid: str, initializer: Dict) -> None
5151
self._timeout_settings = TimeoutSettings(None)
5252
self._browser: Optional["Browser"] = None
5353
self._owner_page: Optional[Page] = None
54+
self._is_closed_or_closing = False
5455

5556
for channel in initializer["pages"]:
5657
page = from_channel(channel)
@@ -190,6 +191,7 @@ async def waitForEvent(
190191
)
191192

192193
def _on_close(self) -> None:
194+
self._is_closed_or_closing = True
193195
if self._browser:
194196
self._browser._contexts.remove(self)
195197

@@ -202,4 +204,7 @@ def _on_close(self) -> None:
202204
self._scope.dispose()
203205

204206
async def close(self) -> None:
207+
if self._is_closed_or_closing:
208+
return
209+
self._is_closed_or_closing = True
205210
await self._channel.send("close")

playwright/helper.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,11 @@ class ParsedMessageParams(TypedDict):
8989
initializer: Dict
9090

9191

92+
class Viewport(TypedDict):
93+
width: int
94+
height: int
95+
96+
9297
class ParsedMessagePayload(TypedDict, total=False):
9398
id: int
9499
guid: str

playwright/page.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
KeyboardModifier,
4848
DocumentLoadState,
4949
ColorScheme,
50+
Viewport,
5051
)
5152
from playwright.network import Request, Response, Route
5253
from playwright.worker import Worker
@@ -460,7 +461,7 @@ async def setViewportSize(self, width: int, height: int) -> None:
460461
"setViewportSize", dict(viewportSize=locals_to_params(locals()))
461462
)
462463

463-
def viewportSize(self) -> Optional[Dict]:
464+
def viewportSize(self) -> Optional[Viewport]:
464465
return self._viewport_size
465466

466467
async def addInitScript(self, source: str = None, path: str = None) -> None:

tests/assets/mobile.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<meta name = "viewport" content = "initial-scale = 1, user-scalable = no">

tests/test_browsercontext.py

Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
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+
import asyncio
16+
import pytest
17+
from playwright import Error, devices
18+
19+
20+
async def test_should_create_new_context(browser):
21+
assert len(browser.contexts) == 0
22+
context = await browser.newContext()
23+
assert len(browser.contexts) == 1
24+
assert context in browser.contexts
25+
await context.close()
26+
assert len(browser.contexts) == 0
27+
28+
29+
async def test_window_open_should_use_parent_tab_context(browser, server):
30+
context = await browser.newContext()
31+
page = await context.newPage()
32+
await page.goto(server.EMPTY_PAGE)
33+
[popup, _] = await asyncio.gather(
34+
page.waitForEvent("popup"),
35+
page.evaluate("url => window.open(url)", server.EMPTY_PAGE),
36+
)
37+
assert popup.context == context
38+
await context.close()
39+
40+
41+
async def test_should_isolate_localStorage_and_cookies(browser, server):
42+
# Create two incognito contexts.
43+
context1 = await browser.newContext()
44+
context2 = await browser.newContext()
45+
assert len(context1.pages) == 0
46+
assert len(context2.pages) == 0
47+
48+
# Create a page in first incognito context.
49+
page1 = await context1.newPage()
50+
await page1.goto(server.EMPTY_PAGE)
51+
await page1.evaluate(
52+
"""() => {
53+
localStorage.setItem('name', 'page1')
54+
document.cookie = 'name=page1'
55+
}"""
56+
)
57+
58+
assert len(context1.pages) == 1
59+
assert len(context2.pages) == 0
60+
61+
# Create a page in second incognito context.
62+
page2 = await context2.newPage()
63+
await page2.goto(server.EMPTY_PAGE)
64+
await page2.evaluate(
65+
"""() => {
66+
localStorage.setItem('name', 'page2')
67+
document.cookie = 'name=page2'
68+
}"""
69+
)
70+
71+
assert len(context1.pages) == 1
72+
assert len(context2.pages) == 1
73+
assert context1.pages[0] == page1
74+
assert context2.pages[0] == page2
75+
76+
# Make sure pages don't share localstorage or cookies.
77+
assert await page1.evaluate("localStorage.getItem('name')") == "page1"
78+
assert await page1.evaluate("document.cookie") == "name=page1"
79+
assert await page2.evaluate("localStorage.getItem('name')") == "page2"
80+
assert await page2.evaluate("document.cookie") == "name=page2"
81+
82+
# Cleanup contexts.
83+
await asyncio.gather(context1.close(), context2.close())
84+
assert browser.contexts == []
85+
86+
87+
async def test_should_propagate_default_viewport_to_the_page(browser, utils):
88+
context = await browser.newContext(viewport={"width": 456, "height": 789})
89+
page = await context.newPage()
90+
await utils.verify_viewport(page, 456, 789)
91+
await context.close()
92+
93+
94+
async def test_should_make_a_copy_of_default_viewport(browser, utils):
95+
viewport = {"width": 456, "height": 789}
96+
context = await browser.newContext(viewport=viewport)
97+
viewport["width"] = 567
98+
page = await context.newPage()
99+
await utils.verify_viewport(page, 456, 789)
100+
await context.close()
101+
102+
103+
async def test_should_respect_deviceScaleFactor(browser):
104+
context = await browser.newContext(deviceScaleFactor=3)
105+
page = await context.newPage()
106+
assert await page.evaluate("window.devicePixelRatio") == 3
107+
await context.close()
108+
109+
110+
async def test_should_not_allow_deviceScaleFactor_with_null_viewport(browser):
111+
with pytest.raises(Error) as exc_info:
112+
await browser.newContext(viewport=0, deviceScaleFactor=1)
113+
assert (
114+
exc_info.value.message
115+
== '"deviceScaleFactor" option is not supported with null "viewport"'
116+
)
117+
118+
119+
async def test_should_not_allow_isMobile_with_null_viewport(browser):
120+
with pytest.raises(Error) as exc_info:
121+
await browser.newContext(viewport=0, isMobile=True)
122+
assert (
123+
exc_info.value.message
124+
== '"isMobile" option is not supported with null "viewport"'
125+
)
126+
127+
128+
async def test_close_should_work_for_empty_context(browser):
129+
context = await browser.newContext()
130+
await context.close()
131+
132+
133+
async def test_close_should_abort_waitForEvent(browser):
134+
context = await browser.newContext()
135+
promise = asyncio.ensure_future(context.waitForEvent("page"))
136+
await context.close()
137+
with pytest.raises(Error) as exc_info:
138+
await promise
139+
assert "Context closed" in exc_info.value.message
140+
141+
142+
async def test_close_should_be_callable_twice(browser):
143+
context = await browser.newContext()
144+
await asyncio.gather(
145+
context.close(), context.close(),
146+
)
147+
await context.close()
148+
149+
150+
async def test_user_agent_should_work(browser, server):
151+
async def baseline():
152+
context = await browser.newContext()
153+
page = await context.newPage()
154+
assert "Mozilla" in await page.evaluate("navigator.userAgent")
155+
await context.close()
156+
157+
await baseline()
158+
159+
async def override():
160+
context = await browser.newContext(userAgent="foobar")
161+
page = await context.newPage()
162+
[request, _] = await asyncio.gather(
163+
server.wait_for_request("/empty.html"), page.goto(server.EMPTY_PAGE),
164+
)
165+
assert request.getHeader("user-agent") == "foobar"
166+
await context.close()
167+
168+
await override()
169+
170+
171+
async def test_user_agent_should_work_for_subframes(browser, server, utils):
172+
context = await browser.newContext(userAgent="foobar")
173+
page = await context.newPage()
174+
[request, _] = await asyncio.gather(
175+
server.wait_for_request("/empty.html"),
176+
utils.attach_frame(page, "frame1", server.EMPTY_PAGE),
177+
)
178+
assert request.getHeader("user-agent") == "foobar"
179+
await context.close()
180+
181+
182+
async def test_user_agent_should_emulate_device_user_agent(browser, server):
183+
context = await browser.newContext(userAgent=devices["iPhone 6"]["userAgent"])
184+
page = await context.newPage()
185+
await page.goto(server.PREFIX + "/mobile.html")
186+
assert "iPhone" in await page.evaluate("navigator.userAgent")
187+
await context.close()
188+
189+
190+
async def test_user_agent_should_make_a_copy_of_default_options(browser, server):
191+
options = {"userAgent": "foobar"}
192+
context = await browser.newContext(**options)
193+
options["userAgent"] = "wrong"
194+
page = await context.newPage()
195+
[request, _] = await asyncio.gather(
196+
server.wait_for_request("/empty.html"), page.goto(server.EMPTY_PAGE),
197+
)
198+
assert request.getHeader("user-agent") == "foobar"
199+
await context.close()
200+
201+
202+
async def test_should_bypass_csp_meta_tag(browser, server):
203+
async def baseline():
204+
context = await browser.newContext()
205+
page = await context.newPage()
206+
await page.goto(server.PREFIX + "/csp.html")
207+
with pytest.raises(Error):
208+
await page.addScriptTag(content="window.__injected = 42;")
209+
assert await page.evaluate("window.__injected") is None
210+
await context.close()
211+
212+
await baseline()
213+
214+
# By-pass CSP and try one more time.
215+
async def override():
216+
context = await browser.newContext(bypassCSP=True)
217+
page = await context.newPage()
218+
await page.goto(server.PREFIX + "/csp.html")
219+
await page.addScriptTag(content="window.__injected = 42;")
220+
assert await page.evaluate("() => window.__injected") == 42
221+
await context.close()
222+
223+
await override()

tests/utils.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
from playwright.element_handle import ElementHandle
1919
from playwright.frame import Frame
20+
from playwright.helper import Viewport
2021
from playwright.page import Page
2122

2223

@@ -53,5 +54,11 @@ def dump_frames(self, frame: Frame, indentation: str = "") -> List[str]:
5354
result = result + utils.dump_frames(child, " " + indentation)
5455
return result
5556

57+
async def verify_viewport(self, page: Page, width: int, height: int):
58+
assert cast(Viewport, page.viewportSize())["width"] == width
59+
assert cast(Viewport, page.viewportSize())["height"] == height
60+
assert await page.evaluate("window.innerWidth") == width
61+
assert await page.evaluate("window.innerHeight") == height
62+
5663

5764
utils = Utils()

0 commit comments

Comments
 (0)