Skip to content

Commit 8154cc7

Browse files
authored
test: added worker tests (#62)
1 parent 9d127d8 commit 8154cc7

File tree

6 files changed

+226
-2
lines changed

6 files changed

+226
-2
lines changed

playwright/page.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -648,6 +648,7 @@ async def waitForFunction(
648648
force_expr = True
649649
return await self._main_frame.waitForFunction(**locals_to_params(locals()))
650650

651+
@property
651652
def workers(self) -> List[Worker]:
652653
return self._workers.copy()
653654

playwright/worker.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,25 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
from typing import Any, Dict
16+
17+
from types import SimpleNamespace
1518
from playwright.connection import ChannelOwner, ConnectionScope, from_channel
1619
from playwright.js_handle import JSHandle, parse_result, serialize_argument
17-
from typing import Any, Dict
20+
from playwright.helper import is_function_body
1821

1922

2023
class Worker(ChannelOwner):
24+
Events = SimpleNamespace(Close="close")
25+
2126
def __init__(self, scope: ConnectionScope, guid: str, initializer: Dict) -> None:
2227
super().__init__(scope, guid, initializer)
28+
self._channel.on("close", lambda _: self._on_close())
29+
30+
def _on_close(self) -> None:
31+
if self._page:
32+
self._page._workers.remove(self)
33+
self.emit(Worker.Events.Close, self)
2334

2435
@property
2536
def url(self) -> str:
@@ -28,6 +39,8 @@ def url(self) -> str:
2839
async def evaluate(
2940
self, expression: str, arg: Any = None, force_expr: bool = False
3041
) -> Any:
42+
if not is_function_body(expression):
43+
force_expr = True
3144
return parse_result(
3245
await self._channel.send(
3346
"evaluateExpression",

tests/assets/worker/worker.html

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<title>Worker test</title>
5+
</head>
6+
<body>
7+
<script>
8+
var worker = new Worker('worker.js');
9+
worker.onmessage = function(message) {
10+
console.log(message.data);
11+
};
12+
</script>
13+
</body>
14+
</html>

tests/assets/worker/worker.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
console.log('hello from the worker');
2+
3+
function workerFunction() {
4+
return 'worker function result';
5+
}
6+
7+
self.addEventListener('message', event => {
8+
console.log('got this data: ' + event.data);
9+
});
10+
11+
(async function() {
12+
while (true) {
13+
self.postMessage(workerFunction.toString());
14+
await new Promise(x => setTimeout(x, 100));
15+
}
16+
})();

tests/test_network.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ async def test_page_events_request_should_fire_for_fetches(page, server):
9191
assert len(requests) == 2
9292

9393

94-
@pytest.mark.skip_platform("win32") # TODO: needs to be investigated
94+
@pytest.mark.skip("sometimes hanging") # TODO: needs to be investigated
9595
async def test_page_events_request_should_report_requests_and_responses_handled_by_service_worker(
9696
page: Page, server
9797
):

tests/test_worker.py

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
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+
from asyncio.futures import Future
17+
from playwright.worker import Worker
18+
import pytest
19+
from playwright import Error
20+
from playwright.page import Page
21+
22+
23+
async def test_workers_page_workers(page, server):
24+
await asyncio.gather(
25+
page.waitForEvent("worker"), page.goto(server.PREFIX + "/worker/worker.html")
26+
)
27+
worker = page.workers[0]
28+
assert "worker.js" in worker.url
29+
30+
assert (
31+
await worker.evaluate('() => self["workerFunction"]()')
32+
== "worker function result"
33+
)
34+
35+
await page.goto(server.EMPTY_PAGE)
36+
assert len(page.workers) == 0
37+
38+
39+
async def test_workers_should_emit_created_and_destroyed_events(page: Page):
40+
worker_createdpromise = asyncio.ensure_future(page.waitForEvent("worker"))
41+
worker_obj = await page.evaluateHandle(
42+
"() => new Worker(URL.createObjectURL(new Blob(['1'], {type: 'application/javascript'})))"
43+
)
44+
worker = await worker_createdpromise
45+
worker_this_obj = await worker.evaluateHandle("() => this")
46+
worker_destroyed_promise: Future[Worker] = asyncio.Future()
47+
worker.once("close", lambda w: worker_destroyed_promise.set_result(w))
48+
await page.evaluate("workerObj => workerObj.terminate()", worker_obj)
49+
assert await worker_destroyed_promise == worker
50+
with pytest.raises(Error) as exc:
51+
await worker_this_obj.getProperty("self")
52+
assert "Most likely the worker has been closed." in exc.value.message
53+
54+
55+
async def test_workers_should_report_console_logs(page):
56+
[message, _] = await asyncio.gather(
57+
page.waitForEvent("console"),
58+
page.evaluate(
59+
'() => new Worker(URL.createObjectURL(new Blob(["console.log(1)"], {type: "application/javascript"})))'
60+
),
61+
)
62+
assert message.text == "1"
63+
64+
65+
@pytest.mark.skip_browser("firefox") # TODO: investigate further @pavelfeldman
66+
async def test_workers_should_have_JSHandles_for_console_logs(page):
67+
log_promise = asyncio.Future()
68+
page.on("console", lambda m: log_promise.set_result(m))
69+
await page.evaluate(
70+
"() => new Worker(URL.createObjectURL(new Blob(['console.log(1,2,3,this)'], {type: 'application/javascript'})))"
71+
)
72+
log = await log_promise
73+
assert log.text == "1 2 3 JSHandle@object"
74+
assert len(log.args) == 4
75+
assert await (await log.args[3].getProperty("origin")).jsonValue() == "null"
76+
77+
78+
async def test_workers_should_evaluate(page):
79+
worker_createdpromise = asyncio.ensure_future(page.waitForEvent("worker"))
80+
await page.evaluate(
81+
"() => new Worker(URL.createObjectURL(new Blob(['console.log(1)'], {type: 'application/javascript'})))"
82+
)
83+
worker = await worker_createdpromise
84+
assert await worker.evaluate("1+1") == 2
85+
86+
87+
async def test_workers_should_report_errors(page):
88+
error_promise = asyncio.Future()
89+
page.on("pageerror", lambda e: error_promise.set_result(e))
90+
await page.evaluate(
91+
"""() => new Worker(URL.createObjectURL(new Blob([`
92+
setTimeout(() => {
93+
// Do a console.log just to check that we do not confuse it with an error.
94+
console.log('hey');
95+
throw new Error('this is my error');
96+
})
97+
`], {type: 'application/javascript'})))"""
98+
)
99+
error_log = await error_promise
100+
assert "this is my error" in error_log.message
101+
102+
103+
async def test_workers_should_clear_upon_navigation(server, page):
104+
await page.goto(server.EMPTY_PAGE)
105+
worker_createdpromise = asyncio.ensure_future(page.waitForEvent("worker"))
106+
await page.evaluate(
107+
'() => new Worker(URL.createObjectURL(new Blob(["console.log(1)"], {type: "application/javascript"})))'
108+
)
109+
worker = await worker_createdpromise
110+
assert len(page.workers) == 1
111+
destroyed = []
112+
worker.once("close", lambda _: destroyed.append(True))
113+
await page.goto(server.PREFIX + "/one-style.html")
114+
assert destroyed
115+
assert len(page.workers) == 0
116+
117+
118+
async def test_workers_should_clear_upon_cross_process_navigation(server, page):
119+
await page.goto(server.EMPTY_PAGE)
120+
worker_createdpromise = asyncio.ensure_future(page.waitForEvent("worker"))
121+
await page.evaluate(
122+
"() => new Worker(URL.createObjectURL(new Blob(['console.log(1)'], {type: 'application/javascript'})))"
123+
)
124+
worker = await worker_createdpromise
125+
assert len(page.workers) == 1
126+
destroyed = []
127+
worker.once("close", lambda _: destroyed.append(True))
128+
await page.goto(server.CROSS_PROCESS_PREFIX + "/empty.html")
129+
assert destroyed
130+
assert len(page.workers) == 0
131+
132+
133+
async def test_workers_should_report_network_activity(page, server):
134+
[worker, _] = await asyncio.gather(
135+
page.waitForEvent("worker"), page.goto(server.PREFIX + "/worker/worker.html"),
136+
)
137+
url = server.PREFIX + "/one-style.css"
138+
request_promise = asyncio.ensure_future(page.waitForRequest(url))
139+
response_promise = asyncio.ensure_future(page.waitForResponse(url))
140+
await worker.evaluate(
141+
"url => fetch(url).then(response => response.text()).then(console.log)", url
142+
)
143+
request = await request_promise
144+
response = await response_promise
145+
assert request.url == url
146+
assert response.request == request
147+
assert response.ok
148+
149+
150+
async def test_workers_should_report_network_activity_on_worker_creation(page, server):
151+
# Chromium needs waitForDebugger enabled for this one.
152+
await page.goto(server.EMPTY_PAGE)
153+
url = server.PREFIX + "/one-style.css"
154+
request_promise = asyncio.ensure_future(page.waitForRequest(url))
155+
response_promise = asyncio.ensure_future(page.waitForResponse(url))
156+
await page.evaluate(
157+
"""url => new Worker(URL.createObjectURL(new Blob([`
158+
fetch("${url}").then(response => response.text()).then(console.log);
159+
`], {type: 'application/javascript'})))""",
160+
url,
161+
)
162+
request = await request_promise
163+
response = await response_promise
164+
assert request.url == url
165+
assert response.request == request
166+
assert response.ok
167+
168+
169+
async def test_workers_should_format_number_using_context_locale(browser, server):
170+
context = await browser.newContext(locale="ru-RU")
171+
page = await context.newPage()
172+
await page.goto(server.EMPTY_PAGE)
173+
[worker, _] = await asyncio.gather(
174+
page.waitForEvent("worker"),
175+
page.evaluate(
176+
"() => new Worker(URL.createObjectURL(new Blob(['console.log(1)'], {type: 'application/javascript'})))"
177+
),
178+
)
179+
assert await worker.evaluate("() => (10000.20).toLocaleString()") == "10\u00A0000,2"
180+
await context.close()

0 commit comments

Comments
 (0)