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
+7
View File
@@ -43,3 +43,10 @@ packages = ["src/invisible_playwright"]
[tool.hatch.build.targets.wheel.force-include] [tool.hatch.build.targets.wheel.force-include]
"src/invisible_playwright/data" = "invisible_playwright/data" "src/invisible_playwright/data" = "invisible_playwright/data"
"src/invisible_playwright/_fpforge/data" = "invisible_playwright/_fpforge/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
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)
+7 -3
View File
@@ -1,19 +1,23 @@
import subprocess import subprocess
import sys import sys
import pytest
@pytest.mark.unit
def test_version_subcommand(): def test_version_subcommand():
r = subprocess.run( r = subprocess.run(
[sys.executable, "-m", "invisible-playwright", "version"], [sys.executable, "-m", "invisible_playwright", "version"],
capture_output=True, text=True, check=True, capture_output=True, text=True, check=True,
) )
assert "firefox-" in r.stdout 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(): def test_help_subcommand():
r = subprocess.run( r = subprocess.run(
[sys.executable, "-m", "invisible-playwright", "--help"], [sys.executable, "-m", "invisible_playwright", "--help"],
capture_output=True, text=True, capture_output=True, text=True,
) )
assert r.returncode == 0 assert r.returncode == 0
+8 -2
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(): def test_binary_version_format():
assert BINARY_VERSION.startswith("firefox-") assert BINARY_VERSION.startswith("firefox-")
assert BINARY_VERSION.split("-", 1)[1].isdigit() assert BINARY_VERSION.split("-", 1)[1].isdigit()
@pytest.mark.unit
def test_archive_name_windows(): def test_archive_name_windows():
name = ARCHIVE_NAME("win32", "AMD64") name = ARCHIVE_NAME("win32", "AMD64")
assert name.endswith(".zip") assert name.endswith(".zip")
assert "win-x86_64" in name assert "win-x86_64" in name
@pytest.mark.unit
def test_archive_name_linux(): def test_archive_name_linux():
name = ARCHIVE_NAME("linux", "x86_64") name = ARCHIVE_NAME("linux", "x86_64")
assert name.endswith(".tar.gz") assert name.endswith(".tar.gz")
assert "linux-x86_64" in name assert "linux-x86_64" in name
@pytest.mark.unit
def test_archive_name_unsupported_raises(): def test_archive_name_unsupported_raises():
import pytest
with pytest.raises(NotImplementedError): with pytest.raises(NotImplementedError):
ARCHIVE_NAME("darwin", "arm64") ARCHIVE_NAME("darwin", "arm64")
@pytest.mark.unit
def test_binary_basename_format(): def test_binary_basename_format():
assert "firefox" in BINARY_BASENAME.lower() assert "firefox" in BINARY_BASENAME.lower()
assert "stealth" in BINARY_BASENAME.lower() assert "stealth" in BINARY_BASENAME.lower()
+3 -1
View File
@@ -4,8 +4,8 @@ from pathlib import Path
import pytest import pytest
import responses import responses
from invisible_playwright.download import ensure_binary
from invisible_playwright.constants import BINARY_VERSION 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: 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 return data
@pytest.mark.unit
@responses.activate @responses.activate
def test_ensure_binary_downloads_and_verifies(tmp_path, monkeypatch): def test_ensure_binary_downloads_and_verifies(tmp_path, monkeypatch):
"""Full path: cache miss -> HTTP GET -> SHA256 check -> extract -> return path.""" """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" assert Path(path).name == "firefox.exe"
@pytest.mark.unit
@responses.activate @responses.activate
def test_ensure_binary_rejects_sha_mismatch(tmp_path, monkeypatch): def test_ensure_binary_rejects_sha_mismatch(tmp_path, monkeypatch):
cache = tmp_path / "cache" cache = tmp_path / "cache"
+11 -3
View File
@@ -1,14 +1,19 @@
import pytest
from invisible_playwright._fpforge import generate_profile from invisible_playwright._fpforge import generate_profile
from invisible_playwright.prefs import translate_profile_to_prefs 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) p = generate_profile(seed=42)
prefs = translate_profile_to_prefs(p) prefs = translate_profile_to_prefs(p)
assert prefs["zoom.stealth.webgl.renderer"] == p.gpu.renderer assert prefs["zoom.stealth.webgl.renderer"] == ""
assert prefs["zoom.stealth.webgl.vendor"] == p.gpu.vendor assert prefs["zoom.stealth.webgl.vendor"] == ""
@pytest.mark.unit
def test_translate_includes_screen(): def test_translate_includes_screen():
p = generate_profile(seed=42) p = generate_profile(seed=42)
prefs = translate_profile_to_prefs(p) prefs = translate_profile_to_prefs(p)
@@ -16,18 +21,21 @@ def test_translate_includes_screen():
assert prefs["zoom.stealth.screen.height"] == p.screen.height assert prefs["zoom.stealth.screen.height"] == p.screen.height
@pytest.mark.unit
def test_translate_is_deterministic_per_seed(): def test_translate_is_deterministic_per_seed():
a = translate_profile_to_prefs(generate_profile(seed=42)) a = translate_profile_to_prefs(generate_profile(seed=42))
b = translate_profile_to_prefs(generate_profile(seed=42)) b = translate_profile_to_prefs(generate_profile(seed=42))
assert a == b assert a == b
@pytest.mark.unit
def test_translate_varies_across_seeds(): def test_translate_varies_across_seeds():
a = translate_profile_to_prefs(generate_profile(seed=1)) a = translate_profile_to_prefs(generate_profile(seed=1))
b = translate_profile_to_prefs(generate_profile(seed=2)) b = translate_profile_to_prefs(generate_profile(seed=2))
assert a != b assert a != b
@pytest.mark.unit
def test_translate_has_stealth_baseline_constants(): def test_translate_has_stealth_baseline_constants():
p = generate_profile(seed=42) p = generate_profile(seed=42)
prefs = translate_profile_to_prefs(p) prefs = translate_profile_to_prefs(p)