DEV Community

Custodia-Admin
Custodia-Admin

Posted on • Originally published at pagebolt.dev

How to screenshot your app on 10 different devices in one script

How to Screenshot Your App on 10 Different Devices in One Script

Checking your app on multiple devices usually means: spinning up iOS Simulator, Android Emulator, resizing your browser, or paying for BrowserStack. None of these run headlessly or fit naturally into a CI pipeline.

Here's a script that captures your app on a full device matrix — phones, tablets, laptops — in parallel, under 60 seconds, with no emulators.

Available devices

// Run this to see the full list of 25+ supported device presets
const res = await fetch("https://pagebolt.dev/api/v1/list-devices", {
  headers: { "x-api-key": process.env.PAGEBOLT_API_KEY },
});
const { devices } = await res.json();
console.log(devices.map((d) => d.id).join("\n"));
Enter fullscreen mode Exit fullscreen mode

Key presets:

iphone_14_pro       iphone_se
iphone_14_pro_max   ipad_pro_12_9
ipad_mini           galaxy_s23
galaxy_tab_s8       macbook_pro_16
macbook_air_m2      surface_pro_9
Enter fullscreen mode Exit fullscreen mode

Full device matrix script

import fs from "fs/promises";
import path from "path";

const PAGEBOLT_API_KEY = process.env.PAGEBOLT_API_KEY;

const DEVICES = [
  { id: "iphone_se",          label: "iPhone SE (small)" },
  { id: "iphone_14_pro",      label: "iPhone 14 Pro" },
  { id: "iphone_14_pro_max",  label: "iPhone 14 Pro Max" },
  { id: "ipad_mini",          label: "iPad mini" },
  { id: "ipad_pro_12_9",      label: "iPad Pro 12.9" },
  { id: "galaxy_s23",         label: "Samsung Galaxy S23" },
  { id: "galaxy_tab_s8",      label: "Samsung Galaxy Tab S8" },
  { id: "macbook_air_m2",     label: "MacBook Air M2" },
  { id: "macbook_pro_16",     label: "MacBook Pro 16" },
  { id: "surface_pro_9",      label: "Surface Pro 9" },
];

const PAGES = [
  { name: "home",     url: "https://yourapp.com" },
  { name: "pricing",  url: "https://yourapp.com/pricing" },
  { name: "signin",   url: "https://yourapp.com/signin" },
];

async function screenshot(url, deviceId) {
  const res = await fetch("https://pagebolt.dev/api/v1/screenshot", {
    method: "POST",
    headers: {
      "x-api-key": PAGEBOLT_API_KEY,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      url,
      viewportDevice: deviceId,
      fullPage: false,
      blockBanners: true,
    }),
  });
  if (!res.ok) throw new Error(`${res.status}: ${await res.text()}`);
  return Buffer.from(await res.arrayBuffer());
}

async function main() {
  const outDir = "device-screenshots";
  await fs.mkdir(outDir, { recursive: true });

  for (const page of PAGES) {
    const pageDir = path.join(outDir, page.name);
    await fs.mkdir(pageDir, { recursive: true });

    console.log(`\n${page.name} — capturing ${DEVICES.length} devices in parallel...`);

    const results = await Promise.allSettled(
      DEVICES.map(async (device) => {
        const image = await screenshot(page.url, device.id);
        const filename = `${device.id}.png`;
        await fs.writeFile(path.join(pageDir, filename), image);
        return { device, filename, bytes: image.length };
      })
    );

    for (const result of results) {
      if (result.status === "fulfilled") {
        const { device, bytes } = result.value;
        console.log(`  ✓ ${device.label} (${bytes} bytes)`);
      } else {
        console.error(`  ✗ ${result.reason}`);
      }
    }
  }

  console.log(`\nDone. Screenshots in ${outDir}/`);
}

main().catch(console.error);
Enter fullscreen mode Exit fullscreen mode

Output structure:

device-screenshots/
  home/
    iphone_se.png
    iphone_14_pro.png
    iphone_14_pro_max.png
    ipad_mini.png
    ipad_pro_12_9.png
    galaxy_s23.png
    galaxy_tab_s8.png
    macbook_air_m2.png
    macbook_pro_16.png
    surface_pro_9.png
  pricing/
    ...
  signin/
    ...
Enter fullscreen mode Exit fullscreen mode

Dark mode matrix

const MATRIX = DEVICES.flatMap((device) => [
  { device, darkMode: false, label: `${device.id}-light` },
  { device, darkMode: true,  label: `${device.id}-dark` },
]);

await Promise.allSettled(
  MATRIX.map(async ({ device, darkMode, label }) => {
    const res = await fetch("https://pagebolt.dev/api/v1/screenshot", {
      method: "POST",
      headers: { "x-api-key": PAGEBOLT_API_KEY, "Content-Type": "application/json" },
      body: JSON.stringify({
        url: "https://yourapp.com",
        viewportDevice: device.id,
        darkMode,
        blockBanners: true,
      }),
    });
    const image = Buffer.from(await res.arrayBuffer());
    await fs.writeFile(path.join(outDir, `${label}.png`), image);
    console.log(`✓ ${label}`);
  })
);
Enter fullscreen mode Exit fullscreen mode

GitHub Actions — run on every PR

name: Device matrix screenshots

on:
  pull_request:
    branches: [main]

jobs:
  device-matrix:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Wait for preview
        run: |
          for i in $(seq 1 24); do
            [ "$(curl -s -o /dev/null -w '%{http_code}' '${{ vars.PREVIEW_URL }}')" = "200" ] && break
            sleep 5
          done

      - name: Screenshot device matrix
        env:
          PAGEBOLT_API_KEY: ${{ secrets.PAGEBOLT_API_KEY }}
          BASE_URL: ${{ vars.PREVIEW_URL }}
        run: node scripts/device-matrix.js

      - name: Upload screenshots
        uses: actions/upload-artifact@v4
        with:
          name: device-matrix-${{ github.run_id }}
          path: device-screenshots/

      - name: Comment on PR
        uses: actions/github-script@v7
        with:
          script: |
            await github.rest.issues.createComment({
              owner: context.repo.owner,
              repo: context.repo.repo,
              issue_number: context.issue.number,
              body: `## Device matrix screenshots\n\n10 devices × ${process.env.PAGE_COUNT || 3} pages captured. [Download artifacts](https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}).`
            });
Enter fullscreen mode Exit fullscreen mode

Check for responsive breakage automatically

Capture at specific widths to catch layout breakpoints:

const WIDTHS = [320, 375, 768, 1024, 1280, 1440, 1920];

await Promise.allSettled(
  WIDTHS.map(async (width) => {
    const res = await fetch("https://pagebolt.dev/api/v1/screenshot", {
      method: "POST",
      headers: { "x-api-key": PAGEBOLT_API_KEY, "Content-Type": "application/json" },
      body: JSON.stringify({
        url: "https://yourapp.com",
        viewport: { width, height: 900 },
        blockBanners: true,
      }),
    });
    const image = Buffer.from(await res.arrayBuffer());
    await fs.writeFile(`screenshots/width-${width}.png`, image);
    console.log(`✓ ${width}px`);
  })
);
Enter fullscreen mode Exit fullscreen mode

10 devices in parallel typically completes in 8–12 seconds. No emulator cold-start, no BrowserStack session limit, no Simulator installation.


Try it free — 100 requests/month, no credit card. → Get started in 2 minutes

Top comments (0)