mirror of
https://github.com/johannesjo/super-productivity.git
synced 2026-07-17 16:37:43 +00:00
feat(screenshots): automate flathub output
This commit is contained in:
parent
bca1dba174
commit
4534789571
4 changed files with 212 additions and 34 deletions
|
|
@ -5,7 +5,7 @@ Reproducible app-store screenshots driven by Playwright + a single seed dataset.
|
|||
## Quick start
|
||||
|
||||
```bash
|
||||
# Capture everything (all viewports × all scenarios that apply to that platform)
|
||||
# Capture the web-store matrix (all web viewports × matching scenarios)
|
||||
npm run screenshots
|
||||
|
||||
# Or split:
|
||||
|
|
@ -14,6 +14,8 @@ npm run screenshots:capture:desktop # desktopMaster only
|
|||
npm run screenshots:capture:mobile # iPhone/iPad/Android viewports only
|
||||
npm run screenshots:capture:electron # Electron build → dist/screenshots/_master_electron/
|
||||
npm run screenshots:electron # capture:electron + build (lands in dist/)
|
||||
npm run screenshots:build:flathub # rebuild only dist/screenshots/flathub/
|
||||
npm run screenshots:flathub # Linux Electron capture + Flathub-ready build
|
||||
npm run screenshots:build # rebuild dist/ layout from existing masters
|
||||
|
||||
# One group while iterating
|
||||
|
|
@ -28,8 +30,9 @@ npx playwright test --config e2e/playwright.store-screenshots.config.ts \
|
|||
| `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_DISABLE=1` | Drop background images entirely (set by `screenshots:flathub`). |
|
||||
| `SP_SCREENSHOT_BG_OVERLAY_OPACITY=80` | Drives the per-context "Darken/lighten background image for better contrast" slider (0–99). Default 80 for screenshots vs. 20 in the app. |
|
||||
| `SP_SCREENSHOTS_STORE=flathub` | Restrict post-processing to one store rule. Used by `screenshots:build:flathub` so Linux captures do not regenerate Mac App Store output. |
|
||||
|
||||
Master captures land in `dist/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).
|
||||
|
|
@ -74,7 +77,7 @@ Specs are platform-grouped: `scenarios/desktop/all.spec.ts` and `scenarios/mobil
|
|||
| `scenarios/desktop/all.spec.ts` | 12 desktop captures: hero + 11 scenes / light variants |
|
||||
| `scenarios/mobile/all.spec.ts` | 7 mobile slots: hero + 6 scenes |
|
||||
| `scenarios/tablet/all.spec.ts` | 6 tablet slots: hero + 5 scenes |
|
||||
| `build-store-assets.ts` | Renames + copies masters into shared/per-store layouts; JPEG re-encode for `maxBytes`-capped stores (Snap); emits `_preview.html` contact sheet |
|
||||
| `build-store-assets.ts` | Renames + copies masters into shared/per-store layouts; filters/frames Flathub; JPEG re-encode for `maxBytes`-capped stores (Snap); emits `_preview.html` contact sheet |
|
||||
| `../playwright.store-screenshots.config.ts` | Separate Playwright config; one project per viewport |
|
||||
|
||||
## How it works
|
||||
|
|
@ -113,7 +116,7 @@ for (const locale of LOCALES) {
|
|||
- **Snap** uses the same desktop master content but stays separate because it 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`).
|
||||
- **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:flathub`; it disables decorative backgrounds, drops the marketing hero/duplicate variants, and frames the final PNGs with transparent rounded corners + shadow.
|
||||
|
||||
## Electron pipeline (Mac App Store, Flathub)
|
||||
|
||||
|
|
@ -126,6 +129,10 @@ npm run screenshots:capture:electron
|
|||
# Capture + build — masters and deliverables under dist/screenshots/
|
||||
# (macappstore/, flathub/). Mirrors `npm run screenshots` for the web pipeline.
|
||||
npm run screenshots:electron
|
||||
|
||||
# Flathub-ready Linux capture + targeted build. Disables decorative backgrounds
|
||||
# and emits the filtered/framed dist/screenshots/flathub/ gallery.
|
||||
npm run screenshots:flathub
|
||||
```
|
||||
|
||||
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.
|
||||
|
|
@ -140,7 +147,7 @@ Per-OS tooling (must be on PATH):
|
|||
- **Linux X11** — ImageMagick (`apt install imagemagick`, ships `import`)
|
||||
- **Linux Wayland** — `grim` (`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.
|
||||
The Mac App Store and Flathub store rules have `masterDir: 'electron'` in `STORE_RULES`, so the post-processor pulls those captures into `dist/screenshots/macappstore/` and `dist/screenshots/flathub/`. Flathub additionally pins its gallery order and applies rounded transparent window framing in `build-store-assets.ts`. All other stores still come from the web pipeline.
|
||||
|
||||
## Status
|
||||
|
||||
|
|
@ -149,7 +156,7 @@ The Mac App Store store rule has `masterDir: 'electron'` in `STORE_RULES`, so th
|
|||
- ✅ Per-build `_preview.html` contact sheet under `dist/screenshots/` for one-click QA
|
||||
- ✅ Electron-mode pipeline with macOS traffic-light compositing and Linux OS chrome capture (`grim` / `import`)
|
||||
- ✅ Mac App Store wired to source from `_master_electron/`
|
||||
- ✅ Flathub STORE_RULE (single-gallery, sourced from `_master_electron/`)
|
||||
- ✅ Flathub STORE_RULE (single-gallery, sourced from `_master_electron/`, filtered/framed for Flathub)
|
||||
- ✅ 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)
|
||||
|
|
|
|||
|
|
@ -7,7 +7,8 @@
|
|||
* Captures are made at each store's native pixel size (see playwright.store-screenshots.config.ts),
|
||||
* so this is mostly rename + copy — no resizing. Stores with a per-file byte
|
||||
* cap (`maxBytes` in STORE_RULES; currently Snap @ 2 MB) get re-encoded as
|
||||
* JPEG until they fit; everything else stays lossless PNG.
|
||||
* JPEG until they fit; everything else stays lossless PNG. Store rules can also
|
||||
* filter/reorder scenarios and apply store-specific framing (Flathub).
|
||||
*
|
||||
* Run: npm run screenshots:build
|
||||
*/
|
||||
|
|
@ -31,11 +32,70 @@ const masterDirFor = (which: 'web' | 'electron'): string =>
|
|||
which === 'electron' ? MASTER_DIR_ELECTRON : MASTER_DIR_WEB;
|
||||
|
||||
type ScenarioFile = { scenario: string; file: string; theme: string; absPath: string };
|
||||
type WriteOptions = {
|
||||
maxBytes?: number;
|
||||
frame?: StoreRule['frame'];
|
||||
};
|
||||
|
||||
const FLATHUB_WINDOW_FRAME = {
|
||||
padding: 48,
|
||||
radius: 16,
|
||||
shadowBlur: 24,
|
||||
shadowOffsetY: 12,
|
||||
shadowOpacity: 0.35,
|
||||
} as const;
|
||||
|
||||
/** Strip leading `(mobile|desktop)-NN-` so output names don't double up on the index. */
|
||||
const cleanScenarioLabel = (scenario: string): string =>
|
||||
scenario.replace(/^(?:(?:mobile|desktop)-)?\d+-/, '');
|
||||
|
||||
const labelForScenario = (rule: StoreRule, scenario: string): string =>
|
||||
rule.scenarioLabels?.find((entry) => entry.scenario === scenario)?.label ??
|
||||
cleanScenarioLabel(scenario);
|
||||
|
||||
const applyScenarioOrder = (
|
||||
scenarios: ScenarioFile[],
|
||||
order?: readonly string[],
|
||||
store?: string,
|
||||
): ScenarioFile[] => {
|
||||
if (!order) return scenarios;
|
||||
|
||||
const byScenario = new Map<string, ScenarioFile>();
|
||||
for (const scenario of scenarios) byScenario.set(scenario.scenario, scenario);
|
||||
|
||||
return order.map((scenario) => {
|
||||
const match = byScenario.get(scenario);
|
||||
if (!match) {
|
||||
const tag = store ? `${store}: ` : '';
|
||||
throw new Error(`${tag}missing ordered screenshot scenario "${scenario}"`);
|
||||
}
|
||||
return match;
|
||||
});
|
||||
};
|
||||
|
||||
const storeFilter = (): Set<string> | null => {
|
||||
const raw = process.env.SP_SCREENSHOTS_STORE;
|
||||
if (!raw) return null;
|
||||
const stores = raw
|
||||
.split(',')
|
||||
.map((store) => store.trim())
|
||||
.filter(Boolean);
|
||||
return stores.length ? new Set(stores) : null;
|
||||
};
|
||||
|
||||
const filterStoreRules = (rules: readonly StoreRule[]): StoreRule[] => {
|
||||
const filter = storeFilter();
|
||||
if (!filter) return [...rules];
|
||||
|
||||
const out = rules.filter((rule) => filter.has(rule.store));
|
||||
if (out.length === 0) {
|
||||
throw new Error(
|
||||
`SP_SCREENSHOTS_STORE matched no store rules: ${[...filter].join(', ')}`,
|
||||
);
|
||||
}
|
||||
return out;
|
||||
};
|
||||
|
||||
/**
|
||||
* Each scenario locks one theme. List all (theme × scenario) captures present
|
||||
* for a given viewport+locale, sorted by scenario name (which encodes slot order
|
||||
|
|
@ -74,8 +134,73 @@ const listScenariosForVariant = (
|
|||
return out;
|
||||
};
|
||||
|
||||
const transparentBg = { r: 0, g: 0, b: 0, alpha: 0 } as const;
|
||||
|
||||
const roundedRectSvg = (
|
||||
width: number,
|
||||
height: number,
|
||||
radius: number,
|
||||
fill: string,
|
||||
extraAttrs = '',
|
||||
): Buffer =>
|
||||
Buffer.from(
|
||||
`<svg width="${width}" height="${height}" viewBox="0 0 ${width} ${height}" xmlns="http://www.w3.org/2000/svg"><rect width="${width}" height="${height}" rx="${radius}" ry="${radius}" fill="${fill}" ${extraAttrs}/></svg>`,
|
||||
);
|
||||
|
||||
const frameFlathubWindow = async (src: string): Promise<Buffer> => {
|
||||
const metadata = await sharp(src).metadata();
|
||||
const width = metadata.width;
|
||||
const height = metadata.height;
|
||||
if (!width || !height) {
|
||||
throw new Error(`Could not read screenshot dimensions for ${src}`);
|
||||
}
|
||||
|
||||
const { padding, radius, shadowBlur, shadowOffsetY, shadowOpacity } =
|
||||
FLATHUB_WINDOW_FRAME;
|
||||
const framePadding = padding * 2;
|
||||
const frameWidth = width + framePadding;
|
||||
const frameHeight = height + framePadding;
|
||||
const windowMask = roundedRectSvg(width, height, radius, '#fff');
|
||||
const shadowShape = roundedRectSvg(
|
||||
width,
|
||||
height,
|
||||
radius,
|
||||
'#000',
|
||||
`fill-opacity="${shadowOpacity}"`,
|
||||
);
|
||||
|
||||
const roundedWindow = await sharp(src)
|
||||
.ensureAlpha()
|
||||
.composite([{ input: windowMask, blend: 'dest-in' }])
|
||||
.png()
|
||||
.toBuffer();
|
||||
|
||||
const shadow = await sharp({
|
||||
create: {
|
||||
width: frameWidth,
|
||||
height: frameHeight,
|
||||
channels: 4,
|
||||
background: transparentBg,
|
||||
},
|
||||
})
|
||||
.composite([{ input: shadowShape, left: padding, top: padding + shadowOffsetY }])
|
||||
.blur(shadowBlur)
|
||||
.png()
|
||||
.toBuffer();
|
||||
|
||||
return sharp(shadow)
|
||||
.composite([{ input: roundedWindow, left: padding, top: padding }])
|
||||
.png()
|
||||
.toBuffer();
|
||||
};
|
||||
|
||||
const renderSource = async (src: string, frame?: StoreRule['frame']): Promise<Buffer> => {
|
||||
if (frame === 'flathub-window') return frameFlathubWindow(src);
|
||||
return fs.readFileSync(src);
|
||||
};
|
||||
|
||||
/**
|
||||
* Copy `src` to `target`, then enforce `maxBytes` if set. PNGs that already
|
||||
* Render `src` to `target`, then enforce `maxBytes` if set. PNGs that already
|
||||
* fit go through unchanged. PNGs that exceed the cap are re-encoded as JPEG
|
||||
* at stepped-down quality until they fit; the .png target is replaced with a
|
||||
* .jpg sibling. Returns the actual path written.
|
||||
|
|
@ -83,29 +208,30 @@ const listScenariosForVariant = (
|
|||
const writeWithCap = async (
|
||||
src: string,
|
||||
target: string,
|
||||
maxBytes?: number,
|
||||
opts: WriteOptions = {},
|
||||
): Promise<string> => {
|
||||
fs.copyFileSync(src, target);
|
||||
if (!maxBytes) return target;
|
||||
const initialSize = fs.statSync(target).size;
|
||||
if (initialSize <= maxBytes) return target;
|
||||
const buf = await renderSource(src, opts.frame);
|
||||
fs.writeFileSync(target, buf);
|
||||
if (!opts.maxBytes) return target;
|
||||
if (buf.byteLength <= opts.maxBytes) return target;
|
||||
|
||||
const jpgTarget = target.replace(/\.png$/i, '.jpg');
|
||||
const buf = fs.readFileSync(src);
|
||||
// Step down quality 90 → 60. If even q60 exceeds the cap (vanishingly rare
|
||||
// on screenshots that are mostly UI), bail loudly so the operator notices.
|
||||
for (const quality of [90, 85, 80, 75, 70, 65, 60]) {
|
||||
const out = await sharp(buf)
|
||||
.jpeg({ quality, mozjpeg: true, progressive: true, chromaSubsampling: '4:2:0' })
|
||||
.toBuffer();
|
||||
if (out.byteLength <= maxBytes) {
|
||||
if (out.byteLength <= opts.maxBytes) {
|
||||
fs.writeFileSync(jpgTarget, out);
|
||||
if (jpgTarget !== target) fs.rmSync(target);
|
||||
return jpgTarget;
|
||||
}
|
||||
}
|
||||
throw new Error(
|
||||
`${path.basename(target)} still > ${maxBytes} bytes at JPEG quality 60 — capture too large`,
|
||||
`${path.basename(target)} still > ${
|
||||
opts.maxBytes
|
||||
} bytes at JPEG quality 60 — capture too large`,
|
||||
);
|
||||
};
|
||||
|
||||
|
|
@ -114,7 +240,7 @@ const writeFastlanePath = async (
|
|||
bucket: NonNullable<StoreRule['fastlaneBucket']>,
|
||||
index: number,
|
||||
src: string,
|
||||
maxBytes?: number,
|
||||
opts: WriteOptions = {},
|
||||
): Promise<string> => {
|
||||
const bucketDir = `${bucket}Screenshots`;
|
||||
const dir = path.join(
|
||||
|
|
@ -129,36 +255,36 @@ const writeFastlanePath = async (
|
|||
);
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
const target = path.join(dir, `${index + 1}.png`);
|
||||
return writeWithCap(src, target, maxBytes);
|
||||
return writeWithCap(src, target, opts);
|
||||
};
|
||||
|
||||
const writePerLocale = async (
|
||||
store: string,
|
||||
locale: string,
|
||||
index: number,
|
||||
scenario: string,
|
||||
label: string,
|
||||
src: string,
|
||||
maxBytes?: number,
|
||||
opts: WriteOptions = {},
|
||||
): Promise<string> => {
|
||||
const dir = path.join(OUT_DIR, store, locale);
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
const num = `${index + 1}`.padStart(2, '0');
|
||||
const target = path.join(dir, `${num}-${cleanScenarioLabel(scenario)}.png`);
|
||||
return writeWithCap(src, target, maxBytes);
|
||||
const target = path.join(dir, `${num}-${label}.png`);
|
||||
return writeWithCap(src, target, opts);
|
||||
};
|
||||
|
||||
const writeGlobal = async (
|
||||
store: string,
|
||||
index: number,
|
||||
scenario: string,
|
||||
label: string,
|
||||
src: string,
|
||||
maxBytes?: number,
|
||||
opts: WriteOptions = {},
|
||||
): Promise<string> => {
|
||||
const dir = path.join(OUT_DIR, store);
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
const num = `${index + 1}`.padStart(2, '0');
|
||||
const target = path.join(dir, `${num}-${cleanScenarioLabel(scenario)}.png`);
|
||||
return writeWithCap(src, target, maxBytes);
|
||||
const target = path.join(dir, `${num}-${label}.png`);
|
||||
return writeWithCap(src, target, opts);
|
||||
};
|
||||
|
||||
const buildOneRule = async (
|
||||
|
|
@ -169,21 +295,29 @@ const buildOneRule = async (
|
|||
const masterRoot = masterDirFor(rule.masterDir ?? 'web');
|
||||
|
||||
for (const locale of LOCALES) {
|
||||
const scenarios = listScenariosForVariant(rule.source, locale, masterRoot);
|
||||
const scenarios = applyScenarioOrder(
|
||||
listScenariosForVariant(rule.source, locale, masterRoot),
|
||||
rule.scenarioOrder,
|
||||
rule.store,
|
||||
);
|
||||
if (scenarios.length === 0) {
|
||||
skipped += 1;
|
||||
continue;
|
||||
}
|
||||
const limited = rule.maxCount ? scenarios.slice(0, rule.maxCount) : scenarios;
|
||||
const writeOptions: WriteOptions = {};
|
||||
if (rule.maxBytes) writeOptions.maxBytes = rule.maxBytes;
|
||||
if (rule.frame) writeOptions.frame = rule.frame;
|
||||
for (let i = 0; i < limited.length; i += 1) {
|
||||
const s = limited[i];
|
||||
const label = labelForScenario(rule, s.scenario);
|
||||
if (rule.localeLayout === 'fastlane' && rule.fastlaneBucket) {
|
||||
await writeFastlanePath(locale, rule.fastlaneBucket, i, s.absPath, rule.maxBytes);
|
||||
await writeFastlanePath(locale, rule.fastlaneBucket, i, s.absPath, writeOptions);
|
||||
} else if (rule.localeLayout === 'global') {
|
||||
if (locale !== LOCALES[0]) continue; // Snap / Flathub are single-gallery
|
||||
await writeGlobal(rule.store, i, s.scenario, s.absPath, rule.maxBytes);
|
||||
await writeGlobal(rule.store, i, label, s.absPath, writeOptions);
|
||||
} else {
|
||||
await writePerLocale(rule.store, locale, i, s.scenario, s.absPath, rule.maxBytes);
|
||||
await writePerLocale(rule.store, locale, i, label, s.absPath, writeOptions);
|
||||
}
|
||||
written += 1;
|
||||
}
|
||||
|
|
@ -203,6 +337,17 @@ const cleanDerivedOutput = (): void => {
|
|||
}
|
||||
};
|
||||
|
||||
const cleanSelectedOutput = (rules: readonly StoreRule[]): void => {
|
||||
fs.mkdirSync(OUT_DIR, { recursive: true });
|
||||
const targets = new Set<string>();
|
||||
for (const rule of rules) {
|
||||
targets.add(rule.localeLayout === 'fastlane' ? 'fdroid' : rule.store);
|
||||
}
|
||||
for (const target of targets) {
|
||||
fs.rmSync(path.join(OUT_DIR, target), { recursive: true, force: true });
|
||||
}
|
||||
};
|
||||
|
||||
const main = async (): Promise<void> => {
|
||||
if (!fs.existsSync(MASTER_DIR_WEB) && !fs.existsSync(MASTER_DIR_ELECTRON)) {
|
||||
console.error('No master captures found.');
|
||||
|
|
@ -212,10 +357,12 @@ const main = async (): Promise<void> => {
|
|||
process.exit(1);
|
||||
}
|
||||
|
||||
cleanDerivedOutput();
|
||||
const rules = filterStoreRules(STORE_RULES);
|
||||
if (storeFilter()) cleanSelectedOutput(rules);
|
||||
else cleanDerivedOutput();
|
||||
|
||||
let total = 0;
|
||||
for (const rule of STORE_RULES) {
|
||||
for (const rule of rules) {
|
||||
const res = await buildOneRule(rule);
|
||||
total += res.written;
|
||||
const tag = rule.fastlaneBucket ? `${rule.store}/${rule.fastlaneBucket}` : rule.store;
|
||||
|
|
|
|||
|
|
@ -133,6 +133,12 @@ export type StoreRule = {
|
|||
* each entry at 2 MB; everything else keeps the lossless PNG.
|
||||
*/
|
||||
maxBytes?: number;
|
||||
/** Optional explicit gallery order/filter for stores with tighter curation. */
|
||||
scenarioOrder?: readonly string[];
|
||||
/** Optional output filename labels for raw scenario ids. */
|
||||
scenarioLabels?: readonly { scenario: string; label: string }[];
|
||||
/** Optional post-processing frame applied before writing the store output. */
|
||||
frame?: 'flathub-window';
|
||||
};
|
||||
|
||||
export const STORE_RULES: readonly StoreRule[] = [
|
||||
|
|
@ -158,13 +164,28 @@ export const STORE_RULES: readonly StoreRule[] = [
|
|||
maxBytes: 2 * 1024 * 1024,
|
||||
},
|
||||
// Flathub: metainfo.xml `<screenshot>` is single-gallery and sourced from the
|
||||
// Electron pipeline so the captures include native GTK chrome (Linux X11
|
||||
// host required — see README "Electron pipeline" section).
|
||||
// Electron pipeline so the captures include native GTK chrome. Keep the
|
||||
// gallery concise: no marketing hero, no duplicate light/dark variants.
|
||||
{
|
||||
store: 'flathub',
|
||||
source: 'desktopMaster',
|
||||
localeLayout: 'global',
|
||||
masterDir: 'electron',
|
||||
scenarioOrder: [
|
||||
'desktop-01-list-with-schedule',
|
||||
'desktop-08-planner',
|
||||
'desktop-02-eisenhower',
|
||||
'desktop-03-schedule-dark',
|
||||
'desktop-04-list-with-notes',
|
||||
'desktop-05-focus-mode',
|
||||
'desktop-07-project-dark',
|
||||
'desktop-10-task-detail-panel',
|
||||
],
|
||||
scenarioLabels: [
|
||||
{ scenario: 'desktop-03-schedule-dark', label: 'schedule' },
|
||||
{ scenario: 'desktop-07-project-dark', label: 'project' },
|
||||
],
|
||||
frame: 'flathub-window',
|
||||
},
|
||||
{
|
||||
store: 'ios/iphone-69',
|
||||
|
|
|
|||
|
|
@ -75,8 +75,11 @@
|
|||
"screenshots:capture:desktop": "npx playwright test --config e2e/playwright.store-screenshots.config.ts --project=desktopMaster",
|
||||
"screenshots:capture:mobile": "npx playwright test --config e2e/playwright.store-screenshots.config.ts --project=iphone69 --project=ipad13 --project=androidPhone --project=android7Tablet --project=android10Tablet",
|
||||
"screenshots:build": "TS_NODE_COMPILER_OPTIONS='{\"module\":\"commonjs\",\"moduleResolution\":\"node\"}' npx ts-node --transpile-only e2e/store-screenshots/build-store-assets.ts",
|
||||
"screenshots:build:flathub": "SP_SCREENSHOTS_STORE=flathub npm run screenshots:build",
|
||||
"screenshots:capture:electron": "npm run electron:build && SCREENSHOT_MODE=electron npx playwright test --config e2e/playwright.store-screenshots.electron.config.ts",
|
||||
"screenshots:capture:flathub": "SP_SCREENSHOT_BG_DISABLE=1 npm run screenshots:capture:electron",
|
||||
"screenshots:electron": "npm run screenshots:capture:electron && npm run screenshots:build",
|
||||
"screenshots:flathub": "npm run screenshots:capture:flathub && npm run screenshots:build:flathub",
|
||||
"video": "npm run video:capture && npm run video:build && npm run video:open",
|
||||
"video:capture": "npx playwright test --config e2e/playwright.store-video.config.ts",
|
||||
"video:build": "TS_NODE_COMPILER_OPTIONS='{\"module\":\"commonjs\",\"moduleResolution\":\"node\"}' npx ts-node --transpile-only e2e/store-video/build-video.ts",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue