mirror of
https://github.com/feder-cr/invisible_playwright.git
synced 2026-06-13 08:55:12 +02:00
headless=True now hides the window via the binary's own cloak pref (zoom.stealth.cloak_windows) on Windows and macOS instead of the broken thread-level SetThreadDesktop; macOS is now supported. Linux keeps Xvfb. Adds e2e guards that also run per-platform in the release drive-gate: - test_cloak: the window is hidden (Windows DWMWA_CLOAKED / macOS CGWindowAlpha) yet still renders + drives; the macOS leg is where the cocoa cloak patch runs. - a WebGL readPixels masking guard: the gamma noise must stay a smooth gamma remap, not the pixelscan-maskable +-1 spikes.
106 lines
4.1 KiB
Python
106 lines
4.1 KiB
Python
"""Cloak guard (e2e) — verifies the source-level "invisible headless" cloak:
|
|
the chrome window is hidden from the screen YET keeps rendering on the real GPU
|
|
(not Playwright's native headless, which has no WebGL). Runs per-platform in CI:
|
|
- Windows: the DWMWA_CLOAK attribute (queried via DWMWA_CLOAKED).
|
|
- macOS: the NSWindow alpha (queried via Quartz CGWindowListCopyWindowInfo).
|
|
- Linux: skipped — there the wrapper hides via Xvfb, not a source-level cloak.
|
|
|
|
This is the CI validation for the macOS cocoa cloak patch, which can't be built
|
|
or run on the Windows/Linux dev boxes.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import sys
|
|
import time
|
|
|
|
import pytest
|
|
|
|
from invisible_playwright import InvisiblePlaywright
|
|
|
|
CLOAK_PREFS = {
|
|
"zoom.stealth.cloak_windows": True,
|
|
"widget.windows.window_occlusion_tracking.enabled": False,
|
|
}
|
|
|
|
_WEBGL_RENDERER = """() => {
|
|
const g = document.createElement('canvas').getContext('webgl');
|
|
if (!g) return 'NO-WEBGL';
|
|
const d = g.getExtension('WEBGL_debug_renderer_info');
|
|
return d ? g.getParameter(d.UNMASKED_RENDERER_WEBGL) : (g.getParameter(g.RENDERER) || '');
|
|
}"""
|
|
|
|
|
|
def _windows_moz_window_cloaked() -> bool:
|
|
"""True if at least one MozillaWindowClass top-level window is DWMWA_CLOAKED."""
|
|
import ctypes
|
|
from ctypes import wintypes
|
|
|
|
user32 = ctypes.windll.user32
|
|
dwm = ctypes.windll.dwmapi
|
|
DWMWA_CLOAKED = 14
|
|
ENUM = ctypes.WINFUNCTYPE(wintypes.BOOL, wintypes.HWND, wintypes.LPARAM)
|
|
found = []
|
|
|
|
def cb(hwnd, _):
|
|
c = ctypes.create_unicode_buffer(256)
|
|
user32.GetClassNameW(hwnd, c, 256)
|
|
if c.value == "MozillaWindowClass":
|
|
v = wintypes.DWORD(0)
|
|
dwm.DwmGetWindowAttribute(wintypes.HWND(hwnd), DWMWA_CLOAKED,
|
|
ctypes.byref(v), 4)
|
|
found.append(v.value)
|
|
return True
|
|
|
|
user32.EnumWindows(ENUM(cb), 0)
|
|
return any(state != 0 for state in found)
|
|
|
|
|
|
def _macos_firefox_window_alpha_zero() -> bool:
|
|
"""True if a Firefox on-screen window reports ~0 alpha (cloaked)."""
|
|
from Quartz import ( # type: ignore
|
|
CGWindowListCopyWindowInfo,
|
|
kCGWindowListOptionOnScreenOnly,
|
|
kCGNullWindowID,
|
|
)
|
|
|
|
infos = CGWindowListCopyWindowInfo(kCGWindowListOptionOnScreenOnly, kCGNullWindowID)
|
|
alphas = []
|
|
for w in infos or []:
|
|
owner = (w.get("kCGWindowOwnerName") or "")
|
|
if "firefox" in owner.lower() or "nightly" in owner.lower():
|
|
alphas.append(float(w.get("kCGWindowAlpha", 1.0)))
|
|
# cloaked windows are alpha 0; if Firefox has any window it must be ~0.
|
|
return bool(alphas) and all(a < 0.05 for a in alphas)
|
|
|
|
|
|
@pytest.mark.e2e
|
|
@pytest.mark.skipif(
|
|
sys.platform.startswith("linux"),
|
|
reason="source-level cloak is Windows/macOS only; Linux hides via Xvfb",
|
|
)
|
|
def test_cloak_hides_window_but_keeps_rendering(firefox_binary):
|
|
with InvisiblePlaywright(
|
|
seed=42, binary_path=firefox_binary, headless=False, extra_prefs=CLOAK_PREFS
|
|
) as browser:
|
|
page = browser.new_context().new_page()
|
|
page.goto("https://example.com", timeout=30_000)
|
|
time.sleep(2)
|
|
|
|
# 1) still renders on the real GPU pipeline (a non-blank screenshot proves
|
|
# the compositor is alive despite the window being hidden).
|
|
shot = page.screenshot()
|
|
assert len(shot) > 3000, "cloaked window produced a blank screenshot (rendering paused)"
|
|
|
|
# 2) real WebGL present (native headless has none) -> headed pipeline intact.
|
|
renderer = page.evaluate(_WEBGL_RENDERER)
|
|
assert renderer and renderer != "NO-WEBGL", f"no real WebGL under cloak: {renderer!r}"
|
|
|
|
# 3) the window is actually hidden (per-platform).
|
|
if sys.platform == "win32":
|
|
assert _windows_moz_window_cloaked(), "Firefox window is not DWMWA_CLOAKED"
|
|
elif sys.platform == "darwin":
|
|
try:
|
|
hidden = _macos_firefox_window_alpha_zero()
|
|
except ImportError:
|
|
pytest.skip("pyobjc Quartz not available to verify macOS cloak alpha")
|
|
assert hidden, "Firefox macOS window is not alpha-cloaked"
|