super-productivity/e2e/store-screenshots
Johannes Millan 1d52843cd7 feat(video): Playwright-driven marketing reel pipeline
Adds an end-to-end pipeline for generating the marketing reel via
Playwright, including tight/full variants, drag ghost, integrations
layout, animated stats, brand-color logos, fade-to-black scene cuts,
and a handover CLAUDE.md for the reel pipeline.
2026-05-07 23:08:24 +02:00
..
scenarios feat(video): Playwright-driven marketing reel pipeline 2026-05-07 23:08:24 +02:00
seed feat(video): Playwright-driven marketing reel pipeline 2026-05-07 23:08:24 +02:00
build-store-assets.ts feat(video): Playwright-driven marketing reel pipeline 2026-05-07 23:08:24 +02:00
fixture.ts feat(screenshots): single-session capture, tablet spec, polished seed 2026-05-07 01:06:22 +02:00
helpers.ts feat(video): Playwright-driven marketing reel pipeline 2026-05-07 23:08:24 +02:00
matrix.ts feat(video): Playwright-driven marketing reel pipeline 2026-05-07 23:08:24 +02:00
README.md feat(video): Playwright-driven marketing reel pipeline 2026-05-07 23:08:24 +02:00

Automated screenshot pipeline

Reproducible app-store screenshots driven by Playwright + a single seed dataset.

Quick start

# Capture everything (all viewports × all scenarios that apply to that platform)
npm run screenshots

# Or split:
npm run screenshots:capture          # full web matrix → .tmp/screenshots/_master/
npm run screenshots:capture:desktop  # desktopMaster only
npm run screenshots:capture:mobile   # iPhone/iPad/Android viewports only
npm run screenshots:capture:electron # Electron build → .tmp/screenshots/_master_electron/
npm run screenshots:electron         # capture:electron + build (lands in dist/)
npm run screenshots:build            # rebuild dist/ layout from existing masters

# One group while iterating (pattern matches `desktop dark|light|catppuccin` etc.)
npx playwright test --config e2e/playwright.store-screenshots.config.ts \
  --project=desktopMaster --grep "desktop dark \(en\)"

Environment overrides

Var Effect
SCREENSHOT_MODE=electron Switches the fixture to the Electron pipeline (set by screenshots:capture:electron).
SCREENSHOT_BASE_DATE=2026-05-06T09:30:00 Pin the "today" anchor used by the seed builder. Default is a Wednesday well clear of midnight in CI timezones.
SP_SCREENSHOT_BG_DARK_URL / SP_SCREENSHOT_BG_LIGHT_URL Override the default Unsplash backgrounds (e.g. point to a vendored asset for offline / privacy-sensitive runs).
SP_SCREENSHOT_BG_DISABLE=1 Drop background images entirely.
SP_SCREENSHOT_BG_OVERLAY_OPACITY=80 Drives the per-context "Darken/lighten background image for better contrast" slider (099). Default 80 for screenshots vs. 20 in the app.

Master captures land in .tmp/screenshots/_master/<viewport>/<locale>/<theme>/<scenario>/<name>.png. Per-store assets land in dist/screenshots/<store>/<locale>/NN-name.png (and the F-Droid fastlane/... layout).

Scenario lineup

Slot Platform Theme What it shows
mobile-01 mobile dark Planner
mobile-02 mobile dark Planner with calendar nav expanded
mobile-03 mobile dark Eisenhower matrix board
mobile-04 mobile light Planner expanded (light variant)
mobile-05 mobile dark Schedule view
mobile-06 mobile dark Today task list
desktop-01 desktop dark Today + schedule day-panel open
desktop-02 desktop dark Eisenhower matrix board
desktop-03 desktop dark Schedule view
desktop-04 desktop light Project (Work) + notes panel populated
desktop-05 desktop dark Focus mode
desktop-06 desktop light Schedule (light variant)
desktop-07 desktop catppuccin-mocha Today list with custom theme

Specs are platform-grouped: scenarios/desktop/all.spec.ts and scenarios/mobile/all.spec.ts each capture every dark+light slot in a single session, flipping DARK_MODE between groups via applyTheme() (Playwright addInitScript is append-only, so a later script wins on each reload). The catppuccin slot stays in its own spec because changing customTheme requires a different seed file. Each spec runs once per locale (en + de).

Files

Path Purpose
matrix.ts Locales, themes, viewports, mobile/desktop classification, store rules
seed/seed.template.json Curated dataset with date offsets and @@PLANNER_OFFSET_+N placeholders
seed/build-seed.ts Materializes offsets to absolute dates, injects locale + customTheme
fixture.ts Pins clock, applies dark-mode, imports seed via UI flow, exposes screenshotMaster (which reads live DARK_MODE so light/dark scenes land in the right directory)
helpers.ts gotoAndSettle, openNotesPanel, openSchedulePanel, resetView, applyTheme
scenarios/desktop/all.spec.ts 6 desktop slots (dark + light, single session)
scenarios/desktop/catppuccin.spec.ts desktop-07 (different seed → standalone)
scenarios/mobile/all.spec.ts 6 mobile slots (dark + light, single session)
build-store-assets.ts Renames + copies masters into per-store directory layouts; JPEG re-encode for maxBytes-capped stores (Snap)
../playwright.store-screenshots.config.ts Separate Playwright config; one project per viewport

