Skip to content

Commit 34475fc

Browse files
authored
feat(install): introduce explict post-installation step (microsoft#102)
1 parent 736f80e commit 34475fc

File tree

5 files changed

+102
-26
lines changed

5 files changed

+102
-26
lines changed

.github/workflows/ci.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ jobs:
7272
PKG_CACHE_PATH: ${{ steps.node-pkg-cache.outputs.dir }}
7373
- name: Build package
7474
run: python build_package.py
75+
- name: Install
76+
run: python -m playwright install
7577
- name: Test
7678
run: pytest -vv --browser=${{ matrix.browser }} --junitxml=junit/test-results-${{ matrix.os }}-${{ matrix.python-version }}-${{ matrix.browser }}.xml --cov=playwright --cov-report xml
7779
- name: Coveralls

playwright/__main__.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
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+
from playwright.main import main
16+
17+
main()

playwright/browser_type.py

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
from playwright.browser import Browser
1818
from playwright.browser_context import BrowserContext
1919
from playwright.connection import ChannelOwner, ConnectionScope, from_channel
20-
from playwright.helper import ColorScheme, locals_to_params
20+
from playwright.helper import ColorScheme, locals_to_params, not_installed_error
2121

2222

2323
class BrowserType(ChannelOwner):
@@ -49,9 +49,14 @@ async def launch(
4949
slowMo: int = None,
5050
chromiumSandbox: bool = None,
5151
) -> Browser:
52-
return from_channel(
53-
await self._channel.send("launch", locals_to_params(locals()))
54-
)
52+
try:
53+
return from_channel(
54+
await self._channel.send("launch", locals_to_params(locals()))
55+
)
56+
except Exception as e:
57+
if f"{self.name}-" in str(e):
58+
raise not_installed_error(f'"{self.name}" browser was not found.')
59+
raise e
5560

5661
async def launchServer(
5762
self,
@@ -70,9 +75,14 @@ async def launchServer(
7075
port: int = None,
7176
chromiumSandbox: bool = None,
7277
) -> Browser:
73-
return from_channel(
74-
await self._channel.send("launchServer", locals_to_params(locals()))
75-
)
78+
try:
79+
return from_channel(
80+
await self._channel.send("launchServer", locals_to_params(locals()))
81+
)
82+
except Exception as e:
83+
if f"{self.name}-" in str(e):
84+
raise not_installed_error(f'"{self.name}" browser was not found.')
85+
raise e
7686

7787
async def launchPersistentContext(
7888
self,
@@ -108,11 +118,16 @@ async def launchPersistentContext(
108118
colorScheme: ColorScheme = None,
109119
acceptDownloads: bool = None,
110120
) -> BrowserContext:
111-
return from_channel(
112-
await self._channel.send(
113-
"launchPersistentContext", locals_to_params(locals())
121+
try:
122+
return from_channel(
123+
await self._channel.send(
124+
"launchPersistentContext", locals_to_params(locals())
125+
)
114126
)
115-
)
127+
except Exception as e:
128+
if f"{self.name}-" in str(e):
129+
raise not_installed_error(f'"{self.name}" browser was not found.')
130+
raise e
116131

117132
async def connect(
118133
self, wsEndpoint: str = None, slowMo: int = None, timeout: int = None

playwright/helper.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,3 +243,17 @@ class RouteHandlerEntry:
243243
def __init__(self, matcher: URLMatcher, handler: RouteHandler):
244244
self.matcher = matcher
245245
self.handler = handler
246+
247+
248+
def not_installed_error(message: str) -> Exception:
249+
return Exception(
250+
f"""
251+
================================================================================
252+
{message}
253+
Please complete Playwright installation via running
254+
255+
"python -m playwright install"
256+
257+
================================================================================
258+
"""
259+
)

playwright/main.py

Lines changed: 43 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -25,37 +25,36 @@
2525

2626
from playwright.async_api import Playwright as AsyncPlaywright
2727
from playwright.connection import Connection
28+
from playwright.helper import not_installed_error
2829
from playwright.object_factory import create_remote_object
2930
from playwright.playwright import Playwright
3031
from playwright.sync_api import Playwright as SyncPlaywright
3132
from playwright.sync_base import dispatcher_fiber, set_dispatcher_fiber
3233

3334

34-
async def run_driver_async() -> Connection:
35-
package_path = os.path.dirname(os.path.abspath(__file__))
35+
def compute_driver_name() -> str:
3636
platform = sys.platform
3737
if platform == "darwin":
38-
driver_name = "driver-macos"
38+
result = "driver-macos"
3939
elif platform == "linux":
40-
driver_name = "driver-linux"
40+
result = "driver-linux"
4141
elif platform == "win32":
42-
driver_name = "driver-win.exe"
42+
result = "driver-win.exe"
43+
return result
44+
45+
46+
async def run_driver_async() -> Connection:
47+
package_path = os.path.dirname(os.path.abspath(__file__))
48+
driver_name = compute_driver_name()
4349
driver_executable = os.path.join(package_path, driver_name)
4450
archive_name = os.path.join(package_path, "drivers", driver_name + ".gz")
4551

4652
if not os.path.exists(driver_executable) or os.path.getmtime(
4753
driver_executable
4854
) < os.path.getmtime(archive_name):
49-
with gzip.open(archive_name, "rb") as f_in, open(
50-
driver_executable, "wb"
51-
) as f_out:
52-
shutil.copyfileobj(f_in, f_out)
53-
54-
st = os.stat(driver_executable)
55-
if st.st_mode & stat.S_IEXEC == 0:
56-
os.chmod(driver_executable, st.st_mode | stat.S_IEXEC)
57-
58-
subprocess.run(f"{driver_executable} install", shell=True)
55+
raise not_installed_error(
56+
"Playwright requires additional post-installation step to be made."
57+
)
5958

6059
proc = await asyncio.create_subprocess_exec(
6160
driver_executable,
@@ -116,3 +115,32 @@ async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
116115
# Use ProactorEventLoop in 3.7, which is default in 3.8
117116
loop = asyncio.ProactorEventLoop()
118117
asyncio.set_event_loop(loop)
118+
119+
120+
def main() -> None:
121+
if "install" not in sys.argv:
122+
print('Run "python -m playwright install" to complete installation')
123+
return
124+
package_path = os.path.dirname(os.path.abspath(__file__))
125+
driver_name = compute_driver_name()
126+
driver_executable = os.path.join(package_path, driver_name)
127+
archive_name = os.path.join(package_path, "drivers", driver_name + ".gz")
128+
129+
if not os.path.exists(driver_executable) or os.path.getmtime(
130+
driver_executable
131+
) < os.path.getmtime(archive_name):
132+
print(f"Extracting {archive_name} into {driver_executable}...")
133+
with gzip.open(archive_name, "rb") as f_in, open(
134+
driver_executable, "wb"
135+
) as f_out:
136+
shutil.copyfileobj(f_in, f_out)
137+
138+
st = os.stat(driver_executable)
139+
if st.st_mode & stat.S_IEXEC == 0:
140+
print(f"Making {driver_executable} executable...")
141+
os.chmod(driver_executable, st.st_mode | stat.S_IEXEC)
142+
143+
print("Installing the browsers...")
144+
subprocess.run(f"{driver_executable} install", shell=True)
145+
146+
print("Playwright is now ready for use")

0 commit comments

Comments
 (0)