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
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,35 @@ async def run():
asyncio.get_event_loop().run_until_complete(run())
```

#### Mobile and geolocation

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

```py
import asyncio
from playwright import webkit, devices

iphone_11 = devices['iPhone 11 Pro']
print(iphone_11)

async def run():
browser = await webkit.launch(headless=False)
context = await browser.newContext(
**iphone_11,
locale='en-US',
geolocation={ 'longitude': 12.492507, 'latitude': 41.889938 },
permissions=['geolocation']
)
page = await context.newPage()
await page.goto('https://maps.google.com')
await page.click('text="Your location"')
await page.waitForRequest('*preview/pwa')
await page.screenshot(path='colosseum-iphone.png')
await browser.close()

asyncio.get_event_loop().run_until_complete(run())
```

#### Evaluate in browser context

This code snippet navigates to example.com in Firefox, and executes a script in the page context.
Expand Down
12 changes: 6 additions & 6 deletions driver/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion driver/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
},
"license": "Apache-2.0",
"dependencies": {
"playwright": "1.2.0-next.1594076413467"
"playwright": "1.2.0-next.1594327663260"
},
"devDependencies": {
"pkg": "^4.4.9"
Expand Down
14 changes: 8 additions & 6 deletions playwright/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,18 @@
# See the License for the specific language governing permissions and
# limitations under the License.

from playwright.playwright import playwright
from playwright.main import playwright_object

chromium = playwright.chromium
firefox = playwright.firefox
webkit = playwright.webkit
browser_types = playwright.browser_types
chromium = playwright_object.chromium
firefox = playwright_object.firefox
webkit = playwright_object.webkit
devices = playwright_object.devices
browser_types = playwright_object.browser_types

__all__ = [
'browser_types',
'chromium',
'firefox',
'webkit',
'browser_types'
'devices'
]
65 changes: 65 additions & 0 deletions playwright/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# 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 gzip
import os
import shutil
import stat
import sys
import subprocess

from playwright.connection import Connection
from playwright.object_factory import create_remote_object
from playwright.playwright import Playwright
from typing import Dict

playwright_object: Playwright = None

async def async_init():
global playwright_object
package_path = os.path.dirname(os.path.abspath(__file__))
platform = sys.platform
if platform == 'darwin':
driver_name = 'driver-macos'
elif platform == 'linux':
driver_name = 'driver-linux'
elif platform == 'win32':
driver_name = 'driver-win.exe'
driver_executable = os.path.join(package_path, driver_name)
archive_name = os.path.join(package_path, 'drivers', driver_name + '.gz')

if not os.path.exists(driver_executable) or os.path.getmtime(driver_executable) < os.path.getmtime(archive_name):
with gzip.open(archive_name, 'rb') as f_in, open(driver_executable, 'wb') as f_out:
shutil.copyfileobj(f_in, f_out)

st = os.stat(driver_executable)
if st.st_mode & stat.S_IEXEC == 0:
os.chmod(driver_executable, st.st_mode | stat.S_IEXEC)

subprocess.run(f"{driver_executable} install", shell=True)

proc = await asyncio.create_subprocess_exec(driver_executable,
stdin=asyncio.subprocess.PIPE,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
limit=32768)
connection = Connection(proc.stdout, proc.stdin, create_remote_object, asyncio.get_event_loop())
playwright_object = await connection.wait_for_object_with_known_name('playwright')

if sys.platform == 'win32':
# Use ProactorEventLoop in 3.7, which is default in 3.8
loop = asyncio.ProactorEventLoop()
asyncio.set_event_loop(loop)
asyncio.get_event_loop().run_until_complete(async_init())
4 changes: 2 additions & 2 deletions playwright/network.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ async def fulfill(self, status: int = 200, headers: Dict[str,str] = dict(), body
elif isinstance(body, bytes):
response['body'] = base64.b64encode(body)
response['isBase64'] = True
await self._channel.send('fulfill', dict(response=response))
await self._channel.send('fulfill', response)

async def continue_(self, method: str = None, headers: Dict[str,str] = None, postData: Union[str, bytes] = None) -> None:
overrides = dict()
Expand All @@ -108,7 +108,7 @@ async def continue_(self, method: str = None, headers: Dict[str,str] = None, pos
overrides['postData'] = base64.b64encode(bytes(postData, 'utf-8'))
elif isinstance(postData, bytes):
overrides['postData'] = base64.b64encode(postData)
await self._channel.send('continue', dict(overrides=overrides))
await self._channel.send('continue', overrides)


class Response(ChannelOwner):
Expand Down
3 changes: 3 additions & 0 deletions playwright/object_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
from playwright.js_handle import JSHandle
from playwright.network import Request, Response, Route
from playwright.page import BindingCall, Page
from playwright.playwright import Playwright
from playwright.worker import Worker
from typing import Any, Awaitable, Dict, List

Expand Down Expand Up @@ -53,6 +54,8 @@ def create_remote_object(scope: ConnectionScope, type: str, guid: str, initializ
return JSHandle(scope, guid, initializer)
if type == 'page':
return Page(scope, guid, initializer)
if type == 'playwright':
return Playwright(scope, guid, initializer)
if type == 'request':
return Request(scope, guid, initializer)
if type == 'response':
Expand Down
28 changes: 15 additions & 13 deletions playwright/page.py
Original file line number Diff line number Diff line change
Expand Up @@ -285,32 +285,34 @@ async def waitForNavigation(self,

async def waitForRequest(self, urlOrPredicate: Union[str, Callable[[Request], bool]]) -> Optional[Request]:
matcher = URLMatcher(urlOrPredicate) if isinstance(urlOrPredicate, str) else None
def predicate(request: Request):
def predicate(request: Request) -> bool:
if matcher:
return matcher.matches(request.url())
return urlOrPredicate(request)
params = locals_to_params(locals())
params['predicate'] = predicate
return self.waitForEvent(Page.Events.Request, **params)
return self.waitForEvent(Page.Events.Request, predicate=predicate)

async def waitForResponse(self, urlOrPredicate: Union[str, Callable[[Request], bool]]) -> Optional[Response]:
async def waitForResponse(self, urlOrPredicate: Union[str, Callable[[Response], bool]]) -> Optional[Response]:
matcher = URLMatcher(urlOrPredicate) if isinstance(urlOrPredicate, str) else None
def predicate(request: Request):
def predicate(response: Response) -> bool:
if matcher:
return matcher.matches(request.url())
return urlOrPredicate(request)
params = locals_to_params(locals())
params['predicate'] = predicate
return await self.waitForEvent(Page.Events.Response, **params)
return matcher.matches(response.url())
return urlOrPredicate(response)
return self.waitForEvent(Page.Events.Response, predicate=predicate)

async def waitForEvent(self, event: str) -> Any:
async def waitForEvent(self, event: str, predicate: Callable[[Any], bool] = None) -> Any:
# TODO: support timeout

future = self._scope._loop.create_future()
self.once(event, lambda e: future.set_result(e))
def listener(e: Any):
if not predicate or predicate(e):
future.set_result(e)

self.on(event, listener)
pending_event = PendingWaitEvent(event, future)
self._pending_wait_for_events.append(pending_event)
result = await future
self._pending_wait_for_events.remove(pending_event)
self.remove_listener(event, listener)
return result

async def goBack(self,
Expand Down
64 changes: 9 additions & 55 deletions playwright/playwright.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,63 +12,17 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import asyncio
import gzip
import os
import shutil
import stat
import sys
import subprocess

from playwright.connection import Connection
from playwright.object_factory import create_remote_object
from playwright.connection import Channel, ChannelOwner, ConnectionScope, from_channel
from playwright.browser_type import BrowserType
from typing import Dict

class Playwright:
def __init__(self) -> None:
# Use ProactorEventLoop in 3.7, which is default in 3.8
if sys.platform == 'win32':
loop = asyncio.ProactorEventLoop()
asyncio.set_event_loop(loop)
self.loop = asyncio.get_event_loop()
self.loop.run_until_complete(self._sync_init())

async def _sync_init(self):
package_path = os.path.dirname(os.path.abspath(__file__))
platform = sys.platform
if platform == 'darwin':
driver_name = 'driver-macos'
elif platform == 'linux':
driver_name = 'driver-linux'
elif platform == 'win32':
driver_name = 'driver-win.exe'
driver_executable = os.path.join(package_path, driver_name)
archive_name = os.path.join(package_path, 'drivers', driver_name + '.gz')

if not os.path.exists(driver_executable) or os.path.getmtime(driver_executable) < os.path.getmtime(archive_name):
with gzip.open(archive_name, 'rb') as f_in, open(driver_executable, 'wb') as f_out:
shutil.copyfileobj(f_in, f_out)

st = os.stat(driver_executable)
if st.st_mode & stat.S_IEXEC == 0:
os.chmod(driver_executable, st.st_mode | stat.S_IEXEC)

subprocess.run(f"{driver_executable} install", shell=True)
class Playwright(ChannelOwner):

self._proc = await asyncio.create_subprocess_exec(driver_executable,
stdin=asyncio.subprocess.PIPE,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
limit=32768)
self._connection = Connection(self._proc.stdout, self._proc.stdin, create_remote_object, self.loop)
chromium, firefox, webkit = await asyncio.gather(
self._connection.wait_for_object_with_known_name('chromium'),
self._connection.wait_for_object_with_known_name('firefox'),
self._connection.wait_for_object_with_known_name('webkit'))
self.chromium: BrowserType = chromium
self.firefox: BrowserType = firefox
self.webkit: BrowserType = webkit
self.browser_types: Dict[str, BrowserType] = dict(chromium=self.chromium, firefox=self.firefox, webkit=self.webkit)
def __init__(self, scope: ConnectionScope, guid: str, initializer: Dict) -> None:
super().__init__(scope, guid, initializer)
self.chromium: BrowserType = from_channel(initializer['chromium'])
self.firefox: BrowserType = from_channel(initializer['firefox'])
self.webkit: BrowserType = from_channel(initializer['webkit'])
self.devices = initializer['deviceDescriptors']
self.browser_types: Dict[str, BrowserType] = dict(chromium=self.chromium, webkit=self.webkit, firefox=self.firefox)

playwright = Playwright()
3 changes: 2 additions & 1 deletion tests/conftest.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.

import asyncio
import os
import pytest
import playwright
Expand All @@ -37,7 +38,7 @@ def pytest_generate_tests(metafunc):

@pytest.fixture(scope='session')
def event_loop():
loop = playwright.playwright.loop
loop = asyncio.get_event_loop()
yield loop
loop.close()

Expand Down
1 change: 0 additions & 1 deletion tests/test_network.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,3 @@ async def handle_request(route, request, intercepted):
assert response.ok
assert intercepted == [True]
assert await page.title() == ''