fix(hyde): harden Elmhurst browser automation for the dev container

In-container Playwright runs were flaky: the chromium renderer crashed
mid-build ("Target crashed") on the 64M /dev/shm, and login intermittently
hung. Added `--disable-dev-shm-usage` + `--no-sandbox` launch args, a
4-attempt login retry loop (domcontentloaded + explicit selector wait),
and an `ELM_GUID` env override so a per-UPRN assessment can be targeted
without editing the module. Tooling only — no calculator impact.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Khalim Conn-Kowlessar 2026-06-23 19:46:41 +00:00
parent 7b30b464e5
commit aad4050915

View file

@ -64,8 +64,12 @@ HERE = Path(__file__).parent
SESSION_DIR = HERE / ".elmhurst-session"
CREDS_FILE = HERE / ".elmhurst-creds.json" # gitignored; {"access":..,"pwd":..}
# The single reusable campaign assessment ("Khalim-test"). Overwrite it per UPRN.
ASSESSMENT_GUID = "B44A0DB4-4C08-4241-B818-86F060172105"
# The reusable campaign assessment ("Khalim-test"), overwritten per UPRN. Can be
# overridden per run via the `ELM_GUID` env var (e.g. a per-UPRN assessment under
# a different account) — see Limitations "parameterize the GUID".
ASSESSMENT_GUID = os.environ.get(
"ELM_GUID", "B44A0DB4-4C08-4241-B818-86F060172105"
)
ENTRY_URL = (
"https://rdsap10online.elmhurstenergy.co.uk/Processing/WebFormAddress.aspx"
f"?Guid={ASSESSMENT_GUID}"
@ -98,17 +102,31 @@ def session() -> Generator[tuple[BrowserContext, Page], None, None]:
headless=False,
accept_downloads=True,
viewport={"width": 1400, "height": 1000},
# Container /dev/shm is tiny (64M) → chromium renderer "Target
# crashed" mid-build without this. --no-sandbox for rootless Xvfb.
args=["--disable-dev-shm-usage", "--no-sandbox"],
)
page = ctx.pages[0] if ctx.pages else ctx.new_page()
page.on("dialog", lambda d: d.accept())
page.goto(ENTRY_URL, wait_until="networkidle", timeout=60_000)
if "WebFormLogin" in page.url:
access, pwd = _creds()
page.fill("#ctl00_ctl00_ContentBody_ContentBody_TextBoxAccessCode", access)
page.fill("#ctl00_ctl00_ContentBody_ContentBody_TextBoxPassword", pwd)
with page.expect_navigation(wait_until="networkidle", timeout=60_000):
page.click("#ctl00_ctl00_ContentBody_ContentBody_ImageButtonEnter")
page.wait_for_timeout(1000)
# Login can be slow/flaky; retry the whole goto+fill a few times.
for attempt in range(4):
try:
page.goto(ENTRY_URL, wait_until="domcontentloaded", timeout=60_000)
if "WebFormLogin" not in page.url:
break # already authed (server session still valid)
access, pwd = _creds()
ac = "#ctl00_ctl00_ContentBody_ContentBody_TextBoxAccessCode"
page.wait_for_selector(ac, state="visible", timeout=30_000)
page.fill(ac, access)
page.fill("#ctl00_ctl00_ContentBody_ContentBody_TextBoxPassword", pwd)
with page.expect_navigation(wait_until="networkidle", timeout=60_000):
page.click("#ctl00_ctl00_ContentBody_ContentBody_ImageButtonEnter")
page.wait_for_timeout(1000)
if "WebFormLogin" not in page.url:
break
except PlaywrightTimeoutError:
print(f" login attempt {attempt+1} timed out, retrying...", flush=True)
page.wait_for_timeout(2000)
try:
yield ctx, page
finally: