Skip to content

Commit 736f80e

Browse files
authored
chore: use fibers for the sync api (#100)
1 parent 004a8fb commit 736f80e

22 files changed

+348
-139
lines changed

.github/workflows/ci.yml

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -73,13 +73,7 @@ jobs:
7373
- name: Build package
7474
run: python build_package.py
7575
- name: Test
76-
if: ${{ matrix.os == 'windows-latest' }}
77-
# pytest-xdist does not exit on Windows
78-
# https://github.com/pytest-dev/pytest-xdist/issues/60
7976
run: pytest -vv --browser=${{ matrix.browser }} --junitxml=junit/test-results-${{ matrix.os }}-${{ matrix.python-version }}-${{ matrix.browser }}.xml --cov=playwright --cov-report xml
80-
- name: Test
81-
if: ${{ matrix.os != 'windows-latest' }}
82-
run: pytest -vv --browser=${{ matrix.browser }} -n auto --junitxml=junit/test-results-${{ matrix.os }}-${{ matrix.python-version }}-${{ matrix.browser }}.xml --cov=playwright --cov-report xml
8377
- name: Coveralls
8478
run: coveralls
8579
env:

README.md

Lines changed: 52 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -48,97 +48,83 @@ Playwright is built to automate the broad and growing set of web browser capabil
4848
This code snippet navigates to whatsmyuseragent.org in Chromium, Firefox and WebKit, and saves 3 screenshots.
4949

5050
```py
51-
import asyncio
52-
from playwright import chromium, firefox, webkit
53-
54-
async def run():
55-
for browser_type in [chromium, firefox, webkit]:
56-
browser = await browser_type.launch()
57-
page = await browser.newPage()
58-
await page.goto('http://whatsmyuseragent.org/')
59-
await page.screenshot(path=f'example-{browser_type.name}.png')
60-
await browser.close()
61-
62-
asyncio.get_event_loop().run_until_complete(run())
51+
from playwright import sync_playwright
52+
53+
with sync_playwright() as p:
54+
for browser_type in [p.chromium, p.firefox, p.webkit]:
55+
browser = browser_type.launch()
56+
page = browser.newPage()
57+
page.goto('http://whatsmyuseragent.org/')
58+
page.screenshot(path=f'example-{browser_type.name}.png')
59+
browser.close()
6360
```
6461

6562
#### Mobile and geolocation
6663

6764
This snippet emulates Mobile Safari on a device at a given geolocation, navigates to maps.google.com, performs action and takes a screenshot.
6865

6966
```py
70-
import asyncio
71-
from playwright import webkit, devices
72-
73-
iphone_11 = devices['iPhone 11 Pro']
74-
print(iphone_11)
75-
76-
async def run():
77-
browser = await webkit.launch(headless=False)
78-
context = await browser.newContext(
79-
**iphone_11,
80-
locale='en-US',
81-
geolocation={ 'longitude': 12.492507, 'latitude': 41.889938 },
82-
permissions=['geolocation']
83-
)
84-
page = await context.newPage()
85-
await page.goto('https://maps.google.com')
86-
await page.click('text="Your location"')
87-
await page.waitForRequest('*preview/pwa')
88-
await page.screenshot(path='colosseum-iphone.png')
89-
await browser.close()
90-
91-
asyncio.get_event_loop().run_until_complete(run())
67+
from playwright import sync_playwright
68+
69+
with sync_playwright() as p:
70+
iphone_11 = p.devices['iPhone 11 Pro']
71+
browser = p.webkit.launch(headless=False)
72+
context = browser.newContext(
73+
**iphone_11,
74+
locale='en-US',
75+
geolocation={ 'longitude': 12.492507, 'latitude': 41.889938 },
76+
permissions=['geolocation']
77+
)
78+
page = context.newPage()
79+
page.goto('https://maps.google.com')
80+
page.click('text="Your location"')
81+
page.waitForRequest('*preview/pwa')
82+
page.screenshot(path='colosseum-iphone.png')
83+
browser.close()
9284
```
9385

9486
#### Evaluate in browser context
9587

9688
This code snippet navigates to example.com in Firefox, and executes a script in the page context.
9789

9890
```py
99-
import asyncio
100-
from playwright import firefox
101-
102-
async def run():
103-
browser = await firefox.launch()
104-
page = await browser.newPage()
105-
await page.goto('https://www.example.com/')
106-
dimensions = await page.evaluate('''() => {
107-
return {
108-
width: document.documentElement.clientWidth,
109-
height: document.documentElement.clientHeight,
110-
deviceScaleFactor: window.devicePixelRatio
111-
}
112-
}''')
113-
print(dimensions)
114-
await browser.close()
115-
116-
asyncio.get_event_loop().run_until_complete(run())
91+
from playwright import sync_playwright
92+
93+
with sync_playwright() as p:
94+
browser = p.firefox.launch()
95+
page = browser.newPage()
96+
page.goto('https://www.example.com/')
97+
dimensions = page.evaluate('''() => {
98+
return {
99+
width: document.documentElement.clientWidth,
100+
height: document.documentElement.clientHeight,
101+
deviceScaleFactor: window.devicePixelRatio
102+
}
103+
}''')
104+
print(dimensions)
105+
browser.close()
117106
```
118107

119108
#### Intercept network requests
120109

121110
This code snippet sets up request routing for a Chromium page to log all network requests.
122111

123112
```py
124-
import asyncio
125-
from playwright import chromium
113+
from playwright import sync_playwright
126114

127-
async def run():
128-
browser = await chromium.launch()
129-
page = await browser.newPage()
115+
with sync_playwright() as p:
116+
browser = p.chromium.launch()
117+
page = browser.newPage()
130118

131-
def log_and_continue_request(route, request):
132-
print(request.url)
133-
await asyncio.create_task(route.continue_())
119+
def log_and_continue_request(route, request):
120+
print(request.url)
121+
route.continue_()
134122

135-
# Log and continue all network requests
136-
await page.route('**', lambda route, request: log_and_continue_request(route, request))
123+
# Log and continue all network requests
124+
page.route('**', lambda route, request: log_and_continue_request(route, request))
137125

138-
await page.goto('http://todomvc.com')
139-
await browser.close()
140-
141-
asyncio.get_event_loop().run_until_complete(run())
126+
page.goto('http://todomvc.com')
127+
browser.close()
142128
```
143129

144130
# Is Playwright for Python ready?

client.py

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
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 playwright
16+
from playwright.sync_api import Playwright
17+
18+
19+
def main(playwright: Playwright) -> None:
20+
browser = playwright.chromium.launch(headless=False)
21+
page = browser.newPage(viewport=0)
22+
page.setContent(
23+
"<button id=button onclick=\"window.open('http://webkit.org', '_blank')\">Click me</input>"
24+
)
25+
26+
with page.expect_popup() as popup_info:
27+
page.click("#button")
28+
print(popup_info.value)
29+
30+
print("Contexts in browser: %d" % len(browser.contexts))
31+
print("Creating context...")
32+
context = browser.newContext(viewport=0)
33+
print("Contexts in browser: %d" % len(browser.contexts))
34+
print("Pages in context: %d" % len(context.pages))
35+
36+
print("\nCreating page1...")
37+
page1 = context.newPage()
38+
print("Pages in context: %d" % len(context.pages))
39+
page1.on("framenavigated", lambda frame: print("Frame navigated to %s" % frame.url))
40+
page1.on("request", lambda request: print("Request %s" % request.url))
41+
page1.on(
42+
"requestFinished", lambda request: print("Request finished %s" % request.url)
43+
)
44+
page1.on(
45+
"response",
46+
lambda response: print(
47+
"Response %s, request %s in frame %s"
48+
% (response.url, response.request.url, response.frame.url)
49+
),
50+
)
51+
print("Navigating page1 to https://example.com...")
52+
page1.goto("https://example.com")
53+
print("Page1 main frame url: %s" % page1.mainFrame.url)
54+
print("Page1 tile: %s" % page1.title())
55+
print("Frames in page1: %d" % len(page1.frames))
56+
page1.screenshot(path="example.png")
57+
58+
print("\nCreating page2...")
59+
page2 = context.newPage()
60+
page2.on("framenavigated", lambda frame: print("Frame navigated to %s" % frame.url))
61+
62+
print("Navigating page2 to https://webkit.org...")
63+
page2.goto("https://webkit.org")
64+
print("Page2 tile: %s" % page2.title())
65+
print("Pages in context: %d" % len(context.pages))
66+
67+
print("\nQuerying body...")
68+
body1 = page1.querySelector("body")
69+
assert body1
70+
print("Body text %s" % body1.textContent())
71+
72+
print("Closing page1...")
73+
page1.close()
74+
print("Pages in context: %d" % len(context.pages))
75+
76+
print("Navigating page2 to https://cnn.com...")
77+
page2.goto("https://cnn.com")
78+
print("Page2 main frame url: %s" % page2.mainFrame.url)
79+
print("Page2 tile: %s" % page2.title())
80+
print("Frames in page2: %d" % len(page2.frames))
81+
print("Pages in context: %d" % len(context.pages))
82+
83+
print("Closing context...")
84+
context.close()
85+
print("Contexts in browser: %d" % len(browser.contexts))
86+
print("Closing browser")
87+
browser.close()
88+
89+
90+
if __name__ == "__main__":
91+
with playwright.sync_playwright() as p:
92+
main(p)

playwright/__init__.py

Lines changed: 13 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13,28 +13,23 @@
1313
# limitations under the License.
1414

1515
import playwright.helper as helper
16-
from playwright._repo_version import version as __version__ # noqa:F401
17-
from playwright.async_api import Playwright as AsyncPlaywright
18-
from playwright.main import playwright_impl
19-
from playwright.sync_api import Playwright as SyncPlaywright
20-
21-
playwright_sync = SyncPlaywright(playwright_impl)
22-
playwright_async = AsyncPlaywright(playwright_impl)
23-
24-
chromium = playwright_async.chromium
25-
firefox = playwright_async.firefox
26-
webkit = playwright_async.webkit
27-
devices = playwright_async.devices
28-
selectors = playwright_async.selectors
16+
from playwright.main import AsyncPlaywrightContextManager, SyncPlaywrightContextManager
17+
2918
Error = helper.Error
3019
TimeoutError = helper.TimeoutError
3120

21+
22+
def async_playwright() -> AsyncPlaywrightContextManager:
23+
return AsyncPlaywrightContextManager()
24+
25+
26+
def sync_playwright() -> SyncPlaywrightContextManager:
27+
return SyncPlaywrightContextManager()
28+
29+
3230
__all__ = [
33-
"playwright_sync",
34-
"firefox",
35-
"webkit",
36-
"devices",
37-
"selectors",
31+
"async_playwright",
32+
"sync_playwright",
3833
"Error",
3934
"TimeoutError",
4035
]

playwright/accessibility.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ def _ax_node_from_protocol(axNode: Dict[str, Any]) -> Dict[str, Any]:
5555
class Accessibility:
5656
def __init__(self, channel: Channel) -> None:
5757
self._channel = channel
58+
self._loop = channel._scope._loop
5859

5960
async def snapshot(
6061
self, interestingOnly: bool = True, root: ElementHandle = None

playwright/async_base.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,19 +27,19 @@
2727
class AsyncEventInfo(Generic[T]):
2828
def __init__(
2929
self,
30-
sync_base: "AsyncBase",
30+
async_base: "AsyncBase",
3131
event: str,
3232
predicate: Callable[[T], bool] = None,
3333
timeout: int = None,
3434
) -> None:
3535
self._value: Optional[T] = None
3636

37-
wait_helper = WaitHelper()
37+
wait_helper = WaitHelper(async_base._loop)
3838
wait_helper.reject_on_timeout(
3939
timeout or 30000, f'Timeout while waiting for event "${event}"'
4040
)
4141
self._future = asyncio.get_event_loop().create_task(
42-
wait_helper.wait_for_event(sync_base._impl_obj, event, predicate)
42+
wait_helper.wait_for_event(async_base._impl_obj, event, predicate)
4343
)
4444

4545
@property
@@ -52,12 +52,12 @@ async def value(self) -> T:
5252
class AsyncEventContextManager(Generic[T]):
5353
def __init__(
5454
self,
55-
sync_base: "AsyncBase",
55+
async_base: "AsyncBase",
5656
event: str,
5757
predicate: Callable[[T], bool] = None,
5858
timeout: int = None,
5959
) -> None:
60-
self._event = AsyncEventInfo(sync_base, event, predicate, timeout)
60+
self._event = AsyncEventInfo(async_base, event, predicate, timeout)
6161

6262
async def __aenter__(self) -> AsyncEventInfo[T]:
6363
return self._event
@@ -69,6 +69,7 @@ async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
6969
class AsyncBase(ImplWrapper):
7070
def __init__(self, impl_obj: Any) -> None:
7171
super().__init__(impl_obj)
72+
self._loop = impl_obj._loop
7273

7374
def __str__(self) -> str:
7475
return self._impl_obj.__str__()

playwright/browser_context.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ async def waitForEvent(
185185
) -> Any:
186186
if timeout is None:
187187
timeout = self._timeout_settings.timeout()
188-
wait_helper = WaitHelper()
188+
wait_helper = WaitHelper(self._loop)
189189
wait_helper.reject_on_timeout(
190190
timeout, f'Timeout while waiting for event "${event}"'
191191
)

0 commit comments

Comments
 (0)