How to Capture Screenshots of Multiple URLs with Loki
Loki captures and compares Storybook stories, not arbitrary URL lists. Learn its baseline workflow, filtering options, and a Playwright approach for unrelated pages.
Short answer: Loki is a visual regression tool for Storybook. Its documented workflow captures tested stories and compares them with checked-in reference images; the Loki documentation reviewed here does not describe an input option for an arbitrary list of unrelated URLs. If you mean Storybook stories, use Loki’s baseline and test workflow below. If you mean independent websites or pages, use a browser automation script such as Playwright or a screenshot API.
This distinction matters: a Storybook story is the unit Loki captures. Filtering can narrow which stories, configurations, or targets run, but it does not turn Loki into a general URL-list screenshotter. See the Loki getting started guide and CLI arguments reference.
1. Capture multiple Storybook stories with Loki
To capture a set of Storybook stories and compare them with approved references, install Loki, start Storybook, create baselines, and run the comparison. The commands below use Yarn as in Loki’s getting started guide.
- Install and initialize Loki. Run these from your project directory:
yarn add loki --dev
yarn loki init
The initialization command detects the project and adds default configuration to package.json. Commit the resulting configuration so local and CI runs use the same setup.
- Start Storybook. Keep the Storybook server running and reachable by Loki. Use your project’s existing Storybook start command and the port/configuration set for Loki.
- Create the initial reference screenshots.
yarn loki update
The default reference location is a loki folder. Review the generated images and check accepted references into version control. Loki’s guide mentions Git LFS as an option if image files make the repository cumbersome.
- Capture and compare.
yarn loki test
Loki captures the tested stories and compares them with the references. Inspect the current and difference output folders when a comparison fails; do not accept a change until you have reviewed what changed.
- Approve intentional visual changes. If the difference is expected, update the reference images with
yarn loki approve, or use the failed-test-limited approval command suggested by your installed Loki version. Review the exact command and its scope before running it.
Run a narrower set
The Loki CLI reference documents filters for stories, configurations, and targets. It also documents settings for output, reference, and difference folders. For example, pass the filter supported by your installed version like this:
yarn loki test -- --storiesFilter "Button/*"
The extra -- tells Yarn to forward the following arguments to Loki. The CLI reference also lists --configurationFilter and --targetFilter. Check the installed version’s help and configuration before copying a flag: the cited CLI page was last updated August 27, 2024, and options can differ between versions.
Capture settings that affect a run
The CLI reference documents Chrome capture controls including concurrency, load timeout, selector, animations, and retries. The documented defaults include four concurrent stories and a 60,000 ms page-load timeout. These are operational defaults, not performance guarantees. Tune them to your Storybook and CI environment, and verify the setting names against the Loki version in your lockfile.
| Setting or workflow | What to consider |
|---|---|
| Story/configuration/target filters | Use filters to limit the stories or targets under examination. Confirm the filter syntax and matching behavior for your installed version. |
| Concurrency | More parallel stories may shorten a run, but can increase resource pressure and rendering variability. Keep it consistent in CI. |
| Load timeout | Increase it for slow Storybook startup or stories with delayed content. A timeout that is too short creates failures; an unnecessarily long timeout delays feedback. |
| Selector and animations | Use the documented controls where the story’s capture area or motion affects the screenshot. Make sure the selected element is present and stable before capture. |
| Retries | Retries can help expose intermittent failures, but they do not fix nondeterministic rendering. Investigate recurring differences. |
| Output/reference/difference folders | Keep generated output and accepted baselines organized so reviewers can find current captures and visual diffs. |
2. If you mean arbitrary URLs: use a browser script
For unrelated URLs, write a small runner that navigates to each URL and saves a screenshot. The example below uses Playwright’s Node.js API. It reads URLs from a text file, captures full pages, and creates filesystem-safe numbered filenames. It is a minimal batch runner, not a Loki feature.
Install Playwright and its Chromium browser in your project:
npm install --save-dev playwright
npx playwright install chromium
Save one URL per line in urls.txt, then save this as capture.mjs:
import { chromium } from 'playwright';
import { readFile, mkdir } from 'node:fs/promises';
const urls = (await readFile('urls.txt', 'utf8'))
.split(/\r?\n/)
.map((line) => line.trim())
.filter((line) => line && !line.startsWith('#'));
if (urls.length === 0) {
throw new Error('urls.txt has no URLs');
}
await mkdir('screenshots', { recursive: true });
const browser = await chromium.launch({ headless: true });
try {
const context = await browser.newContext({ viewport: { width: 1440, height: 900 } });
for (let i = 0; i < urls.length; i++) {
const url = urls[i];
const page = await context.newPage();
try {
const response = await page.goto(url, {
waitUntil: 'networkidle',
timeout: 60000,
});
if (!response) {
console.warn(`No main-document response for ${url}`);
} else if (!response.ok()) {
console.warn(`${url} returned HTTP ${response.status()}`);
}
await page.screenshot({
path: `screenshots/${String(i + 1).padStart(3, '0')}.png`,
fullPage: true,
});
console.log(`Saved screenshot for ${url}`);
} catch (error) {
console.error(`Failed to capture ${url}: ${error.message}`);
} finally {
await page.close();
}
}
await context.close();
} finally {
await browser.close();
}
Run it with:
node capture.mjs
networkidle can be unsuitable for pages that keep network connections open or poll continuously. If that happens, wait for a page-specific selector or use a deliberate fixed delay after navigation instead. A full-page screenshot can also be very tall; use fullPage: false for the viewport, or capture a specific element with Playwright’s locator screenshot API. Playwright documents page screenshots and visual comparisons in its official visual comparisons documentation.
Reliability for URL batches
- Validate input: Keep one absolute HTTP or HTTPS URL per line and reject malformed entries before starting a long batch.
- Use stable filenames: The example uses input order. For repeatable output across reordered lists, derive a sanitized filename or hash from each URL and handle collisions.
- Bound concurrency: The sample is sequential, which is easier on the browser and target sites. If you add parallelism, cap it and expect higher CPU and memory use.
- Choose readiness per site: Network idle is a useful default, not a universal definition of “ready.” Wait for a meaningful selector when the page has a clear content landmark.
- Keep the environment stable for visual comparisons: Browser version, operating system, settings, hardware, power source, and headless mode can affect rendering. Playwright recommends generating and comparing snapshots in the same environment.
- Store failures separately: Log the URL and error, continue when appropriate, and return a failing process status if your CI policy requires every URL to succeed.
3. Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server from ScreenshotNeo. One GET request captures a URL as PNG, JPEG, WebP, or PDF. This is useful when you need screenshots of arbitrary pages without maintaining a browser runner. See the ScreenshotNeo API documentation.
Here is a cURL request for one URL; repeat it for each URL in your list or use the API’s bulk capture option for up to 100 URLs per call:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
Python:
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()
open("shot.webp", "wb").write(r.content)
Node.js:
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}`);
await import('node:fs/promises').then(({ writeFile }) => writeFile('shot.webp', Buffer.from(await res.arrayBuffer())));
ScreenshotNeo removes cookie and consent banners, newsletter popups, and chat widgets before capture; each cleanup step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing status. Its MCP server provides take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 screenshots.
Create a free ScreenshotNeo account for 1,000 screenshots a month with no card.
4. Troubleshooting
| Problem | Likely cause | What to do |
|---|---|---|
| Loki does not capture arbitrary URLs | The documented Loki workflow is based on Storybook stories, not an unrelated URL list. | Use Loki for Storybook visual regression. Use a browser automation runner or screenshot API for standalone pages. |
| Loki cannot reach Storybook | The Storybook server is not running, or the configured address/port is wrong. | Start Storybook and check the host, port, and Loki configuration for your project. |
| A Loki argument appears ignored | The package runner may not have forwarded the option, or the flag differs in your Loki version. | With Yarn or npm, put -- before CLI arguments; check the installed version’s help and the official CLI reference. |
| A story times out | Startup or page readiness exceeds the configured load timeout, or the story is stuck. | Check the story directly, then tune the documented timeout setting if the content is legitimately slow. |
| Visual differences appear on every run | Rendering may vary with browser, operating system, hardware, settings, headless mode, or dynamic page content. | Keep the capture environment consistent and remove or stabilize time-dependent content before changing baselines. |
| Playwright waits indefinitely or reaches its timeout | networkidle may never occur on pages with continuous requests, or navigation may be slow. |
Choose a page-specific readiness selector or a suitable explicit wait, and set a timeout that matches the workflow. |
| Some URLs fail while the batch continues | A page may be unavailable, return an error status, or fail navigation. | Check the logged URL and status, retry only where appropriate, and make the script’s exit status reflect failures if CI depends on it. |
| Screenshot output is missing or overwrites another page | Output directory or naming logic is wrong, or filenames collide. | Create the output directory first and use stable unique names derived from each input URL. |
5. Performance and cost considerations
Loki’s runtime depends on the number and weight of stories, configured concurrency, Storybook readiness, and the CI machine. Its documented default concurrency is four stories; increasing it may use more resources, while lowering it can make runs more predictable on constrained machines. Treat the 60-second timeout as a default to tune, not a guarantee that each story finishes within that time.
A Playwright script uses compute and time on the machine where Chromium runs. Sequential processing limits resource pressure and is a sensible starting point for a long list; measured, bounded parallelism can improve throughput when the machine and target pages can support it. Retries should be limited to transient errors so persistent failures remain visible.
ScreenshotNeo pricing is based on the stated monthly plans: Free includes 1,000 shots; Starter is $5 for 3,000; Growth is $15 for 15,000; Pro is $39 for 60,000; Scale is $99 for 250,000; Business is $249 for 1,000,000. Yearly billing gives two months free, and every feature is on every plan. Only clean shots are billed; the response’s X-Page-Verdict and X-Billed headers report the outcome. Check the documentation for request options such as viewport/device, full page, output format, waits, caching, bulk capture, and async jobs.
6. FAQ
Can Loki take a screenshot of every URL in a text file?
The reviewed Loki documentation does not describe a general URL-list input. Loki’s documented capture unit is a tested Storybook story.
Does loki update run the regression test?
It creates or refreshes reference screenshots. Use loki test to capture and compare against the references.
Should I approve every Loki difference?
No. Inspect the current and difference images first, then approve only changes that are intended.
Can I use Loki and Playwright in the same project?
Yes. Loki can cover Storybook story regressions while a separate Playwright script captures standalone URLs; choose the tool that matches each set of pages.


