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
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ jobs:
PKG_CACHE_PATH: ${{ steps.node-pkg-cache.outputs.dir }}
- name: Build package
run: python build_package.py
- name: Install
run: python -m playwright install
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
run: python -m playwright install
run: playwright install

Since we do pip install -e . it should be available in the PATH.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fact that we pollute env with another executable in users's path makes me sad. I'd like to emphasize that playwright is a library, not an executable and recommend its use via -m. Once we gain a handful of cli capabilities, I'll be comfortable saying playwright in my cli!

- name: Test
run: pytest -vv --browser=${{ matrix.browser }} --junitxml=junit/test-results-${{ matrix.os }}-${{ matrix.python-version }}-${{ matrix.browser }}.xml --cov=playwright --cov-report xml
- name: Coveralls
Expand Down
17 changes: 17 additions & 0 deletions playwright/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# 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.

from playwright.main import main

main()
37 changes: 26 additions & 11 deletions playwright/browser_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from playwright.browser import Browser
from playwright.browser_context import BrowserContext
from playwright.connection import ChannelOwner, ConnectionScope, from_channel
from playwright.helper import ColorScheme, locals_to_params
from playwright.helper import ColorScheme, locals_to_params, not_installed_error


class BrowserType(ChannelOwner):
Expand Down Expand Up @@ -49,9 +49,14 @@ async def launch(
slowMo: int = None,
chromiumSandbox: bool = None,
) -> Browser:
return from_channel(
await self._channel.send("launch", locals_to_params(locals()))
)
try:
return from_channel(
await self._channel.send("launch", locals_to_params(locals()))
)
except Exception as e:
if f"{self.name}-" in str(e):
raise not_installed_error(f'"{self.name}" browser was not found.')
raise e

async def launchServer(
self,
Expand All @@ -70,9 +75,14 @@ async def launchServer(
port: int = None,
chromiumSandbox: bool = None,
) -> Browser:
return from_channel(
await self._channel.send("launchServer", locals_to_params(locals()))
)
try:
return from_channel(
await self._channel.send("launchServer", locals_to_params(locals()))
)
except Exception as e:
if f"{self.name}-" in str(e):
raise not_installed_error(f'"{self.name}" browser was not found.')
raise e

async def launchPersistentContext(
self,
Expand Down Expand Up @@ -108,11 +118,16 @@ async def launchPersistentContext(
colorScheme: ColorScheme = None,
acceptDownloads: bool = None,
) -> BrowserContext:
return from_channel(
await self._channel.send(
"launchPersistentContext", locals_to_params(locals())
try:
return from_channel(
await self._channel.send(
"launchPersistentContext", locals_to_params(locals())
)
)
)
except Exception as e:
if f"{self.name}-" in str(e):
raise not_installed_error(f'"{self.name}" browser was not found.')
raise e

async def connect(
self, wsEndpoint: str = None, slowMo: int = None, timeout: int = None
Expand Down
14 changes: 14 additions & 0 deletions playwright/helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -243,3 +243,17 @@ class RouteHandlerEntry:
def __init__(self, matcher: URLMatcher, handler: RouteHandler):
self.matcher = matcher
self.handler = handler


def not_installed_error(message: str) -> Exception:
return Exception(
f"""
================================================================================
{message}
Please complete Playwright installation via running

"python -m playwright install"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we compare Playwright Python with Pytest, they also use just the pytest command instead of python -m pytest. So for that I would use playwright install too.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above, let's celebrate the discoverable execution once we have cli options.


================================================================================
"""
)
58 changes: 43 additions & 15 deletions playwright/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,37 +25,36 @@

from playwright.async_api import Playwright as AsyncPlaywright
from playwright.connection import Connection
from playwright.helper import not_installed_error
from playwright.object_factory import create_remote_object
from playwright.playwright import Playwright
from playwright.sync_api import Playwright as SyncPlaywright
from playwright.sync_base import dispatcher_fiber, set_dispatcher_fiber


async def run_driver_async() -> Connection:
package_path = os.path.dirname(os.path.abspath(__file__))
def compute_driver_name() -> str:
platform = sys.platform
if platform == "darwin":
driver_name = "driver-macos"
result = "driver-macos"
elif platform == "linux":
driver_name = "driver-linux"
result = "driver-linux"
elif platform == "win32":
driver_name = "driver-win.exe"
result = "driver-win.exe"
return result


async def run_driver_async() -> Connection:
package_path = os.path.dirname(os.path.abspath(__file__))
driver_name = compute_driver_name()
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)
raise not_installed_error(
"Playwright requires additional post-installation step to be made."
)

proc = await asyncio.create_subprocess_exec(
driver_executable,
Expand Down Expand Up @@ -116,3 +115,32 @@ async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
# Use ProactorEventLoop in 3.7, which is default in 3.8
loop = asyncio.ProactorEventLoop()
asyncio.set_event_loop(loop)


def main() -> None:
if "install" not in sys.argv:
print('Run "python -m playwright install" to complete installation')
return
package_path = os.path.dirname(os.path.abspath(__file__))
driver_name = compute_driver_name()
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):
print(f"Extracting {archive_name} into {driver_executable}...")
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:
print(f"Making {driver_executable} executable...")
os.chmod(driver_executable, st.st_mode | stat.S_IEXEC)

print("Installing the browsers...")
subprocess.run(f"{driver_executable} install", shell=True)

print("Playwright is now ready for use")