spa-fetch
Fetch rendered content from JavaScript-driven or login-gated pages with Playwright, including internal sites behind SSO or bot checks, when WebFetch cannot. Skip static HTML (WebFetch first), API endp
Files4
View on GitHubSKILL.mdai/skills/spa-fetch/SKILL.md
GitHubFetch rendered content from a JavaScript-heavy SPA site.
Arguments: $ARGUMENTS
Script
spa-fetch.js in this skill's base directory (shown above as "Base directory for this skill: ...").
All bash commands below use <base-dir> as a placeholder — substitute the actual base directory path.
Steps
1. Headless fetch
node <base-dir>/spa-fetch.js <url> [css-selector] [--html]; echo "EXIT:$?"
Use dangerouslyDisableSandbox: true and timeout: 45000.
Check the EXIT: line in stdout to determine the result:
- EXIT:0: success — go to step 3
- EXIT:10: login required — go to step 2
- EXIT:11: bot detection (Cloudflare/CAPTCHA) — go to step 2 (establish browser session first)
- EXIT:1: error — report to user
2. Login flow (only if exit code 10)
Run the login browser in background:
node <base-dir>/spa-fetch.js --open-login <url>
Use dangerouslyDisableSandbox: true, timeout: 300000, and run_in_background: true.
Then immediately prompt interactively:
로그인이 필요합니다. 브라우저가 열렸으니 5분 내 로그인해주세요 (timeout 300s — 초과 시 background process가 종료되어 인증이 저장되지 않습니다). 로그인 완료 후 자동으로 닫힙니다. 닫히면 알려주세요.
When user confirms, retry step 1. If step 1 still returns EXIT:10, the background process likely timed out before login completed — re-run step 2 and prompt the user again.
3. Output
Present the fetched content, or use it for the requested analysis.
4. Deep exploration (optional)
If the fetched content is a list/table and the user wants detail from individual items:
- Write a custom Playwright script to
$TMPDIR - Reuse the auth profile at
<base-dir>/.spa-auth/<domain>/ - Require Playwright from
<base-dir>/node_modules/playwright - Use
chromium.launchPersistentContext(profileDir, { headless: true })for session reuse
Boilerplate:
const { chromium } = require('<base-dir>/node_modules/playwright');
const path = require('path');
const profileDir = path.join('<base-dir>', '.spa-auth', '<domain>');
(async () => {
const ctx = await chromium.launchPersistentContext(profileDir, {
headless: true,
timeout: 30000,
args: ['--disable-blink-features=AutomationControlled'],
userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
});
const page = ctx.pages()[0] || await ctx.newPage();
// ... navigate, interact, extract
await ctx.close();
})();
Use dangerouslyDisableSandbox: true and appropriate timeout.
Rules
- Always use
dangerouslyDisableSandbox: true - Do NOT combine --open-login with fetch in a single call
- If script not found, run
cd <base-dir> && npm install