playwright-checkpoint 0.3.0 → 0.4.0
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/README.md +57 -0
- package/dist/{chunk-KG37WSYS.js → chunk-M3BRR3LT.js} +9 -3
- package/dist/{chunk-KG37WSYS.js.map → chunk-M3BRR3LT.js.map} +1 -1
- package/dist/{chunk-X5IPL32H.js → chunk-WXZOP7XI.js} +153 -35
- package/dist/chunk-WXZOP7XI.js.map +1 -0
- package/dist/{chunk-K5DX32TO.js → chunk-YUFXGGZM.js} +2 -2
- package/dist/cli/bin.cjs +2501 -2386
- package/dist/cli/bin.cjs.map +1 -1
- package/dist/cli/bin.js +3 -2
- package/dist/cli/bin.js.map +1 -1
- package/dist/cli/index.cjs +1405 -68
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.d.cts +2 -2
- package/dist/cli/index.d.ts +2 -2
- package/dist/cli/index.js +3 -2
- package/dist/{core-CD4jHGgI.d.cts → core-6gyzs35M.d.ts} +2 -1
- package/dist/{core-CZvnc0rE.d.ts → core-Dd3WLuTs.d.cts} +2 -1
- package/dist/core.cjs +8 -2
- package/dist/core.cjs.map +1 -1
- package/dist/core.d.cts +2 -2
- package/dist/core.d.ts +2 -2
- package/dist/core.js +1 -1
- package/dist/{index-BjYQX_hK.d.ts → index-CvcgBzvl.d.ts} +1 -1
- package/dist/{index-Cabk31qi.d.cts → index-OQx9qcVO.d.cts} +1 -1
- package/dist/index.cjs +212 -38
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +69 -15
- package/dist/index.js.map +1 -1
- package/dist/mcp/index.cjs +148 -34
- package/dist/mcp/index.cjs.map +1 -1
- package/dist/mcp/index.js +4 -4
- package/dist/teardown.cjs +1409 -72
- package/dist/teardown.cjs.map +1 -1
- package/dist/teardown.js +3 -2
- package/dist/teardown.js.map +1 -1
- package/dist/{types-G7w4n8kR.d.cts → types-wX4eB9mb.d.cts} +16 -1
- package/dist/{types-G7w4n8kR.d.ts → types-wX4eB9mb.d.ts} +16 -1
- package/package.json +2 -1
- package/dist/chunk-X5IPL32H.js.map +0 -1
- /package/dist/{chunk-K5DX32TO.js.map → chunk-YUFXGGZM.js.map} +0 -0
package/dist/index.d.cts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { T as TestCheckpointConfig, C as CheckpointConfig, e as CheckpointManifest, f as CheckpointOptions, g as CheckpointRecord, c as CheckpointCollector, a as ReportGenerator, R as RunRecord, B as BoundingBox } from './types-
|
|
2
|
-
export { A as AriaSnapshotCollectorData, i as
|
|
3
|
-
export { C as CaptureCheckpointOptions, a as CheckpointSession, b as CheckpointSessionMetadata, c as CheckpointSessionOptions, R as RunCollectorPipelineArgs, d as captureCheckpoint, e as checkpointSlug, f as collectPageTitle, g as createCheckpointSession, h as getBuiltinCollectors, r as registerBuiltinCollector, i as registerBuiltinCollectors, j as resolveCollectors, k as runCollectorPipeline, l as runCollectorSetup, m as runCollectorTeardown, s as sanitizeSegment, n as settlePage, w as warn } from './core-
|
|
1
|
+
import { T as TestCheckpointConfig, C as CheckpointConfig, e as CheckpointManifest, f as CheckpointOptions, g as CheckpointRecord, c as CheckpointCollector, a as ReportGenerator, R as RunRecord, B as BoundingBox } from './types-wX4eB9mb.cjs';
|
|
2
|
+
export { A as AriaSnapshotCollectorData, i as ArticleDefinition, j as ArticleMetadata, k as AxeCollectorData, l as CollectorArtifact, d as CollectorConfig, m as CollectorContext, n as CollectorOptions, o as CollectorResult, p as ConsoleErrorRecord, D as DomStatsCollectorData, F as FailedRequestRecord, q as FormFieldState, r as FormFieldValue, s as FormsCollectorData, H as HtmlCollectorData, N as NetworkTimingBreakdown, t as NetworkTimingCollectorData, u as NetworkTimingRecord, P as PageMetadata, b as ReportGenerationResults, v as ReportGeneratorContext, w as ReportGeneratorResult, x as ReporterConfig, h as ResolvedCollectorConfig, S as ScreenshotCollectorData, y as StorageCollectorData, z as StorageCookieState, E as StorageEntryState, W as WebVitalMetric, G as WebVitalRating, I as WebVitalsSnapshot } from './types-wX4eB9mb.cjs';
|
|
3
|
+
export { C as CaptureCheckpointOptions, a as CheckpointSession, b as CheckpointSessionMetadata, c as CheckpointSessionOptions, R as RunCollectorPipelineArgs, d as captureCheckpoint, e as checkpointSlug, f as collectPageTitle, g as createCheckpointSession, h as getBuiltinCollectors, r as registerBuiltinCollector, i as registerBuiltinCollectors, j as resolveCollectors, k as runCollectorPipeline, l as runCollectorSetup, m as runCollectorTeardown, s as sanitizeSegment, n as settlePage, w as warn } from './core-Dd3WLuTs.cjs';
|
|
4
4
|
import * as PlaywrightModule from '@playwright/test';
|
|
5
5
|
import { TestInfo, Page, TestType, PlaywrightTestArgs, PlaywrightTestOptions, PlaywrightWorkerArgs, PlaywrightWorkerOptions } from '@playwright/test';
|
|
6
|
-
export { d as dedupeRuns, l as loadRuns, a as registerBuiltinReporter, r as runReporters } from './index-
|
|
6
|
+
export { d as dedupeRuns, l as loadRuns, a as registerBuiltinReporter, r as runReporters } from './index-OQx9qcVO.cjs';
|
|
7
7
|
|
|
8
8
|
type DeviceSurface = 'desktop' | 'mobile';
|
|
9
9
|
type DeviceProfile = {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { T as TestCheckpointConfig, C as CheckpointConfig, e as CheckpointManifest, f as CheckpointOptions, g as CheckpointRecord, c as CheckpointCollector, a as ReportGenerator, R as RunRecord, B as BoundingBox } from './types-
|
|
2
|
-
export { A as AriaSnapshotCollectorData, i as
|
|
3
|
-
export { C as CaptureCheckpointOptions, a as CheckpointSession, b as CheckpointSessionMetadata, c as CheckpointSessionOptions, R as RunCollectorPipelineArgs, d as captureCheckpoint, e as checkpointSlug, f as collectPageTitle, g as createCheckpointSession, h as getBuiltinCollectors, r as registerBuiltinCollector, i as registerBuiltinCollectors, j as resolveCollectors, k as runCollectorPipeline, l as runCollectorSetup, m as runCollectorTeardown, s as sanitizeSegment, n as settlePage, w as warn } from './core-
|
|
1
|
+
import { T as TestCheckpointConfig, C as CheckpointConfig, e as CheckpointManifest, f as CheckpointOptions, g as CheckpointRecord, c as CheckpointCollector, a as ReportGenerator, R as RunRecord, B as BoundingBox } from './types-wX4eB9mb.js';
|
|
2
|
+
export { A as AriaSnapshotCollectorData, i as ArticleDefinition, j as ArticleMetadata, k as AxeCollectorData, l as CollectorArtifact, d as CollectorConfig, m as CollectorContext, n as CollectorOptions, o as CollectorResult, p as ConsoleErrorRecord, D as DomStatsCollectorData, F as FailedRequestRecord, q as FormFieldState, r as FormFieldValue, s as FormsCollectorData, H as HtmlCollectorData, N as NetworkTimingBreakdown, t as NetworkTimingCollectorData, u as NetworkTimingRecord, P as PageMetadata, b as ReportGenerationResults, v as ReportGeneratorContext, w as ReportGeneratorResult, x as ReporterConfig, h as ResolvedCollectorConfig, S as ScreenshotCollectorData, y as StorageCollectorData, z as StorageCookieState, E as StorageEntryState, W as WebVitalMetric, G as WebVitalRating, I as WebVitalsSnapshot } from './types-wX4eB9mb.js';
|
|
3
|
+
export { C as CaptureCheckpointOptions, a as CheckpointSession, b as CheckpointSessionMetadata, c as CheckpointSessionOptions, R as RunCollectorPipelineArgs, d as captureCheckpoint, e as checkpointSlug, f as collectPageTitle, g as createCheckpointSession, h as getBuiltinCollectors, r as registerBuiltinCollector, i as registerBuiltinCollectors, j as resolveCollectors, k as runCollectorPipeline, l as runCollectorSetup, m as runCollectorTeardown, s as sanitizeSegment, n as settlePage, w as warn } from './core-6gyzs35M.js';
|
|
4
4
|
import * as PlaywrightModule from '@playwright/test';
|
|
5
5
|
import { TestInfo, Page, TestType, PlaywrightTestArgs, PlaywrightTestOptions, PlaywrightWorkerArgs, PlaywrightWorkerOptions } from '@playwright/test';
|
|
6
|
-
export { d as dedupeRuns, l as loadRuns, a as registerBuiltinReporter, r as runReporters } from './index-
|
|
6
|
+
export { d as dedupeRuns, l as loadRuns, a as registerBuiltinReporter, r as runReporters } from './index-CvcgBzvl.js';
|
|
7
7
|
|
|
8
8
|
type DeviceSurface = 'desktop' | 'mobile';
|
|
9
9
|
type DeviceProfile = {
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
import {
|
|
2
|
+
annotateScreenshot,
|
|
3
|
+
dedupeRuns,
|
|
4
|
+
groupByStory,
|
|
5
|
+
htmlReporter,
|
|
6
|
+
loadRuns,
|
|
7
|
+
markdownReporter,
|
|
8
|
+
mdxReporter,
|
|
9
|
+
orderedCheckpointNames,
|
|
10
|
+
registerBuiltinReporter,
|
|
11
|
+
runReporters
|
|
12
|
+
} from "./chunk-WXZOP7XI.js";
|
|
1
13
|
import {
|
|
2
14
|
ariaSnapshotCollector,
|
|
3
15
|
axeCollector,
|
|
@@ -27,19 +39,7 @@ import {
|
|
|
27
39
|
storageCollector,
|
|
28
40
|
warn,
|
|
29
41
|
webVitalsCollector
|
|
30
|
-
} from "./chunk-
|
|
31
|
-
import {
|
|
32
|
-
annotateScreenshot,
|
|
33
|
-
dedupeRuns,
|
|
34
|
-
groupByStory,
|
|
35
|
-
htmlReporter,
|
|
36
|
-
loadRuns,
|
|
37
|
-
markdownReporter,
|
|
38
|
-
mdxReporter,
|
|
39
|
-
orderedCheckpointNames,
|
|
40
|
-
registerBuiltinReporter,
|
|
41
|
-
runReporters
|
|
42
|
-
} from "./chunk-X5IPL32H.js";
|
|
42
|
+
} from "./chunk-M3BRR3LT.js";
|
|
43
43
|
import "./chunk-DGUM43GV.js";
|
|
44
44
|
|
|
45
45
|
// src/fixture.ts
|
|
@@ -91,11 +91,54 @@ function mergeCollectorOverrides(current, updates) {
|
|
|
91
91
|
}
|
|
92
92
|
function mergeTestConfig(current, update) {
|
|
93
93
|
const collectors = mergeCollectorOverrides(current?.collectors, update.collectors);
|
|
94
|
+
const article = mergeArticleMetadata(current?.article, update.article);
|
|
95
|
+
const articles = update.articles ? cloneArticleDefinitions(update.articles) : current?.articles ? cloneArticleDefinitions(current.articles) : void 0;
|
|
94
96
|
return {
|
|
95
97
|
description: update.description ?? current?.description,
|
|
98
|
+
...article ? { article } : {},
|
|
99
|
+
...articles ? { articles } : {},
|
|
96
100
|
...collectors ? { collectors } : {}
|
|
97
101
|
};
|
|
98
102
|
}
|
|
103
|
+
function mergeArticleMetadata(current, update) {
|
|
104
|
+
if (!current && !update) {
|
|
105
|
+
return void 0;
|
|
106
|
+
}
|
|
107
|
+
const merged = {
|
|
108
|
+
...current ?? {},
|
|
109
|
+
...update ?? {}
|
|
110
|
+
};
|
|
111
|
+
if (current?.frontmatter || update?.frontmatter) {
|
|
112
|
+
merged.frontmatter = {
|
|
113
|
+
...current?.frontmatter ?? {},
|
|
114
|
+
...update?.frontmatter ?? {}
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
return merged;
|
|
118
|
+
}
|
|
119
|
+
function cloneArticleMetadata(article) {
|
|
120
|
+
return {
|
|
121
|
+
...article,
|
|
122
|
+
...article.frontmatter ? { frontmatter: { ...article.frontmatter } } : {}
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
function cloneArticleDefinition(article) {
|
|
126
|
+
return {
|
|
127
|
+
...cloneArticleMetadata(article),
|
|
128
|
+
steps: [...article.steps]
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
function cloneArticleDefinitions(articles) {
|
|
132
|
+
return articles.map((article) => cloneArticleDefinition(article));
|
|
133
|
+
}
|
|
134
|
+
function syncManifestArticle(manifest, testConfig) {
|
|
135
|
+
if (testConfig?.article) {
|
|
136
|
+
manifest.article = cloneArticleMetadata(testConfig.article);
|
|
137
|
+
}
|
|
138
|
+
if (testConfig?.articles) {
|
|
139
|
+
manifest.articles = cloneArticleDefinitions(testConfig.articles);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
99
142
|
function manifestEnvironment() {
|
|
100
143
|
return process.env.PLAYWRIGHT_CHECKPOINT_ENV || process.env.NODE_ENV || "test";
|
|
101
144
|
}
|
|
@@ -172,6 +215,8 @@ function createCheckpoint(globalConfig = {}) {
|
|
|
172
215
|
const base = playwright.test;
|
|
173
216
|
const test2 = base.extend({
|
|
174
217
|
checkpointManifest: [
|
|
218
|
+
// Playwright fixture callbacks must use object destructuring for the first arg.
|
|
219
|
+
// eslint-disable-next-line no-empty-pattern
|
|
175
220
|
async ({}, use, testInfo) => {
|
|
176
221
|
const manifest = createCheckpointManifestRecord(testInfo);
|
|
177
222
|
try {
|
|
@@ -186,6 +231,8 @@ function createCheckpoint(globalConfig = {}) {
|
|
|
186
231
|
},
|
|
187
232
|
{ auto: true }
|
|
188
233
|
],
|
|
234
|
+
// Playwright fixture callbacks must use object destructuring for the first arg.
|
|
235
|
+
// eslint-disable-next-line no-empty-pattern
|
|
189
236
|
testCheckpointConfig: async ({}, use) => {
|
|
190
237
|
let current = null;
|
|
191
238
|
const controller = {
|
|
@@ -201,6 +248,8 @@ function createCheckpoint(globalConfig = {}) {
|
|
|
201
248
|
};
|
|
202
249
|
await use(controller);
|
|
203
250
|
},
|
|
251
|
+
// Playwright fixture callbacks must use object destructuring for the first arg.
|
|
252
|
+
// eslint-disable-next-line no-empty-pattern
|
|
204
253
|
deviceProfile: async ({}, use, testInfo) => {
|
|
205
254
|
await use(createDeviceProfile(testInfo));
|
|
206
255
|
},
|
|
@@ -209,15 +258,20 @@ function createCheckpoint(globalConfig = {}) {
|
|
|
209
258
|
outputDir: testInfo.outputPath("checkpoints"),
|
|
210
259
|
manifestPath: testInfo.outputPath("checkpoint-manifest.json"),
|
|
211
260
|
manifest: checkpointManifest,
|
|
212
|
-
collectors:
|
|
261
|
+
collectors: globalConfig.collectors,
|
|
262
|
+
testConfig: () => testCheckpointConfig.get(),
|
|
213
263
|
custom: globalConfig.custom,
|
|
214
264
|
redact: globalConfig.redact,
|
|
215
265
|
testInfo,
|
|
216
266
|
adjustTimeout: createAdjustTimeout(testInfo)
|
|
217
267
|
});
|
|
218
268
|
try {
|
|
219
|
-
await use((name, options = {}) =>
|
|
269
|
+
await use((name, options = {}) => {
|
|
270
|
+
syncManifestArticle(checkpointManifest, testCheckpointConfig.get());
|
|
271
|
+
return session.checkpoint(name, options);
|
|
272
|
+
});
|
|
220
273
|
} finally {
|
|
274
|
+
syncManifestArticle(checkpointManifest, testCheckpointConfig.get());
|
|
221
275
|
await session.finalize();
|
|
222
276
|
}
|
|
223
277
|
}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/fixture.ts","../src/device-profile.ts","../src/collectors/index.ts","../src/index.ts"],"sourcesContent":["import fs from 'node:fs/promises';\nimport { createRequire } from 'node:module';\nimport path from 'node:path';\nimport type * as PlaywrightModule from '@playwright/test';\nimport type {\n Page,\n PlaywrightTestArgs,\n PlaywrightTestOptions,\n PlaywrightWorkerArgs,\n PlaywrightWorkerOptions,\n TestInfo,\n TestType,\n} from '@playwright/test';\nimport {\n createCheckpointSession,\n registerBuiltinCollector,\n resolveCollectors,\n sanitizeSegment,\n settlePage,\n warn,\n} from './core';\nimport { createDeviceProfile, type DeviceProfile } from './device-profile';\nimport type {\n CheckpointConfig,\n CheckpointManifest,\n CheckpointOptions,\n CheckpointRecord,\n CollectorConfig,\n CollectorOptions,\n TestCheckpointConfig,\n} from './types';\n\ntype PlaywrightRuntime = typeof PlaywrightModule;\n\ntype TestCheckpointConfigController = {\n set(config: TestCheckpointConfig): void;\n get(): TestCheckpointConfig | null;\n};\n\ntype CheckpointFixtures = {\n checkpoint: (name: string, options?: CheckpointOptions) => Promise<CheckpointRecord>;\n checkpointManifest: CheckpointManifest;\n testCheckpointConfig: TestCheckpointConfigController;\n deviceProfile: DeviceProfile;\n};\n\nconst require = (() => {\n try {\n return Function('return require')() as NodeRequire;\n } catch {\n return createRequire(path.join(process.cwd(), 'playwright-checkpoint-runtime.cjs'));\n }\n})();\n\nfunction loadPlaywright(): PlaywrightRuntime {\n return require('@playwright/test') as PlaywrightRuntime;\n}\n\nfunction mergeCollectorOverrides(\n current: Partial<Record<string, boolean | CollectorOptions>> | undefined,\n updates: Partial<Record<string, boolean | CollectorOptions>> | undefined,\n): Partial<Record<string, boolean | CollectorOptions>> | undefined {\n if (!current && !updates) {\n return undefined;\n }\n\n const merged: Partial<Record<string, boolean | CollectorOptions>> = {\n ...(current ?? {}),\n };\n\n for (const [name, value] of Object.entries(updates ?? {})) {\n const previous = merged[name];\n\n if (value && typeof value === 'object' && !Array.isArray(value) && previous && typeof previous === 'object' && !Array.isArray(previous)) {\n merged[name] = {\n ...previous,\n ...value,\n };\n continue;\n }\n\n merged[name] = value;\n }\n\n return merged;\n}\n\nfunction mergeTestConfig(current: TestCheckpointConfig | null, update: TestCheckpointConfig): TestCheckpointConfig {\n const collectors = mergeCollectorOverrides(current?.collectors, update.collectors);\n\n return {\n description: update.description ?? current?.description,\n ...(collectors ? { collectors } : {}),\n };\n}\n\nfunction manifestEnvironment(): string {\n return process.env.PLAYWRIGHT_CHECKPOINT_ENV || process.env.NODE_ENV || 'test';\n}\n\nfunction explicitTestTags(testInfo: TestInfo): string[] {\n return (((testInfo as TestInfo & { tags?: string[] }).tags ?? []) as string[]).map((tag) => tag.toLowerCase());\n}\n\nexport function titleParts(testInfo: TestInfo): string[] {\n const maybeTitlePath = (testInfo as { titlePath?: unknown }).titlePath;\n return typeof maybeTitlePath === 'function' ? maybeTitlePath.call(testInfo) : [testInfo.title];\n}\n\nexport function collectTags(parts: string[]): Set<string> {\n const tags = new Set<string>();\n\n for (const part of parts) {\n for (const token of part.match(/@[a-z0-9-]+/gi) || []) {\n tags.add(token.toLowerCase());\n }\n }\n\n return tags;\n}\n\nexport function manifestTags(testInfo: TestInfo): string[] {\n return Array.from(new Set([...explicitTestTags(testInfo), ...collectTags(titleParts(testInfo))]));\n}\n\nexport function createCheckpointManifestRecord(testInfo: TestInfo): CheckpointManifest {\n return {\n environment: manifestEnvironment(),\n project: testInfo.project.name,\n testId: testInfo.testId,\n title: testInfo.title,\n tags: manifestTags(testInfo),\n startedAt: new Date().toISOString(),\n checkpoints: [],\n };\n}\n\nexport async function writeCheckpointManifest(testInfo: TestInfo, manifest: CheckpointManifest): Promise<string> {\n const manifestPath = testInfo.outputPath('checkpoint-manifest.json');\n await fs.mkdir(path.dirname(manifestPath), { recursive: true });\n await fs.writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}\\n`, 'utf8');\n return manifestPath;\n}\n\nfunction createAdjustTimeout(testInfo: TestInfo): (ms: number) => void {\n return (ms: number) => {\n if (ms > 0 && typeof testInfo.setTimeout === 'function') {\n testInfo.setTimeout(testInfo.timeout + ms);\n }\n };\n}\n\nfunction mergeConfig(\n globalConfig: CheckpointConfig = {},\n testConfig: TestCheckpointConfig | null,\n): Partial<Record<string, boolean | CollectorConfig>> | undefined {\n return mergeCollectorOverrides(\n globalConfig.collectors,\n testConfig?.collectors,\n ) as Partial<Record<string, boolean | CollectorConfig>> | undefined;\n}\n\nexport async function captureCheckpointRecord(args: {\n globalConfig?: CheckpointConfig;\n page: Page;\n testInfo: TestInfo;\n checkpointManifest: CheckpointManifest;\n testConfig?: TestCheckpointConfig | null;\n name: string;\n options?: CheckpointOptions;\n}): Promise<CheckpointRecord> {\n const globalConfig = args.globalConfig ?? {};\n const session = await createCheckpointSession(args.page, {\n outputDir: args.testInfo.outputPath('checkpoints'),\n manifestPath: args.testInfo.outputPath('checkpoint-manifest.json'),\n manifest: args.checkpointManifest,\n collectors: mergeConfig(globalConfig, args.testConfig ?? null),\n custom: globalConfig.custom,\n redact: globalConfig.redact,\n testInfo: args.testInfo,\n adjustTimeout: createAdjustTimeout(args.testInfo),\n });\n\n try {\n return await session.checkpoint(args.name, args.options);\n } finally {\n await session.finalize();\n }\n}\n\nexport function createCheckpoint(globalConfig: CheckpointConfig = {}): {\n test: TestType<PlaywrightTestArgs & PlaywrightTestOptions & CheckpointFixtures, PlaywrightWorkerArgs & PlaywrightWorkerOptions>;\n} {\n const playwright = loadPlaywright();\n const base = playwright.test as TestType<\n PlaywrightTestArgs & PlaywrightTestOptions,\n PlaywrightWorkerArgs & PlaywrightWorkerOptions\n >;\n\n const test = base.extend<CheckpointFixtures>({\n checkpointManifest: [\n async ({}, use, testInfo) => {\n const manifest = createCheckpointManifestRecord(testInfo);\n\n try {\n await use(manifest);\n } finally {\n try {\n await writeCheckpointManifest(testInfo, manifest);\n } catch (error) {\n warn(`Failed to write checkpoint manifest for test \"${testInfo.title}\".`, error);\n }\n }\n },\n { auto: true },\n ],\n\n testCheckpointConfig: async ({}, use) => {\n let current: TestCheckpointConfig | null = null;\n\n const controller: TestCheckpointConfigController = {\n set(config) {\n current = mergeTestConfig(current, config);\n },\n get() {\n return current\n ? {\n ...current,\n ...(current.collectors ? { collectors: mergeCollectorOverrides(undefined, current.collectors) } : {}),\n }\n : null;\n },\n };\n\n await use(controller);\n },\n\n deviceProfile: async ({}, use, testInfo) => {\n await use(createDeviceProfile(testInfo));\n },\n\n checkpoint: async ({ page, checkpointManifest, testCheckpointConfig }, use, testInfo) => {\n const session = await createCheckpointSession(page, {\n outputDir: testInfo.outputPath('checkpoints'),\n manifestPath: testInfo.outputPath('checkpoint-manifest.json'),\n manifest: checkpointManifest,\n collectors: mergeConfig(globalConfig, testCheckpointConfig.get()),\n custom: globalConfig.custom,\n redact: globalConfig.redact,\n testInfo,\n adjustTimeout: createAdjustTimeout(testInfo),\n });\n\n try {\n await use((name, options = {}) => session.checkpoint(name, options));\n } finally {\n await session.finalize();\n }\n },\n });\n\n return { test };\n}\n\nexport const expect = loadPlaywright().expect;\nexport const { test } = createCheckpoint();\nexport { createCheckpointSession, createDeviceProfile, registerBuiltinCollector, resolveCollectors, sanitizeSegment, settlePage, warn };\nexport type { DeviceProfile, TestCheckpointConfigController };\n","import type { TestInfo } from '@playwright/test';\n\nexport type DeviceSurface = 'desktop' | 'mobile';\n\nexport type DeviceProfile = {\n name: string;\n isMobile: boolean;\n surface: DeviceSurface;\n};\n\nexport function createDeviceProfile(testInfo: TestInfo): DeviceProfile {\n const use = testInfo.project.use as { isMobile?: boolean } | undefined;\n const isMobile = Boolean(use?.isMobile);\n\n return {\n name: testInfo.project.name,\n isMobile,\n surface: isMobile ? 'mobile' : 'desktop',\n };\n}\n","import { builtinCollectors } from './builtin-collectors';\nimport { registerBuiltinCollectors } from './registry';\n\nregisterBuiltinCollectors(builtinCollectors);\n\nexport { registerBuiltinCollector, registerBuiltinCollectors, getBuiltinCollectors } from './registry';\nexport { screenshotCollector } from './screenshot';\nexport { htmlCollector } from './html';\nexport { axeCollector, setAxeLoaderForTests } from './axe';\nexport { webVitalsCollector } from './web-vitals';\nexport { consoleCollector } from './console';\nexport { networkCollector } from './network';\nexport { metadataCollector } from './metadata';\nexport { ariaSnapshotCollector } from './aria-snapshot';\nexport { domStatsCollector } from './dom-stats';\nexport { formsCollector } from './forms';\nexport { storageCollector } from './storage';\nexport { networkTimingCollector } from './network-timing';\nexport type { CheckpointCollector } from '../types';\n","export type * from './types';\nexport * from './core';\nexport * from './fixture';\nexport { type CheckpointCollector } from './types';\nexport { type ReportGenerator } from './types';\nexport * from './collectors';\nexport * from './report';\n\nexport const VERSION = '0.1.0';\n"],"mappings":"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","names":["require","test"]}
|
|
1
|
+
{"version":3,"sources":["../src/fixture.ts","../src/device-profile.ts","../src/collectors/index.ts","../src/index.ts"],"sourcesContent":["import fs from 'node:fs/promises';\nimport { createRequire } from 'node:module';\nimport path from 'node:path';\nimport type * as PlaywrightModule from '@playwright/test';\nimport type {\n Page,\n PlaywrightTestArgs,\n PlaywrightTestOptions,\n PlaywrightWorkerArgs,\n PlaywrightWorkerOptions,\n TestInfo,\n TestType,\n} from '@playwright/test';\nimport {\n createCheckpointSession,\n registerBuiltinCollector,\n resolveCollectors,\n sanitizeSegment,\n settlePage,\n warn,\n} from './core';\nimport { createDeviceProfile, type DeviceProfile } from './device-profile';\nimport type {\n ArticleDefinition,\n ArticleMetadata,\n CheckpointConfig,\n CheckpointManifest,\n CheckpointOptions,\n CheckpointRecord,\n CollectorConfig,\n CollectorOptions,\n TestCheckpointConfig,\n} from './types';\n\ntype PlaywrightRuntime = typeof PlaywrightModule;\n\ntype TestCheckpointConfigController = {\n set(config: TestCheckpointConfig): void;\n get(): TestCheckpointConfig | null;\n};\n\ntype CheckpointFixtures = {\n checkpoint: (name: string, options?: CheckpointOptions) => Promise<CheckpointRecord>;\n checkpointManifest: CheckpointManifest;\n testCheckpointConfig: TestCheckpointConfigController;\n deviceProfile: DeviceProfile;\n};\n\nconst require = (() => {\n try {\n return Function('return require')() as NodeRequire;\n } catch {\n return createRequire(path.join(process.cwd(), 'playwright-checkpoint-runtime.cjs'));\n }\n})();\n\nfunction loadPlaywright(): PlaywrightRuntime {\n return require('@playwright/test') as PlaywrightRuntime;\n}\n\nfunction mergeCollectorOverrides(\n current: Partial<Record<string, boolean | CollectorOptions>> | undefined,\n updates: Partial<Record<string, boolean | CollectorOptions>> | undefined,\n): Partial<Record<string, boolean | CollectorOptions>> | undefined {\n if (!current && !updates) {\n return undefined;\n }\n\n const merged: Partial<Record<string, boolean | CollectorOptions>> = {\n ...(current ?? {}),\n };\n\n for (const [name, value] of Object.entries(updates ?? {})) {\n const previous = merged[name];\n\n if (value && typeof value === 'object' && !Array.isArray(value) && previous && typeof previous === 'object' && !Array.isArray(previous)) {\n merged[name] = {\n ...previous,\n ...value,\n };\n continue;\n }\n\n merged[name] = value;\n }\n\n return merged;\n}\n\nfunction mergeTestConfig(current: TestCheckpointConfig | null, update: TestCheckpointConfig): TestCheckpointConfig {\n const collectors = mergeCollectorOverrides(current?.collectors, update.collectors);\n const article = mergeArticleMetadata(current?.article, update.article);\n const articles = update.articles ? cloneArticleDefinitions(update.articles) : current?.articles ? cloneArticleDefinitions(current.articles) : undefined;\n\n return {\n description: update.description ?? current?.description,\n ...(article ? { article } : {}),\n ...(articles ? { articles } : {}),\n ...(collectors ? { collectors } : {}),\n };\n}\n\nfunction mergeArticleMetadata(\n current: ArticleMetadata | undefined,\n update: ArticleMetadata | undefined,\n): ArticleMetadata | undefined {\n if (!current && !update) {\n return undefined;\n }\n\n const merged: ArticleMetadata = {\n ...(current ?? {}),\n ...(update ?? {}),\n };\n\n if (current?.frontmatter || update?.frontmatter) {\n merged.frontmatter = {\n ...(current?.frontmatter ?? {}),\n ...(update?.frontmatter ?? {}),\n };\n }\n\n return merged;\n}\n\nfunction cloneArticleMetadata(article: ArticleMetadata): ArticleMetadata {\n return {\n ...article,\n ...(article.frontmatter ? { frontmatter: { ...article.frontmatter } } : {}),\n };\n}\n\nfunction cloneArticleDefinition(article: ArticleDefinition): ArticleDefinition {\n return {\n ...cloneArticleMetadata(article),\n steps: [...article.steps],\n };\n}\n\nfunction cloneArticleDefinitions(articles: ArticleDefinition[]): ArticleDefinition[] {\n return articles.map((article) => cloneArticleDefinition(article));\n}\n\nfunction syncManifestArticle(manifest: CheckpointManifest, testConfig: TestCheckpointConfig | null): void {\n if (testConfig?.article) {\n manifest.article = cloneArticleMetadata(testConfig.article);\n }\n\n if (testConfig?.articles) {\n manifest.articles = cloneArticleDefinitions(testConfig.articles);\n }\n}\n\nfunction manifestEnvironment(): string {\n return process.env.PLAYWRIGHT_CHECKPOINT_ENV || process.env.NODE_ENV || 'test';\n}\n\nfunction explicitTestTags(testInfo: TestInfo): string[] {\n return (((testInfo as TestInfo & { tags?: string[] }).tags ?? []) as string[]).map((tag) => tag.toLowerCase());\n}\n\nexport function titleParts(testInfo: TestInfo): string[] {\n const maybeTitlePath = (testInfo as { titlePath?: unknown }).titlePath;\n return typeof maybeTitlePath === 'function' ? maybeTitlePath.call(testInfo) : [testInfo.title];\n}\n\nexport function collectTags(parts: string[]): Set<string> {\n const tags = new Set<string>();\n\n for (const part of parts) {\n for (const token of part.match(/@[a-z0-9-]+/gi) || []) {\n tags.add(token.toLowerCase());\n }\n }\n\n return tags;\n}\n\nexport function manifestTags(testInfo: TestInfo): string[] {\n return Array.from(new Set([...explicitTestTags(testInfo), ...collectTags(titleParts(testInfo))]));\n}\n\nexport function createCheckpointManifestRecord(testInfo: TestInfo): CheckpointManifest {\n return {\n environment: manifestEnvironment(),\n project: testInfo.project.name,\n testId: testInfo.testId,\n title: testInfo.title,\n tags: manifestTags(testInfo),\n startedAt: new Date().toISOString(),\n checkpoints: [],\n };\n}\n\nexport async function writeCheckpointManifest(testInfo: TestInfo, manifest: CheckpointManifest): Promise<string> {\n const manifestPath = testInfo.outputPath('checkpoint-manifest.json');\n await fs.mkdir(path.dirname(manifestPath), { recursive: true });\n await fs.writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}\\n`, 'utf8');\n return manifestPath;\n}\n\nfunction createAdjustTimeout(testInfo: TestInfo): (ms: number) => void {\n return (ms: number) => {\n if (ms > 0 && typeof testInfo.setTimeout === 'function') {\n testInfo.setTimeout(testInfo.timeout + ms);\n }\n };\n}\n\nfunction mergeConfig(\n globalConfig: CheckpointConfig = {},\n testConfig: TestCheckpointConfig | null,\n): Partial<Record<string, boolean | CollectorConfig>> | undefined {\n return mergeCollectorOverrides(\n globalConfig.collectors,\n testConfig?.collectors,\n ) as Partial<Record<string, boolean | CollectorConfig>> | undefined;\n}\n\nexport async function captureCheckpointRecord(args: {\n globalConfig?: CheckpointConfig;\n page: Page;\n testInfo: TestInfo;\n checkpointManifest: CheckpointManifest;\n testConfig?: TestCheckpointConfig | null;\n name: string;\n options?: CheckpointOptions;\n}): Promise<CheckpointRecord> {\n const globalConfig = args.globalConfig ?? {};\n const session = await createCheckpointSession(args.page, {\n outputDir: args.testInfo.outputPath('checkpoints'),\n manifestPath: args.testInfo.outputPath('checkpoint-manifest.json'),\n manifest: args.checkpointManifest,\n collectors: mergeConfig(globalConfig, args.testConfig ?? null),\n custom: globalConfig.custom,\n redact: globalConfig.redact,\n testInfo: args.testInfo,\n adjustTimeout: createAdjustTimeout(args.testInfo),\n });\n\n try {\n return await session.checkpoint(args.name, args.options);\n } finally {\n await session.finalize();\n }\n}\n\nexport function createCheckpoint(globalConfig: CheckpointConfig = {}): {\n test: TestType<PlaywrightTestArgs & PlaywrightTestOptions & CheckpointFixtures, PlaywrightWorkerArgs & PlaywrightWorkerOptions>;\n} {\n const playwright = loadPlaywright();\n const base = playwright.test as TestType<\n PlaywrightTestArgs & PlaywrightTestOptions,\n PlaywrightWorkerArgs & PlaywrightWorkerOptions\n >;\n\n const test = base.extend<CheckpointFixtures>({\n checkpointManifest: [\n // Playwright fixture callbacks must use object destructuring for the first arg.\n // eslint-disable-next-line no-empty-pattern\n async ({}, use, testInfo) => {\n const manifest = createCheckpointManifestRecord(testInfo);\n\n try {\n await use(manifest);\n } finally {\n try {\n await writeCheckpointManifest(testInfo, manifest);\n } catch (error) {\n warn(`Failed to write checkpoint manifest for test \"${testInfo.title}\".`, error);\n }\n }\n },\n { auto: true },\n ],\n\n // Playwright fixture callbacks must use object destructuring for the first arg.\n // eslint-disable-next-line no-empty-pattern\n testCheckpointConfig: async ({}, use) => {\n let current: TestCheckpointConfig | null = null;\n\n const controller: TestCheckpointConfigController = {\n set(config) {\n current = mergeTestConfig(current, config);\n },\n get() {\n return current\n ? {\n ...current,\n ...(current.collectors ? { collectors: mergeCollectorOverrides(undefined, current.collectors) } : {}),\n }\n : null;\n },\n };\n\n await use(controller);\n },\n\n // Playwright fixture callbacks must use object destructuring for the first arg.\n // eslint-disable-next-line no-empty-pattern\n deviceProfile: async ({}, use, testInfo) => {\n await use(createDeviceProfile(testInfo));\n },\n\n checkpoint: async ({ page, checkpointManifest, testCheckpointConfig }, use, testInfo) => {\n const session = await createCheckpointSession(page, {\n outputDir: testInfo.outputPath('checkpoints'),\n manifestPath: testInfo.outputPath('checkpoint-manifest.json'),\n manifest: checkpointManifest,\n collectors: globalConfig.collectors,\n testConfig: () => testCheckpointConfig.get(),\n custom: globalConfig.custom,\n redact: globalConfig.redact,\n testInfo,\n adjustTimeout: createAdjustTimeout(testInfo),\n });\n\n try {\n await use((name, options = {}) => {\n syncManifestArticle(checkpointManifest, testCheckpointConfig.get());\n return session.checkpoint(name, options);\n });\n } finally {\n syncManifestArticle(checkpointManifest, testCheckpointConfig.get());\n await session.finalize();\n }\n },\n });\n\n return { test };\n}\n\nexport const expect = loadPlaywright().expect;\nexport const { test } = createCheckpoint();\nexport { createCheckpointSession, createDeviceProfile, registerBuiltinCollector, resolveCollectors, sanitizeSegment, settlePage, warn };\nexport type { DeviceProfile, TestCheckpointConfigController };\n","import type { TestInfo } from '@playwright/test';\n\nexport type DeviceSurface = 'desktop' | 'mobile';\n\nexport type DeviceProfile = {\n name: string;\n isMobile: boolean;\n surface: DeviceSurface;\n};\n\nexport function createDeviceProfile(testInfo: TestInfo): DeviceProfile {\n const use = testInfo.project.use as { isMobile?: boolean } | undefined;\n const isMobile = Boolean(use?.isMobile);\n\n return {\n name: testInfo.project.name,\n isMobile,\n surface: isMobile ? 'mobile' : 'desktop',\n };\n}\n","import { builtinCollectors } from './builtin-collectors';\nimport { registerBuiltinCollectors } from './registry';\n\nregisterBuiltinCollectors(builtinCollectors);\n\nexport { registerBuiltinCollector, registerBuiltinCollectors, getBuiltinCollectors } from './registry';\nexport { screenshotCollector } from './screenshot';\nexport { htmlCollector } from './html';\nexport { axeCollector, setAxeLoaderForTests } from './axe';\nexport { webVitalsCollector } from './web-vitals';\nexport { consoleCollector } from './console';\nexport { networkCollector } from './network';\nexport { metadataCollector } from './metadata';\nexport { ariaSnapshotCollector } from './aria-snapshot';\nexport { domStatsCollector } from './dom-stats';\nexport { formsCollector } from './forms';\nexport { storageCollector } from './storage';\nexport { networkTimingCollector } from './network-timing';\nexport type { CheckpointCollector } from '../types';\n","export type * from './types';\nexport * from './core';\nexport * from './fixture';\nexport { type CheckpointCollector } from './types';\nexport { type ReportGenerator } from './types';\nexport * from './collectors';\nexport * from './report';\n\nexport const VERSION = '0.1.0';\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,OAAO,QAAQ;AACf,SAAS,qBAAqB;AAC9B,OAAO,UAAU;;;ACQV,SAAS,oBAAoB,UAAmC;AACrE,QAAM,MAAM,SAAS,QAAQ;AAC7B,QAAM,WAAW,QAAQ,KAAK,QAAQ;AAEtC,SAAO;AAAA,IACL,MAAM,SAAS,QAAQ;AAAA,IACvB;AAAA,IACA,SAAS,WAAW,WAAW;AAAA,EACjC;AACF;;;AD6BA,IAAMA,YAAW,MAAM;AACrB,MAAI;AACF,WAAO,SAAS,gBAAgB,EAAE;AAAA,EACpC,QAAQ;AACN,WAAO,cAAc,KAAK,KAAK,QAAQ,IAAI,GAAG,mCAAmC,CAAC;AAAA,EACpF;AACF,GAAG;AAEH,SAAS,iBAAoC;AAC3C,SAAOA,SAAQ,kBAAkB;AACnC;AAEA,SAAS,wBACP,SACA,SACiE;AACjE,MAAI,CAAC,WAAW,CAAC,SAAS;AACxB,WAAO;AAAA,EACT;AAEA,QAAM,SAA8D;AAAA,IAClE,GAAI,WAAW,CAAC;AAAA,EAClB;AAEA,aAAW,CAAC,MAAM,KAAK,KAAK,OAAO,QAAQ,WAAW,CAAC,CAAC,GAAG;AACzD,UAAM,WAAW,OAAO,IAAI;AAE5B,QAAI,SAAS,OAAO,UAAU,YAAY,CAAC,MAAM,QAAQ,KAAK,KAAK,YAAY,OAAO,aAAa,YAAY,CAAC,MAAM,QAAQ,QAAQ,GAAG;AACvI,aAAO,IAAI,IAAI;AAAA,QACb,GAAG;AAAA,QACH,GAAG;AAAA,MACL;AACA;AAAA,IACF;AAEA,WAAO,IAAI,IAAI;AAAA,EACjB;AAEA,SAAO;AACT;AAEA,SAAS,gBAAgB,SAAsC,QAAoD;AACjH,QAAM,aAAa,wBAAwB,SAAS,YAAY,OAAO,UAAU;AACjF,QAAM,UAAU,qBAAqB,SAAS,SAAS,OAAO,OAAO;AACrE,QAAM,WAAW,OAAO,WAAW,wBAAwB,OAAO,QAAQ,IAAI,SAAS,WAAW,wBAAwB,QAAQ,QAAQ,IAAI;AAE9I,SAAO;AAAA,IACL,aAAa,OAAO,eAAe,SAAS;AAAA,IAC5C,GAAI,UAAU,EAAE,QAAQ,IAAI,CAAC;AAAA,IAC7B,GAAI,WAAW,EAAE,SAAS,IAAI,CAAC;AAAA,IAC/B,GAAI,aAAa,EAAE,WAAW,IAAI,CAAC;AAAA,EACrC;AACF;AAEA,SAAS,qBACP,SACA,QAC6B;AAC7B,MAAI,CAAC,WAAW,CAAC,QAAQ;AACvB,WAAO;AAAA,EACT;AAEA,QAAM,SAA0B;AAAA,IAC9B,GAAI,WAAW,CAAC;AAAA,IAChB,GAAI,UAAU,CAAC;AAAA,EACjB;AAEA,MAAI,SAAS,eAAe,QAAQ,aAAa;AAC/C,WAAO,cAAc;AAAA,MACnB,GAAI,SAAS,eAAe,CAAC;AAAA,MAC7B,GAAI,QAAQ,eAAe,CAAC;AAAA,IAC9B;AAAA,EACF;AAEA,SAAO;AACT;AAEA,SAAS,qBAAqB,SAA2C;AACvE,SAAO;AAAA,IACL,GAAG;AAAA,IACH,GAAI,QAAQ,cAAc,EAAE,aAAa,EAAE,GAAG,QAAQ,YAAY,EAAE,IAAI,CAAC;AAAA,EAC3E;AACF;AAEA,SAAS,uBAAuB,SAA+C;AAC7E,SAAO;AAAA,IACL,GAAG,qBAAqB,OAAO;AAAA,IAC/B,OAAO,CAAC,GAAG,QAAQ,KAAK;AAAA,EAC1B;AACF;AAEA,SAAS,wBAAwB,UAAoD;AACnF,SAAO,SAAS,IAAI,CAAC,YAAY,uBAAuB,OAAO,CAAC;AAClE;AAEA,SAAS,oBAAoB,UAA8B,YAA+C;AACxG,MAAI,YAAY,SAAS;AACvB,aAAS,UAAU,qBAAqB,WAAW,OAAO;AAAA,EAC5D;AAEA,MAAI,YAAY,UAAU;AACxB,aAAS,WAAW,wBAAwB,WAAW,QAAQ;AAAA,EACjE;AACF;AAEA,SAAS,sBAA8B;AACrC,SAAO,QAAQ,IAAI,6BAA6B,QAAQ,IAAI,YAAY;AAC1E;AAEA,SAAS,iBAAiB,UAA8B;AACtD,UAAU,SAA4C,QAAQ,CAAC,GAAgB,IAAI,CAAC,QAAQ,IAAI,YAAY,CAAC;AAC/G;AAEO,SAAS,WAAW,UAA8B;AACvD,QAAM,iBAAkB,SAAqC;AAC7D,SAAO,OAAO,mBAAmB,aAAa,eAAe,KAAK,QAAQ,IAAI,CAAC,SAAS,KAAK;AAC/F;AAEO,SAAS,YAAY,OAA8B;AACxD,QAAM,OAAO,oBAAI,IAAY;AAE7B,aAAW,QAAQ,OAAO;AACxB,eAAW,SAAS,KAAK,MAAM,eAAe,KAAK,CAAC,GAAG;AACrD,WAAK,IAAI,MAAM,YAAY,CAAC;AAAA,IAC9B;AAAA,EACF;AAEA,SAAO;AACT;AAEO,SAAS,aAAa,UAA8B;AACzD,SAAO,MAAM,KAAK,oBAAI,IAAI,CAAC,GAAG,iBAAiB,QAAQ,GAAG,GAAG,YAAY,WAAW,QAAQ,CAAC,CAAC,CAAC,CAAC;AAClG;AAEO,SAAS,+BAA+B,UAAwC;AACrF,SAAO;AAAA,IACL,aAAa,oBAAoB;AAAA,IACjC,SAAS,SAAS,QAAQ;AAAA,IAC1B,QAAQ,SAAS;AAAA,IACjB,OAAO,SAAS;AAAA,IAChB,MAAM,aAAa,QAAQ;AAAA,IAC3B,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,IAClC,aAAa,CAAC;AAAA,EAChB;AACF;AAEA,eAAsB,wBAAwB,UAAoB,UAA+C;AAC/G,QAAM,eAAe,SAAS,WAAW,0BAA0B;AACnE,QAAM,GAAG,MAAM,KAAK,QAAQ,YAAY,GAAG,EAAE,WAAW,KAAK,CAAC;AAC9D,QAAM,GAAG,UAAU,cAAc,GAAG,KAAK,UAAU,UAAU,MAAM,CAAC,CAAC;AAAA,GAAM,MAAM;AACjF,SAAO;AACT;AAEA,SAAS,oBAAoB,UAA0C;AACrE,SAAO,CAAC,OAAe;AACrB,QAAI,KAAK,KAAK,OAAO,SAAS,eAAe,YAAY;AACvD,eAAS,WAAW,SAAS,UAAU,EAAE;AAAA,IAC3C;AAAA,EACF;AACF;AAEA,SAAS,YACP,eAAiC,CAAC,GAClC,YACgE;AAChE,SAAO;AAAA,IACL,aAAa;AAAA,IACb,YAAY;AAAA,EACd;AACF;AAEA,eAAsB,wBAAwB,MAQhB;AAC5B,QAAM,eAAe,KAAK,gBAAgB,CAAC;AAC3C,QAAM,UAAU,MAAM,wBAAwB,KAAK,MAAM;AAAA,IACvD,WAAW,KAAK,SAAS,WAAW,aAAa;AAAA,IACjD,cAAc,KAAK,SAAS,WAAW,0BAA0B;AAAA,IACjE,UAAU,KAAK;AAAA,IACf,YAAY,YAAY,cAAc,KAAK,cAAc,IAAI;AAAA,IAC7D,QAAQ,aAAa;AAAA,IACrB,QAAQ,aAAa;AAAA,IACrB,UAAU,KAAK;AAAA,IACf,eAAe,oBAAoB,KAAK,QAAQ;AAAA,EAClD,CAAC;AAED,MAAI;AACF,WAAO,MAAM,QAAQ,WAAW,KAAK,MAAM,KAAK,OAAO;AAAA,EACzD,UAAE;AACA,UAAM,QAAQ,SAAS;AAAA,EACzB;AACF;AAEO,SAAS,iBAAiB,eAAiC,CAAC,GAEjE;AACA,QAAM,aAAa,eAAe;AAClC,QAAM,OAAO,WAAW;AAKxB,QAAMC,QAAO,KAAK,OAA2B;AAAA,IAC3C,oBAAoB;AAAA;AAAA;AAAA,MAGlB,OAAO,CAAC,GAAG,KAAK,aAAa;AAC3B,cAAM,WAAW,+BAA+B,QAAQ;AAExD,YAAI;AACF,gBAAM,IAAI,QAAQ;AAAA,QACpB,UAAE;AACA,cAAI;AACF,kBAAM,wBAAwB,UAAU,QAAQ;AAAA,UAClD,SAAS,OAAO;AACd,iBAAK,iDAAiD,SAAS,KAAK,MAAM,KAAK;AAAA,UACjF;AAAA,QACF;AAAA,MACF;AAAA,MACA,EAAE,MAAM,KAAK;AAAA,IACf;AAAA;AAAA;AAAA,IAIA,sBAAsB,OAAO,CAAC,GAAG,QAAQ;AACvC,UAAI,UAAuC;AAE3C,YAAM,aAA6C;AAAA,QACjD,IAAI,QAAQ;AACV,oBAAU,gBAAgB,SAAS,MAAM;AAAA,QAC3C;AAAA,QACA,MAAM;AACJ,iBAAO,UACH;AAAA,YACE,GAAG;AAAA,YACH,GAAI,QAAQ,aAAa,EAAE,YAAY,wBAAwB,QAAW,QAAQ,UAAU,EAAE,IAAI,CAAC;AAAA,UACrG,IACA;AAAA,QACN;AAAA,MACF;AAEA,YAAM,IAAI,UAAU;AAAA,IACtB;AAAA;AAAA;AAAA,IAIA,eAAe,OAAO,CAAC,GAAG,KAAK,aAAa;AAC1C,YAAM,IAAI,oBAAoB,QAAQ,CAAC;AAAA,IACzC;AAAA,IAEA,YAAY,OAAO,EAAE,MAAM,oBAAoB,qBAAqB,GAAG,KAAK,aAAa;AACvF,YAAM,UAAU,MAAM,wBAAwB,MAAM;AAAA,QAClD,WAAW,SAAS,WAAW,aAAa;AAAA,QAC5C,cAAc,SAAS,WAAW,0BAA0B;AAAA,QAC5D,UAAU;AAAA,QACV,YAAY,aAAa;AAAA,QACzB,YAAY,MAAM,qBAAqB,IAAI;AAAA,QAC3C,QAAQ,aAAa;AAAA,QACrB,QAAQ,aAAa;AAAA,QACrB;AAAA,QACA,eAAe,oBAAoB,QAAQ;AAAA,MAC7C,CAAC;AAED,UAAI;AACF,cAAM,IAAI,CAAC,MAAM,UAAU,CAAC,MAAM;AAChC,8BAAoB,oBAAoB,qBAAqB,IAAI,CAAC;AAClE,iBAAO,QAAQ,WAAW,MAAM,OAAO;AAAA,QACzC,CAAC;AAAA,MACH,UAAE;AACA,4BAAoB,oBAAoB,qBAAqB,IAAI,CAAC;AAClE,cAAM,QAAQ,SAAS;AAAA,MACzB;AAAA,IACF;AAAA,EACF,CAAC;AAED,SAAO,EAAE,MAAAA,MAAK;AAChB;AAEO,IAAM,SAAS,eAAe,EAAE;AAChC,IAAM,EAAE,KAAK,IAAI,iBAAiB;;;AE1UzC,0BAA0B,iBAAiB;;;ACKpC,IAAM,UAAU;","names":["require","test"]}
|
package/dist/mcp/index.cjs
CHANGED
|
@@ -2379,6 +2379,28 @@ function stripTags(value) {
|
|
|
2379
2379
|
const stripped = value.replace(/\s+@[a-z0-9-]+/gi, " ").replace(/\s+/g, " ").trim();
|
|
2380
2380
|
return stripped || value.trim() || "Untitled story";
|
|
2381
2381
|
}
|
|
2382
|
+
function articleTitle(run) {
|
|
2383
|
+
const override = run.article?.title?.trim();
|
|
2384
|
+
return override || stripTags(run.title);
|
|
2385
|
+
}
|
|
2386
|
+
function articleDescription(run) {
|
|
2387
|
+
const description = run.article?.description?.trim();
|
|
2388
|
+
return description ? description : null;
|
|
2389
|
+
}
|
|
2390
|
+
function articleSlug(run) {
|
|
2391
|
+
const override = run.article?.slug?.trim();
|
|
2392
|
+
return slugify2(override || stripTags(run.title));
|
|
2393
|
+
}
|
|
2394
|
+
function uniqueArticleSlug(baseSlug, usedSlugs) {
|
|
2395
|
+
if (!usedSlugs.has(baseSlug)) {
|
|
2396
|
+
return baseSlug;
|
|
2397
|
+
}
|
|
2398
|
+
let index = 1;
|
|
2399
|
+
while (usedSlugs.has(`${baseSlug}-${index}`)) {
|
|
2400
|
+
index += 1;
|
|
2401
|
+
}
|
|
2402
|
+
return `${baseSlug}-${index}`;
|
|
2403
|
+
}
|
|
2382
2404
|
function normalizeConfig(config) {
|
|
2383
2405
|
return {
|
|
2384
2406
|
storiesDir: typeof config.storiesDir === "string" ? config.storiesDir : ".",
|
|
@@ -2389,7 +2411,8 @@ function normalizeConfig(config) {
|
|
|
2389
2411
|
footer: typeof config.footer === "string" ? config.footer : void 0,
|
|
2390
2412
|
frontmatter: config.frontmatter === true || config.frontmatter === false || config.frontmatter != null && typeof config.frontmatter === "object" && !Array.isArray(config.frontmatter) ? config.frontmatter : false,
|
|
2391
2413
|
imagePathPrefix: typeof config.imagePathPrefix === "string" ? config.imagePathPrefix : void 0,
|
|
2392
|
-
copyScreenshots: typeof config.copyScreenshots === "boolean" ? config.copyScreenshots : true
|
|
2414
|
+
copyScreenshots: typeof config.copyScreenshots === "boolean" ? config.copyScreenshots : true,
|
|
2415
|
+
requireExplicitStep: typeof config.requireExplicitStep === "boolean" ? config.requireExplicitStep : false
|
|
2393
2416
|
};
|
|
2394
2417
|
}
|
|
2395
2418
|
function normalizeTags(tags) {
|
|
@@ -2401,6 +2424,9 @@ function shouldIncludeRun(run, config) {
|
|
|
2401
2424
|
const runTags = new Set(normalizeTags(run.tags));
|
|
2402
2425
|
return includeTags.some((tag) => runTags.has(tag));
|
|
2403
2426
|
}
|
|
2427
|
+
if (run.articles) {
|
|
2428
|
+
return true;
|
|
2429
|
+
}
|
|
2404
2430
|
return run.checkpoints.some((checkpoint) => {
|
|
2405
2431
|
const hasDescription = typeof checkpoint.description === "string" && checkpoint.description.trim().length > 0;
|
|
2406
2432
|
return hasDescription || typeof checkpoint.step === "number";
|
|
@@ -2519,17 +2545,22 @@ async function materializeScreenshot(args) {
|
|
|
2519
2545
|
if (!sourcePath) {
|
|
2520
2546
|
return null;
|
|
2521
2547
|
}
|
|
2548
|
+
const cachedTargetPath = args.screenshotCopies?.get(sourcePath);
|
|
2549
|
+
if (cachedTargetPath) {
|
|
2550
|
+
return rewriteImagePath(args.markdownFile, cachedTargetPath, args.outputDir, args.config.imagePathPrefix);
|
|
2551
|
+
}
|
|
2522
2552
|
const extension = import_node_path15.default.extname(sourcePath) || ".png";
|
|
2523
2553
|
const targetPath = import_node_path15.default.join(
|
|
2524
2554
|
args.outputDir,
|
|
2525
2555
|
args.config.screenshotsDir ?? "screenshots",
|
|
2526
|
-
args.
|
|
2527
|
-
`${
|
|
2556
|
+
args.screenshotDirSlug,
|
|
2557
|
+
`${args.screenshotFileSlug}${extension}`
|
|
2528
2558
|
);
|
|
2529
2559
|
try {
|
|
2530
2560
|
if (args.config.copyScreenshots !== false) {
|
|
2531
2561
|
await import_promises14.default.mkdir(import_node_path15.default.dirname(targetPath), { recursive: true });
|
|
2532
2562
|
await import_promises14.default.copyFile(sourcePath, targetPath);
|
|
2563
|
+
args.screenshotCopies?.set(sourcePath, targetPath);
|
|
2533
2564
|
args.writtenFiles.add(targetPath);
|
|
2534
2565
|
return rewriteImagePath(args.markdownFile, targetPath, args.outputDir, args.config.imagePathPrefix);
|
|
2535
2566
|
}
|
|
@@ -2549,10 +2580,31 @@ function orderedCheckpoints(checkpoints) {
|
|
|
2549
2580
|
});
|
|
2550
2581
|
}
|
|
2551
2582
|
async function buildSteps(args) {
|
|
2552
|
-
|
|
2583
|
+
let checkpoints;
|
|
2584
|
+
if (args.stepNames && args.stepNames.length > 0) {
|
|
2585
|
+
const byName = /* @__PURE__ */ new Map();
|
|
2586
|
+
for (const checkpoint of args.run.checkpoints) {
|
|
2587
|
+
if (byName.has(checkpoint.name)) {
|
|
2588
|
+
warn(`Duplicate checkpoint name "${checkpoint.name}" in "${args.run.title}". Using the latest capture for article generation.`);
|
|
2589
|
+
}
|
|
2590
|
+
byName.set(checkpoint.name, checkpoint);
|
|
2591
|
+
}
|
|
2592
|
+
checkpoints = args.stepNames.map((stepName) => {
|
|
2593
|
+
const checkpoint = byName.get(stepName);
|
|
2594
|
+
if (!checkpoint) {
|
|
2595
|
+
warn(`Markdown article step "${stepName}" was not captured in "${args.run.title}". Skipping step.`);
|
|
2596
|
+
return null;
|
|
2597
|
+
}
|
|
2598
|
+
return checkpoint;
|
|
2599
|
+
}).filter((checkpoint) => checkpoint !== null);
|
|
2600
|
+
} else {
|
|
2601
|
+
checkpoints = orderedCheckpoints(args.run.checkpoints).filter(
|
|
2602
|
+
(checkpoint) => !args.config.requireExplicitStep || typeof checkpoint.step === "number"
|
|
2603
|
+
);
|
|
2604
|
+
}
|
|
2553
2605
|
const steps = [];
|
|
2554
2606
|
for (const [index, checkpoint] of checkpoints.entries()) {
|
|
2555
|
-
const order = typeof checkpoint.step === "number" ? checkpoint.step : index + 1;
|
|
2607
|
+
const order = args.stepNames ? index + 1 : typeof checkpoint.step === "number" ? checkpoint.step : index + 1;
|
|
2556
2608
|
steps.push({
|
|
2557
2609
|
checkpoint,
|
|
2558
2610
|
order,
|
|
@@ -2561,12 +2613,13 @@ async function buildSteps(args) {
|
|
|
2561
2613
|
imagePath: await materializeScreenshot({
|
|
2562
2614
|
run: args.run,
|
|
2563
2615
|
checkpoint,
|
|
2564
|
-
|
|
2565
|
-
|
|
2616
|
+
screenshotDirSlug: args.screenshotDirSlug,
|
|
2617
|
+
screenshotFileSlug: args.stepNames ? checkpoint.slug : `${String(order).padStart(2, "0")}-${slugify2(checkpoint.name)}`,
|
|
2566
2618
|
outputDir: args.outputDir,
|
|
2567
2619
|
markdownFile: args.markdownFile,
|
|
2568
2620
|
config: args.config,
|
|
2569
|
-
writtenFiles: args.writtenFiles
|
|
2621
|
+
writtenFiles: args.writtenFiles,
|
|
2622
|
+
screenshotCopies: args.screenshotCopies
|
|
2570
2623
|
}),
|
|
2571
2624
|
urlLabel: urlLabel(checkpoint.url),
|
|
2572
2625
|
breadcrumbLabel: breadcrumbLabel(checkpoint.url),
|
|
@@ -2577,13 +2630,14 @@ async function buildSteps(args) {
|
|
|
2577
2630
|
}
|
|
2578
2631
|
function renderMarkdown(args) {
|
|
2579
2632
|
const frontmatterFields = args.config.frontmatter === true || typeof args.config.frontmatter === "object" ? {
|
|
2580
|
-
title: args.title,
|
|
2581
2633
|
project: args.run.project,
|
|
2582
|
-
testId: args.run.testId,
|
|
2583
2634
|
tags: args.run.tags,
|
|
2635
|
+
...args.config.frontmatter && typeof args.config.frontmatter === "object" ? args.config.frontmatter : {},
|
|
2636
|
+
...args.article?.frontmatter ?? {},
|
|
2637
|
+
testId: args.run.testId,
|
|
2584
2638
|
startedAt: args.run.startedAt,
|
|
2585
2639
|
generatedAt: args.generatedAt,
|
|
2586
|
-
|
|
2640
|
+
title: args.title
|
|
2587
2641
|
} : null;
|
|
2588
2642
|
const sections = args.steps.map((step) => {
|
|
2589
2643
|
const lines = [`## Step ${step.order}: ${step.heading}`, ""];
|
|
@@ -2603,6 +2657,7 @@ function renderMarkdown(args) {
|
|
|
2603
2657
|
const parts = [
|
|
2604
2658
|
frontmatterFields ? serializeFrontmatter(frontmatterFields) : "",
|
|
2605
2659
|
`# ${args.title}`,
|
|
2660
|
+
args.description?.trim() ?? "",
|
|
2606
2661
|
args.config.header ? args.config.header.trim() : "",
|
|
2607
2662
|
sections,
|
|
2608
2663
|
args.config.footer ? args.config.footer.trim() : ""
|
|
@@ -2610,6 +2665,42 @@ function renderMarkdown(args) {
|
|
|
2610
2665
|
return `${parts.join("\n\n")}
|
|
2611
2666
|
`;
|
|
2612
2667
|
}
|
|
2668
|
+
function resolveArticles(run) {
|
|
2669
|
+
const multiArticles = (run.articles ?? []).filter((article) => Array.isArray(article.steps));
|
|
2670
|
+
if (run.articles && multiArticles.length === 0) {
|
|
2671
|
+
warn(`Markdown reporter received an empty articles array for "${run.title}". Falling back to the default single-article output.`);
|
|
2672
|
+
}
|
|
2673
|
+
if (multiArticles.length === 0) {
|
|
2674
|
+
return [
|
|
2675
|
+
{
|
|
2676
|
+
title: articleTitle(run),
|
|
2677
|
+
description: articleDescription(run),
|
|
2678
|
+
slug: articleSlug(run),
|
|
2679
|
+
metadata: run.article,
|
|
2680
|
+
screenshotDirSlug: articleSlug(run)
|
|
2681
|
+
}
|
|
2682
|
+
];
|
|
2683
|
+
}
|
|
2684
|
+
const usedSlugs = /* @__PURE__ */ new Set();
|
|
2685
|
+
const screenshotDirSlug = slugify2(stripTags(run.title));
|
|
2686
|
+
return multiArticles.map((article, index) => {
|
|
2687
|
+
const fallbackSlug = `${screenshotDirSlug}-${index + 1}`;
|
|
2688
|
+
const baseSlug = slugify2(article.slug?.trim() || fallbackSlug);
|
|
2689
|
+
const uniqueSlug = uniqueArticleSlug(baseSlug, usedSlugs);
|
|
2690
|
+
if (uniqueSlug !== baseSlug) {
|
|
2691
|
+
warn(`Markdown article slug collision for "${article.title ?? run.title}" resolved as "${uniqueSlug}".`);
|
|
2692
|
+
}
|
|
2693
|
+
usedSlugs.add(uniqueSlug);
|
|
2694
|
+
return {
|
|
2695
|
+
title: article.title?.trim() || stripTags(run.title),
|
|
2696
|
+
description: article.description?.trim() || null,
|
|
2697
|
+
slug: uniqueSlug,
|
|
2698
|
+
metadata: article,
|
|
2699
|
+
stepNames: [...article.steps],
|
|
2700
|
+
screenshotDirSlug
|
|
2701
|
+
};
|
|
2702
|
+
});
|
|
2703
|
+
}
|
|
2613
2704
|
var markdownReporter = {
|
|
2614
2705
|
name: "markdown",
|
|
2615
2706
|
description: "Generates one Markdown help article per captured story.",
|
|
@@ -2621,37 +2712,56 @@ var markdownReporter = {
|
|
|
2621
2712
|
const stories = groupByStory(context.runs);
|
|
2622
2713
|
const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2623
2714
|
const writtenFiles = /* @__PURE__ */ new Set();
|
|
2715
|
+
const usedStorySlugs = /* @__PURE__ */ new Set();
|
|
2716
|
+
const screenshotCopies = /* @__PURE__ */ new Map();
|
|
2624
2717
|
let articleCount = 0;
|
|
2625
2718
|
for (const [storyTitle, runs] of stories) {
|
|
2626
2719
|
const primaryRun = choosePrimaryRun(runs, config.preferredProject);
|
|
2627
2720
|
if (!primaryRun || !shouldIncludeRun(primaryRun, config)) {
|
|
2628
2721
|
continue;
|
|
2629
2722
|
}
|
|
2630
|
-
const
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
markdownFile,
|
|
2644
|
-
renderMarkdown({
|
|
2645
|
-
title,
|
|
2646
|
-
steps,
|
|
2723
|
+
for (const article of resolveArticles(primaryRun)) {
|
|
2724
|
+
let storySlug = article.slug;
|
|
2725
|
+
if (usedStorySlugs.has(storySlug)) {
|
|
2726
|
+
let index = 2;
|
|
2727
|
+
while (usedStorySlugs.has(`${article.slug}-${index}`)) {
|
|
2728
|
+
index += 1;
|
|
2729
|
+
}
|
|
2730
|
+
storySlug = `${article.slug}-${index}`;
|
|
2731
|
+
warn(`Markdown article slug collision for "${article.title || storyTitle}" resolved as "${storySlug}".`);
|
|
2732
|
+
}
|
|
2733
|
+
usedStorySlugs.add(storySlug);
|
|
2734
|
+
const markdownFile = import_node_path15.default.join(context.outputDir, config.storiesDir ?? ".", `${storySlug}.md`);
|
|
2735
|
+
const steps = await buildSteps({
|
|
2647
2736
|
run: primaryRun,
|
|
2737
|
+
stepNames: article.stepNames,
|
|
2738
|
+
screenshotDirSlug: article.screenshotDirSlug,
|
|
2739
|
+
outputDir: context.outputDir,
|
|
2740
|
+
markdownFile,
|
|
2648
2741
|
config,
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2742
|
+
writtenFiles,
|
|
2743
|
+
screenshotCopies
|
|
2744
|
+
});
|
|
2745
|
+
if (steps.length === 0) {
|
|
2746
|
+
continue;
|
|
2747
|
+
}
|
|
2748
|
+
await import_promises14.default.mkdir(import_node_path15.default.dirname(markdownFile), { recursive: true });
|
|
2749
|
+
await import_promises14.default.writeFile(
|
|
2750
|
+
markdownFile,
|
|
2751
|
+
renderMarkdown({
|
|
2752
|
+
title: article.title,
|
|
2753
|
+
description: article.description,
|
|
2754
|
+
steps,
|
|
2755
|
+
run: primaryRun,
|
|
2756
|
+
article: article.metadata,
|
|
2757
|
+
config,
|
|
2758
|
+
generatedAt
|
|
2759
|
+
}),
|
|
2760
|
+
"utf8"
|
|
2761
|
+
);
|
|
2762
|
+
writtenFiles.add(markdownFile);
|
|
2763
|
+
articleCount += 1;
|
|
2764
|
+
}
|
|
2655
2765
|
}
|
|
2656
2766
|
return {
|
|
2657
2767
|
files: [...writtenFiles],
|
|
@@ -3064,6 +3174,8 @@ function toRunRecord(manifest, sourceManifestPath) {
|
|
|
3064
3174
|
project: manifest.project,
|
|
3065
3175
|
testId: manifest.testId,
|
|
3066
3176
|
title: manifest.title,
|
|
3177
|
+
...manifest.article ? { article: manifest.article } : {},
|
|
3178
|
+
...manifest.articles ? { articles: manifest.articles } : {},
|
|
3067
3179
|
tags: manifest.tags,
|
|
3068
3180
|
startedAt: manifest.startedAt,
|
|
3069
3181
|
checkpoints: manifest.checkpoints
|
|
@@ -3075,6 +3187,8 @@ function toManifest(run) {
|
|
|
3075
3187
|
project: run.project,
|
|
3076
3188
|
testId: run.testId,
|
|
3077
3189
|
title: run.title,
|
|
3190
|
+
...run.article ? { article: run.article } : {},
|
|
3191
|
+
...run.articles ? { articles: run.articles } : {},
|
|
3078
3192
|
tags: run.tags,
|
|
3079
3193
|
startedAt: run.startedAt,
|
|
3080
3194
|
checkpoints: run.checkpoints
|