From 1f4f7c4f9420f65cb378a1fc718368d22737c8f5 Mon Sep 17 00:00:00 2001 From: axsaucedo Date: Sun, 6 Sep 2026 14:52:09 +0200 Subject: [PATCH] fix(newsletter): record walks headed, skip the root load, and dismiss signup modals Two of five articles in issue 403 could not be recorded at all, and the third recorded the wrong thing. All three failures were in how the walk reaches and holds the page. capture-gifs now passes --headed and --no-root to the engine. Headless Chromium is fingerprinted and blocked outright by Cloudflare-fronted sites: the same Medium article returns the real page headed and "Sorry, you have been blocked" headless. Separately, the engine loads the site root before handing over, and on those sites the root-then-deep-link hop is itself what trips the bot check, while going straight to the article is served normally. The root load was also wasted footage the runner only trimmed back off. Both flags are new in site-capture and need that engine. The flow now presses Escape and clicks a close control at each beat. Medium interrupts a scroll partway down with a signup modal that then covers the body for the rest of the walk, which is what the first successful Netflix recording captured. Dismissing costs nothing on a page with no modal, so it runs at every beat rather than only on arrival. Verified by extracting frames rather than trusting the beat log: the Netflix walk now shows article body throughout. openai.com still fails, serving its own "This page couldn't load" error to the automation browser regardless of these flags, so that article needs an image or a hand-recorded clip. --- scripts/newsletter/capture-flow.mjs | 15 +++++++++++++++ scripts/newsletter/capture-gifs.mjs | 7 +++++++ 2 files changed, 22 insertions(+) diff --git a/scripts/newsletter/capture-flow.mjs b/scripts/newsletter/capture-flow.mjs index 5100fe2b..3a3c61a4 100644 --- a/scripts/newsletter/capture-flow.mjs +++ b/scripts/newsletter/capture-flow.mjs @@ -22,7 +22,21 @@ export default async function flow({ goto, mark, page, pause, scrollTo }) { await goto(process.env.CAPTURE_TARGET_URL); mark('article'); + // Third-party articles interrupt a scroll with a newsletter or signup modal + // (Medium does it partway down), which then sits over the body for the rest of + // the walk. Escape closes every one seen so far, and costs nothing on a page + // that has none, so it is pressed at each beat rather than only on arrival. + const dismiss = async () => { + await page.keyboard.press('Escape').catch(() => {}); + await page + .locator('[aria-label="close" i], button[data-testid="close-button"]') + .first() + .click({ timeout: 700 }) + .catch(() => {}); + }; + await pause(1600); + await dismiss(); mark('top'); const depth = await page.evaluate(() => { @@ -36,6 +50,7 @@ export default async function flow({ goto, mark, page, pause, scrollTo }) { if (depth > 200) { await scrollTo(depth); + await dismiss(); await pause(1500); mark('body'); await scrollTo(0); diff --git a/scripts/newsletter/capture-gifs.mjs b/scripts/newsletter/capture-gifs.mjs index b080740d..0920905c 100644 --- a/scripts/newsletter/capture-gifs.mjs +++ b/scripts/newsletter/capture-gifs.mjs @@ -238,6 +238,13 @@ async function capture(target, { engine, postsDir, theme, workDir }) { 'node', [ engine, + // Headed because headless Chromium is fingerprinted and blocked outright by + // some of the sites the newsletter links, and --no-root because the engine + // otherwise loads the site root first: wasted footage the runner trims, and + // on a Cloudflare-fronted site the root-then-deep-link hop is itself what + // trips the bot check. + '--headed', + '--no-root', '--url', target.url, '--flow',