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
22 changes: 22 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ on:
branches: [ main ]
pull_request:
branches: [ main ]
permissions:
contents: read
jobs:
lint:
runs-on: ubuntu-latest
Expand Down Expand Up @@ -53,3 +55,23 @@ jobs:
- name: Test on Linux
if: ${{ matrix.os == 'ubuntu-latest' }}
run: xvfb-run pytest --cov=pytest_playwright --cov-report xml
test-without-base-url:
# pytest-base-url is an optional extra; run the base-url tests without it installed.
timeout-minutes: 15
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
- name: Set up Python
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version: 3.12
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r local-requirements.txt
pip install -e pytest-playwright
pip install -e pytest-playwright-asyncio
pip uninstall -y pytest-base-url
playwright install chromium --with-deps
- name: Test
run: xvfb-run pytest -k base_url
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
1 change: 1 addition & 0 deletions local-requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,6 @@ pytest-xdist==3.8.0
# Only used by the test suite; the inner pytest runs block it unless a test opts in.
pytest-rerunfailures==16.7
pytest-html==4.2.0
pytest-base-url==2.1.0
pytest-asyncio==1.3.0
playwright>=1.60
4 changes: 3 additions & 1 deletion pytest-playwright-asyncio/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,13 @@ dynamic = ["version"]
dependencies = [
"playwright>=1.18",
"pytest>=6.2.4,<10.0.0",
"pytest-base-url>=1.0.0,<3.0.0",
"python-slugify>=6.0.0,<10.0.0",
"pytest-asyncio>=0.24.0",
]

[project.optional-dependencies]
base-url = ["pytest-base-url>=1.0.0,<3.0.0"]

[project.urls]
homepage = "https://github.com/microsoft/playwright-pytest"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -762,6 +762,47 @@ def pytest_addoption(
)


@pytest.hookimpl(hookwrapper=True)
def pytest_load_initial_conftests(
early_config: Any, parser: pytest.Parser
) -> Generator[None, None, None]:
yield
# By now every plugin is registered: from -p, PYTEST_PLUGINS, the entry points
# (unless autoloading is disabled) and the initial conftests. So this is the
# first point where it is known whether pytest-base-url is in use, and options
# can still be added, as the command line is parsed afterwards.
if _is_pytest_base_url_loaded(early_config.pluginmanager):
return
# pytest-base-url is optional (pip install pytest-playwright[base-url]). When
# it is not in use, provide the same option, ini value and fixture.
parser.addini("base_url", help="base url for the application under test.")
parser.getgroup("playwright", "Playwright").addoption(
"--base-url",
metavar="url",
default=os.getenv("PYTEST_BASE_URL", None),
help="base url for the application under test.",
)
early_config.pluginmanager.register(_BaseUrlFallback(), "playwright-base-url")


def _is_pytest_base_url_loaded(pluginmanager: pytest.PytestPluginManager) -> bool:
# Registered as "base_url" through its entry point, or under its module name
# with -p pytest_base_url.plugin; checking the module covers both.
module = sys.modules.get("pytest_base_url.plugin")
return module is not None and pluginmanager.is_registered(module)


class _BaseUrlFallback:
# Provides the base_url fixture when pytest-base-url is not in use.
@pytest.fixture(scope="session")
def base_url(self, pytestconfig: Any) -> Optional[str]:
return (
pytestconfig.getoption("--base-url")
or pytestconfig.getini("base_url")
or None
)


