From ebc71fdcd09dbd38ddc2adb78e1f9bdc0afa075f Mon Sep 17 00:00:00 2001 From: Malin Date: Thu, 20 Aug 2026 21:43:56 +0200 Subject: [PATCH] docs: add sandbox-browser-automation skill Documents what's actually installed for browser automation in this sandbox (Playwright via Python, no global Node package, browsers pre-cached) and a DNS-routing quirk on *.palmasolutions.net domains, plus the circular overflow:hidden badge-clipping bug class found while debugging sateulera-dev. --- skills/sandbox-browser-automation/SKILL.md | 91 ++++++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 skills/sandbox-browser-automation/SKILL.md diff --git a/skills/sandbox-browser-automation/SKILL.md b/skills/sandbox-browser-automation/SKILL.md new file mode 100644 index 0000000..428a87c --- /dev/null +++ b/skills/sandbox-browser-automation/SKILL.md @@ -0,0 +1,91 @@ +--- +name: sandbox-browser-automation +description: Use when you need to actually render a page (screenshot, computed styles, DOM inspection) instead of guessing from curl/static file inspection — e.g. "the CSS fix is deployed but the user still says it's not showing." Documents what's really installed in this sandbox and a live DNS-routing quirk that affects `*.palmasolutions.net` fleet domains specifically. +--- + +# Sandbox Browser Automation + +Static HTTP/CSS inspection cannot prove what actually renders — clipping, +cascade wins, and z-index/overflow interactions only show up in a real +browser. This sandbox has Playwright fully installed; use it before +concluding "the fix is deployed, must be a caching issue on the user's end." + +## What's actually installed (verified 2026-08-20) + +- **Playwright 1.62.1**, browsers pre-downloaded at `~/.cache/ms-playwright/` + (chromium + ffmpeg). No re-download needed. +- **Python path (preferred — no setup required):** the `playwright` package + is globally importable — `python3 -c "from playwright.sync_api import + sync_playwright"` works immediately, no venv/install step. `/usr/local/bin/playwright` + is this same package's CLI (`playwright codegen`, etc.). +- **Node path:** no *global* npm `playwright` package — a script needs its + own local `node_modules/playwright` to `require()` it. Check `/tmp/` first + for a scratch dir a prior session may have already set up (e.g. past names + seen: `sateulera-render`, `mobisol-render`, `st-pw-package`) before running + a fresh `npm install playwright` — reusing one skips the install entirely + since the browser binaries are already cached globally. `/tmp` is shared + across concurrent sandboxes/sessions and not durable — don't assume a given + scratch dir still exists, always `ls` first, and don't rely on one surviving + into a future session. +- No system `chromium`/`google-chrome` binary on `PATH` — always go through + Playwright's own managed browser, not a system browser launch. + +Default to the Python one-shot form when you just need a screenshot or a +computed-style check — it has zero setup cost. Reach for a Node script only +when reusing an existing scratch project's `node_modules` is actually faster, +or the task needs a JS-heavy interaction sequence. + +```python +from playwright.sync_api import sync_playwright +with sync_playwright() as p: + browser = p.chromium.launch() + page = browser.new_context(viewport={"width": 375, "height": 812}, + ignore_https_errors=True).new_page() + page.goto("https://example.com/", wait_until="domcontentloaded", timeout=45000) + page.screenshot(path="/root/.claude/jobs//tmp/out.png") + browser.close() +``` + +Use `wait_until="domcontentloaded"` by default, not `"networkidle"` — +sites with long-polling/analytics connections can hang `networkidle` well +past a reasonable timeout. + +## `*.palmasolutions.net` (and possibly other fleet domains) resolve differently inside this sandbox + +Confirmed 2026-08-20 on `sateulera-dev.palmasolutions.net`: `dig` from this +box returns the real public path (madri's Caddy IP, `195.20.235.141`), but +the box's own system resolver — what `curl`, Node, and Playwright actually +use via `getaddrinfo` — resolves the same name **directly to the backend's +Tailscale IP** (funky, `100.127.21.100`), bypassing Caddy entirely. This is +likely a split-DNS/hosts entry specific to this sandbox's network setup, not +present for the end user's real browser. + +In this case the content served both ways turned out to be byte-identical +(verified via `md5sum`), so it wasn't the cause of a "still broken" report — +but don't assume that holds for every fleet domain or every time. If a fix +verifies correctly in Playwright from this box but the user still reports it +broken, explicitly re-check via the real public path before ruling out a +routing/caching difference: + +```bash +curl -sk --resolve :443: "https:///path" | md5sum +curl -sk "https:///path" | md5sum # this box's default (possibly-shortcut) path +``` + +If the hashes differ, you've found a real routing/cache split, not a false +alarm — investigate the reverse proxy (Caddy on madri, or nginx on proxie) +rather than the origin. + +## Real bug class this catches that static inspection misses + +A badge/element positioned with a negative offset (`top:0;left:-2px`, etc.) +to overlap the corner of a **circular** `overflow:hidden` + `border-radius:50%` +container gets clipped by the circle's curve even while the badge's own +rectangular bounding box is still inside the container's rectangular bounds. +`border-radius` and `padding` fixes on the badge itself cannot fix this — +computed-style inspection via `getComputedStyle` will even report the badge's +own box as unclipped (`overflow: visible` on the badge). Only a rendered +screenshot (or checking the *parent's* computed `overflow`) reveals it. Fix +is to drop `overflow:hidden` from the circular parent (the inner icon +element usually already carries its own `border-radius:50%` and doesn't need +the outer clip).