Maintainer notes for the App Store screenshot editor. Covers headline alternatives, dependency upkeep, the end-to-end test suite, its flow matrix and its limits.
The editor in ios/app-store-screenshots builds the App Store screenshots. The iOS release page covers the editor commands, the captures, the export sizes and the Fastlane lanes. This page covers upkeep of the editor itself.
Headline alternatives
The saved decks use the "Outcome" column. The other columns keep the same feature order. The inspector replaces a headline on the selected slide.
| Slide | Moment | Outcome | Pain |
|---|---|---|---|
| Overview | Check your pipelines. Wherever you are. | Every pipeline, one place. | Skip the dashboard hopping. |
| Run | Start a run. Away from your desk. | Run workflows. From anywhere. | No laptop. Still in control. |
| Failure | Open the failed run. | Find the failure. See the details. | Stop guessing which run failed. |
| Status | Check before you retry. | Provider status. In context. | Fewer status pages to check. |
| Notifications | See what changed. | Outage alerts. In one inbox. | Less hunting for outage updates. |
| Closer | Check. Run. Review. | Your pipelines. Under control. | Fewer dashboards to manage. |
Dependency maintenance
These commands update every direct dependency to its latest stable release, refresh bun.lock and check the result. Run them from ios/app-store-screenshots.
bun update --latest
bun outdated
bun audit
bun run typecheck
bun run test:export-fastlane
bun run build- Tailwind CSS 4:
@tailwindcss/postcssinpostcss.config.mjsruns Tailwind. The theme lives insrc/app/globals.css, which loads thetailwindcss-animateplugin. The project has notailwind.config.tsand no Autoprefixer. - Browser minimums: Tailwind CSS 4 sets them. The editor needs Safari 16.4, Chrome 111 or Firefox 128.
zodoverride:package.jsonselectszod^4.6.7, because[email protected]pins the vulnerable version4.6.1. Delete the override when thee2erunner depends on a patchedzod.allowedDevOrigins:next.config.mjspermits127.0.0.1. The documented development command binds that host, and the dev server must accept requests from it.
End-to-end tests
The suite drives the editor in a real browser with Tester Army, the e2e runner. It covers the editor flows, the Style Lab, the Scene Playground, the iPhone Duo and App Store creative decks, and the export contract. The suite uses no agent.* steps and needs no model credentials.
Requirements
- Node.js
^22.22.3 || >=24.8.0 - Bun
- Installed Google Chrome. The runner opens it with the Playwright
chromechannel and has no Chromium fallback.
The devDependencies pin e2e to 0.21.3, @e2e-dev/web to 0.15.3 and playwright to 1.64.1.
Run the suite
Run these commands from ios/app-store-screenshots:
bun install --frozen-lockfile
bun run typecheck
bun run build
bun run test:e2e:list
bun run test:e2e
SCREENSHOTS_E2E_PRODUCTION=1 bun run test:e2etest:e2e:list prints the selected tests without a run. test:e2e runs the suite against a dev server. With SCREENSHOTS_E2E_PRODUCTION=1, it runs against the production build. Run the dev pass and the production pass one after the other.
The test files live in tests/:
| File | Covers |
|---|---|
tests/editor.e2e.ts | Editor flows on every device deck |
tests/scene-style-lab.e2e.ts | Style Lab, Scene Playground and the magnifier |
tests/targets.e2e.ts | iPhone Duo, App Store creatives and the export of cleared copy |
tests/regression.e2e.ts | Three tests, one for each regression script |
Nested loops cover each advertised device size, layout, orientation, theme and built-in font.
The regression scripts sit in tests/harness/. api-bug-bash.cjs holds the API groups, ui-bug-bash.cjs the UI groups and bug-bash.cjs the browser and export groups. The runner starts each script without a filter.
Run one test
--grep selects a direct test by name:
E2E_TELEMETRY_DISABLED=1 npx e2e run tests/editor.e2e.ts --grep 'watchos opens'SCREENSHOTS_E2E_HEADED=1 opens a visible Chrome window. SCREENSHOTS_E2E_PORT changes the port, which defaults to 4312.
Manual regression scripts
The regression scripts also run against a server that is already running. Use only a disposable project for this. The scripts write the project file and the uploads of that server.
bun run test:regression http://localhost:4312
BUG_BASH_FILTER='connected overlays' node tests/harness/bug-bash.cjs http://localhost:4312test:regression runs the API, UI and browser scripts in this order and stops at the first failure. Each script takes the server URL as its first argument.
BUG_BASH_FILTER: keeps the groups whose name contains the text. The UI and browser scripts read it.PLAYWRIGHT_MODULE: selects another Playwright module for the UI and browser scripts.BUG_BASH_ARTIFACTS: sets a directory for the screenshots of the UI script.
The normal test:e2e command creates a disposable server by itself.
Cold dev compile check
This check exercises the first compile of the page in dev mode after API-only traffic.
- Start the disposable server with
node e2e-army/server.cjs 4312. - In a second terminal, run
node tests/harness/api-bug-bash.cjs http://localhost:4312before any browser requests the page. - Stop the server with Ctrl-C.
Isolation
e2e.config.ts starts e2e-army/server.cjs on port 4312. The script creates a unique directory in the system temporary folder and copies the app into it. The copy skips node_modules, .next, .e2e, .git, tests and e2e-army. A symlink provides node_modules.
Next then starts with the copy as its working directory. The project API, the image and font uploads and the runtime asset routes read and write the copy only. The copy holds the bezels from public/frames/, so every run covers the shipped frames. Teardown deletes the copy.
Dev mode runs next dev --webpack, because Turbopack refuses dependency symlinks outside the copy. Production mode copies .next from the existing build and runs next start. That mode needs bun run build first.
An occupied port fails the startup. The configuration never reuses a running editor. The API and export scripts change the project of their server, so the suites must run one after the other. The tests restore the API project after each change.
Artifacts
The runner writes to .e2e/, which Git ignores:
report.json,summary.mdandjunit.xml: the selected tests, their status, steps, errors and artifact linksresults/: downloads, failure screenshots and retained traces of each testartifacts/: files from the regression scriptslogs/app.log: output of the editor serverlogs/api-bug-bash.log,logs/ui-bug-bash.logandlogs/bug-bash.log: output of each regression script
ZIP checks read the absolutePath of the download fixture. They do not depend on generated test IDs or artifact folder names. The font upload tests use the font that Next.js bundles, so they need no page preload.
Flow matrix
The table lists each product flow and the test that covers it. "Direct" means a test in tests/*.e2e.ts. "API", "UI" and "browser" mean the regression script of that name.
| Flow | Verification |
|---|---|
| iOS, Mac and Android tabs, last selected device | A direct round trip checks the device and the preserved copy. The UI script checks all combinations. |
| 17 device decks | A direct test per deck edits the copy, adds a screen and undoes. The decks are iPhone, iPad, 4 iPhone Duo decks, Apple TV, Apple Watch, CarPlay and Mac. They also include Android phone, 7-inch and 10-inch tablets, feature graphic and 3 App Store creatives. The browser script exports every device at every advertised size and checks the pixels. |
| iPhone Duo orientations and displays | A direct test turns the deck from outer portrait to outer landscape, inner landscape, inner portrait and back. It checks that each deck keeps its own copy and a hand-placed rotated transform. It also checks the saved device and orientation after a reload. The UI script runs every layout on both displays in both orientations. |
| iPhone Duo and creative export sizes | ZIP tests check the exact PNG sizes 1398x2034, 2034x1398, 2007x2853, 2853x2007, 5244x2950, 3840x1646, 3840x2560 and 1920x1280. They also check the file paths, PNG color type 2 and the absence of alpha. |
| Upgrade of a legacy project | A project with schema version 2 and a hand-placed caption saves as version 3. The existing deck stays unchanged and every new deck exists. |
| Creative guides | The safe area guide and both placement previews appear on the editable canvas and never in the export canvas. |
| Cleared copy in exports | The editable canvas has the LABEL and Headline goes here hints. An export of a cleared screen and an export of a screen with a zero-width space must match pixel for pixel. |
| Portrait and landscape, every layout | The UI script runs devices, layouts and both tablet orientations. It checks editing and persistence. |
| Screens: create, select, duplicate, delete, empty state, reorder | Direct tests cover create, delete and keyboard reorder. The UI script covers pointer and keyboard order, the selected copy, the deletion of the last slide with undo and a usable empty state. |
| Undo, redo, cross-deck editing, shortcuts | A direct regression types a headline, adds a screen and undoes on every device deck. The copy must survive. The UI script checks typing, menu arrows, cross-deck history, undo and redo. |
| Inspector and canvas label, headline, tagline, copy ideas | A direct test edits the headline formula, the locale copy and the deck. The UI script checks inline clearing and retyping, plain paste, fallback behavior and the caption. |
| Text and image elements, size, transforms, layering | The UI script adds, selects and deletes elements, and drags and resizes with a real pointer. It covers rotation, fit, fade, stacking and undo. Escape cancels a draft size. |
| Layout, background, text contrast, theme and font menus | The UI script explores layouts, secondary screenshots, custom backgrounds, draft hex values and the typography reset. It iterates the built-in themes and fonts and keeps the content. The browser script checks the contrast of the feature graphic. |
| Screenshot picker and upload, secondary screenshots, icons | PNG and JPEG round trips, the inspector picker, the icon composition of the feature graphic and the reload of uploaded assets. An invalid file keeps the previous selection. The latest upload wins a race or a clear. |
| Font import, errors, overlapping imports | A valid WOFF2 file uploads, decodes in the browser, survives a reload and exports. Invalid or truncated containers fail. The latest response wins. A font load timeout unlocks the controls. |
| Locale and RTL | A direct test edits EN, DE and AR and checks the computed Arabic direction. The browser script checks inline locale clearing, fallback and independent Hebrew and Latin direction. ZIP tests check the names and counts per locale. |
| Autosave, reload, local cache, retry, revision conflict | Direct tests edit on the real disk, reload, use the cache and send a stale API revision. The browser script covers serialized slow saves, a queued revision advance and two real tabs. It also covers an invalid project load, a retry after a failed save and the beforeunload warning. |
| Connected and isolated crops, off-canvas transforms | A direct test toggles the mode with undo and redo. The browser script decodes exported PNG pixels across a boundary in both modes. It compares the composition outside the canvas. |
| Export ZIP, files, count, dimensions, RGB, composition | A direct test exports a real 24-PNG Apple Watch ZIP. It checks the file names, the EN and DE folders, the advertised sizes, PNG color type 2 and non-uniform pixels. The browser script checks every device, file size, screenshot, overlay and icon. |
| Export lock, progress, failure, fallback | The editor becomes inert at once, stays inert until the ZIP completes, downloads once and unlocks. Missing, corrupt or stalled assets and fonts fail with an actionable message. Failing, silent or throwing workers fall back to the inline encoder. |
| Narrow viewport, touch, keyboard access | A direct test checks a 390 px layout and keyboard reorder. The UI script checks layouts from 390 px to 1440 px, a scrollable inspector and touch actions without hover. It also checks keyboard focus on a canvas image and "Fit active screen". |
| API inputs, origin, limits, asset serving, migration | Direct tests send an invalid project, a stale revision, a corrupt image or font and foreign origins. The API script checks JSON types, origins, request size limits including chunked uploads and concurrent atomic uploads. It also checks the rejection of runtime file names with path traversal, and the legacy migration. tests/harness/migration-fixture.cjs supplies the migration recipe. |
| Scene Playground | Direct tests change backdrop, flow, decoration, tilt and case. The change renders on every screen, persists, undoes as one step and resets to the classic scene. A ZIP test decodes a tilted, glowing, spotlit export at the exact size. A flowing spotlight deck has equal brightness on every exported screen. |
| Style Lab: looks, locks, saved looks, comparison | Direct tests list four looks. Apply is one undo step that keeps the copy and the layouts restorable. The Colors and Layout locks hold across Shuffle, and all locks together disable Shuffle. Per-screen headline sizes survive a look. The undo in the Apply toast refuses to revert a later edit. Saved looks survive a reload, apply and deletion. The comparison download is a real tall PNG. |
| Magnifier | A direct test adds a magnifier, focuses it with the keyboard, zooms, changes the shape and deletes it. A ZIP export contains a magnifier. The API accepts and completes partial callouts and rejects a malformed scene, saved look or callout. Load clamps values out of range. |
| Reset and cancel reset | The UI script resets everything and keeps the app, theme and locales. The confirmation dialog allows a cancel. A structural reset has its own undo boundary. |
Limits
- The creative header and search crops in the universal asset use a centered estimate. They do not match the crops that Apple publishes. The art safe areas come from the templates of Apple.
- The suite tests Chrome on a desktop with emulated touch and viewport sizes. It does not cover real iOS or Android hardware, WebKit or Firefox rendering, or installed macOS system fonts on other operating systems.
- The suite is flow and regression coverage. It is no complete accessibility audit, visual pixel baseline, load test or security scan. No test judges screen readers, every contrast combination or every tab sequence.
- The editor has a busy state and error recovery during an export. It has no cancel control for an export, so no test covers one. The cancel of the reset dialog is a separate flow.
- Font tests use a valid bundled WOFF2 file and corrupt containers. They do not cover every TTF, OTF or WOFF file or font family.
- The suite never submits to App Store Connect or Google Play. It tests the export contract of the editor and not the acceptance of a store.
- The regression scripts keep fixed short waits. The direct flows use readiness assertions, responses and download events. All selected tests must pass, and the suite declares no skips.
- The folder holds its own workflow,
.github/workflows/e2e.yml. GitHub does not run a workflow from a nested folder, so the suite runs by hand.