How it works

  1. Per-test seed file is materialized to .tmp/screenshot-seeds/seed-<date>-<locale>[-<customTheme>].json.
  2. Fixture boots the app with page.clock.install({ time: SCREENSHOT_BASE_DATE }), sets localStorage.DARK_MODE, pins browser context to en-US (so ImportPage's English text matchers always work), then imports the seed via BackupService.importCompleteBackup. Locale flows through globalConfig.localization.lngapplyLanguageFromState$ effect. Custom themes flow through globalConfig.misc.customTheme.
  3. Each scenario spec drives the app to a state and calls screenshotMaster(scenario, name). The Playwright project name (e.g. desktopMaster) determines the viewport.
  4. Post-processor copies masters into per-store layouts. No resizing — captures are already at native size for each store.

Adding a scenario

// scenarios/desktop/08-new-thing.spec.ts
import { test } from '../../fixture';
import { LOCALES } from '../../matrix';
import { gotoAndSettle, onlyOn } from '../../helpers';

for (const locale of LOCALES) {
  test.describe(`@screenshot desktop-08-new-thing (${locale})`, () => {
    test.use({ locale, theme: 'dark' });

    test('new thing', async ({ seededPage, screenshotMaster }, testInfo) => {
      onlyOn(testInfo, 'desktop');
      await gotoAndSettle(seededPage, '/#/whatever');
      await seededPage.locator('whatever-component').waitFor();
      await screenshotMaster('desktop-08-new-thing', 'new-thing');
    });
  });
}

Per-store gotchas

  • MS Store reserves bottom 1/4 for system-rendered captions → keep critical UI in top 3/4.
  • Mac App Store rejects letterboxed 16:9 in 16:10 frames — capture native 2880×1800.
  • Snap caps at 5 items, ≤2 MB each, single global gallery (no per-locale). Pipeline emits all desktops re-encoded as JPEG (mozjpeg, q90→q60 step-down) so each fits the cap; trim manually to 5 before Snap upload.
  • Play / Apple explicitly forbid / discourage device frames.
  • Apple requires only iPhone 6.9" (1290×2796) and iPad 13" (2064×2752); smaller sizes auto-derive.
  • Flathub requires native window chrome and forbids overlays — sourced from the Electron capture pipeline (single global gallery; run on a Linux X11/Wayland host via npm run screenshots:capture:electron).

Electron pipeline (Mac App Store, Flathub)

Web Chromium captures don't look "native" on macOS — wrong fonts, wrong scrollbars, no traffic-lights. Flathub explicitly requires native window chrome. So there's a parallel pipeline that runs the actual SP Electron build via Playwright's _electron API and captures via OS-level region tools (screencapture on macOS, grim/import on Linux).

# Capture only — masters land in .tmp/screenshots/_master_electron/.
npm run screenshots:capture:electron

# Capture + build — masters under .tmp/, deliverables under dist/screenshots/
# (macappstore/, flathub/). Mirrors `npm run screenshots` for the web pipeline.
npm run screenshots:electron

Same scenarios, same fixture file — store-screenshots/fixture.ts branches on the SCREENSHOT_MODE env var (the npm script sets it). Each desktop spec runs unchanged in either mode.

The OS-level capture grabs the full window rect including titlebar, shadow, and traffic-lights / GTK decoration. Bounds come from BrowserWindow.getBounds(); on macOS Retina, 1440×900 points capture as 2880×1800 px. On Linux X11/Wayland bounds == pixels.

Per-OS tooling (must be on PATH):

  • macOSscreencapture (built-in)
  • Linux X11 — ImageMagick (apt install imagemagick, ships import)
  • Linux Waylandgrim (apt install grim, wlroots-based compositors only)

The Mac App Store store rule has masterDir: 'electron' in STORE_RULES, so the post-processor pulls those captures into dist/screenshots/macappstore/. All other stores still come from the web pipeline.

Status

  • Foundation: matrix, seed builder, fixture (web + electron modes), helpers, post-processor
  • 13 scenarios (6 mobile + 7 desktop) covering planner, boards, schedule, focus, notes, custom theme
  • Electron-mode pipeline with OS-level chrome capture (screencapture / grim / import)
  • Mac App Store wired to source from _master_electron/
  • Flathub STORE_RULE (single-gallery, sourced from _master_electron/)
  • Snap JPEG re-encode under 2 MB cap (mozjpeg, automatic per-file)
  • Tooltip suppression + cursor parking so leftover Material tooltips don't bleed into captures
  • Smoke-test the Electron pipeline on a real Mac (or Linux X11 for plumbing)