It started simple. One Playwright script to capture the homepage.
const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto('https://myapp.com');
await page.screenshot({ path: 'homepage.png' });
await browser.close();
Then the team needed the pricing page. So I added another script. Then the dashboard (which needs login first). Then the settings page (which needs a specific tab clicked). Then mobile versions.
Two months later I had 14 Playwright scripts. Some shared a login helper. Some had hardcoded waits. One had a try-catch that silently swallowed errors because the cookie banner sometimes loaded and sometimes didn't.
I was maintaining a bespoke test suite, except it wasn't testing anything. It was just taking pictures.
Config, not code
Here's what those 14 scripts look like as config:
{
"hiddenElements": {
"myapp.com": [".cookie-banner", ".chat-widget"]
},
"screenshots": [
{ "name": "homepage", "url": "https://myapp.com", "selector": ".hero" },
{ "name": "pricing", "url": "https://myapp.com/pricing", "selector": ".pricing-grid" },
{ "name": "dashboard", "url": "https://myapp.com/dashboard", "selector": ".dashboard" },
{
"name": "settings",
"url": "https://myapp.com/settings",
"selector": ".settings-panel",
"actions": [
{ "type": "click", "selector": "[data-tab='notifications']" }
]
}
]
}
npx heroshot
All 14 screenshots. One command. No scripts to maintain.
The cookie banner problem
In my scripts, I had this pattern everywhere:
try {
await page.click('.cookie-banner .dismiss', { timeout: 3000 });
} catch { /* maybe it didn't show */ }
With heroshot, you define hidden elements once per domain:
{
"hiddenElements": {
"myapp.com": [".cookie-banner"],
"docs.myapp.com": [".cookie-banner", ".announcement-bar"]
}
}
Every screenshot on that domain hides those elements automatically. No try-catch. No timeouts. No "maybe it showed, maybe it didn't."
Actions for complex state
The settings page needed a tab clicked first. In Playwright, that's 5 lines of setup. In config:
{
"actions": [
{ "type": "click", "selector": "[data-tab='notifications']" },
{ "type": "wait", "text": "Email preferences" }
]
}
There are 14 action types: click, type, hover, select_option, press_key, drag, wait, navigate, evaluate, fill_form, handle_dialog, file_upload, resize, and hide. Covers pretty much every pre-screenshot setup I've needed.
When to use Playwright directly
If you need complex conditional logic, dynamic data generation, or integration with a test framework, raw Playwright is the right tool.
But if you're just taking pictures of known pages at known states, config is simpler, more readable, and doesn't break when someone renames a helper function.
Top comments (0)