mirror of
https://github.com/feder-cr/invisible_playwright.git
synced 2026-06-07 08:35:12 +02:00
test(prefs, headless): add 16 Linux-specific tests for Phase 6 + 10
Cover the Linux branches in prefs.py that previously had no tests (font metrics, GPU spoofing, MSAA from profile, canvas noise mask per renderer, WebGL extension preservation, Xvfb workarounds, virtual_display no-op) and add construction smoke tests for _LinuxVirtualDisplay. Also fix two host-platform-dependent tests so the suite stays green on both Windows and Linux. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
4f6254469e
commit
d392ca2971
2 changed files with 215 additions and 2 deletions
|
|
@ -13,6 +13,8 @@ does no I/O — Xvfb is only spawned in ``start()``, which we never call.
|
|||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
|
||||
import invisible_playwright._headless as headless
|
||||
|
|
@ -85,11 +87,65 @@ def test_windows_desktop_initial_state_is_clean():
|
|||
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.skipif(sys.platform != "win32", reason="exercises Win32 ctypes")
|
||||
def test_windows_desktop_stop_is_idempotent_without_start():
|
||||
"""``stop()`` after never calling ``start()`` must be a no-op, so
|
||||
``__exit__`` from a failed launch can call it unconditionally."""
|
||||
``__exit__`` from a failed launch can call it unconditionally.
|
||||
|
||||
Skipped off Windows because ``stop()`` unconditionally resolves
|
||||
``ctypes.windll.user32`` at the top of the function — that symbol
|
||||
only exists on Windows. The early-return logic is safe because
|
||||
callers only instantiate this class via ``make_virtual_display()``
|
||||
which already routes on ``sys.platform == 'win32'``.
|
||||
"""
|
||||
vd = _WindowsVirtualDesktop()
|
||||
vd.stop()
|
||||
vd.stop()
|
||||
assert vd._desktop is None
|
||||
assert vd._original_handle == 0
|
||||
|
||||
|
||||
# ──────────────────────────────────────────────────────────────────────
|
||||
# _LinuxVirtualDisplay — construction-only smoke tests. ``start()`` is
|
||||
# E2E because it spawns Xvfb; ``stop()`` is safe to call when no Xvfb
|
||||
# was ever started, so we exercise that path explicitly.
|
||||
# ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_linux_virtual_display_initial_state_is_clean():
|
||||
"""Construction must not spawn Xvfb or mutate the environment — only
|
||||
``start()`` does. Mirrors the Windows construction-state test."""
|
||||
vd = _LinuxVirtualDisplay()
|
||||
assert vd._proc is None
|
||||
assert vd._display is None
|
||||
assert vd._saved_env == {}
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_linux_virtual_display_geometry_default():
|
||||
"""Default geometry is 1920x1080x24 — matches the profile sampler's
|
||||
default screen and avoids the Xvfb default of 1280x1024 which the
|
||||
fingerprint pipeline never produces."""
|
||||
vd = _LinuxVirtualDisplay()
|
||||
assert vd._geometry == "1920x1080x24"
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_linux_virtual_display_custom_geometry():
|
||||
"""Caller-supplied width/height feed straight into the Xvfb geometry
|
||||
spec; the depth is always 24 (Firefox/ANGLE assume true-color)."""
|
||||
vd = _LinuxVirtualDisplay(width=2560, height=1440)
|
||||
assert vd._geometry == "2560x1440x24"
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_linux_virtual_display_stop_without_start_is_safe():
|
||||
"""``stop()`` before ``start()`` must be a no-op — supports the
|
||||
``__exit__`` path on a launcher that failed before Xvfb was spawned.
|
||||
Verifies no AttributeError on env restore (saved_env is empty)."""
|
||||
vd = _LinuxVirtualDisplay()
|
||||
vd.stop()
|
||||
vd.stop()
|
||||
assert vd._proc is None
|
||||
assert vd._display is None
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import pytest
|
|||
|
||||
from invisible_playwright._fpforge import generate_profile
|
||||
from invisible_playwright.prefs import (
|
||||
_LINUX_GENERIC_FONT_FACTORS,
|
||||
_accept_language,
|
||||
_font_metrics_for_platform,
|
||||
_WIN_LIGHT_COLORS,
|
||||
|
|
@ -13,8 +14,9 @@ from invisible_playwright.prefs import (
|
|||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_translate_includes_gpu_renderer_windows():
|
||||
def test_translate_includes_gpu_renderer_windows(monkeypatch):
|
||||
"""On Windows, renderer/vendor are cleared so ANGLE reports native hardware."""
|
||||
monkeypatch.setattr(sys, "platform", "win32")
|
||||
p = generate_profile(seed=42)
|
||||
prefs = translate_profile_to_prefs(p)
|
||||
assert prefs["zoom.stealth.webgl.renderer"] == ""
|
||||
|
|
@ -356,3 +358,158 @@ def test_lan_ip_seed_zero_has_no_zero_octets():
|
|||
assert octets[1] == "168"
|
||||
assert int(octets[2]) >= 1
|
||||
assert int(octets[3]) >= 1
|
||||
|
||||
|
||||
# ──────────────────────────────────────────────────────────────────────
|
||||
# Linux-specific tests — exercise the branches that only fire when
|
||||
# ``sys.platform.startswith("linux")``. Patched via ``monkeypatch`` so
|
||||
# these run on any host CI environment.
|
||||
# ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_font_metrics_linux_prepends_generic_factors(monkeypatch):
|
||||
# FM1: Linux prepends the GTK/DejaVu compensation block to the
|
||||
# per-font metrics string sampled from the profile.
|
||||
monkeypatch.setattr(sys, "platform", "linux")
|
||||
out = _font_metrics_for_platform("Arial|1.0,Verdana|0.9,")
|
||||
assert out.startswith(_LINUX_GENERIC_FONT_FACTORS)
|
||||
assert out.endswith("Arial|1.0,Verdana|0.9,")
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_font_metrics_linux_empty_input_returns_empty(monkeypatch):
|
||||
# FM1b: even on Linux, empty profile metrics short-circuits before
|
||||
# the prepend so we never emit a metrics pref containing only the
|
||||
# generic block (which would surface as a tampering signal).
|
||||
monkeypatch.setattr(sys, "platform", "linux")
|
||||
assert _font_metrics_for_platform("") == ""
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_font_metrics_linux2_variant_uses_linux_branch(monkeypatch):
|
||||
# FM1c: ``sys.platform`` can be ``linux2`` on older Pythons / odd
|
||||
# WSL builds. ``startswith("linux")`` accepts both.
|
||||
monkeypatch.setattr(sys, "platform", "linux2")
|
||||
out = _font_metrics_for_platform("Verdana|0.9,")
|
||||
assert out.startswith(_LINUX_GENERIC_FONT_FACTORS)
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_gpu_renderer_set_from_profile_on_linux(monkeypatch):
|
||||
# PG1: on Linux we spoof to the profile's Windows-ANGLE renderer
|
||||
# string so cross-platform sessions present a consistent Windows GPU.
|
||||
monkeypatch.setattr(sys, "platform", "linux")
|
||||
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"] # non-empty
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_msaa_from_profile_on_linux(monkeypatch):
|
||||
# PG3: on Linux, MSAA comes from the profile's sampled value rather
|
||||
# than being pinned to 4 (which is the Windows ANGLE default).
|
||||
monkeypatch.setattr(sys, "platform", "linux")
|
||||
p = generate_profile(seed=42, pin={"webgl.msaa_samples": 8})
|
||||
prefs = translate_profile_to_prefs(p)
|
||||
assert prefs["zoom.stealth.webgl.msaa"] == 8
|
||||
assert prefs["webgl.msaa-samples"] == 8
|
||||
assert prefs["webgl.msaa-force"] is True
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_msaa_zero_disables_force_on_linux(monkeypatch):
|
||||
# PG3b: MSAA=0 means "no MSAA" so ``webgl.msaa-force`` must be False.
|
||||
# Verifies the ``> 0`` guard on the force flag.
|
||||
monkeypatch.setattr(sys, "platform", "linux")
|
||||
p = generate_profile(seed=42, pin={"webgl.msaa_samples": 0})
|
||||
prefs = translate_profile_to_prefs(p)
|
||||
assert prefs["zoom.stealth.webgl.msaa"] == 0
|
||||
assert prefs["webgl.msaa-force"] is False
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_canvas_noise_mask_intel_on_linux(monkeypatch):
|
||||
# CN1: Intel renderer → 1/16 noise (mask=15). Pinning the renderer
|
||||
# exercises the live ``_renderer_lo`` branch on Linux (where the
|
||||
# value is read from the profile rather than hardcoded as on Windows).
|
||||
monkeypatch.setattr(sys, "platform", "linux")
|
||||
p = generate_profile(
|
||||
seed=42,
|
||||
pin={
|
||||
"gpu.renderer": "ANGLE (Intel, Intel(R) UHD Graphics 630 Direct3D11 vs_5_0 ps_5_0, D3D11)",
|
||||
"gpu.vendor": "Google Inc. (Intel)",
|
||||
},
|
||||
)
|
||||
prefs = translate_profile_to_prefs(p)
|
||||
assert prefs["zoom.stealth.canvas.noise_skip_mask"] == 15
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_canvas_noise_mask_nvidia_on_linux(monkeypatch):
|
||||
# CN2: NVIDIA/AMD renderer → 1/8 noise (mask=7). The "intel" substring
|
||||
# check must NOT match here.
|
||||
monkeypatch.setattr(sys, "platform", "linux")
|
||||
p = generate_profile(
|
||||
seed=42,
|
||||
pin={
|
||||
"gpu.renderer": "ANGLE (NVIDIA, NVIDIA GeForce RTX 4090 Direct3D11 vs_5_0 ps_5_0, D3D11)",
|
||||
"gpu.vendor": "Google Inc. (NVIDIA)",
|
||||
},
|
||||
)
|
||||
prefs = translate_profile_to_prefs(p)
|
||||
assert prefs["zoom.stealth.canvas.noise_skip_mask"] == 7
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_webgl_extensions_preserved_on_linux(monkeypatch):
|
||||
# WE1: on Linux the curated WebGL1/2 extension lists from _BASELINE
|
||||
# remain in the prefs dict so the patched binary publishes them
|
||||
# instead of native Mesa's set.
|
||||
monkeypatch.setattr(sys, "platform", "linux")
|
||||
p = generate_profile(seed=42)
|
||||
prefs = translate_profile_to_prefs(p)
|
||||
assert prefs["zoom.stealth.webgl.extensions"]
|
||||
assert prefs["zoom.stealth.webgl2.extensions"]
|
||||
# Spot-check a canonical Windows ANGLE extension is in the list.
|
||||
assert "ANGLE_instanced_arrays" in prefs["zoom.stealth.webgl.extensions"]
|
||||
assert "OVR_multiview2" in prefs["zoom.stealth.webgl2.extensions"]
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_xvfb_workarounds_applied_on_linux(monkeypatch):
|
||||
# XW1: Linux Firefox under Xvfb can't run WebRender, so we force the
|
||||
# software path. These are added via ``setdefault`` so callers can
|
||||
# still override them via ``extra_prefs``.
|
||||
monkeypatch.setattr(sys, "platform", "linux")
|
||||
p = generate_profile(seed=42)
|
||||
prefs = translate_profile_to_prefs(p)
|
||||
assert prefs["gfx.webrender.all"] is False
|
||||
assert prefs["gfx.webrender.force-disabled"] is True
|
||||
assert prefs["webgl.force-enabled"] is True
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_xvfb_workarounds_caller_can_override(monkeypatch):
|
||||
# XW1b: the workarounds are added with ``setdefault``, so a user-
|
||||
# supplied ``extra_prefs`` value wins. Verifies the override path
|
||||
# doesn't get clobbered by the platform branch.
|
||||
monkeypatch.setattr(sys, "platform", "linux")
|
||||
p = generate_profile(seed=42)
|
||||
prefs = translate_profile_to_prefs(
|
||||
p, extra_prefs={"webgl.force-enabled": False}
|
||||
)
|
||||
assert prefs["webgl.force-enabled"] is False
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_virtual_display_no_op_on_linux(monkeypatch):
|
||||
# VD3: ``virtual_display`` is a Windows-only concept (CreateDesktop
|
||||
# alt-desktop GPU sandbox workaround). Even when True, Linux must
|
||||
# not pick up ``security.sandbox.gpu.level``.
|
||||
monkeypatch.setattr(sys, "platform", "linux")
|
||||
p = generate_profile(seed=42)
|
||||
prefs = translate_profile_to_prefs(p, virtual_display=True)
|
||||
assert "security.sandbox.gpu.level" not in prefs
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue