DEV Community

Ondrej Machala
Ondrej Machala

Posted on

You Wrote 14 Playwright Scripts Just to Screenshot Your Own App

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();
Enter fullscreen mode Exit fullscreen mode

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']" }
      ]
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode
npx heroshot
Enter fullscreen mode Exit fullscreen mode

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 */ }
Enter fullscreen mode Exit fullscreen mode

With heroshot, you define hidden elements once per domain:

{
  "hiddenElements": {
    "myapp.com": [".cookie-banner"],
    "docs.myapp.com": [".cookie-banner", ".announcement-bar"]
  }
}
Enter fullscreen mode Exit fullscreen mode

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" }
  ]
}
Enter fullscreen mode Exit fullscreen mode

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)