How to Use Loki to Capture a Page Behind a Login
The Loki package documented for Storybook is not a general login automation tool. Here’s how to identify it, capture an authorized page, and automate repeat captures.
Short answer: first confirm which “Loki” you mean. The Loki package documented in the available project README is for capturing Storybook stories and comparing screenshots; that documentation does not establish that it can sign in to arbitrary websites or capture pages behind a login. For a one-off capture, sign in normally in a browser you are authorized to use, open the protected URL, and save a screenshot. For repeatable automation, use a browser automation tool such as Playwright with an authenticated browser context. [Loki project documentation] [Playwright authentication]
1. Identify the Loki product
The name alone is ambiguous. The documented JavaScript package describes a Storybook screenshot and visual-comparison workflow. That is useful for checking UI changes in Storybook, but its documentation does not say it automates sign-in to an arbitrary website. Do not assume a Loki command or configuration option for protected pages without documentation for the specific Loki product you have.
If you mean a different tool named Loki, check its official documentation for support for browser navigation, login flows, session persistence, and screenshots. Those capabilities determine the correct steps. The workflow below uses a regular browser for a manual capture and Playwright for repeatable browser automation.
2. Capture a page manually
- Open the site in a browser and sign in through its normal login flow. Complete any MFA or other required verification yourself.
- Navigate to the exact protected URL, not just the site’s home page.
- Wait until the private content has loaded. Confirm that you are not looking at a login redirect, loading placeholder, or error page.
- Capture the visible viewport with your browser’s screenshot feature, or use a browser extension that captures the full page if you need content below the fold.
- Open the saved image and inspect it. Check the page identity, loaded content, and whether account details or other sensitive information are visible.
This is the simplest option for an occasional screenshot. It avoids scripting session state, but it requires a person to sign in and repeat the capture each time. The exact browser menu or shortcut varies by browser and operating system.
3. Automate repeat captures with Playwright
Playwright documents a storage-state workflow: authenticate in a browser context, save its state, and load that state into another context later. This can preserve authentication data such as cookies and local storage. It is not a guarantee that every site’s login method, browser credential, or MFA mechanism can be reused this way. Treat the saved state file as a credential.
Install the browser automation package
mkdir login-screenshot
cd login-screenshot
npm init -y
npm install playwright
npx playwright install chromium
Sign in once and save browser state
Create auth.setup.js. This example opens a visible browser so you can complete the site’s normal sign-in and any MFA. Replace the URL with the site’s login page. Once you confirm you are signed in, return to the terminal and press Enter.
const { chromium } = require('playwright');
const readline = require('node:readline/promises');
const { stdin: input, stdout: output } = require('node:process');
(async () => {
const browser = await chromium.launch({ headless: false });
const context = await browser.newContext();
const page = await context.newPage();
await page.goto('https://example.com/login', { waitUntil: 'domcontentloaded' });
console.log('Sign in in the browser window. Complete MFA if prompted.');
const rl = readline.createInterface({ input, output });
await rl.question('When you are signed in, press Enter here. ');
rl.close();
await context.storageState({ path: 'playwright/.auth/state.json' });
await browser.close();
console.log('Saved browser storage state. Keep it private.');
})().catch((error) => {
console.error(error);
process.exitCode = 1;
});
Create the output directory before running the script:
mkdir -p playwright/.auth
node auth.setup.js
Playwright’s documentation recommends keeping authentication state out of source control. Add it to .gitignore and restrict access to the file:
printf '\nplaywright/.auth/\n' >> .gitignore
Load the saved state and capture the protected page
Create capture.js. Change TARGET_URL to the protected page. Set WAIT_FOR_SELECTOR to a stable element that appears only after successful sign-in, such as a page heading or account navigation item. The script takes a viewport screenshot by default; set FULL_PAGE=true to capture the full page.
const { chromium } = require('playwright');
const TARGET_URL = process.env.TARGET_URL || 'https://example.com/account';
const WAIT_FOR_SELECTOR = process.env.WAIT_FOR_SELECTOR || 'main';
const FULL_PAGE = process.env.FULL_PAGE === 'true';
(async () => {
const browser = await chromium.launch({ headless: true });
const context = await browser.newContext({
storageState: 'playwright/.auth/state.json',
viewport: { width: 1440, height: 1000 },
deviceScaleFactor: 1
});
const page = await context.newPage();
try {
await page.goto(TARGET_URL, { waitUntil: 'domcontentloaded', timeout: 30000 });
await page.locator(WAIT_FOR_SELECTOR).waitFor({ state: 'visible', timeout: 15000 });
const finalUrl = page.url();
if (finalUrl.includes('/login')) {
throw new Error(`Redirected to login page: ${finalUrl}`);
}
await page.screenshot({
path: 'protected-page.png',
fullPage: FULL_PAGE,
animations: 'disabled'
});
console.log(`Saved protected-page.png from ${finalUrl}`);
} finally {
await context.close();
await browser.close();
}
})().catch((error) => {
console.error(error);
process.exitCode = 1;
});
Run it with the target and, if needed, a site-specific signed-in marker:
TARGET_URL='https://example.com/account/orders' \
WAIT_FOR_SELECTOR='[data-testid="account-orders"]' \
node capture.js
For a full-page image:
FULL_PAGE=true TARGET_URL='https://example.com/account/orders' node capture.js
Important Playwright choices
- Wait for the right signal:
domcontentloadedsays the initial document was parsed; it does not guarantee that a client-rendered page or private data is ready. Wait for a selector that proves the intended content appeared. - Viewport or full page:
fullPage: falsecaptures the current viewport.fullPage: truecaptures the full scrollable page and can create a very tall image. - Viewport size and scale: set
viewportanddeviceScaleFactorin the browser context to control layout size and pixel density. The page may reflow at different widths. - Animations:
animations: 'disabled'reduces variation from CSS animations and transitions. It does not make all dynamic page content deterministic. - Session lifetime: storage state can expire, be invalidated by logout, or be bound to a device or other condition. Be ready to perform sign-in again.
Playwright’s screenshot guide documents viewport and full-page screenshots, while its authentication guide covers saving and reusing storage state. These guides do not promise that storage state works with every authentication setup. [Playwright screenshots] [Playwright authentication]
4. HTTP Basic Authentication and screenshot APIs
HTTP Basic Authentication is a specific web authentication mechanism. A screenshot API that documents Basic Auth credentials can use them when the target site actually protects the page with that mechanism. This does not mean the API can complete a normal HTML login form, handle an arbitrary identity provider, or pass MFA. Check the API’s documented authentication method against the site’s login flow before relying on it. [Playwright authentication patterns]
For a regular login form or a site that requires an interactive identity provider, browser automation with an authorized session is the more general workflow described here. Keep session files and credentials private, and capture only pages you are allowed to access and share.
5. Verify and protect the screenshot
- Confirm the saved file shows the requested protected page rather than a login page or redirect.
- Check that asynchronous content, images, and lower-page sections have loaded before capture.
- Inspect for account names, email addresses, balances, customer data, tokens, or other details that should not be distributed.
- Store the Playwright storage-state file as a secret. Anyone who can use a valid session may be able to access the same account.
- Delete or rotate saved state when it is no longer needed, and avoid committing it to a repository or placing it in a public artifact directory.
6. Troubleshooting
| Symptom | Likely cause | What to do |
|---|---|---|
| The page is a login screen | The saved session is missing, expired, or rejected; the site may also require a different login step. | Run the visible sign-in setup again, verify the final signed-in URL before saving state, and check the site’s authentication requirements. |
| The URL redirects after navigation | The session is not accepted for that route, or the target URL requires another permission. | Inspect page.url(), sign in with an account authorized for that page, and test the exact URL in the same browser session. |
| The selector wait times out | The selector is wrong, the page has not rendered it, or the page did not reach the expected signed-in state. | Inspect the page in a headed browser, choose a stable selector present on success, and check for an error or login redirect. |
| The screenshot is blank or incomplete | The page is client-rendered, data loads asynchronously, or capture starts before the content is ready. | Wait for a content-specific selector. If needed, wait for a known page event or a short, justified delay after that signal. |
| Images or lower sections are missing | Lazy-loaded content may load only as the page scrolls, or the capture is viewport-only. | Use full-page capture and, for pages that load on scroll, scroll through the page before capturing and wait for images to load. |
| Authentication works manually but not headlessly | The site may require interactive challenges, device-bound credentials, or an authentication method not represented by the saved browser storage. | Use the site’s supported automation or test login approach if available; do not try to bypass access controls. Refresh state through an authorized visible login when needed. |
| The Loki command does not accept a URL or login settings | The documented Loki package targets Storybook stories, not arbitrary authenticated pages. | Confirm the exact Loki product and version. Use its own documentation or use the Playwright workflow above for browser navigation and capture. |
7. Performance, reliability, and cost
For a single page, a manual browser capture has little setup but requires a person. A saved browser context avoids repeating the sign-in steps for each run, but it adds maintenance: sessions expire, pages render at different speeds, and a stable signed-in marker is needed. Full-page screenshots use more time and memory than viewport shots, especially for very long pages. Limit image dimensions and choose only the output needed.
For more reliable runs, use a specific selector to identify successful page readiness, keep the browser and authentication state isolated per job, set navigation and selector timeouts, and save the final URL alongside the screenshot for diagnosis. Do not treat a successful navigation event alone as proof that private content loaded. Browser automation has no per-capture service fee in the examples above, but it does use compute and requires maintaining the browser environment and authorized session.
Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server. It takes a URL in one GET request and returns an image or PDF. Its API documentation describes HTTP Basic Authentication for pages that use that mechanism; it should not be assumed to sign in to an ordinary login form or an arbitrary identity provider. See the ScreenshotNeo API documentation for the supported parameters and authentication options.
curl -G "https://api.screenshotneo.com/v1/shot" \
-d access_key=YOUR_API_KEY \
--data-urlencode url=https://stripe.com \
-o shot.webp
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
timeout=90,
)
r.raise_for_status()
with open("shot.webp", "wb") as f:
f.write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
const fs = await import('node:fs/promises');
await fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));
Replace the example URL with a page the API can access using a documented authentication method. ScreenshotNeo removes cookie banners, newsletter popups, and chat widgets before capture. Bot checks, blank pages, and failed loads are never billed; response headers report the page verdict and billing status. Its MCP server lets AI agents use screenshot tools. The free plan includes 1,000 screenshots per month with no card, and paid plans start at $5 for 3,000 screenshots.
Sign up for ScreenshotNeo and get 1,000 screenshots a month free, with no card required.
FAQ
Can the Loki Storybook package capture any URL?
The cited project documentation describes Storybook story screenshots and comparisons. It does not establish arbitrary URL navigation or authenticated website capture. Check documentation for the exact Loki product you have.
Does Playwright storage state save my password?
The storage-state workflow saves browser state such as cookies and local storage; it is not a general export of every credential held by a browser. Protect the file because a valid session may grant account access.
Should I use Basic Auth for a normal login form?
No. Basic Auth applies only when the site uses HTTP Basic Authentication. A regular login form or identity-provider flow needs an authentication method that supports that flow.
How can I tell whether the screenshot captured the signed-in content?
Wait for a stable element unique to the protected page, check the final URL, and inspect the image before sharing it.


