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.
92 lines
5.0 KiB
Markdown
92 lines
5.0 KiB
Markdown
---
|
|
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/<job>/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 <domain>:443:<public-ip-from-dig> "https://<domain>/path" | md5sum
|
|
curl -sk "https://<domain>/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).
|