Files
felhom.eu/documentation/audits/rehearsal-2026-10-20/tools/shot.py
T
admin f84f94d987 Rehearsal 2026-10-20 (Tester4) + data-loss hunt: baselines, B1 install evidence, A6 drawn order, tools, first two reviewers' candidates
No release, deploy or publish. Off-site is OFF for Tester4 and the owner passphrase is needed from the operator (bind blocked after 2 of 5 tries).
2026-10-20 11:57:55 +02:00

68 lines
3.6 KiB
Python
Executable File

#!/opt/pw/bin/python
"""shot.py — open a page in real Chromium (Playwright) and screenshot it, phone and computer, HU and EN.
Rehearsal 2026-10-20, A4. Runs in the throwaway LXC rehearsal1020-browser on demo-hp.
Chromium is Debian's package (pinned by the image: `chromium --version` is in A4-browser/versions.txt);
Playwright 1.52.0 from PyPI drives it via executable_path.
usage: shot.py URL OUTPREFIX [--sizes phone,desktop] [--langs hu,en] [--state FILE] [--host-header H]
[--url-env NAME] [--wait MS] [--full]
--url-env NAME read the URL (or its secret tail) from env NAME and append it to URL — so a capability
link never appears on a command line or in this script's output.
--state FILE Playwright storage_state JSON (cookies) to load — a logged-in session.
Writes OUTPREFIX-<size>-<lang>.png and prints one line per shot: status, final path (secret tails masked), title.
"""
import argparse, os, re, sys
from playwright.sync_api import sync_playwright
SIZES = {"phone": dict(width=390, height=844, is_mobile=True, device_scale_factor=2, has_touch=True),
"desktop": dict(width=1366, height=768, is_mobile=False, device_scale_factor=1, has_touch=False)}
LANGS = {"hu": "hu-HU", "en": "en-US"}
UA_PHONE = ("Mozilla/5.0 (Linux; Android 14; Pixel 8) AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/155.0.0.0 Mobile Safari/537.36")
def mask(u: str) -> str:
return re.sub(r"[0-9a-f]{32,}", "<token>", u)
def main():
ap = argparse.ArgumentParser()
ap.add_argument("url"); ap.add_argument("out")
ap.add_argument("--sizes", default="phone,desktop"); ap.add_argument("--langs", default="hu,en")
ap.add_argument("--state"); ap.add_argument("--url-env"); ap.add_argument("--wait", type=int, default=1500)
ap.add_argument("--full", action="store_true"); ap.add_argument("--ignore-https-errors", action="store_true")
a = ap.parse_args()
url = a.url + (os.environ[a.url_env] if a.url_env else "")
with sync_playwright() as p:
b = p.chromium.launch(executable_path="/usr/bin/chromium", args=["--no-sandbox"])
for s in a.sizes.split(","):
for l in a.langs.split(","):
kw = dict(viewport={"width": SIZES[s]["width"], "height": SIZES[s]["height"]},
device_scale_factor=SIZES[s]["device_scale_factor"], is_mobile=SIZES[s]["is_mobile"],
has_touch=SIZES[s]["has_touch"], locale=LANGS[l],
extra_http_headers={"Accept-Language": LANGS[l] + "," + l + ";q=0.9"},
ignore_https_errors=a.ignore_https_errors, timezone_id="Europe/Budapest")
if s == "phone":
kw["user_agent"] = UA_PHONE
if a.state:
kw["storage_state"] = a.state
ctx = b.new_context(**kw)
pg = ctx.new_page()
try:
r = pg.goto(url, wait_until="networkidle", timeout=45000)
st = r.status if r else "-"
except Exception as e: # noqa: BLE001 — a failed load is a result, recorded
st = "ERR " + type(e).__name__
pg.wait_for_timeout(a.wait)
out = f"{a.out}-{s}-{l}.png"
pg.screenshot(path=out, full_page=a.full)
hw = pg.evaluate("document.documentElement.scrollWidth > window.innerWidth")
print(f"{st}\t{s}\t{l}\t{mask(pg.url)}\t{pg.title()!r}\thscroll={hw}\t{out}")
ctx.close()
b.close()
if __name__ == "__main__":
sys.exit(main())