test: add pytest markers, conftest, fix Windows-incompatible existing tests

- Add tests/conftest.py with deterministic_rng + sample_profile fixtures
- Register unit/integration/e2e markers in pyproject.toml
- Mark existing 14 tests as @pytest.mark.unit
- Fix test_cli.py: use 'invisible_playwright' (underscore) for 'python -m'
- Fix test_translate_includes_gpu_renderer: assert Windows behavior (empty renderer)
This commit is contained in:
chrissbaumann 2026-05-14 11:21:10 +02:00
parent c690dbfd33
commit 957f84d9a5
6 changed files with 53 additions and 9 deletions

View file

@ -43,3 +43,10 @@ packages = ["src/invisible_playwright"]
[tool.hatch.build.targets.wheel.force-include]
"src/invisible_playwright/data" = "invisible_playwright/data"
"src/invisible_playwright/_fpforge/data" = "invisible_playwright/_fpforge/data"
[tool.pytest.ini_options]
markers = [
"unit: pure-logic tests, no I/O or external deps",
"integration: multi-module tests, no browser",
"e2e: requires patched Firefox binary and display",
]

17
tests/conftest.py Normal file
View file

@ -0,0 +1,17 @@
import random
import pytest
from invisible_playwright._fpforge import generate_profile
@pytest.fixture
def deterministic_rng():
"""Seeded RNG for reproducible tests."""
return random.Random(42)
@pytest.fixture
def sample_profile():
"""A Profile generated from seed=42 for reuse across tests."""
return generate_profile(seed=42)

View file

@ -1,19 +1,23 @@
import subprocess
import sys
import pytest
@pytest.mark.unit
def test_version_subcommand():
r = subprocess.run(
[sys.executable, "-m", "invisible-playwright", "version"],
[sys.executable, "-m", "invisible_playwright", "version"],
capture_output=True, text=True, check=True,
)
assert "firefox-" in r.stdout
assert "invisible-playwright" in r.stdout.lower()
assert "invisible_playwright" in r.stdout.lower()
@pytest.mark.unit
def test_help_subcommand():
r = subprocess.run(
[sys.executable, "-m", "invisible-playwright", "--help"],
[sys.executable, "-m", "invisible_playwright", "--help"],
capture_output=True, text=True,
)
assert r.returncode == 0

View file

@ -1,29 +1,35 @@
from invisible_playwright.constants import BINARY_VERSION, BINARY_BASENAME, ARCHIVE_NAME
import pytest
from invisible_playwright.constants import ARCHIVE_NAME, BINARY_BASENAME, BINARY_VERSION
@pytest.mark.unit
def test_binary_version_format():
assert BINARY_VERSION.startswith("firefox-")
assert BINARY_VERSION.split("-", 1)[1].isdigit()
@pytest.mark.unit
def test_archive_name_windows():
name = ARCHIVE_NAME("win32", "AMD64")
assert name.endswith(".zip")
assert "win-x86_64" in name
@pytest.mark.unit
def test_archive_name_linux():
name = ARCHIVE_NAME("linux", "x86_64")
assert name.endswith(".tar.gz")
assert "linux-x86_64" in name
@pytest.mark.unit
def test_archive_name_unsupported_raises():
import pytest
with pytest.raises(NotImplementedError):
ARCHIVE_NAME("darwin", "arm64")
@pytest.mark.unit
def test_binary_basename_format():
assert "firefox" in BINARY_BASENAME.lower()
assert "stealth" in BINARY_BASENAME.lower()

View file

@ -4,8 +4,8 @@ from pathlib import Path
import pytest
import responses
from invisible_playwright.download import ensure_binary
from invisible_playwright.constants import BINARY_VERSION
from invisible_playwright.download import ensure_binary
def _make_zip(path: Path, inner_name: str, payload: bytes) -> bytes:
@ -19,6 +19,7 @@ def _make_zip(path: Path, inner_name: str, payload: bytes) -> bytes:
return data
@pytest.mark.unit
@responses.activate
def test_ensure_binary_downloads_and_verifies(tmp_path, monkeypatch):
"""Full path: cache miss -> HTTP GET -> SHA256 check -> extract -> return path."""
@ -48,6 +49,7 @@ def test_ensure_binary_downloads_and_verifies(tmp_path, monkeypatch):
assert Path(path).name == "firefox.exe"
@pytest.mark.unit
@responses.activate
def test_ensure_binary_rejects_sha_mismatch(tmp_path, monkeypatch):
cache = tmp_path / "cache"

View file

@ -1,14 +1,19 @@
import pytest
from invisible_playwright._fpforge import generate_profile
from invisible_playwright.prefs import translate_profile_to_prefs
def test_translate_includes_gpu_renderer():
@pytest.mark.unit
def test_translate_includes_gpu_renderer_windows():
"""On Windows, renderer/vendor are cleared so ANGLE reports native hardware."""
p = generate_profile(seed=42)
prefs = translate_profile_to_prefs(p)
assert prefs["zoom.stealth.webgl.renderer"] == p.gpu.renderer
assert prefs["zoom.stealth.webgl.vendor"] == p.gpu.vendor
assert prefs["zoom.stealth.webgl.renderer"] == ""
assert prefs["zoom.stealth.webgl.vendor"] == ""
@pytest.mark.unit
def test_translate_includes_screen():
p = generate_profile(seed=42)
prefs = translate_profile_to_prefs(p)
@ -16,18 +21,21 @@ def test_translate_includes_screen():
assert prefs["zoom.stealth.screen.height"] == p.screen.height
@pytest.mark.unit
def test_translate_is_deterministic_per_seed():
a = translate_profile_to_prefs(generate_profile(seed=42))
b = translate_profile_to_prefs(generate_profile(seed=42))
assert a == b
@pytest.mark.unit
def test_translate_varies_across_seeds():
a = translate_profile_to_prefs(generate_profile(seed=1))
b = translate_profile_to_prefs(generate_profile(seed=2))
assert a != b
@pytest.mark.unit
def test_translate_has_stealth_baseline_constants():
p = generate_profile(seed=42)
prefs = translate_profile_to_prefs(p)