class ArtifactsRecorder:
def __init__(
self,
Expand Down
4 changes: 3 additions & 1 deletion pytest-playwright/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,12 @@ dynamic = ["version"]
dependencies = [
"playwright>=1.18",
"pytest>=6.2.4,<10.0.0",
"pytest-base-url>=1.0.0,<3.0.0",
"python-slugify>=6.0.0,<10.0.0",
]

[project.optional-dependencies]
base-url = ["pytest-base-url>=1.0.0,<3.0.0"]

[project.urls]
homepage = "https://github.com/microsoft/playwright-pytest"

Expand Down
41 changes: 41 additions & 0 deletions pytest-playwright/pytest_playwright/pytest_playwright.py
Original file line number Diff line number Diff line change
Expand Up @@ -759,6 +759,47 @@ def pytest_addoption(
)


@pytest.hookimpl(hookwrapper=True)
def pytest_load_initial_conftests(
early_config: Any, parser: pytest.Parser
) -> Generator[None, None, None]:
yield
# By now every plugin is registered: from -p, PYTEST_PLUGINS, the entry points
# (unless autoloading is disabled) and the initial conftests. So this is the
# first point where it is known whether pytest-base-url is in use, and options
# can still be added, as the command line is parsed afterwards.
if _is_pytest_base_url_loaded(early_config.pluginmanager):
return
# pytest-base-url is optional (pip install pytest-playwright[base-url]). When
# it is not in use, provide the same option, ini value and fixture.
parser.addini("base_url", help="base url for the application under test.")
parser.getgroup("playwright", "Playwright").addoption(
"--base-url",
metavar="url",
default=os.getenv("PYTEST_BASE_URL", None),
help="base url for the application under test.",
)
early_config.pluginmanager.register(_BaseUrlFallback(), "playwright-base-url")


def _is_pytest_base_url_loaded(pluginmanager: pytest.PytestPluginManager) -> bool:
# Registered as "base_url" through its entry point, or under its module name
# with -p pytest_base_url.plugin; checking the module covers both.
module = sys.modules.get("pytest_base_url.plugin")
return module is not None and pluginmanager.is_registered(module)


class _BaseUrlFallback:
# Provides the base_url fixture when pytest-base-url is not in use.
@pytest.fixture(scope="session")
def base_url(self, pytestconfig: Any) -> Optional[str]:
return (
pytestconfig.getoption("--base-url")
or pytestconfig.getini("base_url")
or None
)


class ArtifactsRecorder:
def __init__(
self,
Expand Down
72 changes: 72 additions & 0 deletions tests/test_asyncio.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 importlib.util
import os
import signal
import subprocess
Expand Down Expand Up @@ -446,6 +447,77 @@ async def test_base_url(page, base_url):
result.assert_outcomes(passed=1)


@pytest.mark.parametrize("source", ["option", "ini", "env", "none"])
def test_base_url_without_pytest_base_url(
testdir: pytest.Testdir,
test_server: HTTPTestServer,
monkeypatch: pytest.MonkeyPatch,
source: str,
) -> None:
# https://github.com/microsoft/playwright-pytest/issues/311
args = ["-p", "no:base_url"]
if source == "option":
args += ["--base-url", test_server.PREFIX]
elif source == "ini":
args += ["-o", f"base_url={test_server.PREFIX}"]
elif source == "env":
monkeypatch.setenv("PYTEST_BASE_URL", test_server.PREFIX)
expected = None if source == "none" else test_server.PREFIX
testdir.makepyfile(
f"""
import pytest

@pytest.mark.asyncio
async def test_base_url(page, base_url):
assert base_url == {expected!r}
await page.goto("{test_server.PREFIX}/foobar")
if base_url:
await page.goto("/foobar")
assert page.url == "{test_server.PREFIX}/foobar"
"""
)
result = testdir.runpytest(*args)
result.assert_outcomes(passed=1)


def test_base_url_without_plugin_autoload(
testdir: pytest.Testdir,
test_server: HTTPTestServer,
monkeypatch: pytest.MonkeyPatch,
) -> None:
# Only plugins that are actually loaded count, not installed ones.
monkeypatch.setenv("PYTEST_DISABLE_PLUGIN_AUTOLOAD", "1")
testdir.makepyfile(
f"""
import pytest

@pytest.mark.asyncio
async def test_base_url(page, base_url):
assert base_url == "{test_server.PREFIX}"
await page.goto("/foobar")
assert page.url == "{test_server.PREFIX}/foobar"
"""
)
playwright_plugin = [
"-p",
"pytest_playwright_asyncio.pytest_playwright",
"-p",
"pytest_asyncio.plugin",
]
plugin_sets = [playwright_plugin]
if importlib.util.find_spec("pytest_base_url") is not None:
# Loaded explicitly, in either order and under either name, pytest-base-url
# provides the option instead.
plugin_sets += [
playwright_plugin + ["-p", "base_url"],
["-p", "base_url"] + playwright_plugin,
playwright_plugin + ["-p", "pytest_base_url.plugin"],
]
for plugins in plugin_sets:
result = testdir.runpytest(*plugins, "--base-url", test_server.PREFIX)
result.assert_outcomes(passed=1)


def test_skip_browsers(testdir: pytest.Testdir) -> None:
testdir.makepyfile(
"""
Expand Down
61 changes: 61 additions & 0 deletions tests/test_sync.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 importlib.util
import os
from pathlib import Path
import signal
Expand Down Expand Up @@ -462,6 +463,66 @@ def test_base_url(page, base_url):
result.assert_outcomes(passed=1)


@pytest.mark.parametrize("source", ["option", "ini", "env", "none"])
def test_base_url_without_pytest_base_url(
testdir: pytest.Testdir,
test_server: HTTPTestServer,
monkeypatch: pytest.MonkeyPatch,
source: str,
) -> None:
# https://github.com/microsoft/playwright-pytest/issues/311
args = ["-p", "no:base_url"]
if source == "option":
args += ["--base-url", test_server.PREFIX]
elif source == "ini":
args += ["-o", f"base_url={test_server.PREFIX}"]
elif source == "env":
monkeypatch.setenv("PYTEST_BASE_URL", test_server.PREFIX)
expected = None if source == "none" else test_server.PREFIX
testdir.makepyfile(
f"""
def test_base_url(page, base_url):
assert base_url == {expected!r}
page.goto("{test_server.PREFIX}/foobar")
if base_url:
page.goto("/foobar")
assert page.url == "{test_server.PREFIX}/foobar"
"""
)
result = testdir.runpytest(*args)
result.assert_outcomes(passed=1)


def test_base_url_without_plugin_autoload(
testdir: pytest.Testdir,
test_server: HTTPTestServer,
monkeypatch: pytest.MonkeyPatch,
) -> None:
# Only plugins that are actually loaded count, not installed ones.
monkeypatch.setenv("PYTEST_DISABLE_PLUGIN_AUTOLOAD", "1")
testdir.makepyfile(
f"""
def test_base_url(page, base_url):
assert base_url == "{test_server.PREFIX}"
page.goto("/foobar")
assert page.url == "{test_server.PREFIX}/foobar"
"""
)
playwright_plugin = ["-p", "pytest_playwright.pytest_playwright"]
plugin_sets = [playwright_plugin]
if importlib.util.find_spec("pytest_base_url") is not None:
# Loaded explicitly, in either order and under either name, pytest-base-url
# provides the option instead.
plugin_sets += [
playwright_plugin + ["-p", "base_url"],
["-p", "base_url"] + playwright_plugin,
playwright_plugin + ["-p", "pytest_base_url.plugin"],
]
for plugins in plugin_sets:
result = testdir.runpytest(*plugins, "--base-url", test_server.PREFIX)
result.assert_outcomes(passed=1)


def test_skip_browsers(testdir: pytest.Testdir) -> None:
testdir.makepyfile(
"""
Expand Down
Loading