Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add safari runner for playwright #16

Draft
wants to merge 10 commits into
base: main
Choose a base branch
from
Draft
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
8 changes: 6 additions & 2 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ jobs:
# playwright browser versions are pinned to playwright version
{runner: playwright, runtime: firefox, playwright-version: 1.22.0, node-version: 18},
{runner: playwright, runtime: chrome, playwright-version: 1.22.0, node-version: 18},
{runner: playwright, runtime: safari, playwright-version: 1.22.0, node-version: 18},
]
steps:
- uses: actions/checkout@v2
Expand All @@ -86,8 +87,11 @@ jobs:
if: ${{ matrix.test-config.runner == 'playwright' }}
run: |
pip install playwright==${{ matrix.test-config.playwright-version }}
# TODO: install only browsers that are required
python -m playwright install --with-deps
RUNTIME=${{ matrix.test-config.runtime }}
RUNTIME=${RUNTIME//safari/webkit}
RUNTIME=${RUNTIME//chrome/chromium}

python -m playwright install "${RUNTIME}" --with-deps

- name: Install firefox
uses: browser-actions/setup-firefox@latest
Expand Down
2 changes: 2 additions & 0 deletions pytest_pyodide/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
PlaywrightChromeWrapper,
PlaywrightFirefoxRunner,
PlaywrightFirefoxWrapper,
PlaywrightSafariRunner,
PlaywrightWrapper,
SeleniumChromeRunner,
SeleniumChromeWrapper,
Expand All @@ -29,6 +30,7 @@
"NodeRunner",
"PlaywrightChromeRunner",
"PlaywrightFirefoxRunner",
"PlaywrightSafariRunner",
"SeleniumChromeRunner",
"SeleniumFirefoxRunner",
"set_webdriver_script_timeout",
Expand Down
1 change: 1 addition & 0 deletions pytest_pyodide/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
RUNTIMES = ["firefox", "chrome", "node", "safari"]
94 changes: 55 additions & 39 deletions pytest_pyodide/fixture.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
NodeRunner,
PlaywrightChromeRunner,
PlaywrightFirefoxRunner,
PlaywrightSafariRunner,
SeleniumChromeRunner,
SeleniumFirefoxRunner,
_BrowserBaseRunner,
Expand All @@ -16,10 +17,14 @@
from .utils import parse_driver_timeout, set_webdriver_script_timeout


# FIXME: Using `session` scope can reduce the number of playwright context generation.
# However, generating too many browser contexts in a single playwright context
# sometimes hang when closing the context.
@pytest.fixture(scope="module")
def playwright_browsers(request):
def playwright_session(request):
if request.config.option.runner.lower() != "playwright":
yield {}
yield None

else:
# import playwright here to allow running tests without playwright installation
try:
Expand All @@ -30,27 +35,37 @@ def playwright_browsers(request):
returncode=1,
)

with sync_playwright() as p:
try:
chromium = p.chromium.launch(
args=[
"--js-flags=--expose-gc",
],
)
firefox = p.firefox.launch()
# webkit = p.webkit.launch()
except Exception as e:
pytest.exit(f"playwright failed to launch\n{e}", returncode=1)
try:
yield {
"chrome": chromium,
"firefox": firefox,
# "webkit": webkit,
}
finally:
chromium.close()
firefox.close()
# webkit.close()
p = sync_playwright().start()
yield p
p.stop()


@pytest.fixture(scope="module")
def playwright_browser(request, playwright_session, runtime):
if request.config.option.runner.lower() != "playwright":
yield None
else:
try:
match runtime:
case "chrome":
browser = playwright_session.chromium.launch(
args=[
"--js-flags=--expose-gc",
],
)
case "firefox":
browser = playwright_session.firefox.launch()
case "safari":
browser = playwright_session.webkit.launch()
case "node":
browser = None
except Exception as e:
pytest.exit(f"playwright failed to launch\n{e}", returncode=1)
try:
yield browser
finally:
if browser is not None:
browser.close()


@contextlib.contextmanager
Expand All @@ -60,7 +75,7 @@ def selenium_common(
web_server_main,
load_pyodide=True,
script_type="classic",
browsers=None,
playwright_browser=None,
):
"""Returns an initialized selenium object.

Expand All @@ -78,6 +93,7 @@ def selenium_common(
("selenium", "node"): NodeRunner,
("playwright", "firefox"): PlaywrightFirefoxRunner,
("playwright", "chrome"): PlaywrightChromeRunner,
("playwright", "safari"): PlaywrightSafariRunner,
("playwright", "node"): NodeRunner,
}

Expand All @@ -91,7 +107,7 @@ def selenium_common(
server_hostname=server_hostname,
server_log=server_log,
load_pyodide=load_pyodide,
browsers=browsers,
playwright_browser=playwright_browser,
script_type=script_type,
dist_dir=dist_dir,
)
Expand All @@ -102,9 +118,9 @@ def selenium_common(


@pytest.fixture(scope="function")
def selenium_standalone(request, runtime, web_server_main, playwright_browsers):
def selenium_standalone(request, runtime, web_server_main, playwright_browser):
with selenium_common(
request, runtime, web_server_main, browsers=playwright_browsers
request, runtime, web_server_main, playwright_browser=playwright_browser
) as selenium:
with set_webdriver_script_timeout(
selenium, script_timeout=parse_driver_timeout(request.node)
Expand All @@ -116,13 +132,13 @@ def selenium_standalone(request, runtime, web_server_main, playwright_browsers):


@pytest.fixture(scope="module")
def selenium_esm(request, runtime, web_server_main, playwright_browsers):
def selenium_esm(request, runtime, web_server_main, playwright_browser):
with selenium_common(
request,
runtime,
web_server_main,
load_pyodide=True,
browsers=playwright_browsers,
playwright_browser=playwright_browser,
script_type="module",
) as selenium:
with set_webdriver_script_timeout(
Expand All @@ -136,14 +152,14 @@ def selenium_esm(request, runtime, web_server_main, playwright_browsers):

@contextlib.contextmanager
def selenium_standalone_noload_common(
request, runtime, web_server_main, playwright_browsers, script_type="classic"
request, runtime, web_server_main, playwright_browser, script_type="classic"
):
with selenium_common(
request,
runtime,
web_server_main,
load_pyodide=False,
browsers=playwright_browsers,
playwright_browser=playwright_browser,
script_type=script_type,
) as selenium:
with set_webdriver_script_timeout(
Expand All @@ -157,7 +173,7 @@ def selenium_standalone_noload_common(

@pytest.fixture(scope="function")
def selenium_webworker_standalone(
request, runtime, web_server_main, playwright_browsers, script_type
request, runtime, web_server_main, playwright_browser, script_type
):
# Avoid loading the fixture if the test is going to be skipped
if runtime == "firefox" and script_type == "module":
Expand All @@ -167,27 +183,27 @@ def selenium_webworker_standalone(
pytest.skip("no support in node")

with selenium_standalone_noload_common(
request, runtime, web_server_main, playwright_browsers, script_type=script_type
request, runtime, web_server_main, playwright_browser, script_type=script_type
) as selenium:
yield selenium


@pytest.fixture(scope="function")
def selenium_standalone_noload(request, runtime, web_server_main, playwright_browsers):
def selenium_standalone_noload(request, runtime, web_server_main, playwright_browser):
"""Only difference between this and selenium_webworker_standalone is that
this also tests on node."""

with selenium_standalone_noload_common(
request, runtime, web_server_main, playwright_browsers
request, runtime, web_server_main, playwright_browser
) as selenium:
yield selenium


# selenium instance cached at the module level
@pytest.fixture(scope="module")
def selenium_module_scope(request, runtime, web_server_main, playwright_browsers):
def selenium_module_scope(request, runtime, web_server_main, playwright_browser):
with selenium_common(
request, runtime, web_server_main, browsers=playwright_browsers
request, runtime, web_server_main, playwright_browser=playwright_browser
) as selenium:
yield selenium

Expand All @@ -214,7 +230,7 @@ def selenium(request, selenium_module_scope):


@pytest.fixture(scope="function")
def console_html_fixture(request, runtime, web_server_main, playwright_browsers):
def console_html_fixture(request, runtime, web_server_main, playwright_browser):

if runtime == "node":
pytest.skip("no support in node")
Expand All @@ -224,7 +240,7 @@ def console_html_fixture(request, runtime, web_server_main, playwright_browsers)
runtime,
web_server_main,
load_pyodide=False,
browsers=playwright_browsers,
playwright_browser=playwright_browser,
) as selenium:
selenium.goto(
f"http://{selenium.server_hostname}:{selenium.server_port}/console.html"
Expand Down
6 changes: 4 additions & 2 deletions pytest_pyodide/hook.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,9 @@
pytest_pycollect_makemodule as orig_pytest_pycollect_makemodule,
)

from .constants import RUNTIMES
from .utils import parse_xfail_browsers

RUNTIMES = ["firefox", "chrome", "node"]


def pytest_configure(config):

Expand Down Expand Up @@ -106,9 +105,12 @@ def pytest_pycollect_makemodule(module_path: Path, path: Any, parent: Any) -> No
def pytest_generate_tests(metafunc: Any) -> None:
if "runtime" in metafunc.fixturenames:
runtime = metafunc.config.option.runtime
runner = metafunc.config.option.runner

if runtime == "all":
runtime = RUNTIMES
if runner == "selenium":
runtime = runtime.remove("safari")

metafunc.parametrize("runtime", [runtime], scope="module")

Expand Down
10 changes: 7 additions & 3 deletions pytest_pyodide/runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -358,15 +358,15 @@ def urls(self):


class _PlaywrightBaseRunner(_BrowserBaseRunner):
def __init__(self, browsers, *args, **kwargs):
self.browsers = browsers
def __init__(self, playwright_browser, *args, **kwargs):
self.playwright_browser = playwright_browser
super().__init__(*args, **kwargs)

def goto(self, page):
self.driver.goto(page)

def get_driver(self):
return self.browsers[self.browser].new_page()
return self.playwright_browser.new_page()

def set_script_timeout(self, timeout):
# playwright uses milliseconds for timeout
Expand Down Expand Up @@ -445,6 +445,10 @@ class PlaywrightFirefoxRunner(_PlaywrightBaseRunner):
browser = "firefox"


class PlaywrightSafariRunner(_PlaywrightBaseRunner):
browser = "safari"


class NodeRunner(_BrowserBaseRunner):
browser = "node"

Expand Down
4 changes: 3 additions & 1 deletion pytest_pyodide/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

import pytest

from .constants import RUNTIMES


@contextlib.contextmanager
def set_webdriver_script_timeout(selenium, script_timeout: float | None):
Expand Down Expand Up @@ -47,7 +49,7 @@ def maybe_skip_test(item, dist_dir, delayed=False):

loading the selenium_standalone fixture which takes a long time.
"""
browsers = "|".join(["firefox", "chrome", "node"])
browsers = "|".join(RUNTIMES)

skip_msg = None
# Testing a package. Skip the test if the package is not built.
Expand Down
5 changes: 4 additions & 1 deletion tests/test_marker.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@


@pytest.mark.xfail_browsers(
node="Should xfail", firefox="Should xfail", chrome="Should xfail"
node="Should xfail",
firefox="Should xfail",
chrome="Should xfail",
safari="Should xfail",
)
@run_in_pyodide
def test_xfail_browser(selenium):
Expand Down