@storywright/cli 0.4.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-FST75QN5.js → chunk-HPK2VJXG.js} +13 -10
- package/dist/chunk-HPK2VJXG.js.map +1 -0
- package/dist/{chunk-CRQ4Z34C.js → chunk-XVHJJ6NP.js} +1 -2
- package/dist/{chunk-CRQ4Z34C.js.map → chunk-XVHJJ6NP.js.map} +1 -1
- package/dist/cli.js +2 -2
- package/dist/index.js +1 -1
- package/dist/playwright/reporter.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-FST75QN5.js.map +0 -1
|
@@ -4,7 +4,7 @@ import { createRequire } from "module";
|
|
|
4
4
|
import path from "path";
|
|
5
5
|
var StorywrightReporter = class {
|
|
6
6
|
outputDir;
|
|
7
|
-
results =
|
|
7
|
+
results = /* @__PURE__ */ new Map();
|
|
8
8
|
startTime = 0;
|
|
9
9
|
constructor(options = {}) {
|
|
10
10
|
this.outputDir = options.outputDir || path.resolve(".storywright", "report");
|
|
@@ -13,10 +13,12 @@ var StorywrightReporter = class {
|
|
|
13
13
|
this.startTime = Date.now();
|
|
14
14
|
}
|
|
15
15
|
onTestEnd(test, result) {
|
|
16
|
+
const project = test.parent?.project()?.name ?? "unknown";
|
|
17
|
+
const key = `${test.title}::${project}`;
|
|
16
18
|
const status = result.status === "passed" ? "passed" : result.status === "skipped" ? "skipped" : "failed";
|
|
17
|
-
this.results.
|
|
19
|
+
this.results.set(key, {
|
|
18
20
|
title: test.title,
|
|
19
|
-
project
|
|
21
|
+
project,
|
|
20
22
|
status,
|
|
21
23
|
duration: result.duration,
|
|
22
24
|
attachments: result.attachments.map((a) => ({
|
|
@@ -28,16 +30,17 @@ var StorywrightReporter = class {
|
|
|
28
30
|
}
|
|
29
31
|
async onEnd(_result) {
|
|
30
32
|
const duration = Date.now() - this.startTime;
|
|
31
|
-
const
|
|
32
|
-
const
|
|
33
|
-
const
|
|
34
|
-
const
|
|
33
|
+
const allResults = [...this.results.values()];
|
|
34
|
+
const passed = allResults.filter((r) => r.status === "passed").length;
|
|
35
|
+
const failed = allResults.filter((r) => r.status === "failed").length;
|
|
36
|
+
const skipped = allResults.filter((r) => r.status === "skipped").length;
|
|
37
|
+
const browsers = [...new Set(allResults.map((r) => r.project))];
|
|
35
38
|
const failures = [];
|
|
36
39
|
const assetsDir = path.join(this.outputDir, "assets");
|
|
37
40
|
for (const dir of ["expected", "actual", "diff"]) {
|
|
38
41
|
fs.mkdirSync(path.join(assetsDir, dir), { recursive: true });
|
|
39
42
|
}
|
|
40
|
-
for (const testResult of
|
|
43
|
+
for (const testResult of allResults) {
|
|
41
44
|
if (testResult.status !== "failed") continue;
|
|
42
45
|
const titleParts = testResult.title.split(": ");
|
|
43
46
|
const storyTitle = titleParts[0] ?? testResult.title;
|
|
@@ -78,7 +81,7 @@ var StorywrightReporter = class {
|
|
|
78
81
|
failures.push(failure);
|
|
79
82
|
}
|
|
80
83
|
const summary = {
|
|
81
|
-
total:
|
|
84
|
+
total: allResults.length,
|
|
82
85
|
passed,
|
|
83
86
|
failed,
|
|
84
87
|
skipped,
|
|
@@ -133,4 +136,4 @@ export {
|
|
|
133
136
|
generateHtmlReport,
|
|
134
137
|
reporter_default
|
|
135
138
|
};
|
|
136
|
-
//# sourceMappingURL=chunk-
|
|
139
|
+
//# sourceMappingURL=chunk-HPK2VJXG.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/playwright/reporter.ts"],"sourcesContent":["import fs from 'node:fs';\nimport { createRequire } from 'node:module';\nimport path from 'node:path';\nimport type {\n\tFullConfig,\n\tFullResult,\n\tTestResult as PwTestResult,\n\tReporter,\n\tSuite,\n\tTestCase,\n} from '@playwright/test/reporter';\nimport type { FailureEntry, TestSummary } from '../core/types.js';\n\ninterface StorywrightReporterOptions {\n\toutputDir?: string;\n}\n\nclass StorywrightReporter implements Reporter {\n\tprivate outputDir: string;\n\tprivate results = new Map<\n\t\tstring,\n\t\t{\n\t\t\ttitle: string;\n\t\t\tproject: string;\n\t\t\tstatus: 'passed' | 'failed' | 'skipped';\n\t\t\tduration: number;\n\t\t\tattachments: { name: string; path?: string; contentType: string }[];\n\t\t}\n\t>();\n\tprivate startTime = 0;\n\n\tconstructor(options: StorywrightReporterOptions = {}) {\n\t\tthis.outputDir = options.outputDir || path.resolve('.storywright', 'report');\n\t}\n\n\tonBegin(_config: FullConfig, _suite: Suite): void {\n\t\tthis.startTime = Date.now();\n\t}\n\n\tonTestEnd(test: TestCase, result: PwTestResult): void {\n\t\tconst project = test.parent?.project()?.name ?? 'unknown';\n\t\tconst key = `${test.title}::${project}`;\n\t\tconst status =\n\t\t\tresult.status === 'passed' ? 'passed' : result.status === 'skipped' ? 'skipped' : 'failed';\n\n\t\t// Overwrite previous attempts so only the final retry result is kept\n\t\tthis.results.set(key, {\n\t\t\ttitle: test.title,\n\t\t\tproject,\n\t\t\tstatus,\n\t\t\tduration: result.duration,\n\t\t\tattachments: result.attachments.map((a) => ({\n\t\t\t\tname: a.name,\n\t\t\t\tpath: a.path,\n\t\t\t\tcontentType: a.contentType,\n\t\t\t})),\n\t\t});\n\t}\n\n\tasync onEnd(_result: FullResult): Promise<void> {\n\t\tconst duration = Date.now() - this.startTime;\n\t\tconst allResults = [...this.results.values()];\n\t\tconst passed = allResults.filter((r) => r.status === 'passed').length;\n\t\tconst failed = allResults.filter((r) => r.status === 'failed').length;\n\t\tconst skipped = allResults.filter((r) => r.status === 'skipped').length;\n\n\t\tconst browsers = [...new Set(allResults.map((r) => r.project))];\n\t\tconst failures: FailureEntry[] = [];\n\n\t\t// Collect failure images\n\t\tconst assetsDir = path.join(this.outputDir, 'assets');\n\t\tfor (const dir of ['expected', 'actual', 'diff']) {\n\t\t\tfs.mkdirSync(path.join(assetsDir, dir), { recursive: true });\n\t\t}\n\n\t\tfor (const testResult of allResults) {\n\t\t\tif (testResult.status !== 'failed') continue;\n\n\t\t\tconst titleParts = testResult.title.split(': ');\n\t\t\tconst storyTitle = titleParts[0] ?? testResult.title;\n\t\t\tconst variant = titleParts.slice(1).join(': ') || 'default';\n\t\t\tconst sanitizedName = testResult.title.replace(/[^a-zA-Z0-9-_]/g, '-').toLowerCase();\n\n\t\t\tconst imageAttachments = testResult.attachments.filter(\n\t\t\t\t(a) => a.path && a.contentType.startsWith('image/'),\n\t\t\t);\n\t\t\tconst hasDiff = imageAttachments.some((a) => a.name.includes('diff'));\n\n\t\t\tconst failure: FailureEntry = {\n\t\t\t\ttype: hasDiff ? 'diff' : 'new',\n\t\t\t\tstory: storyTitle,\n\t\t\t\tvariant,\n\t\t\t\tbrowser: testResult.project,\n\t\t\t\tdiffRatio: 0,\n\t\t\t\texpected: '',\n\t\t\t\tactual: '',\n\t\t\t\tdiff: '',\n\t\t\t};\n\n\t\t\tfor (const attachment of testResult.attachments) {\n\t\t\t\tif (!attachment.path) continue;\n\t\t\t\tconst ext = path.extname(attachment.path);\n\t\t\t\tconst destName = `${sanitizedName}-${testResult.project}${ext}`;\n\n\t\t\t\tif (attachment.name.includes('expected')) {\n\t\t\t\t\tconst dest = path.join(assetsDir, 'expected', destName);\n\t\t\t\t\tcopyFileIfExists(attachment.path, dest);\n\t\t\t\t\tfailure.expected = `assets/expected/${destName}`;\n\t\t\t\t} else if (attachment.name.includes('actual')) {\n\t\t\t\t\tconst dest = path.join(assetsDir, 'actual', destName);\n\t\t\t\t\tcopyFileIfExists(attachment.path, dest);\n\t\t\t\t\tfailure.actual = `assets/actual/${destName}`;\n\t\t\t\t} else if (attachment.name.includes('diff')) {\n\t\t\t\t\tconst dest = path.join(assetsDir, 'diff', destName);\n\t\t\t\t\tcopyFileIfExists(attachment.path, dest);\n\t\t\t\t\tfailure.diff = `assets/diff/${destName}`;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfailures.push(failure);\n\t\t}\n\n\t\tconst summary: TestSummary = {\n\t\t\ttotal: allResults.length,\n\t\t\tpassed,\n\t\t\tfailed,\n\t\t\tskipped,\n\t\t\tduration,\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tbrowsers,\n\t\t\tfailures,\n\t\t};\n\n\t\tfs.mkdirSync(this.outputDir, { recursive: true });\n\t\tfs.writeFileSync(path.join(this.outputDir, 'summary.json'), JSON.stringify(summary, null, 2));\n\n\t\t// Generate HTML report\n\t\tconst html = generateHtmlReport(summary);\n\t\tfs.writeFileSync(path.join(this.outputDir, 'index.html'), html);\n\t}\n}\n\nfunction copyFileIfExists(src: string, dest: string): void {\n\ttry {\n\t\tfs.copyFileSync(src, dest);\n\t} catch {\n\t\t// source may not exist\n\t}\n}\n\nexport function generateHtmlReport(summary: TestSummary): string {\n\tconst require = createRequire(import.meta.url);\n\tconst bundlePath = require.resolve('@storywright/report');\n\tconst bundleJs = fs.readFileSync(bundlePath, 'utf-8');\n\n\t// Load CSS if it exists as a separate file\n\tconst bundleDir = path.dirname(bundlePath);\n\tconst assetsDir = path.join(bundleDir, 'assets');\n\tlet cssContent = '';\n\tif (fs.existsSync(assetsDir)) {\n\t\tconst cssFiles = fs.readdirSync(assetsDir).filter((f) => f.endsWith('.css'));\n\t\tfor (const cssFile of cssFiles) {\n\t\t\tcssContent += fs.readFileSync(path.join(assetsDir, cssFile), 'utf-8');\n\t\t}\n\t}\n\n\treturn `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n<title>Storywright Report</title>\n${cssContent ? `<style>${cssContent}</style>` : ''}\n</head>\n<body>\n<div id=\"app\"></div>\n<script>window.__STORYWRIGHT_SUMMARY__ = ${JSON.stringify(summary).replace(/</g, '\\\\u003c')};</script>\n<script>${bundleJs}</script>\n</body>\n</html>`;\n}\n\nexport default StorywrightReporter;\n"],"mappings":"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","names":["require"]}
|
|
@@ -643,7 +643,6 @@ function filterStories(storyIndex, config) {
|
|
|
643
643
|
for (const [id, story] of Object.entries(storyIndex.entries)) {
|
|
644
644
|
if (story.type === "docs") continue;
|
|
645
645
|
if (story.name === "Docs") continue;
|
|
646
|
-
if (story.tags?.includes("!vrt")) continue;
|
|
647
646
|
const fullName = `${story.title}/${story.name}`;
|
|
648
647
|
const isIncluded = includeMatchers.some((m) => m(fullName));
|
|
649
648
|
if (!isIncluded) continue;
|
|
@@ -835,4 +834,4 @@ export {
|
|
|
835
834
|
runTests,
|
|
836
835
|
updateBaselines
|
|
837
836
|
};
|
|
838
|
-
//# sourceMappingURL=chunk-
|
|
837
|
+
//# sourceMappingURL=chunk-XVHJJ6NP.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/config/index.ts","../src/config/defaults.ts","../src/reporter/cli-reporter.ts","../src/storage/local.ts","../src/storage/index.ts","../src/utils/logger.ts","../src/core/engine.ts","../src/playwright/config-generator.ts","../src/playwright/test-generator.ts","../src/resolver/index.ts","../src/utils/path.ts","../src/utils/process.ts","../src/core/storybook.ts"],"sourcesContent":["import { loadConfig as unconfigLoad } from 'unconfig';\nimport { DEFAULT_CONFIG } from './defaults.js';\nimport type { DeepPartial, StorywrightConfig } from './types.js';\n\nexport function defineConfig(\n\tconfig: DeepPartial<StorywrightConfig>,\n): DeepPartial<StorywrightConfig> {\n\treturn config;\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n\treturn value !== null && typeof value === 'object' && !Array.isArray(value);\n}\n\nfunction deepMerge(\n\ttarget: Record<string, unknown>,\n\tsource: Record<string, unknown>,\n): Record<string, unknown> {\n\tconst result: Record<string, unknown> = { ...target };\n\tfor (const key of Object.keys(source)) {\n\t\tconst sourceVal = source[key];\n\t\tconst targetVal = result[key];\n\t\tif (isPlainObject(sourceVal) && isPlainObject(targetVal)) {\n\t\t\tresult[key] = deepMerge(targetVal, sourceVal);\n\t\t} else if (sourceVal !== undefined) {\n\t\t\tresult[key] = sourceVal;\n\t\t}\n\t}\n\treturn result;\n}\n\nexport async function loadConfig(\n\tcwd: string = process.cwd(),\n\toverrides?: DeepPartial<StorywrightConfig>,\n): Promise<StorywrightConfig> {\n\tconst { config: userConfig } = await unconfigLoad<DeepPartial<StorywrightConfig>>({\n\t\tsources: [\n\t\t\t{\n\t\t\t\tfiles: 'storywright.config',\n\t\t\t\textensions: ['ts', 'js', 'mjs'],\n\t\t\t},\n\t\t],\n\t\tcwd,\n\t});\n\n\tlet merged = DEFAULT_CONFIG as unknown as Record<string, unknown>;\n\tif (userConfig) {\n\t\tmerged = deepMerge(merged, userConfig as Record<string, unknown>);\n\t}\n\tif (overrides) {\n\t\tmerged = deepMerge(merged, overrides as Record<string, unknown>);\n\t}\n\treturn merged as unknown as StorywrightConfig;\n}\n\nexport type { StorywrightConfig, DeepPartial } from './types.js';\n","import type { StorywrightConfig } from './types.js';\n\nexport const DEFAULT_CONFIG: StorywrightConfig = {\n\tstorybook: {\n\t\tstaticDir: 'storybook-static',\n\t\tbuildCommand: 'npx storybook build --stats-json',\n\t\turl: undefined,\n\t\tcompatibility: 'auto',\n\t},\n\n\tbrowsers: ['chromium'],\n\tbrowserOptions: {},\n\n\tscreenshot: {\n\t\tfullPage: true,\n\t\tanimations: 'disabled',\n\t\tthreshold: 0.02,\n\t\tmaxDiffPixelRatio: 0.02,\n\t\tfreezeTime: '2024-01-01T00:00:00',\n\t\ttimezone: 'UTC',\n\t\tlocale: 'en-US',\n\t\tseed: 1,\n\t},\n\n\tdiffDetection: {\n\t\tenabled: true,\n\t\twatchFiles: ['package.json', 'package-lock.json', '.storybook/**/*'],\n\t\tbaseBranch: 'main',\n\t},\n\n\tstorage: {\n\t\tprovider: 'local',\n\t\tlocal: {\n\t\t\tbaselineDir: '.storywright/baselines',\n\t\t},\n\t\ts3: {\n\t\t\tbucket: '',\n\t\t\tprefix: 'storywright/baselines',\n\t\t\tregion: 'ap-northeast-1',\n\t\t\tcompression: 'zstd',\n\t\t},\n\t},\n\n\treport: {\n\t\toutputDir: '.storywright/report',\n\t\ttitle: 'Storywright Report',\n\t},\n\n\tworkers: 'auto',\n\tretries: 0,\n\n\ttimeout: {\n\t\ttest: 30000,\n\t\tnavigation: 20000,\n\t\texpect: 10000,\n\t},\n\n\tinclude: ['**'],\n\texclude: [],\n\n\thooks: {},\n};\n","import type { TestSummary } from '../core/types.js';\n\nexport function formatSummary(summary: TestSummary, options?: { reportPath?: string }): string {\n\tconst durationSec = Math.round(summary.duration / 1000);\n\tconst minutes = Math.floor(durationSec / 60);\n\tconst seconds = durationSec % 60;\n\tconst durationStr = minutes > 0 ? `${minutes}m ${seconds}s` : `${seconds}s`;\n\n\tconst lines: string[] = [\n\t\t'',\n\t\t'Storywright Results',\n\t\t'\\u2550'.repeat(42),\n\t\t` Total: ${summary.total} Passed: ${summary.passed} Failed: ${summary.failed} Skipped: ${summary.skipped}`,\n\t\t` Duration: ${durationStr}`,\n\t\t` Browsers: ${summary.browsers.join(', ')}`,\n\t];\n\n\tconst newFailures = summary.failures.filter((f) => f.type === 'new');\n\tconst diffFailures = summary.failures.filter((f) => f.type !== 'new');\n\n\tif (newFailures.length > 0) {\n\t\tlines.push('');\n\t\tlines.push(' New (no baseline):');\n\t\tfor (const failure of newFailures) {\n\t\t\tlines.push(` \\u25cb ${failure.story}: ${failure.variant} (${failure.browser})`);\n\t\t}\n\t}\n\n\tif (diffFailures.length > 0) {\n\t\tlines.push('');\n\t\tlines.push(' Failed:');\n\t\tfor (const failure of diffFailures) {\n\t\t\tlines.push(` \\u2717 ${failure.story}: ${failure.variant} (${failure.browser})`);\n\t\t\tif (failure.diffRatio > 0) {\n\t\t\t\tconst pct = (failure.diffRatio * 100).toFixed(1);\n\t\t\t\tlines.push(` \\u2192 Diff: ${pct}% pixels changed`);\n\t\t\t}\n\t\t}\n\t}\n\n\tconst reportPath = options?.reportPath ?? '.storywright/report/index.html';\n\tlines.push('');\n\tlines.push(` Report: ${reportPath}`);\n\tlines.push('\\u2550'.repeat(42));\n\tlines.push('');\n\n\treturn lines.join('\\n');\n}\n","import { execFile } from 'node:child_process';\nimport fs from 'node:fs/promises';\nimport path from 'node:path';\nimport { promisify } from 'node:util';\nimport type { DownloadOptions, StorageAdapter, UploadOptions } from './types.js';\n\nconst execFileAsync = promisify(execFile);\n\nexport class LocalStorageAdapter implements StorageAdapter {\n\tconstructor(private readonly baselineDir: string) {}\n\n\tasync download(options: DownloadOptions): Promise<void> {\n\t\ttry {\n\t\t\tawait fs.access(this.baselineDir);\n\t\t} catch {\n\t\t\treturn;\n\t\t}\n\t\tawait fs.cp(this.baselineDir, options.destDir, { recursive: true });\n\t}\n\n\tasync upload(options: UploadOptions): Promise<void> {\n\t\tconst resolvedSource = path.resolve(options.sourceDir);\n\t\tconst resolvedDest = path.resolve(this.baselineDir);\n\t\tif (resolvedSource === resolvedDest) {\n\t\t\treturn;\n\t\t}\n\t\tawait fs.mkdir(this.baselineDir, { recursive: true });\n\t\tawait fs.cp(options.sourceDir, this.baselineDir, { recursive: true });\n\t}\n\n\tasync exists(_branch: string): Promise<boolean> {\n\t\ttry {\n\t\t\tawait fs.access(this.baselineDir);\n\t\t\tconst entries = await fs.readdir(this.baselineDir);\n\t\t\treturn entries.length > 0;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t/**\n\t * Extract baselines from a git branch using `git ls-tree` + `git show`.\n\t * Binary-safe (PNG files) via `encoding: 'buffer'`.\n\t */\n\tasync downloadFromGit(branch: string, destDir: string, cwd: string): Promise<void> {\n\t\tconst gitPath = this.baselineDir.split(path.sep).join('/');\n\n\t\tlet lsOutput: string;\n\t\ttry {\n\t\t\tconst result = await execFileAsync(\n\t\t\t\t'git',\n\t\t\t\t['ls-tree', '-r', '--name-only', branch, '--', gitPath],\n\t\t\t\t{ cwd },\n\t\t\t);\n\t\t\tlsOutput = result.stdout;\n\t\t} catch (error) {\n\t\t\tthrow new Error(\n\t\t\t\t`Failed to list baselines from git branch '${branch}': ${error instanceof Error ? error.message : error}`,\n\t\t\t);\n\t\t}\n\n\t\tconst files = lsOutput.trim().split('\\n').filter(Boolean);\n\t\tif (files.length === 0) {\n\t\t\treturn;\n\t\t}\n\n\t\tawait fs.mkdir(destDir, { recursive: true });\n\n\t\tconst posixBaselineDir = this.baselineDir.split(path.sep).join('/').replace(/\\/+$/, '');\n\n\t\tfor (const file of files) {\n\t\t\tlet content: Buffer;\n\t\t\ttry {\n\t\t\t\tconst result = await execFileAsync('git', ['show', `${branch}:${file}`], {\n\t\t\t\t\tcwd,\n\t\t\t\t\tencoding: 'buffer' as unknown as BufferEncoding,\n\t\t\t\t\tmaxBuffer: 50 * 1024 * 1024,\n\t\t\t\t});\n\t\t\t\tcontent = result.stdout as unknown as Buffer;\n\t\t\t} catch (error) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Failed to extract '${file}' from git branch '${branch}': ${error instanceof Error ? error.message : error}`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst relativePath = file.slice(posixBaselineDir.length + 1);\n\t\t\tconst destPath = path.join(destDir, ...relativePath.split('/'));\n\t\t\tawait fs.mkdir(path.dirname(destPath), { recursive: true });\n\t\t\tawait fs.writeFile(destPath, content);\n\t\t}\n\t}\n}\n","import { createRequire } from 'node:module';\nimport type { StorageConfig } from '../config/types.js';\nimport { LocalStorageAdapter } from './local.js';\nimport type { StorageAdapter } from './types.js';\n\nexport function createStorageAdapter(config: StorageConfig): StorageAdapter {\n\tswitch (config.provider) {\n\t\tcase 'local':\n\t\t\treturn new LocalStorageAdapter(config.local.baselineDir);\n\t\tcase 's3':\n\t\t\treturn loadS3Adapter(config);\n\t\tdefault:\n\t\t\tthrow new Error(`Unknown storage provider: ${config.provider}`);\n\t}\n}\n\nfunction loadS3Adapter(config: StorageConfig): StorageAdapter {\n\ttry {\n\t\tconst require = createRequire(import.meta.url);\n\t\tconst { S3StorageAdapter } = require('@storywright/storage-s3') as {\n\t\t\tS3StorageAdapter: new (cfg: {\n\t\t\t\tbucket: string;\n\t\t\t\tprefix: string;\n\t\t\t\tregion: string;\n\t\t\t\tcompression?: string;\n\t\t\t}) => StorageAdapter;\n\t\t};\n\t\treturn new S3StorageAdapter(config.s3);\n\t} catch {\n\t\tthrow new Error(\n\t\t\t'S3 storage adapter requires the @storywright/storage-s3 package.\\nInstall it with: pnpm add @storywright/storage-s3',\n\t\t);\n\t}\n}\n\nexport type { StorageAdapter, DownloadOptions, UploadOptions } from './types.js';\n","import { createConsola } from 'consola';\n\nconst isCI = !!(\n\tprocess.env.CI ||\n\tprocess.env.GITHUB_ACTIONS ||\n\tprocess.env.CIRCLECI ||\n\tprocess.env.GITLAB_CI\n);\n\nexport const logger = createConsola({\n\tlevel: process.env.STORYWRIGHT_DEBUG ? 5 : 3,\n});\n\nexport { isCI };\n","import fs from 'node:fs/promises';\nimport path from 'node:path';\nimport picomatch from 'picomatch';\nimport type { StorywrightConfig } from '../config/types.js';\nimport { generatePlaywrightConfig } from '../playwright/config-generator.js';\nimport { generateTestFile } from '../playwright/test-generator.js';\nimport { resolveAffectedStories } from '../resolver/index.js';\nimport { createStorageAdapter } from '../storage/index.js';\nimport { logger } from '../utils/logger.js';\nimport { resolveOutputDir } from '../utils/path.js';\nimport { exec } from '../utils/process.js';\nimport { buildStorybook, discoverStories, filterStories } from './storybook.js';\nimport type { Story, StoryIndex, TestSummary } from './types.js';\n\nexport interface TestOptions {\n\tdiffOnly?: boolean;\n\tshard?: string;\n\tupdateSnapshots?: boolean;\n\tfilter?: string;\n\toutputDir?: string;\n\treporters?: string[];\n}\n\nexport interface TestRunResult {\n\texitCode: number;\n\tsummary?: TestSummary;\n\treportDir?: string;\n\tsnapshotDir?: string;\n}\n\nconst STORIES_PER_FILE = 50;\n\nfunction resolveReporterPath(): string {\n\t// Resolve relative to this file's dist location\n\tconst thisDir = new URL('.', import.meta.url).pathname;\n\treturn path.resolve(thisDir, 'playwright', 'reporter.js');\n}\n\nfunction chunkStories(entries: Record<string, Story>): Record<string, Story>[] {\n\tconst keys = Object.keys(entries);\n\tif (keys.length === 0) return [{}];\n\tconst chunks: Record<string, Story>[] = [];\n\tfor (let i = 0; i < keys.length; i += STORIES_PER_FILE) {\n\t\tconst chunk: Record<string, Story> = {};\n\t\tfor (const key of keys.slice(i, i + STORIES_PER_FILE)) {\n\t\t\tchunk[key] = entries[key];\n\t\t}\n\t\tchunks.push(chunk);\n\t}\n\treturn chunks;\n}\n\nexport async function runTests(\n\tconfig: StorywrightConfig,\n\toptions: TestOptions = {},\n\tcwd: string = process.cwd(),\n): Promise<TestRunResult> {\n\tconst outputRoot = options.outputDir\n\t\t? path.resolve(cwd, options.outputDir)\n\t\t: resolveOutputDir(cwd, '.storywright');\n\tconst tmpDir = path.join(outputRoot, 'tmp');\n\tconst reportDir = options.outputDir\n\t\t? path.join(outputRoot, 'report')\n\t\t: path.resolve(cwd, config.report.outputDir);\n\tconst storybookDir = path.resolve(cwd, config.storybook.staticDir);\n\tconst snapshotDir = path.join(tmpDir, 'snapshots');\n\n\t// Prepare directories early for parallel operations\n\tawait fs.mkdir(snapshotDir, { recursive: true });\n\n\t// Start baseline download in parallel with Storybook build\n\tconst storage = createStorageAdapter(config.storage);\n\tconst baselinePromise = storage\n\t\t.download({ branch: 'current', destDir: snapshotDir })\n\t\t.catch(() => {\n\t\t\tlogger.info('No existing baselines found');\n\t\t});\n\n\t// 1. Build Storybook if needed\n\tawait buildStorybook(config, cwd);\n\n\t// 2. Discover & filter stories\n\tlogger.start('Discovering stories...');\n\tconst allStories = await discoverStories(config, cwd);\n\tlet targetStories = filterStories(allStories, config);\n\n\t// Apply --filter option\n\tif (options.filter) {\n\t\ttargetStories = applyFilter(targetStories, options.filter);\n\t}\n\n\tlogger.info(`${Object.keys(targetStories.entries).length} stories found`);\n\n\t// 3. Diff-only: resolve affected stories (default in CI)\n\tconst effectiveDiffOnly = options.diffOnly ?? !!process.env.CI;\n\tif (effectiveDiffOnly && config.diffDetection.enabled) {\n\t\tlogger.start('Resolving dependencies...');\n\t\tconst diffResult = await resolveAffectedStories(\n\t\t\ttargetStories,\n\t\t\tconfig.diffDetection,\n\t\t\tstorybookDir,\n\t\t\tcwd,\n\t\t);\n\t\tif (!diffResult.allStories) {\n\t\t\ttargetStories = diffResult.targetStories;\n\t\t}\n\t\tlogger.info(`${Object.keys(targetStories.entries).length} stories affected by changes`);\n\t}\n\n\t// 4. Wait for baseline download to complete\n\tawait baselinePromise;\n\n\t// 5. Generate split test files for better worker distribution\n\tconst chunks = chunkStories(targetStories.entries);\n\tconst testFilePattern = chunks.length === 1 ? 'storywright-0.spec.ts' : 'storywright-*.spec.ts';\n\n\tfor (let i = 0; i < chunks.length; i++) {\n\t\tconst chunkIndex: StoryIndex = { ...targetStories, entries: chunks[i] };\n\t\tconst chunkPath = path.join(tmpDir, `target-stories-${i}.json`);\n\t\tawait fs.writeFile(chunkPath, JSON.stringify(chunkIndex));\n\n\t\tconst testContent = generateTestFile(config.screenshot, {\n\t\t\ttargetStoriesPath: chunkPath.replace(/\\\\/g, '/'),\n\t\t});\n\t\tawait fs.writeFile(path.join(tmpDir, `storywright-${i}.spec.ts`), testContent);\n\t}\n\n\tlogger.info(`${chunks.length} test file(s) generated`);\n\n\t// 6. Generate Playwright config\n\tconst reporterWrapperPath = path.join(tmpDir, 'reporter.mjs');\n\tconst resolvedReporterPath = resolveReporterPath().replace(/\\\\/g, '/');\n\tconst reporterOutputDir = reportDir.replace(/\\\\/g, '/');\n\n\tawait fs.writeFile(\n\t\treporterWrapperPath,\n\t\t`import StorywrightReporter from '${resolvedReporterPath}';\\nexport default class extends StorywrightReporter {\\n constructor() { super({ outputDir: '${reporterOutputDir}' }); }\\n}\\n`,\n\t);\n\n\t// Determine Storybook URL\n\tlet actualStorybookUrl = config.storybook.url;\n\tconst needsServer = !actualStorybookUrl;\n\n\tif (needsServer) {\n\t\tactualStorybookUrl = 'http://localhost:6007';\n\t}\n\n\tconst playwrightConfig = generatePlaywrightConfig(config, {\n\t\ttmpDir: tmpDir.replace(/\\\\/g, '/'),\n\t\tstorybookUrl: actualStorybookUrl ?? 'http://localhost:6007',\n\t\tsnapshotDir: snapshotDir.replace(/\\\\/g, '/'),\n\t\treporterPath: reporterWrapperPath.replace(/\\\\/g, '/'),\n\t\ttestMatch: testFilePattern,\n\t\tshard: options.shard,\n\t\treporters: options.reporters,\n\t});\n\n\tconst configPath = path.join(tmpDir, 'playwright.config.ts');\n\tawait fs.writeFile(configPath, playwrightConfig);\n\n\t// 7. Run Playwright tests\n\tlogger.start('Running tests...');\n\tconst args = ['playwright', 'test', '--config', configPath];\n\n\tif (options.updateSnapshots) {\n\t\targs.push('--update-snapshots');\n\t}\n\n\t// Start static server if needed\n\tlet serverProc: { kill: () => void } | undefined;\n\tif (needsServer) {\n\t\tserverProc = await startStaticServer(storybookDir, 6007);\n\t}\n\n\ttry {\n\t\tconst result = await exec('npx', args, { cwd, inherit: true });\n\n\t\t// 8. Read results\n\t\tlet summary: TestSummary | undefined;\n\t\ttry {\n\t\t\tconst summaryPath = path.join(reportDir, 'summary.json');\n\t\t\tconst summaryContent = await fs.readFile(summaryPath, 'utf-8');\n\t\t\tsummary = JSON.parse(summaryContent);\n\t\t} catch {\n\t\t\t// summary may not exist if no tests ran\n\t\t}\n\n\t\t// 9. Map exit codes per SPEC §14.2\n\t\tconst exitCode = mapExitCode(result.exitCode, summary);\n\n\t\treturn { exitCode, summary, reportDir, snapshotDir };\n\t} finally {\n\t\tserverProc?.kill();\n\t}\n}\n\nexport async function updateBaselines(\n\tconfig: StorywrightConfig,\n\toptions: { all?: boolean; upload?: boolean } = {},\n\tcwd: string = process.cwd(),\n): Promise<void> {\n\tconst result = await runTests(config, { updateSnapshots: true, diffOnly: !options.all }, cwd);\n\n\tif (result.exitCode !== 0) {\n\t\tlogger.warn('Some tests failed during baseline update');\n\t}\n\n\t// Save updated snapshots back to baselineDir (local disk operation)\n\tif (result.snapshotDir) {\n\t\tconst baselineDir = path.resolve(cwd, config.storage.local.baselineDir);\n\t\tawait fs.mkdir(baselineDir, { recursive: true });\n\t\tawait fs.cp(result.snapshotDir, baselineDir, { recursive: true });\n\t\tlogger.success(`Baselines saved to ${config.storage.local.baselineDir}`);\n\t}\n\n\t// --upload: upload to remote storage (S3 etc.) only when explicitly requested\n\tif (options.upload) {\n\t\tconst storage = createStorageAdapter(config.storage);\n\t\tconst baselineDir = path.resolve(cwd, config.storage.local.baselineDir);\n\t\tawait storage.upload({\n\t\t\tbranch: 'current',\n\t\t\tsourceDir: baselineDir,\n\t\t});\n\t\tlogger.success('Baselines uploaded to remote storage');\n\t}\n}\n\nfunction applyFilter(storyIndex: StoryIndex, filter: string): StoryIndex {\n\tconst matcher = picomatch(filter);\n\tconst entries: Record<string, StoryIndex['entries'][string]> = {};\n\tfor (const [id, story] of Object.entries(storyIndex.entries)) {\n\t\tconst fullName = `${story.title}/${story.name}`;\n\t\tif (matcher(fullName) || matcher(story.title) || matcher(story.id)) {\n\t\t\tentries[id] = story;\n\t\t}\n\t}\n\treturn { ...storyIndex, entries };\n}\n\nfunction mapExitCode(playwrightCode: number, summary?: TestSummary): number {\n\t// SPEC §14.2: 0 = success (no diff), 1 = success (diff found), 2 = execution error, 130 = SIGINT\n\tif (playwrightCode === 130 || playwrightCode === 143) {\n\t\treturn 130; // SIGINT / SIGTERM\n\t}\n\tif (summary) {\n\t\tif (summary.failed > 0) return 1;\n\t\tif (summary.total === 0 && playwrightCode !== 0) return 2;\n\t\treturn 0;\n\t}\n\t// No summary = likely execution error\n\treturn playwrightCode === 0 ? 0 : 2;\n}\n\nasync function startStaticServer(dir: string, port: number): Promise<{ kill: () => void }> {\n\tconst { createServer } = await import('node:http');\n\tconst sirv = (await import('sirv')).default;\n\n\tconst handler = sirv(dir, { single: false, dev: false });\n\tconst server = createServer(handler);\n\n\tawait new Promise<void>((resolve, reject) => {\n\t\tserver.on('error', reject);\n\t\tserver.listen(port, () => resolve());\n\t});\n\n\treturn { kill: () => server.close() };\n}\n","import type { StorywrightConfig } from '../config/types.js';\n\nexport function generatePlaywrightConfig(\n\tconfig: StorywrightConfig,\n\toptions: {\n\t\ttmpDir: string;\n\t\tstorybookUrl: string;\n\t\tsnapshotDir: string;\n\t\treporterPath: string;\n\t\ttestMatch: string;\n\t\tshard?: string;\n\t\treporters?: string[];\n\t},\n): string {\n\tconst projects = config.browsers.map((browser) => {\n\t\tconst browserOptions = config.browserOptions[browser];\n\t\tconst useObj = browserOptions\n\t\t\t? JSON.stringify(browserOptions, null, '\\t\\t')\n\t\t\t: getDefaultBrowserUse(browser);\n\t\treturn `\\t\\t{\n\\t\\t\\tname: '${browser}',\n\\t\\t\\tuse: ${useObj},\n\\t\\t}`;\n\t});\n\n\tconst workers = config.workers === 'auto' ? \"'100%'\" : String(config.workers);\n\n\tconst shard = options.shard\n\t\t? `\\tshard: { current: ${options.shard.split('/')[0]}, total: ${options.shard.split('/')[1]} },`\n\t\t: '';\n\n\t// Build reporter list: always include custom reporter, plus user-requested ones\n\tconst reporterEntries: string[] = [];\n\tconst requestedReporters = options.reporters ?? ['default', 'html'];\n\tfor (const r of requestedReporters) {\n\t\tif (r === 'default' || r === 'list') {\n\t\t\treporterEntries.push(\"\\t\\t['list']\");\n\t\t} else if (r !== 'html') {\n\t\t\t// Pass through other built-in Playwright reporters (dot, json, junit, etc.)\n\t\t\treporterEntries.push(`\\t\\t['${r}']`);\n\t\t}\n\t}\n\t// Always include custom storywright reporter\n\treporterEntries.push(`\\t\\t['${escapeBackslash(options.reporterPath)}']`);\n\n\treturn `import { defineConfig } from '@playwright/test';\n\nexport default defineConfig({\n\\ttestDir: '${escapeBackslash(options.tmpDir)}',\n\\ttestMatch: '${escapeBackslash(options.testMatch)}',\n\\tsnapshotDir: '${escapeBackslash(options.snapshotDir)}',\n\\tsnapshotPathTemplate: '{snapshotDir}/{arg}-{projectName}{ext}',\n\\ttimeout: ${config.timeout.test},\n\\texpect: {\n\\t\\ttoHaveScreenshot: {\n\\t\\t\\tmaxDiffPixelRatio: ${config.screenshot.maxDiffPixelRatio},\n\\t\\t\\tthreshold: ${config.screenshot.threshold},\n\\t\\t},\n\\t\\ttimeout: ${config.timeout.expect},\n\\t},\n\\tfullyParallel: true,\n\\tforbidOnly: !!process.env.CI,\n\\tretries: ${config.retries},\n\\tworkers: ${workers},\n${shard}\n\\treporter: [\n${reporterEntries.join(',\\n')}\n\\t],\n\\tuse: {\n\\t\\tbaseURL: '${options.storybookUrl}',\n\\t\\tnavigationTimeout: ${config.timeout.navigation},\n\\t\\ttimezoneId: '${config.screenshot.timezone}',\n\\t\\tlocale: '${config.screenshot.locale}',\n\\t},\n\\tprojects: [\n${projects.join(',\\n')}\n\\t],\n});\n`;\n}\n\nfunction getDefaultBrowserUse(browser: string): string {\n\tswitch (browser) {\n\t\tcase 'chromium':\n\t\t\treturn \"{ browserName: 'chromium' }\";\n\t\tcase 'firefox':\n\t\t\treturn \"{ browserName: 'firefox' }\";\n\t\tcase 'webkit':\n\t\t\treturn \"{ browserName: 'webkit' }\";\n\t\tdefault:\n\t\t\treturn `{ browserName: '${browser}' }`;\n\t}\n}\n\nfunction escapeBackslash(str: string): string {\n\treturn str.replace(/\\\\/g, '/');\n}\n","import type { ScreenshotConfig } from '../config/types.js';\n\nexport function generateTestFile(\n\tconfig: ScreenshotConfig,\n\toptions: {\n\t\ttargetStoriesPath: string;\n\t},\n): string {\n\treturn `import { test, expect } from '@playwright/test';\nimport { readFileSync } from 'node:fs';\n\nconst targetList = JSON.parse(\n\\treadFileSync('${escapeBackslash(options.targetStoriesPath)}', 'utf-8'),\n);\n\ntest.describe.parallel('visual regression testing', () => {\n\\tif (Object.keys(targetList.entries).length === 0) {\n\\t\\ttest('no stories to test', () => {\n\\t\\t\\texpect(true).toBeTruthy();\n\\t\\t});\n\\t}\n\n\\tfor (const story of Object.values(targetList.entries)) {\n\\t\\ttest(\\`\\${story.title}: \\${story.name}\\`, async ({ page }) => {\n\\t\\t\\t// Freeze time for reproducibility\n\\t\\t\\tawait page.clock.install({ time: new Date('${config.freezeTime}') });\n\n\\t\\t\\t// Seed Math.random for reproducibility\n\\t\\t\\tawait page.addInitScript((seed) => {\n\\t\\t\\t\\tlet s = seed;\n\\t\\t\\t\\tMath.random = () => {\n\\t\\t\\t\\t\\ts = (s * 16807 + 0) % 2147483647;\n\\t\\t\\t\\t\\treturn (s - 1) / 2147483646;\n\\t\\t\\t\\t};\n\\t\\t\\t}, ${config.seed});\n\n\\t\\t\\tawait page.goto(\\`/iframe.html?id=\\${story.id}\\`, {\n\\t\\t\\t\\twaitUntil: 'domcontentloaded',\n\\t\\t\\t});\n\n\\t\\t\\t// Wait for story to render: content inside #storybook-root OR portal content on body\n\\t\\t\\tawait page.waitForFunction(() => {\n\\t\\t\\t\\tconst root = document.getElementById('storybook-root');\n\\t\\t\\t\\tif (!root) return false;\n\\t\\t\\t\\tif (root.childElementCount > 0) return true;\n\\t\\t\\t\\t// Portal: check for elements on body that aren't part of Storybook's skeleton\n\\t\\t\\t\\tfor (const el of document.body.children) {\n\\t\\t\\t\\t\\tif (el.tagName === 'SCRIPT' || el.id === 'storybook-root' || el.id === 'storybook-docs') continue;\n\\t\\t\\t\\t\\treturn true;\n\\t\\t\\t\\t}\n\\t\\t\\t\\treturn false;\n\\t\\t\\t}, { timeout: 10000 });\n\n\\t\\t\\t// Wait for web fonts to finish loading\n\\t\\t\\tawait page.waitForFunction(() => document.fonts.ready);\n\n\\t\\t\\t// Wait for all images to finish loading (lazy-loaded, dynamic src, etc.)\n\\t\\t\\tawait page.waitForFunction(() => {\n\\t\\t\\t\\tconst images = document.querySelectorAll('img');\n\\t\\t\\t\\treturn Array.from(images).every(img => img.complete);\n\\t\\t\\t});\n\n\\t\\t\\t// Allow async renders (portals, modals, lazy components) to settle\n\\t\\t\\tawait page.waitForFunction(() => new Promise(resolve => requestAnimationFrame(() => resolve(true))));\n\n\\t\\t\\tawait expect(page).toHaveScreenshot(\n\\t\\t\\t\\t[story.title, \\`\\${story.id}.png\\`],\n\\t\\t\\t\\t{\n\\t\\t\\t\\t\\tanimations: '${config.animations}',\n\\t\\t\\t\\t\\tfullPage: ${config.fullPage},\n\\t\\t\\t\\t\\tthreshold: ${config.threshold},\n\\t\\t\\t\\t\\tmaxDiffPixelRatio: ${config.maxDiffPixelRatio},\n\\t\\t\\t\\t},\n\\t\\t\\t);\n\\t\\t});\n\\t}\n});\n`;\n}\n\nfunction escapeBackslash(str: string): string {\n\treturn str.replace(/\\\\/g, '/');\n}\n","import fs from 'node:fs/promises';\nimport path from 'node:path';\nimport picomatch from 'picomatch';\nimport { simpleGit } from 'simple-git';\nimport type { DiffDetectionConfig } from '../config/types.js';\nimport type { StatsIndex, StatsModule, StoryIndex } from '../core/types.js';\nimport { logger } from '../utils/logger.js';\nimport { normalizePath, stripLeadingDotSlash } from '../utils/path.js';\n\nexport interface DependencyResolver {\n\tgetDependencies(filePath: string): string[];\n\tgetStoriesForFiles(pathList: string[]): StoryIndex;\n}\n\nconst STORY_FILE_PATTERNS = ['.stories.', '.mdx'];\n\nfunction isStoryFile(moduleName: string): boolean {\n\treturn STORY_FILE_PATTERNS.some((p) => moduleName.includes(p));\n}\n\nexport class StorybookStatsDependencyResolver implements DependencyResolver {\n\tprivate moduleMap: Record<string, StatsModule>;\n\n\tconstructor(\n\t\tprivate statsJson: StatsIndex,\n\t\tprivate storiesJson: StoryIndex,\n\t) {\n\t\tthis.moduleMap = {};\n\t\tfor (const mod of statsJson.modules) {\n\t\t\t// Key by normalized name (primary) and normalized id (fallback)\n\t\t\tconst normalizedName = normalizePath(mod.name);\n\t\t\tthis.moduleMap[normalizedName] = mod;\n\t\t\tconst normalizedId = normalizePath(mod.id);\n\t\t\tif (normalizedId !== normalizedName) {\n\t\t\t\tthis.moduleMap[normalizedId] ??= mod;\n\t\t\t}\n\t\t}\n\t}\n\n\tgetDependencies(filePath: string): string[] {\n\t\tconst normalizedPath = normalizePath(filePath);\n\t\tconst dependencies = this.collectDependencies(normalizedPath);\n\n\t\tif (this.moduleMap[normalizedPath]) {\n\t\t\tdependencies.add(normalizedPath);\n\t\t}\n\n\t\treturn [...dependencies];\n\t}\n\n\tgetStoriesForFiles(pathList: string[]): StoryIndex {\n\t\tconst result: StoryIndex = { v: this.storiesJson.v, entries: {} };\n\n\t\tfor (const filePath of pathList) {\n\t\t\t// Finding #2 + #3: lookup via normalized moduleMap (name primary, id fallback)\n\t\t\tconst normalizedPath = normalizePath(filePath);\n\t\t\tconst stats = this.moduleMap[normalizedPath];\n\t\t\tif (!stats) continue;\n\n\t\t\t// Finding #1: collect ALL story reasons, not just first\n\t\t\tconst storyReasons = stats.reasons.filter((r) => isStoryFile(r.moduleName));\n\t\t\tfor (const reason of storyReasons) {\n\t\t\t\tconst normalizedImportPath = normalizePath(reason.moduleName);\n\t\t\t\t// Collect ALL matching story entries per reason\n\t\t\t\tfor (const storyObj of Object.values(this.storiesJson.entries)) {\n\t\t\t\t\tif (storyObj.type !== 'story') continue;\n\t\t\t\t\tif (normalizePath(storyObj.importPath) === normalizedImportPath) {\n\t\t\t\t\t\tresult.entries[storyObj.id] = storyObj;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn result;\n\t}\n\n\tprivate collectDependencies(name: string, result = new Set<string>()): Set<string> {\n\t\tconst mod = this.moduleMap[normalizePath(name)];\n\t\tif (mod) {\n\t\t\tfor (const reason of mod.reasons) {\n\t\t\t\tif (!result.has(reason.moduleName)) {\n\t\t\t\t\tresult.add(reason.moduleName);\n\t\t\t\t\tthis.collectDependencies(reason.moduleName, result);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn result;\n\t}\n}\n\nexport interface DiffResult {\n\tallStories: boolean;\n\ttargetStories: StoryIndex;\n}\n\ninterface DiffFileEntry {\n\tfile: string;\n\tfrom?: string;\n}\n\nexport async function resolveAffectedStories(\n\tstoriesJson: StoryIndex,\n\tconfig: DiffDetectionConfig,\n\tstorybookStaticDir: string,\n\tcwd: string,\n): Promise<DiffResult> {\n\tconst git = simpleGit({ baseDir: cwd });\n\n\t// Get diff summary\n\tlet diffEntries: DiffFileEntry[];\n\ttry {\n\t\tconst mergeBase = await git.raw(['merge-base', config.baseBranch, 'HEAD']);\n\t\tconst diff = await git.diffSummary([mergeBase.trim(), 'HEAD']);\n\t\tdiffEntries = diff.files.map((f) => ({\n\t\t\tfile: f.file,\n\t\t\t// Handle renames: include both old and new paths\n\t\t\tfrom: 'from' in f ? (f as { from: string }).from : undefined,\n\t\t}));\n\t} catch {\n\t\tlogger.warn('Failed to resolve git diff, running all stories');\n\t\treturn { allStories: true, targetStories: storiesJson };\n\t}\n\n\tif (diffEntries.length === 0) {\n\t\tlogger.info('No changed files detected');\n\t\treturn { allStories: false, targetStories: { v: storiesJson.v, entries: {} } };\n\t}\n\n\t// Collect all affected paths (including rename sources)\n\tconst allPaths: string[] = [];\n\tfor (const entry of diffEntries) {\n\t\tallPaths.push(entry.file);\n\t\tif (entry.from) {\n\t\t\tallPaths.push(entry.from);\n\t\t}\n\t}\n\n\t// Check watchFiles\n\tfor (const file of allPaths) {\n\t\tfor (const pattern of config.watchFiles) {\n\t\t\tif (picomatch(pattern)(file)) {\n\t\t\t\tlogger.info(`Watch file changed: ${file}, running all stories`);\n\t\t\t\treturn { allStories: true, targetStories: storiesJson };\n\t\t\t}\n\t\t}\n\t}\n\n\t// Load stats json for dependency resolution\n\tconst statsPath = path.resolve(storybookStaticDir, 'preview-stats.json');\n\tlet statsJson: StatsIndex;\n\ttry {\n\t\tstatsJson = JSON.parse(await fs.readFile(statsPath, 'utf-8'));\n\t} catch {\n\t\tlogger.warn('preview-stats.json not found, running all stories');\n\t\treturn { allStories: true, targetStories: storiesJson };\n\t}\n\n\tconst resolver = new StorybookStatsDependencyResolver(statsJson, storiesJson);\n\tconst targetStories: StoryIndex = { v: storiesJson.v, entries: {} };\n\n\t// Direct story file matches (both .stories.* and .mdx)\n\tfor (const file of allPaths) {\n\t\tconst matchedStories = Object.values(storiesJson.entries).filter(\n\t\t\t(story) => stripLeadingDotSlash(story.importPath) === file,\n\t\t);\n\t\tfor (const story of matchedStories) {\n\t\t\ttargetStories.entries[story.id] = story;\n\t\t}\n\t}\n\n\t// Dependency-based matches\n\tfor (const file of allPaths) {\n\t\tconst deps = resolver.getDependencies(normalizePath(file));\n\t\tconst depStories = resolver.getStoriesForFiles(deps);\n\t\tfor (const [id, story] of Object.entries(depStories.entries)) {\n\t\t\ttargetStories.entries[id] = story;\n\t\t}\n\t}\n\n\tlogger.info(`Resolved ${Object.keys(targetStories.entries).length} affected stories`);\n\treturn { allStories: false, targetStories };\n}\n","import path from 'node:path';\n\nexport function normalizePath(filePath: string): string {\n\tconst normalized = filePath.replace(/\\\\/g, '/');\n\tif (normalized.startsWith('./')) {\n\t\treturn normalized;\n\t}\n\treturn `./${normalized}`;\n}\n\nexport function stripLeadingDotSlash(filePath: string): string {\n\treturn filePath.replace(/^\\.\\//, '');\n}\n\nexport function resolveOutputDir(outputDir: string, ...segments: string[]): string {\n\treturn path.resolve(outputDir, ...segments);\n}\n","import { spawn } from 'node:child_process';\n\nexport interface ExecResult {\n\texitCode: number;\n\tstdout: string;\n\tstderr: string;\n}\n\nexport function exec(\n\tcommand: string,\n\targs: string[],\n\toptions?: { cwd?: string; env?: Record<string, string>; inherit?: boolean },\n): Promise<ExecResult> {\n\treturn new Promise((resolve, reject) => {\n\t\tconst proc = spawn(command, args, {\n\t\t\tcwd: options?.cwd,\n\t\t\tenv: { ...process.env, ...options?.env },\n\t\t\tstdio: options?.inherit ? ['ignore', 'inherit', 'inherit'] : ['ignore', 'pipe', 'pipe'],\n\t\t\tshell: false,\n\t\t});\n\n\t\tlet stdout = '';\n\t\tlet stderr = '';\n\n\t\tif (!options?.inherit) {\n\t\t\tproc.stdout?.on('data', (data: Buffer) => {\n\t\t\t\tstdout += data.toString();\n\t\t\t});\n\n\t\t\tproc.stderr?.on('data', (data: Buffer) => {\n\t\t\t\tstderr += data.toString();\n\t\t\t});\n\t\t}\n\n\t\tproc.on('error', reject);\n\n\t\tproc.on('close', (code) => {\n\t\t\tresolve({ exitCode: code ?? 1, stdout, stderr });\n\t\t});\n\t});\n}\n","import fs from 'node:fs/promises';\nimport path from 'node:path';\nimport picomatch from 'picomatch';\nimport type { StorywrightConfig } from '../config/types.js';\nimport { logger } from '../utils/logger.js';\nimport { exec } from '../utils/process.js';\nimport type { Story, StoryIndex } from './types.js';\n\nexport async function buildStorybook(config: StorywrightConfig, cwd: string): Promise<void> {\n\tif (config.storybook.url) {\n\t\tlogger.info('Using running Storybook at', config.storybook.url);\n\t\treturn;\n\t}\n\n\tconst staticDir = path.resolve(cwd, config.storybook.staticDir);\n\ttry {\n\t\tawait fs.access(path.join(staticDir, 'index.json'));\n\t\tlogger.info('Storybook already built at', staticDir);\n\t\treturn;\n\t} catch {\n\t\t// need to build\n\t}\n\n\tlogger.start('Building Storybook...');\n\tconst [command, ...args] = config.storybook.buildCommand.split(' ');\n\tconst result = await exec(command, args, { cwd });\n\tif (result.exitCode !== 0) {\n\t\tthrow new Error(\n\t\t\t`Storybook build failed (exit code ${result.exitCode}):\\n${result.stderr}\\n\\nError code: SW_E_STORYBOOK_BUILD_FAILED`,\n\t\t);\n\t}\n\tlogger.success('Storybook built');\n}\n\nexport async function discoverStories(config: StorywrightConfig, cwd: string): Promise<StoryIndex> {\n\tconst staticDir = path.resolve(cwd, config.storybook.staticDir);\n\tconst indexPath = path.join(staticDir, 'index.json');\n\n\ttry {\n\t\tawait fs.access(indexPath);\n\t} catch {\n\t\tthrow new Error(\n\t\t\t`Storybook build directory not found at '${config.storybook.staticDir}/'\\n\\n Run one of the following:\\n $ npx storybook build --stats-json\\n $ npx storywright test --storybook-url http://localhost:6006\\n\\n Error code: SW_E_STORYBOOK_DIR_NOT_FOUND`,\n\t\t);\n\t}\n\n\tconst raw = JSON.parse(await fs.readFile(indexPath, 'utf-8'));\n\tconst indexJson = normalizeStoryIndex(raw, config.storybook.compatibility);\n\n\t// Version check\n\tif (indexJson.v < 4) {\n\t\tthrow new Error(\n\t\t\t'Storybook 7.x or earlier is not supported. Storywright requires Storybook 8 or later.\\n\\nError code: SW_E_STORYBOOK_UNSUPPORTED',\n\t\t);\n\t}\n\n\treturn indexJson;\n}\n\n/**\n * Parse Storybook index.json (v8+).\n */\nfunction normalizeStoryIndex(\n\traw: Record<string, unknown>,\n\t_compatibility: 'auto' | 'v8',\n): StoryIndex {\n\tconst version = typeof raw.v === 'number' ? raw.v : 0;\n\tconst entries = (raw.entries ?? {}) as Record<string, Story>;\n\n\treturn { v: version, entries };\n}\n\nexport function filterStories(storyIndex: StoryIndex, config: StorywrightConfig): StoryIndex {\n\tconst entries: Record<string, Story> = {};\n\tconst includeMatchers = config.include.map((p) => picomatch(p));\n\tconst excludeMatchers = config.exclude.map((p) => picomatch(p));\n\n\tfor (const [id, story] of Object.entries(storyIndex.entries)) {\n\t\t// Skip docs entries\n\t\tif (story.type === 'docs') continue;\n\t\tif (story.name === 'Docs') continue;\n\n\t\t// Skip stories with !vrt tag\n\t\tif (story.tags?.includes('!vrt')) continue;\n\n\t\tconst fullName = `${story.title}/${story.name}`;\n\n\t\t// Check include patterns\n\t\tconst isIncluded = includeMatchers.some((m) => m(fullName));\n\t\tif (!isIncluded) continue;\n\n\t\t// Check exclude patterns\n\t\tconst isExcluded = excludeMatchers.some((m) => m(fullName));\n\t\tif (isExcluded) continue;\n\n\t\tentries[id] = story;\n\t}\n\n\treturn { ...storyIndex, entries };\n}\n"],"mappings":"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","names":["require","fs","path","picomatch","escapeBackslash","fs","path","path","path","fs","fs","path","picomatch","path","fs","picomatch","path","fs","picomatch"]}
|
|
1
|
+
{"version":3,"sources":["../src/config/index.ts","../src/config/defaults.ts","../src/reporter/cli-reporter.ts","../src/storage/local.ts","../src/storage/index.ts","../src/utils/logger.ts","../src/core/engine.ts","../src/playwright/config-generator.ts","../src/playwright/test-generator.ts","../src/resolver/index.ts","../src/utils/path.ts","../src/utils/process.ts","../src/core/storybook.ts"],"sourcesContent":["import { loadConfig as unconfigLoad } from 'unconfig';\nimport { DEFAULT_CONFIG } from './defaults.js';\nimport type { DeepPartial, StorywrightConfig } from './types.js';\n\nexport function defineConfig(\n\tconfig: DeepPartial<StorywrightConfig>,\n): DeepPartial<StorywrightConfig> {\n\treturn config;\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n\treturn value !== null && typeof value === 'object' && !Array.isArray(value);\n}\n\nfunction deepMerge(\n\ttarget: Record<string, unknown>,\n\tsource: Record<string, unknown>,\n): Record<string, unknown> {\n\tconst result: Record<string, unknown> = { ...target };\n\tfor (const key of Object.keys(source)) {\n\t\tconst sourceVal = source[key];\n\t\tconst targetVal = result[key];\n\t\tif (isPlainObject(sourceVal) && isPlainObject(targetVal)) {\n\t\t\tresult[key] = deepMerge(targetVal, sourceVal);\n\t\t} else if (sourceVal !== undefined) {\n\t\t\tresult[key] = sourceVal;\n\t\t}\n\t}\n\treturn result;\n}\n\nexport async function loadConfig(\n\tcwd: string = process.cwd(),\n\toverrides?: DeepPartial<StorywrightConfig>,\n): Promise<StorywrightConfig> {\n\tconst { config: userConfig } = await unconfigLoad<DeepPartial<StorywrightConfig>>({\n\t\tsources: [\n\t\t\t{\n\t\t\t\tfiles: 'storywright.config',\n\t\t\t\textensions: ['ts', 'js', 'mjs'],\n\t\t\t},\n\t\t],\n\t\tcwd,\n\t});\n\n\tlet merged = DEFAULT_CONFIG as unknown as Record<string, unknown>;\n\tif (userConfig) {\n\t\tmerged = deepMerge(merged, userConfig as Record<string, unknown>);\n\t}\n\tif (overrides) {\n\t\tmerged = deepMerge(merged, overrides as Record<string, unknown>);\n\t}\n\treturn merged as unknown as StorywrightConfig;\n}\n\nexport type { StorywrightConfig, DeepPartial } from './types.js';\n","import type { StorywrightConfig } from './types.js';\n\nexport const DEFAULT_CONFIG: StorywrightConfig = {\n\tstorybook: {\n\t\tstaticDir: 'storybook-static',\n\t\tbuildCommand: 'npx storybook build --stats-json',\n\t\turl: undefined,\n\t\tcompatibility: 'auto',\n\t},\n\n\tbrowsers: ['chromium'],\n\tbrowserOptions: {},\n\n\tscreenshot: {\n\t\tfullPage: true,\n\t\tanimations: 'disabled',\n\t\tthreshold: 0.02,\n\t\tmaxDiffPixelRatio: 0.02,\n\t\tfreezeTime: '2024-01-01T00:00:00',\n\t\ttimezone: 'UTC',\n\t\tlocale: 'en-US',\n\t\tseed: 1,\n\t},\n\n\tdiffDetection: {\n\t\tenabled: true,\n\t\twatchFiles: ['package.json', 'package-lock.json', '.storybook/**/*'],\n\t\tbaseBranch: 'main',\n\t},\n\n\tstorage: {\n\t\tprovider: 'local',\n\t\tlocal: {\n\t\t\tbaselineDir: '.storywright/baselines',\n\t\t},\n\t\ts3: {\n\t\t\tbucket: '',\n\t\t\tprefix: 'storywright/baselines',\n\t\t\tregion: 'ap-northeast-1',\n\t\t\tcompression: 'zstd',\n\t\t},\n\t},\n\n\treport: {\n\t\toutputDir: '.storywright/report',\n\t\ttitle: 'Storywright Report',\n\t},\n\n\tworkers: 'auto',\n\tretries: 0,\n\n\ttimeout: {\n\t\ttest: 30000,\n\t\tnavigation: 20000,\n\t\texpect: 10000,\n\t},\n\n\tinclude: ['**'],\n\texclude: [],\n\n\thooks: {},\n};\n","import type { TestSummary } from '../core/types.js';\n\nexport function formatSummary(summary: TestSummary, options?: { reportPath?: string }): string {\n\tconst durationSec = Math.round(summary.duration / 1000);\n\tconst minutes = Math.floor(durationSec / 60);\n\tconst seconds = durationSec % 60;\n\tconst durationStr = minutes > 0 ? `${minutes}m ${seconds}s` : `${seconds}s`;\n\n\tconst lines: string[] = [\n\t\t'',\n\t\t'Storywright Results',\n\t\t'\\u2550'.repeat(42),\n\t\t` Total: ${summary.total} Passed: ${summary.passed} Failed: ${summary.failed} Skipped: ${summary.skipped}`,\n\t\t` Duration: ${durationStr}`,\n\t\t` Browsers: ${summary.browsers.join(', ')}`,\n\t];\n\n\tconst newFailures = summary.failures.filter((f) => f.type === 'new');\n\tconst diffFailures = summary.failures.filter((f) => f.type !== 'new');\n\n\tif (newFailures.length > 0) {\n\t\tlines.push('');\n\t\tlines.push(' New (no baseline):');\n\t\tfor (const failure of newFailures) {\n\t\t\tlines.push(` \\u25cb ${failure.story}: ${failure.variant} (${failure.browser})`);\n\t\t}\n\t}\n\n\tif (diffFailures.length > 0) {\n\t\tlines.push('');\n\t\tlines.push(' Failed:');\n\t\tfor (const failure of diffFailures) {\n\t\t\tlines.push(` \\u2717 ${failure.story}: ${failure.variant} (${failure.browser})`);\n\t\t\tif (failure.diffRatio > 0) {\n\t\t\t\tconst pct = (failure.diffRatio * 100).toFixed(1);\n\t\t\t\tlines.push(` \\u2192 Diff: ${pct}% pixels changed`);\n\t\t\t}\n\t\t}\n\t}\n\n\tconst reportPath = options?.reportPath ?? '.storywright/report/index.html';\n\tlines.push('');\n\tlines.push(` Report: ${reportPath}`);\n\tlines.push('\\u2550'.repeat(42));\n\tlines.push('');\n\n\treturn lines.join('\\n');\n}\n","import { execFile } from 'node:child_process';\nimport fs from 'node:fs/promises';\nimport path from 'node:path';\nimport { promisify } from 'node:util';\nimport type { DownloadOptions, StorageAdapter, UploadOptions } from './types.js';\n\nconst execFileAsync = promisify(execFile);\n\nexport class LocalStorageAdapter implements StorageAdapter {\n\tconstructor(private readonly baselineDir: string) {}\n\n\tasync download(options: DownloadOptions): Promise<void> {\n\t\ttry {\n\t\t\tawait fs.access(this.baselineDir);\n\t\t} catch {\n\t\t\treturn;\n\t\t}\n\t\tawait fs.cp(this.baselineDir, options.destDir, { recursive: true });\n\t}\n\n\tasync upload(options: UploadOptions): Promise<void> {\n\t\tconst resolvedSource = path.resolve(options.sourceDir);\n\t\tconst resolvedDest = path.resolve(this.baselineDir);\n\t\tif (resolvedSource === resolvedDest) {\n\t\t\treturn;\n\t\t}\n\t\tawait fs.mkdir(this.baselineDir, { recursive: true });\n\t\tawait fs.cp(options.sourceDir, this.baselineDir, { recursive: true });\n\t}\n\n\tasync exists(_branch: string): Promise<boolean> {\n\t\ttry {\n\t\t\tawait fs.access(this.baselineDir);\n\t\t\tconst entries = await fs.readdir(this.baselineDir);\n\t\t\treturn entries.length > 0;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t/**\n\t * Extract baselines from a git branch using `git ls-tree` + `git show`.\n\t * Binary-safe (PNG files) via `encoding: 'buffer'`.\n\t */\n\tasync downloadFromGit(branch: string, destDir: string, cwd: string): Promise<void> {\n\t\tconst gitPath = this.baselineDir.split(path.sep).join('/');\n\n\t\tlet lsOutput: string;\n\t\ttry {\n\t\t\tconst result = await execFileAsync(\n\t\t\t\t'git',\n\t\t\t\t['ls-tree', '-r', '--name-only', branch, '--', gitPath],\n\t\t\t\t{ cwd },\n\t\t\t);\n\t\t\tlsOutput = result.stdout;\n\t\t} catch (error) {\n\t\t\tthrow new Error(\n\t\t\t\t`Failed to list baselines from git branch '${branch}': ${error instanceof Error ? error.message : error}`,\n\t\t\t);\n\t\t}\n\n\t\tconst files = lsOutput.trim().split('\\n').filter(Boolean);\n\t\tif (files.length === 0) {\n\t\t\treturn;\n\t\t}\n\n\t\tawait fs.mkdir(destDir, { recursive: true });\n\n\t\tconst posixBaselineDir = this.baselineDir.split(path.sep).join('/').replace(/\\/+$/, '');\n\n\t\tfor (const file of files) {\n\t\t\tlet content: Buffer;\n\t\t\ttry {\n\t\t\t\tconst result = await execFileAsync('git', ['show', `${branch}:${file}`], {\n\t\t\t\t\tcwd,\n\t\t\t\t\tencoding: 'buffer' as unknown as BufferEncoding,\n\t\t\t\t\tmaxBuffer: 50 * 1024 * 1024,\n\t\t\t\t});\n\t\t\t\tcontent = result.stdout as unknown as Buffer;\n\t\t\t} catch (error) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Failed to extract '${file}' from git branch '${branch}': ${error instanceof Error ? error.message : error}`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst relativePath = file.slice(posixBaselineDir.length + 1);\n\t\t\tconst destPath = path.join(destDir, ...relativePath.split('/'));\n\t\t\tawait fs.mkdir(path.dirname(destPath), { recursive: true });\n\t\t\tawait fs.writeFile(destPath, content);\n\t\t}\n\t}\n}\n","import { createRequire } from 'node:module';\nimport type { StorageConfig } from '../config/types.js';\nimport { LocalStorageAdapter } from './local.js';\nimport type { StorageAdapter } from './types.js';\n\nexport function createStorageAdapter(config: StorageConfig): StorageAdapter {\n\tswitch (config.provider) {\n\t\tcase 'local':\n\t\t\treturn new LocalStorageAdapter(config.local.baselineDir);\n\t\tcase 's3':\n\t\t\treturn loadS3Adapter(config);\n\t\tdefault:\n\t\t\tthrow new Error(`Unknown storage provider: ${config.provider}`);\n\t}\n}\n\nfunction loadS3Adapter(config: StorageConfig): StorageAdapter {\n\ttry {\n\t\tconst require = createRequire(import.meta.url);\n\t\tconst { S3StorageAdapter } = require('@storywright/storage-s3') as {\n\t\t\tS3StorageAdapter: new (cfg: {\n\t\t\t\tbucket: string;\n\t\t\t\tprefix: string;\n\t\t\t\tregion: string;\n\t\t\t\tcompression?: string;\n\t\t\t}) => StorageAdapter;\n\t\t};\n\t\treturn new S3StorageAdapter(config.s3);\n\t} catch {\n\t\tthrow new Error(\n\t\t\t'S3 storage adapter requires the @storywright/storage-s3 package.\\nInstall it with: pnpm add @storywright/storage-s3',\n\t\t);\n\t}\n}\n\nexport type { StorageAdapter, DownloadOptions, UploadOptions } from './types.js';\n","import { createConsola } from 'consola';\n\nconst isCI = !!(\n\tprocess.env.CI ||\n\tprocess.env.GITHUB_ACTIONS ||\n\tprocess.env.CIRCLECI ||\n\tprocess.env.GITLAB_CI\n);\n\nexport const logger = createConsola({\n\tlevel: process.env.STORYWRIGHT_DEBUG ? 5 : 3,\n});\n\nexport { isCI };\n","import fs from 'node:fs/promises';\nimport path from 'node:path';\nimport picomatch from 'picomatch';\nimport type { StorywrightConfig } from '../config/types.js';\nimport { generatePlaywrightConfig } from '../playwright/config-generator.js';\nimport { generateTestFile } from '../playwright/test-generator.js';\nimport { resolveAffectedStories } from '../resolver/index.js';\nimport { createStorageAdapter } from '../storage/index.js';\nimport { logger } from '../utils/logger.js';\nimport { resolveOutputDir } from '../utils/path.js';\nimport { exec } from '../utils/process.js';\nimport { buildStorybook, discoverStories, filterStories } from './storybook.js';\nimport type { Story, StoryIndex, TestSummary } from './types.js';\n\nexport interface TestOptions {\n\tdiffOnly?: boolean;\n\tshard?: string;\n\tupdateSnapshots?: boolean;\n\tfilter?: string;\n\toutputDir?: string;\n\treporters?: string[];\n}\n\nexport interface TestRunResult {\n\texitCode: number;\n\tsummary?: TestSummary;\n\treportDir?: string;\n\tsnapshotDir?: string;\n}\n\nconst STORIES_PER_FILE = 50;\n\nfunction resolveReporterPath(): string {\n\t// Resolve relative to this file's dist location\n\tconst thisDir = new URL('.', import.meta.url).pathname;\n\treturn path.resolve(thisDir, 'playwright', 'reporter.js');\n}\n\nfunction chunkStories(entries: Record<string, Story>): Record<string, Story>[] {\n\tconst keys = Object.keys(entries);\n\tif (keys.length === 0) return [{}];\n\tconst chunks: Record<string, Story>[] = [];\n\tfor (let i = 0; i < keys.length; i += STORIES_PER_FILE) {\n\t\tconst chunk: Record<string, Story> = {};\n\t\tfor (const key of keys.slice(i, i + STORIES_PER_FILE)) {\n\t\t\tchunk[key] = entries[key];\n\t\t}\n\t\tchunks.push(chunk);\n\t}\n\treturn chunks;\n}\n\nexport async function runTests(\n\tconfig: StorywrightConfig,\n\toptions: TestOptions = {},\n\tcwd: string = process.cwd(),\n): Promise<TestRunResult> {\n\tconst outputRoot = options.outputDir\n\t\t? path.resolve(cwd, options.outputDir)\n\t\t: resolveOutputDir(cwd, '.storywright');\n\tconst tmpDir = path.join(outputRoot, 'tmp');\n\tconst reportDir = options.outputDir\n\t\t? path.join(outputRoot, 'report')\n\t\t: path.resolve(cwd, config.report.outputDir);\n\tconst storybookDir = path.resolve(cwd, config.storybook.staticDir);\n\tconst snapshotDir = path.join(tmpDir, 'snapshots');\n\n\t// Prepare directories early for parallel operations\n\tawait fs.mkdir(snapshotDir, { recursive: true });\n\n\t// Start baseline download in parallel with Storybook build\n\tconst storage = createStorageAdapter(config.storage);\n\tconst baselinePromise = storage\n\t\t.download({ branch: 'current', destDir: snapshotDir })\n\t\t.catch(() => {\n\t\t\tlogger.info('No existing baselines found');\n\t\t});\n\n\t// 1. Build Storybook if needed\n\tawait buildStorybook(config, cwd);\n\n\t// 2. Discover & filter stories\n\tlogger.start('Discovering stories...');\n\tconst allStories = await discoverStories(config, cwd);\n\tlet targetStories = filterStories(allStories, config);\n\n\t// Apply --filter option\n\tif (options.filter) {\n\t\ttargetStories = applyFilter(targetStories, options.filter);\n\t}\n\n\tlogger.info(`${Object.keys(targetStories.entries).length} stories found`);\n\n\t// 3. Diff-only: resolve affected stories (default in CI)\n\tconst effectiveDiffOnly = options.diffOnly ?? !!process.env.CI;\n\tif (effectiveDiffOnly && config.diffDetection.enabled) {\n\t\tlogger.start('Resolving dependencies...');\n\t\tconst diffResult = await resolveAffectedStories(\n\t\t\ttargetStories,\n\t\t\tconfig.diffDetection,\n\t\t\tstorybookDir,\n\t\t\tcwd,\n\t\t);\n\t\tif (!diffResult.allStories) {\n\t\t\ttargetStories = diffResult.targetStories;\n\t\t}\n\t\tlogger.info(`${Object.keys(targetStories.entries).length} stories affected by changes`);\n\t}\n\n\t// 4. Wait for baseline download to complete\n\tawait baselinePromise;\n\n\t// 5. Generate split test files for better worker distribution\n\tconst chunks = chunkStories(targetStories.entries);\n\tconst testFilePattern = chunks.length === 1 ? 'storywright-0.spec.ts' : 'storywright-*.spec.ts';\n\n\tfor (let i = 0; i < chunks.length; i++) {\n\t\tconst chunkIndex: StoryIndex = { ...targetStories, entries: chunks[i] };\n\t\tconst chunkPath = path.join(tmpDir, `target-stories-${i}.json`);\n\t\tawait fs.writeFile(chunkPath, JSON.stringify(chunkIndex));\n\n\t\tconst testContent = generateTestFile(config.screenshot, {\n\t\t\ttargetStoriesPath: chunkPath.replace(/\\\\/g, '/'),\n\t\t});\n\t\tawait fs.writeFile(path.join(tmpDir, `storywright-${i}.spec.ts`), testContent);\n\t}\n\n\tlogger.info(`${chunks.length} test file(s) generated`);\n\n\t// 6. Generate Playwright config\n\tconst reporterWrapperPath = path.join(tmpDir, 'reporter.mjs');\n\tconst resolvedReporterPath = resolveReporterPath().replace(/\\\\/g, '/');\n\tconst reporterOutputDir = reportDir.replace(/\\\\/g, '/');\n\n\tawait fs.writeFile(\n\t\treporterWrapperPath,\n\t\t`import StorywrightReporter from '${resolvedReporterPath}';\\nexport default class extends StorywrightReporter {\\n constructor() { super({ outputDir: '${reporterOutputDir}' }); }\\n}\\n`,\n\t);\n\n\t// Determine Storybook URL\n\tlet actualStorybookUrl = config.storybook.url;\n\tconst needsServer = !actualStorybookUrl;\n\n\tif (needsServer) {\n\t\tactualStorybookUrl = 'http://localhost:6007';\n\t}\n\n\tconst playwrightConfig = generatePlaywrightConfig(config, {\n\t\ttmpDir: tmpDir.replace(/\\\\/g, '/'),\n\t\tstorybookUrl: actualStorybookUrl ?? 'http://localhost:6007',\n\t\tsnapshotDir: snapshotDir.replace(/\\\\/g, '/'),\n\t\treporterPath: reporterWrapperPath.replace(/\\\\/g, '/'),\n\t\ttestMatch: testFilePattern,\n\t\tshard: options.shard,\n\t\treporters: options.reporters,\n\t});\n\n\tconst configPath = path.join(tmpDir, 'playwright.config.ts');\n\tawait fs.writeFile(configPath, playwrightConfig);\n\n\t// 7. Run Playwright tests\n\tlogger.start('Running tests...');\n\tconst args = ['playwright', 'test', '--config', configPath];\n\n\tif (options.updateSnapshots) {\n\t\targs.push('--update-snapshots');\n\t}\n\n\t// Start static server if needed\n\tlet serverProc: { kill: () => void } | undefined;\n\tif (needsServer) {\n\t\tserverProc = await startStaticServer(storybookDir, 6007);\n\t}\n\n\ttry {\n\t\tconst result = await exec('npx', args, { cwd, inherit: true });\n\n\t\t// 8. Read results\n\t\tlet summary: TestSummary | undefined;\n\t\ttry {\n\t\t\tconst summaryPath = path.join(reportDir, 'summary.json');\n\t\t\tconst summaryContent = await fs.readFile(summaryPath, 'utf-8');\n\t\t\tsummary = JSON.parse(summaryContent);\n\t\t} catch {\n\t\t\t// summary may not exist if no tests ran\n\t\t}\n\n\t\t// 9. Map exit codes per SPEC §14.2\n\t\tconst exitCode = mapExitCode(result.exitCode, summary);\n\n\t\treturn { exitCode, summary, reportDir, snapshotDir };\n\t} finally {\n\t\tserverProc?.kill();\n\t}\n}\n\nexport async function updateBaselines(\n\tconfig: StorywrightConfig,\n\toptions: { all?: boolean; upload?: boolean } = {},\n\tcwd: string = process.cwd(),\n): Promise<void> {\n\tconst result = await runTests(config, { updateSnapshots: true, diffOnly: !options.all }, cwd);\n\n\tif (result.exitCode !== 0) {\n\t\tlogger.warn('Some tests failed during baseline update');\n\t}\n\n\t// Save updated snapshots back to baselineDir (local disk operation)\n\tif (result.snapshotDir) {\n\t\tconst baselineDir = path.resolve(cwd, config.storage.local.baselineDir);\n\t\tawait fs.mkdir(baselineDir, { recursive: true });\n\t\tawait fs.cp(result.snapshotDir, baselineDir, { recursive: true });\n\t\tlogger.success(`Baselines saved to ${config.storage.local.baselineDir}`);\n\t}\n\n\t// --upload: upload to remote storage (S3 etc.) only when explicitly requested\n\tif (options.upload) {\n\t\tconst storage = createStorageAdapter(config.storage);\n\t\tconst baselineDir = path.resolve(cwd, config.storage.local.baselineDir);\n\t\tawait storage.upload({\n\t\t\tbranch: 'current',\n\t\t\tsourceDir: baselineDir,\n\t\t});\n\t\tlogger.success('Baselines uploaded to remote storage');\n\t}\n}\n\nfunction applyFilter(storyIndex: StoryIndex, filter: string): StoryIndex {\n\tconst matcher = picomatch(filter);\n\tconst entries: Record<string, StoryIndex['entries'][string]> = {};\n\tfor (const [id, story] of Object.entries(storyIndex.entries)) {\n\t\tconst fullName = `${story.title}/${story.name}`;\n\t\tif (matcher(fullName) || matcher(story.title) || matcher(story.id)) {\n\t\t\tentries[id] = story;\n\t\t}\n\t}\n\treturn { ...storyIndex, entries };\n}\n\nfunction mapExitCode(playwrightCode: number, summary?: TestSummary): number {\n\t// SPEC §14.2: 0 = success (no diff), 1 = success (diff found), 2 = execution error, 130 = SIGINT\n\tif (playwrightCode === 130 || playwrightCode === 143) {\n\t\treturn 130; // SIGINT / SIGTERM\n\t}\n\tif (summary) {\n\t\tif (summary.failed > 0) return 1;\n\t\tif (summary.total === 0 && playwrightCode !== 0) return 2;\n\t\treturn 0;\n\t}\n\t// No summary = likely execution error\n\treturn playwrightCode === 0 ? 0 : 2;\n}\n\nasync function startStaticServer(dir: string, port: number): Promise<{ kill: () => void }> {\n\tconst { createServer } = await import('node:http');\n\tconst sirv = (await import('sirv')).default;\n\n\tconst handler = sirv(dir, { single: false, dev: false });\n\tconst server = createServer(handler);\n\n\tawait new Promise<void>((resolve, reject) => {\n\t\tserver.on('error', reject);\n\t\tserver.listen(port, () => resolve());\n\t});\n\n\treturn { kill: () => server.close() };\n}\n","import type { StorywrightConfig } from '../config/types.js';\n\nexport function generatePlaywrightConfig(\n\tconfig: StorywrightConfig,\n\toptions: {\n\t\ttmpDir: string;\n\t\tstorybookUrl: string;\n\t\tsnapshotDir: string;\n\t\treporterPath: string;\n\t\ttestMatch: string;\n\t\tshard?: string;\n\t\treporters?: string[];\n\t},\n): string {\n\tconst projects = config.browsers.map((browser) => {\n\t\tconst browserOptions = config.browserOptions[browser];\n\t\tconst useObj = browserOptions\n\t\t\t? JSON.stringify(browserOptions, null, '\\t\\t')\n\t\t\t: getDefaultBrowserUse(browser);\n\t\treturn `\\t\\t{\n\\t\\t\\tname: '${browser}',\n\\t\\t\\tuse: ${useObj},\n\\t\\t}`;\n\t});\n\n\tconst workers = config.workers === 'auto' ? \"'100%'\" : String(config.workers);\n\n\tconst shard = options.shard\n\t\t? `\\tshard: { current: ${options.shard.split('/')[0]}, total: ${options.shard.split('/')[1]} },`\n\t\t: '';\n\n\t// Build reporter list: always include custom reporter, plus user-requested ones\n\tconst reporterEntries: string[] = [];\n\tconst requestedReporters = options.reporters ?? ['default', 'html'];\n\tfor (const r of requestedReporters) {\n\t\tif (r === 'default' || r === 'list') {\n\t\t\treporterEntries.push(\"\\t\\t['list']\");\n\t\t} else if (r !== 'html') {\n\t\t\t// Pass through other built-in Playwright reporters (dot, json, junit, etc.)\n\t\t\treporterEntries.push(`\\t\\t['${r}']`);\n\t\t}\n\t}\n\t// Always include custom storywright reporter\n\treporterEntries.push(`\\t\\t['${escapeBackslash(options.reporterPath)}']`);\n\n\treturn `import { defineConfig } from '@playwright/test';\n\nexport default defineConfig({\n\\ttestDir: '${escapeBackslash(options.tmpDir)}',\n\\ttestMatch: '${escapeBackslash(options.testMatch)}',\n\\tsnapshotDir: '${escapeBackslash(options.snapshotDir)}',\n\\tsnapshotPathTemplate: '{snapshotDir}/{arg}-{projectName}{ext}',\n\\ttimeout: ${config.timeout.test},\n\\texpect: {\n\\t\\ttoHaveScreenshot: {\n\\t\\t\\tmaxDiffPixelRatio: ${config.screenshot.maxDiffPixelRatio},\n\\t\\t\\tthreshold: ${config.screenshot.threshold},\n\\t\\t},\n\\t\\ttimeout: ${config.timeout.expect},\n\\t},\n\\tfullyParallel: true,\n\\tforbidOnly: !!process.env.CI,\n\\tretries: ${config.retries},\n\\tworkers: ${workers},\n${shard}\n\\treporter: [\n${reporterEntries.join(',\\n')}\n\\t],\n\\tuse: {\n\\t\\tbaseURL: '${options.storybookUrl}',\n\\t\\tnavigationTimeout: ${config.timeout.navigation},\n\\t\\ttimezoneId: '${config.screenshot.timezone}',\n\\t\\tlocale: '${config.screenshot.locale}',\n\\t},\n\\tprojects: [\n${projects.join(',\\n')}\n\\t],\n});\n`;\n}\n\nfunction getDefaultBrowserUse(browser: string): string {\n\tswitch (browser) {\n\t\tcase 'chromium':\n\t\t\treturn \"{ browserName: 'chromium' }\";\n\t\tcase 'firefox':\n\t\t\treturn \"{ browserName: 'firefox' }\";\n\t\tcase 'webkit':\n\t\t\treturn \"{ browserName: 'webkit' }\";\n\t\tdefault:\n\t\t\treturn `{ browserName: '${browser}' }`;\n\t}\n}\n\nfunction escapeBackslash(str: string): string {\n\treturn str.replace(/\\\\/g, '/');\n}\n","import type { ScreenshotConfig } from '../config/types.js';\n\nexport function generateTestFile(\n\tconfig: ScreenshotConfig,\n\toptions: {\n\t\ttargetStoriesPath: string;\n\t},\n): string {\n\treturn `import { test, expect } from '@playwright/test';\nimport { readFileSync } from 'node:fs';\n\nconst targetList = JSON.parse(\n\\treadFileSync('${escapeBackslash(options.targetStoriesPath)}', 'utf-8'),\n);\n\ntest.describe.parallel('visual regression testing', () => {\n\\tif (Object.keys(targetList.entries).length === 0) {\n\\t\\ttest('no stories to test', () => {\n\\t\\t\\texpect(true).toBeTruthy();\n\\t\\t});\n\\t}\n\n\\tfor (const story of Object.values(targetList.entries)) {\n\\t\\ttest(\\`\\${story.title}: \\${story.name}\\`, async ({ page }) => {\n\\t\\t\\t// Freeze time for reproducibility\n\\t\\t\\tawait page.clock.install({ time: new Date('${config.freezeTime}') });\n\n\\t\\t\\t// Seed Math.random for reproducibility\n\\t\\t\\tawait page.addInitScript((seed) => {\n\\t\\t\\t\\tlet s = seed;\n\\t\\t\\t\\tMath.random = () => {\n\\t\\t\\t\\t\\ts = (s * 16807 + 0) % 2147483647;\n\\t\\t\\t\\t\\treturn (s - 1) / 2147483646;\n\\t\\t\\t\\t};\n\\t\\t\\t}, ${config.seed});\n\n\\t\\t\\tawait page.goto(\\`/iframe.html?id=\\${story.id}\\`, {\n\\t\\t\\t\\twaitUntil: 'domcontentloaded',\n\\t\\t\\t});\n\n\\t\\t\\t// Wait for story to render: content inside #storybook-root OR portal content on body\n\\t\\t\\tawait page.waitForFunction(() => {\n\\t\\t\\t\\tconst root = document.getElementById('storybook-root');\n\\t\\t\\t\\tif (!root) return false;\n\\t\\t\\t\\tif (root.childElementCount > 0) return true;\n\\t\\t\\t\\t// Portal: check for elements on body that aren't part of Storybook's skeleton\n\\t\\t\\t\\tfor (const el of document.body.children) {\n\\t\\t\\t\\t\\tif (el.tagName === 'SCRIPT' || el.id === 'storybook-root' || el.id === 'storybook-docs') continue;\n\\t\\t\\t\\t\\treturn true;\n\\t\\t\\t\\t}\n\\t\\t\\t\\treturn false;\n\\t\\t\\t}, { timeout: 10000 });\n\n\\t\\t\\t// Wait for web fonts to finish loading\n\\t\\t\\tawait page.waitForFunction(() => document.fonts.ready);\n\n\\t\\t\\t// Wait for all images to finish loading (lazy-loaded, dynamic src, etc.)\n\\t\\t\\tawait page.waitForFunction(() => {\n\\t\\t\\t\\tconst images = document.querySelectorAll('img');\n\\t\\t\\t\\treturn Array.from(images).every(img => img.complete);\n\\t\\t\\t});\n\n\\t\\t\\t// Allow async renders (portals, modals, lazy components) to settle\n\\t\\t\\tawait page.waitForFunction(() => new Promise(resolve => requestAnimationFrame(() => resolve(true))));\n\n\\t\\t\\tawait expect(page).toHaveScreenshot(\n\\t\\t\\t\\t[story.title, \\`\\${story.id}.png\\`],\n\\t\\t\\t\\t{\n\\t\\t\\t\\t\\tanimations: '${config.animations}',\n\\t\\t\\t\\t\\tfullPage: ${config.fullPage},\n\\t\\t\\t\\t\\tthreshold: ${config.threshold},\n\\t\\t\\t\\t\\tmaxDiffPixelRatio: ${config.maxDiffPixelRatio},\n\\t\\t\\t\\t},\n\\t\\t\\t);\n\\t\\t});\n\\t}\n});\n`;\n}\n\nfunction escapeBackslash(str: string): string {\n\treturn str.replace(/\\\\/g, '/');\n}\n","import fs from 'node:fs/promises';\nimport path from 'node:path';\nimport picomatch from 'picomatch';\nimport { simpleGit } from 'simple-git';\nimport type { DiffDetectionConfig } from '../config/types.js';\nimport type { StatsIndex, StatsModule, StoryIndex } from '../core/types.js';\nimport { logger } from '../utils/logger.js';\nimport { normalizePath, stripLeadingDotSlash } from '../utils/path.js';\n\nexport interface DependencyResolver {\n\tgetDependencies(filePath: string): string[];\n\tgetStoriesForFiles(pathList: string[]): StoryIndex;\n}\n\nconst STORY_FILE_PATTERNS = ['.stories.', '.mdx'];\n\nfunction isStoryFile(moduleName: string): boolean {\n\treturn STORY_FILE_PATTERNS.some((p) => moduleName.includes(p));\n}\n\nexport class StorybookStatsDependencyResolver implements DependencyResolver {\n\tprivate moduleMap: Record<string, StatsModule>;\n\n\tconstructor(\n\t\tprivate statsJson: StatsIndex,\n\t\tprivate storiesJson: StoryIndex,\n\t) {\n\t\tthis.moduleMap = {};\n\t\tfor (const mod of statsJson.modules) {\n\t\t\t// Key by normalized name (primary) and normalized id (fallback)\n\t\t\tconst normalizedName = normalizePath(mod.name);\n\t\t\tthis.moduleMap[normalizedName] = mod;\n\t\t\tconst normalizedId = normalizePath(mod.id);\n\t\t\tif (normalizedId !== normalizedName) {\n\t\t\t\tthis.moduleMap[normalizedId] ??= mod;\n\t\t\t}\n\t\t}\n\t}\n\n\tgetDependencies(filePath: string): string[] {\n\t\tconst normalizedPath = normalizePath(filePath);\n\t\tconst dependencies = this.collectDependencies(normalizedPath);\n\n\t\tif (this.moduleMap[normalizedPath]) {\n\t\t\tdependencies.add(normalizedPath);\n\t\t}\n\n\t\treturn [...dependencies];\n\t}\n\n\tgetStoriesForFiles(pathList: string[]): StoryIndex {\n\t\tconst result: StoryIndex = { v: this.storiesJson.v, entries: {} };\n\n\t\tfor (const filePath of pathList) {\n\t\t\t// Finding #2 + #3: lookup via normalized moduleMap (name primary, id fallback)\n\t\t\tconst normalizedPath = normalizePath(filePath);\n\t\t\tconst stats = this.moduleMap[normalizedPath];\n\t\t\tif (!stats) continue;\n\n\t\t\t// Finding #1: collect ALL story reasons, not just first\n\t\t\tconst storyReasons = stats.reasons.filter((r) => isStoryFile(r.moduleName));\n\t\t\tfor (const reason of storyReasons) {\n\t\t\t\tconst normalizedImportPath = normalizePath(reason.moduleName);\n\t\t\t\t// Collect ALL matching story entries per reason\n\t\t\t\tfor (const storyObj of Object.values(this.storiesJson.entries)) {\n\t\t\t\t\tif (storyObj.type !== 'story') continue;\n\t\t\t\t\tif (normalizePath(storyObj.importPath) === normalizedImportPath) {\n\t\t\t\t\t\tresult.entries[storyObj.id] = storyObj;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn result;\n\t}\n\n\tprivate collectDependencies(name: string, result = new Set<string>()): Set<string> {\n\t\tconst mod = this.moduleMap[normalizePath(name)];\n\t\tif (mod) {\n\t\t\tfor (const reason of mod.reasons) {\n\t\t\t\tif (!result.has(reason.moduleName)) {\n\t\t\t\t\tresult.add(reason.moduleName);\n\t\t\t\t\tthis.collectDependencies(reason.moduleName, result);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn result;\n\t}\n}\n\nexport interface DiffResult {\n\tallStories: boolean;\n\ttargetStories: StoryIndex;\n}\n\ninterface DiffFileEntry {\n\tfile: string;\n\tfrom?: string;\n}\n\nexport async function resolveAffectedStories(\n\tstoriesJson: StoryIndex,\n\tconfig: DiffDetectionConfig,\n\tstorybookStaticDir: string,\n\tcwd: string,\n): Promise<DiffResult> {\n\tconst git = simpleGit({ baseDir: cwd });\n\n\t// Get diff summary\n\tlet diffEntries: DiffFileEntry[];\n\ttry {\n\t\tconst mergeBase = await git.raw(['merge-base', config.baseBranch, 'HEAD']);\n\t\tconst diff = await git.diffSummary([mergeBase.trim(), 'HEAD']);\n\t\tdiffEntries = diff.files.map((f) => ({\n\t\t\tfile: f.file,\n\t\t\t// Handle renames: include both old and new paths\n\t\t\tfrom: 'from' in f ? (f as { from: string }).from : undefined,\n\t\t}));\n\t} catch {\n\t\tlogger.warn('Failed to resolve git diff, running all stories');\n\t\treturn { allStories: true, targetStories: storiesJson };\n\t}\n\n\tif (diffEntries.length === 0) {\n\t\tlogger.info('No changed files detected');\n\t\treturn { allStories: false, targetStories: { v: storiesJson.v, entries: {} } };\n\t}\n\n\t// Collect all affected paths (including rename sources)\n\tconst allPaths: string[] = [];\n\tfor (const entry of diffEntries) {\n\t\tallPaths.push(entry.file);\n\t\tif (entry.from) {\n\t\t\tallPaths.push(entry.from);\n\t\t}\n\t}\n\n\t// Check watchFiles\n\tfor (const file of allPaths) {\n\t\tfor (const pattern of config.watchFiles) {\n\t\t\tif (picomatch(pattern)(file)) {\n\t\t\t\tlogger.info(`Watch file changed: ${file}, running all stories`);\n\t\t\t\treturn { allStories: true, targetStories: storiesJson };\n\t\t\t}\n\t\t}\n\t}\n\n\t// Load stats json for dependency resolution\n\tconst statsPath = path.resolve(storybookStaticDir, 'preview-stats.json');\n\tlet statsJson: StatsIndex;\n\ttry {\n\t\tstatsJson = JSON.parse(await fs.readFile(statsPath, 'utf-8'));\n\t} catch {\n\t\tlogger.warn('preview-stats.json not found, running all stories');\n\t\treturn { allStories: true, targetStories: storiesJson };\n\t}\n\n\tconst resolver = new StorybookStatsDependencyResolver(statsJson, storiesJson);\n\tconst targetStories: StoryIndex = { v: storiesJson.v, entries: {} };\n\n\t// Direct story file matches (both .stories.* and .mdx)\n\tfor (const file of allPaths) {\n\t\tconst matchedStories = Object.values(storiesJson.entries).filter(\n\t\t\t(story) => stripLeadingDotSlash(story.importPath) === file,\n\t\t);\n\t\tfor (const story of matchedStories) {\n\t\t\ttargetStories.entries[story.id] = story;\n\t\t}\n\t}\n\n\t// Dependency-based matches\n\tfor (const file of allPaths) {\n\t\tconst deps = resolver.getDependencies(normalizePath(file));\n\t\tconst depStories = resolver.getStoriesForFiles(deps);\n\t\tfor (const [id, story] of Object.entries(depStories.entries)) {\n\t\t\ttargetStories.entries[id] = story;\n\t\t}\n\t}\n\n\tlogger.info(`Resolved ${Object.keys(targetStories.entries).length} affected stories`);\n\treturn { allStories: false, targetStories };\n}\n","import path from 'node:path';\n\nexport function normalizePath(filePath: string): string {\n\tconst normalized = filePath.replace(/\\\\/g, '/');\n\tif (normalized.startsWith('./')) {\n\t\treturn normalized;\n\t}\n\treturn `./${normalized}`;\n}\n\nexport function stripLeadingDotSlash(filePath: string): string {\n\treturn filePath.replace(/^\\.\\//, '');\n}\n\nexport function resolveOutputDir(outputDir: string, ...segments: string[]): string {\n\treturn path.resolve(outputDir, ...segments);\n}\n","import { spawn } from 'node:child_process';\n\nexport interface ExecResult {\n\texitCode: number;\n\tstdout: string;\n\tstderr: string;\n}\n\nexport function exec(\n\tcommand: string,\n\targs: string[],\n\toptions?: { cwd?: string; env?: Record<string, string>; inherit?: boolean },\n): Promise<ExecResult> {\n\treturn new Promise((resolve, reject) => {\n\t\tconst proc = spawn(command, args, {\n\t\t\tcwd: options?.cwd,\n\t\t\tenv: { ...process.env, ...options?.env },\n\t\t\tstdio: options?.inherit ? ['ignore', 'inherit', 'inherit'] : ['ignore', 'pipe', 'pipe'],\n\t\t\tshell: false,\n\t\t});\n\n\t\tlet stdout = '';\n\t\tlet stderr = '';\n\n\t\tif (!options?.inherit) {\n\t\t\tproc.stdout?.on('data', (data: Buffer) => {\n\t\t\t\tstdout += data.toString();\n\t\t\t});\n\n\t\t\tproc.stderr?.on('data', (data: Buffer) => {\n\t\t\t\tstderr += data.toString();\n\t\t\t});\n\t\t}\n\n\t\tproc.on('error', reject);\n\n\t\tproc.on('close', (code) => {\n\t\t\tresolve({ exitCode: code ?? 1, stdout, stderr });\n\t\t});\n\t});\n}\n","import fs from 'node:fs/promises';\nimport path from 'node:path';\nimport picomatch from 'picomatch';\nimport type { StorywrightConfig } from '../config/types.js';\nimport { logger } from '../utils/logger.js';\nimport { exec } from '../utils/process.js';\nimport type { Story, StoryIndex } from './types.js';\n\nexport async function buildStorybook(config: StorywrightConfig, cwd: string): Promise<void> {\n\tif (config.storybook.url) {\n\t\tlogger.info('Using running Storybook at', config.storybook.url);\n\t\treturn;\n\t}\n\n\tconst staticDir = path.resolve(cwd, config.storybook.staticDir);\n\ttry {\n\t\tawait fs.access(path.join(staticDir, 'index.json'));\n\t\tlogger.info('Storybook already built at', staticDir);\n\t\treturn;\n\t} catch {\n\t\t// need to build\n\t}\n\n\tlogger.start('Building Storybook...');\n\tconst [command, ...args] = config.storybook.buildCommand.split(' ');\n\tconst result = await exec(command, args, { cwd });\n\tif (result.exitCode !== 0) {\n\t\tthrow new Error(\n\t\t\t`Storybook build failed (exit code ${result.exitCode}):\\n${result.stderr}\\n\\nError code: SW_E_STORYBOOK_BUILD_FAILED`,\n\t\t);\n\t}\n\tlogger.success('Storybook built');\n}\n\nexport async function discoverStories(config: StorywrightConfig, cwd: string): Promise<StoryIndex> {\n\tconst staticDir = path.resolve(cwd, config.storybook.staticDir);\n\tconst indexPath = path.join(staticDir, 'index.json');\n\n\ttry {\n\t\tawait fs.access(indexPath);\n\t} catch {\n\t\tthrow new Error(\n\t\t\t`Storybook build directory not found at '${config.storybook.staticDir}/'\\n\\n Run one of the following:\\n $ npx storybook build --stats-json\\n $ npx storywright test --storybook-url http://localhost:6006\\n\\n Error code: SW_E_STORYBOOK_DIR_NOT_FOUND`,\n\t\t);\n\t}\n\n\tconst raw = JSON.parse(await fs.readFile(indexPath, 'utf-8'));\n\tconst indexJson = normalizeStoryIndex(raw, config.storybook.compatibility);\n\n\t// Version check\n\tif (indexJson.v < 4) {\n\t\tthrow new Error(\n\t\t\t'Storybook 7.x or earlier is not supported. Storywright requires Storybook 8 or later.\\n\\nError code: SW_E_STORYBOOK_UNSUPPORTED',\n\t\t);\n\t}\n\n\treturn indexJson;\n}\n\n/**\n * Parse Storybook index.json (v8+).\n */\nfunction normalizeStoryIndex(\n\traw: Record<string, unknown>,\n\t_compatibility: 'auto' | 'v8',\n): StoryIndex {\n\tconst version = typeof raw.v === 'number' ? raw.v : 0;\n\tconst entries = (raw.entries ?? {}) as Record<string, Story>;\n\n\treturn { v: version, entries };\n}\n\nexport function filterStories(storyIndex: StoryIndex, config: StorywrightConfig): StoryIndex {\n\tconst entries: Record<string, Story> = {};\n\tconst includeMatchers = config.include.map((p) => picomatch(p));\n\tconst excludeMatchers = config.exclude.map((p) => picomatch(p));\n\n\tfor (const [id, story] of Object.entries(storyIndex.entries)) {\n\t\t// Skip docs entries\n\t\tif (story.type === 'docs') continue;\n\t\tif (story.name === 'Docs') continue;\n\n\t\tconst fullName = `${story.title}/${story.name}`;\n\n\t\t// Check include patterns\n\t\tconst isIncluded = includeMatchers.some((m) => m(fullName));\n\t\tif (!isIncluded) continue;\n\n\t\t// Check exclude patterns\n\t\tconst isExcluded = excludeMatchers.some((m) => m(fullName));\n\t\tif (isExcluded) continue;\n\n\t\tentries[id] = story;\n\t}\n\n\treturn { ...storyIndex, entries };\n}\n"],"mappings":"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","names":["require","fs","path","picomatch","escapeBackslash","fs","path","path","path","fs","fs","path","picomatch","path","fs","picomatch","path","fs","picomatch"]}
|
package/dist/cli.js
CHANGED
|
@@ -6,10 +6,10 @@ import {
|
|
|
6
6
|
logger,
|
|
7
7
|
runTests,
|
|
8
8
|
updateBaselines
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-XVHJJ6NP.js";
|
|
10
10
|
import {
|
|
11
11
|
generateHtmlReport
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-HPK2VJXG.js";
|
|
13
13
|
|
|
14
14
|
// src/cli/index.ts
|
|
15
15
|
import { defineCommand as defineCommand7, runMain } from "citty";
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@storywright/cli",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "Zero-config visual regression testing powered by Storybook + Playwright",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"simple-git": "^3.27.0",
|
|
32
32
|
"unconfig": "^0.6.0",
|
|
33
33
|
"picomatch": "^4.0.2",
|
|
34
|
-
"@storywright/report": "0.1
|
|
34
|
+
"@storywright/report": "0.4.1"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"@playwright/test": "^1.50.0",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/playwright/reporter.ts"],"sourcesContent":["import fs from 'node:fs';\nimport { createRequire } from 'node:module';\nimport path from 'node:path';\nimport type {\n\tFullConfig,\n\tFullResult,\n\tTestResult as PwTestResult,\n\tReporter,\n\tSuite,\n\tTestCase,\n} from '@playwright/test/reporter';\nimport type { FailureEntry, TestSummary } from '../core/types.js';\n\ninterface StorywrightReporterOptions {\n\toutputDir?: string;\n}\n\nclass StorywrightReporter implements Reporter {\n\tprivate outputDir: string;\n\tprivate results: {\n\t\ttitle: string;\n\t\tproject: string;\n\t\tstatus: 'passed' | 'failed' | 'skipped';\n\t\tduration: number;\n\t\tattachments: { name: string; path?: string; contentType: string }[];\n\t}[] = [];\n\tprivate startTime = 0;\n\n\tconstructor(options: StorywrightReporterOptions = {}) {\n\t\tthis.outputDir = options.outputDir || path.resolve('.storywright', 'report');\n\t}\n\n\tonBegin(_config: FullConfig, _suite: Suite): void {\n\t\tthis.startTime = Date.now();\n\t}\n\n\tonTestEnd(test: TestCase, result: PwTestResult): void {\n\t\tconst status =\n\t\t\tresult.status === 'passed' ? 'passed' : result.status === 'skipped' ? 'skipped' : 'failed';\n\n\t\tthis.results.push({\n\t\t\ttitle: test.title,\n\t\t\tproject: test.parent?.project()?.name ?? 'unknown',\n\t\t\tstatus,\n\t\t\tduration: result.duration,\n\t\t\tattachments: result.attachments.map((a) => ({\n\t\t\t\tname: a.name,\n\t\t\t\tpath: a.path,\n\t\t\t\tcontentType: a.contentType,\n\t\t\t})),\n\t\t});\n\t}\n\n\tasync onEnd(_result: FullResult): Promise<void> {\n\t\tconst duration = Date.now() - this.startTime;\n\t\tconst passed = this.results.filter((r) => r.status === 'passed').length;\n\t\tconst failed = this.results.filter((r) => r.status === 'failed').length;\n\t\tconst skipped = this.results.filter((r) => r.status === 'skipped').length;\n\n\t\tconst browsers = [...new Set(this.results.map((r) => r.project))];\n\t\tconst failures: FailureEntry[] = [];\n\n\t\t// Collect failure images\n\t\tconst assetsDir = path.join(this.outputDir, 'assets');\n\t\tfor (const dir of ['expected', 'actual', 'diff']) {\n\t\t\tfs.mkdirSync(path.join(assetsDir, dir), { recursive: true });\n\t\t}\n\n\t\tfor (const testResult of this.results) {\n\t\t\tif (testResult.status !== 'failed') continue;\n\n\t\t\tconst titleParts = testResult.title.split(': ');\n\t\t\tconst storyTitle = titleParts[0] ?? testResult.title;\n\t\t\tconst variant = titleParts.slice(1).join(': ') || 'default';\n\t\t\tconst sanitizedName = testResult.title.replace(/[^a-zA-Z0-9-_]/g, '-').toLowerCase();\n\n\t\t\tconst imageAttachments = testResult.attachments.filter(\n\t\t\t\t(a) => a.path && a.contentType.startsWith('image/'),\n\t\t\t);\n\t\t\tconst hasDiff = imageAttachments.some((a) => a.name.includes('diff'));\n\n\t\t\tconst failure: FailureEntry = {\n\t\t\t\ttype: hasDiff ? 'diff' : 'new',\n\t\t\t\tstory: storyTitle,\n\t\t\t\tvariant,\n\t\t\t\tbrowser: testResult.project,\n\t\t\t\tdiffRatio: 0,\n\t\t\t\texpected: '',\n\t\t\t\tactual: '',\n\t\t\t\tdiff: '',\n\t\t\t};\n\n\t\t\tfor (const attachment of testResult.attachments) {\n\t\t\t\tif (!attachment.path) continue;\n\t\t\t\tconst ext = path.extname(attachment.path);\n\t\t\t\tconst destName = `${sanitizedName}-${testResult.project}${ext}`;\n\n\t\t\t\tif (attachment.name.includes('expected')) {\n\t\t\t\t\tconst dest = path.join(assetsDir, 'expected', destName);\n\t\t\t\t\tcopyFileIfExists(attachment.path, dest);\n\t\t\t\t\tfailure.expected = `assets/expected/${destName}`;\n\t\t\t\t} else if (attachment.name.includes('actual')) {\n\t\t\t\t\tconst dest = path.join(assetsDir, 'actual', destName);\n\t\t\t\t\tcopyFileIfExists(attachment.path, dest);\n\t\t\t\t\tfailure.actual = `assets/actual/${destName}`;\n\t\t\t\t} else if (attachment.name.includes('diff')) {\n\t\t\t\t\tconst dest = path.join(assetsDir, 'diff', destName);\n\t\t\t\t\tcopyFileIfExists(attachment.path, dest);\n\t\t\t\t\tfailure.diff = `assets/diff/${destName}`;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfailures.push(failure);\n\t\t}\n\n\t\tconst summary: TestSummary = {\n\t\t\ttotal: this.results.length,\n\t\t\tpassed,\n\t\t\tfailed,\n\t\t\tskipped,\n\t\t\tduration,\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tbrowsers,\n\t\t\tfailures,\n\t\t};\n\n\t\tfs.mkdirSync(this.outputDir, { recursive: true });\n\t\tfs.writeFileSync(path.join(this.outputDir, 'summary.json'), JSON.stringify(summary, null, 2));\n\n\t\t// Generate HTML report\n\t\tconst html = generateHtmlReport(summary);\n\t\tfs.writeFileSync(path.join(this.outputDir, 'index.html'), html);\n\t}\n}\n\nfunction copyFileIfExists(src: string, dest: string): void {\n\ttry {\n\t\tfs.copyFileSync(src, dest);\n\t} catch {\n\t\t// source may not exist\n\t}\n}\n\nexport function generateHtmlReport(summary: TestSummary): string {\n\tconst require = createRequire(import.meta.url);\n\tconst bundlePath = require.resolve('@storywright/report');\n\tconst bundleJs = fs.readFileSync(bundlePath, 'utf-8');\n\n\t// Load CSS if it exists as a separate file\n\tconst bundleDir = path.dirname(bundlePath);\n\tconst assetsDir = path.join(bundleDir, 'assets');\n\tlet cssContent = '';\n\tif (fs.existsSync(assetsDir)) {\n\t\tconst cssFiles = fs.readdirSync(assetsDir).filter((f) => f.endsWith('.css'));\n\t\tfor (const cssFile of cssFiles) {\n\t\t\tcssContent += fs.readFileSync(path.join(assetsDir, cssFile), 'utf-8');\n\t\t}\n\t}\n\n\treturn `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n<title>Storywright Report</title>\n${cssContent ? `<style>${cssContent}</style>` : ''}\n</head>\n<body>\n<div id=\"app\"></div>\n<script>window.__STORYWRIGHT_SUMMARY__ = ${JSON.stringify(summary).replace(/</g, '\\\\u003c')};</script>\n<script>${bundleJs}</script>\n</body>\n</html>`;\n}\n\nexport default StorywrightReporter;\n"],"mappings":"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","names":["require"]}
|