@democraft/playwright 0.1.0-beta.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/LICENSE +21 -0
- package/README.md +5 -0
- package/dist/index.d.ts +271 -0
- package/dist/index.js +1548 -0
- package/package.json +42 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Democraft contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import { CaptureArtifactMetadata, DemoIR, RecordedDemoManifest, TargetSnapshot, RecordedStep } from '@democraft/schema';
|
|
2
|
+
|
|
3
|
+
type RuntimeEnvironment = {
|
|
4
|
+
viewport?: {
|
|
5
|
+
width: number;
|
|
6
|
+
height: number;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Device pixel ratio for screenshot capture. Higher values produce sharper
|
|
10
|
+
* screenshots (more device pixels per CSS pixel). Defaults to 2, which
|
|
11
|
+
* captures at 2× resolution — e.g. a 1920×1080 viewport yields 3840×2160
|
|
12
|
+
* PNGs. This gives the Remotion renderer enough pixel density for camera
|
|
13
|
+
* zoom without pixelation.
|
|
14
|
+
*/
|
|
15
|
+
deviceScaleFactor?: number;
|
|
16
|
+
locale?: string;
|
|
17
|
+
timezone?: string;
|
|
18
|
+
storageState?: string;
|
|
19
|
+
/**
|
|
20
|
+
* How the capture runtime decides a page is "ready" before taking each
|
|
21
|
+
* screenshot. When omitted, the runtime uses sensible defaults
|
|
22
|
+
* (`DEFAULT_SETTLE_STRATEGY`) so demos are captured after the page settles
|
|
23
|
+
* without any author configuration. Pass `false` to disable settling and
|
|
24
|
+
* fall back to a fixed hold before each screenshot.
|
|
25
|
+
*
|
|
26
|
+
* See {@link SettleStrategy} for the tunables (idle window, timeout, signal).
|
|
27
|
+
*/
|
|
28
|
+
settle?: SettleStrategy | false;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Strategy for detecting that a page has settled before a screenshot is taken.
|
|
32
|
+
*
|
|
33
|
+
* Modern web apps keep loading content after the DOM is ready — data fetches,
|
|
34
|
+
* hydration, animations, lazy images. A screenshot taken the instant an action
|
|
35
|
+
* returns often captures a half-rendered view, so downstream captions/callouts
|
|
36
|
+
* narrate something the screen isn't showing yet.
|
|
37
|
+
*
|
|
38
|
+
* Rather than wait a fixed duration (too short for slow pages, dead air for
|
|
39
|
+
* fast ones), the settle gate waits for the page to **stop changing**. Two
|
|
40
|
+
* signals detect that, chosen because they measure what the viewer actually
|
|
41
|
+
* sees (not network activity, which `networkidle` relies on and is flaky with
|
|
42
|
+
* polling/analytics/SSE — officially discouraged by Playwright):
|
|
43
|
+
*
|
|
44
|
+
* - `"dom"` — a `MutationObserver` counts DOM mutations; idle when none occur
|
|
45
|
+
* for `idleWindowMs`.
|
|
46
|
+
* - `"visual"` — periodic low-resolution screenshots are compared; idle when
|
|
47
|
+
* two consecutive samples are identical within `idleWindowMs`. Catches
|
|
48
|
+
* pure-visual motion (CSS animations, fade-ins) that doesn't touch the DOM.
|
|
49
|
+
* - `"both"` (default) — idle only when both signals agree. Most robust.
|
|
50
|
+
*
|
|
51
|
+
* Settling is best-effort: if the page never quiets down, the gate gives up at
|
|
52
|
+
* `timeoutMs` and captures anyway (never throws), so a pathological page can't
|
|
53
|
+
* stall the whole capture.
|
|
54
|
+
*/
|
|
55
|
+
type SettleStrategy = {
|
|
56
|
+
/**
|
|
57
|
+
* How long the page must stay quiet (no DOM mutations and/or no visual
|
|
58
|
+
* changes, per `signal`) before it's considered settled. Shorter is more
|
|
59
|
+
* responsive; longer is more conservative. Default 350ms.
|
|
60
|
+
*/
|
|
61
|
+
idleWindowMs?: number;
|
|
62
|
+
/**
|
|
63
|
+
* Maximum time to wait for the page to settle before capturing anyway
|
|
64
|
+
* (best-effort). Should be long enough for real loads but bounded so a
|
|
65
|
+
* misbehaving page (infinite animation, endless polling UI) can't stall the
|
|
66
|
+
* capture. Default 4000ms.
|
|
67
|
+
*/
|
|
68
|
+
timeoutMs?: number;
|
|
69
|
+
/**
|
|
70
|
+
* Which signal(s) to use to detect "quiet".
|
|
71
|
+
* - `"dom"` — DOM mutations only (structural rendering).
|
|
72
|
+
* - `"visual"` — screenshot diffing only (pure-visual motion).
|
|
73
|
+
* - `"network"` — in-flight fetch/XHR only (pending data requests).
|
|
74
|
+
* - `"both"` (default) — all three; settled only when every signal agrees.
|
|
75
|
+
* Most robust, since each catches a different kind of in-flight work
|
|
76
|
+
* (e.g. the network signal reveals a pending data fetch even when the DOM
|
|
77
|
+
* is momentarily quiet).
|
|
78
|
+
*/
|
|
79
|
+
signal?: "dom" | "visual" | "network" | "both";
|
|
80
|
+
};
|
|
81
|
+
/**
|
|
82
|
+
* Default settle strategy: both signals, 350ms idle window, 4s timeout. Tuned
|
|
83
|
+
* for typical SaaS apps (App Router / SPA with hydration + data fetch).
|
|
84
|
+
*/
|
|
85
|
+
declare const DEFAULT_SETTLE_STRATEGY: Required<SettleStrategy>;
|
|
86
|
+
type RunDemoOptions = {
|
|
87
|
+
outputDir?: string;
|
|
88
|
+
/** Managed capture root used only when outputDir is omitted. */
|
|
89
|
+
captureRootDir?: string;
|
|
90
|
+
headless?: boolean;
|
|
91
|
+
environment?: RuntimeEnvironment;
|
|
92
|
+
timeoutMs?: number;
|
|
93
|
+
signal?: AbortSignal;
|
|
94
|
+
/** Called after the capture directory and initial metadata exist. */
|
|
95
|
+
onArtifactCreated?: (artifact: {
|
|
96
|
+
captureRunId: string;
|
|
97
|
+
outputDir: string;
|
|
98
|
+
manifestPath: string;
|
|
99
|
+
metadataPath: string;
|
|
100
|
+
}) => void | Promise<void>;
|
|
101
|
+
};
|
|
102
|
+
type BrowserLike = {
|
|
103
|
+
close(): Promise<void>;
|
|
104
|
+
newContext(options?: Record<string, unknown>): Promise<BrowserContextLike>;
|
|
105
|
+
};
|
|
106
|
+
type BrowserContextLike = {
|
|
107
|
+
newPage(): Promise<PageLike>;
|
|
108
|
+
close(): Promise<void>;
|
|
109
|
+
tracing?: {
|
|
110
|
+
start(options?: Record<string, unknown>): Promise<void>;
|
|
111
|
+
stop(options?: Record<string, unknown>): Promise<void>;
|
|
112
|
+
};
|
|
113
|
+
};
|
|
114
|
+
type PageLike = {
|
|
115
|
+
goto(url: string): Promise<unknown>;
|
|
116
|
+
url(): string;
|
|
117
|
+
getByRole(role: string, options?: {
|
|
118
|
+
name?: string;
|
|
119
|
+
}): LocatorLike;
|
|
120
|
+
getByLabel(text: string): LocatorLike;
|
|
121
|
+
getByTestId(id: string): LocatorLike;
|
|
122
|
+
getByText(text: string): LocatorLike;
|
|
123
|
+
video?(): {
|
|
124
|
+
path(): Promise<string>;
|
|
125
|
+
} | null;
|
|
126
|
+
screenshot?(options?: Record<string, unknown>): Promise<Buffer>;
|
|
127
|
+
waitForTimeout?(durationMs: number): Promise<void>;
|
|
128
|
+
/**
|
|
129
|
+
* Optional navigation gate. SPAs (Next.js App Router, React Router) resolve
|
|
130
|
+
* client-side navigation AFTER `<a>`/`<Link>` click resolves, so a click
|
|
131
|
+
* that changes the route returns before the new view is mounted. When
|
|
132
|
+
* present, the runtime races this against a short timeout after a click so
|
|
133
|
+
* the next step sees the navigated page — without stalling on clicks that
|
|
134
|
+
* don't navigate (dialogs, toggles).
|
|
135
|
+
*/
|
|
136
|
+
waitForLoadState?(state?: string): Promise<void>;
|
|
137
|
+
waitForURL?(url: string | RegExp, options?: {
|
|
138
|
+
timeout?: number;
|
|
139
|
+
}): Promise<void>;
|
|
140
|
+
/**
|
|
141
|
+
* Evaluate a function in the page context. Used by the settle gate to
|
|
142
|
+
* install/read a MutationObserver for DOM-idle detection. Absent in
|
|
143
|
+
* lightweight mocks (settle falls back to the visual signal only).
|
|
144
|
+
*/
|
|
145
|
+
evaluate?<T>(fn: () => T | Promise<T>): Promise<T>;
|
|
146
|
+
};
|
|
147
|
+
type LocatorLike = {
|
|
148
|
+
click(): Promise<void>;
|
|
149
|
+
fill(value: string): Promise<void>;
|
|
150
|
+
selectOption(value: string): Promise<unknown>;
|
|
151
|
+
boundingBox(options?: {
|
|
152
|
+
timeout?: number;
|
|
153
|
+
}): Promise<{
|
|
154
|
+
x: number;
|
|
155
|
+
y: number;
|
|
156
|
+
width: number;
|
|
157
|
+
height: number;
|
|
158
|
+
} | null>;
|
|
159
|
+
isVisible(options?: {
|
|
160
|
+
timeout?: number;
|
|
161
|
+
}): Promise<boolean>;
|
|
162
|
+
textContent(): Promise<string | null>;
|
|
163
|
+
/**
|
|
164
|
+
* Optional auto-polling visibility gate. Unlike `isVisible` (which reports
|
|
165
|
+
* the *current* state), `waitFor({ state: "visible" })` blocks until the
|
|
166
|
+
* element is visible or the timeout elapses — the right primitive for SPA
|
|
167
|
+
* views that mount a frame after navigation. Absent in lightweight mocks.
|
|
168
|
+
*/
|
|
169
|
+
waitFor?(state: {
|
|
170
|
+
state: "visible";
|
|
171
|
+
timeout?: number;
|
|
172
|
+
}): Promise<unknown>;
|
|
173
|
+
};
|
|
174
|
+
type PlaywrightBindings = {
|
|
175
|
+
chromium: {
|
|
176
|
+
launch(options?: {
|
|
177
|
+
headless?: boolean;
|
|
178
|
+
}): Promise<BrowserLike>;
|
|
179
|
+
};
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
declare const CAPTURE_ENVIRONMENT_HASH_PREFIX = "capture-env-v1:sha256:";
|
|
183
|
+
type CaptureRuntimeIdentity = {
|
|
184
|
+
node: string;
|
|
185
|
+
platform: string;
|
|
186
|
+
arch: string;
|
|
187
|
+
engine: "chromium";
|
|
188
|
+
};
|
|
189
|
+
declare function resolveCaptureEnvironment(options?: Pick<RunDemoOptions, "environment" | "headless" | "timeoutMs">, runtime?: CaptureRuntimeIdentity): Promise<{
|
|
190
|
+
environment: CaptureArtifactMetadata["environment"];
|
|
191
|
+
captureEnvironmentHash: string;
|
|
192
|
+
}>;
|
|
193
|
+
|
|
194
|
+
declare function runDemo(ir: DemoIR, options?: RunDemoOptions): Promise<RecordedDemoManifest>;
|
|
195
|
+
declare function runDemoWithBindings(ir: DemoIR, bindings: PlaywrightBindings, options?: RunDemoOptions): Promise<RecordedDemoManifest>;
|
|
196
|
+
|
|
197
|
+
declare function resolveTarget(ir: DemoIR, page: PageLike, targetId: string, timeoutMs?: number): Promise<{
|
|
198
|
+
locator?: LocatorLike;
|
|
199
|
+
snapshot: TargetSnapshot;
|
|
200
|
+
}>;
|
|
201
|
+
|
|
202
|
+
declare function canonicalScreenshotFilename(sceneId: string, stepId: string): string;
|
|
203
|
+
declare function screenshotRelativePath(sceneId: string, stepId: string): string;
|
|
204
|
+
declare function resolveRecordedScreenshotPath(captureDirectory: string, step: Pick<RecordedStep, "sceneId" | "stepId" | "screenshotPath">): string | undefined;
|
|
205
|
+
|
|
206
|
+
type CaptureArtifact = {
|
|
207
|
+
captureRunId: string;
|
|
208
|
+
demoId: string;
|
|
209
|
+
directory: string;
|
|
210
|
+
metadataPath: string;
|
|
211
|
+
manifestPath: string;
|
|
212
|
+
screenshotsPath: string;
|
|
213
|
+
tracePath: string;
|
|
214
|
+
metadata: CaptureArtifactMetadata;
|
|
215
|
+
managed: boolean;
|
|
216
|
+
demoDirectory?: string;
|
|
217
|
+
releaseLock?: () => Promise<void>;
|
|
218
|
+
};
|
|
219
|
+
type CaptureEnvironmentMetadata = CaptureArtifactMetadata["environment"];
|
|
220
|
+
type CreateCaptureArtifactOptions = {
|
|
221
|
+
rootDirectory: string;
|
|
222
|
+
outputDirectory?: string;
|
|
223
|
+
demoId: string;
|
|
224
|
+
definitionHash?: string;
|
|
225
|
+
captureHash?: string;
|
|
226
|
+
captureEnvironmentHash?: string;
|
|
227
|
+
environment: CaptureEnvironmentMetadata;
|
|
228
|
+
/** Internal/test tuning; production defaults use a 30s renewable lease. */
|
|
229
|
+
lockOptions?: CaptureLeaseOptions;
|
|
230
|
+
};
|
|
231
|
+
type ArtifactDependencies = {
|
|
232
|
+
now?: () => Date;
|
|
233
|
+
randomId?: () => string;
|
|
234
|
+
};
|
|
235
|
+
declare function createCaptureArtifact(options: CreateCaptureArtifactOptions, dependencies?: ArtifactDependencies): Promise<CaptureArtifact>;
|
|
236
|
+
declare function startCaptureArtifact(artifact: CaptureArtifact, now?: Date): Promise<void>;
|
|
237
|
+
declare function completeCaptureArtifact(artifact: CaptureArtifact, options?: {
|
|
238
|
+
recordingPath?: string;
|
|
239
|
+
traceAvailable?: boolean;
|
|
240
|
+
now?: Date;
|
|
241
|
+
}): Promise<void>;
|
|
242
|
+
declare function failCaptureArtifact(artifact: CaptureArtifact, error: unknown, now?: Date): Promise<void>;
|
|
243
|
+
declare function cancelCaptureArtifact(artifact: CaptureArtifact, now?: Date): Promise<void>;
|
|
244
|
+
declare function writeCaptureManifestAtomic(artifact: CaptureArtifact, json: string): Promise<void>;
|
|
245
|
+
declare function resolveLatestCompletedCapture(rootDirectory: string, demoId: string): Promise<{
|
|
246
|
+
captureDir: string;
|
|
247
|
+
manifestPath: string;
|
|
248
|
+
captureRunId?: string;
|
|
249
|
+
legacy: boolean;
|
|
250
|
+
} | undefined>;
|
|
251
|
+
declare function isReusableCaptureDirectory(directory: string, demoId: string): Promise<boolean>;
|
|
252
|
+
declare function captureSlug(demoId: string): string;
|
|
253
|
+
declare function captureNamespace(demoId: string): string;
|
|
254
|
+
declare class CaptureAbortError extends Error {
|
|
255
|
+
constructor();
|
|
256
|
+
}
|
|
257
|
+
type CaptureLeaseOptions = {
|
|
258
|
+
leaseMs?: number;
|
|
259
|
+
retryMs?: number;
|
|
260
|
+
maxAttempts?: number;
|
|
261
|
+
malformedStaleMs?: number;
|
|
262
|
+
insideReleaseGuard?: () => void | Promise<void>;
|
|
263
|
+
insideRecoveryGuard?: () => void | Promise<void>;
|
|
264
|
+
};
|
|
265
|
+
type CaptureLeaseRelease = (() => Promise<void>) & {
|
|
266
|
+
refresh: () => Promise<boolean>;
|
|
267
|
+
};
|
|
268
|
+
declare function acquireCaptureLeaseLock(lockPath: string, options?: CaptureLeaseOptions): Promise<CaptureLeaseRelease>;
|
|
269
|
+
declare function redactCaptureErrorMessage(error: unknown, directory?: string): string;
|
|
270
|
+
|
|
271
|
+
export { type BrowserContextLike, type BrowserLike, CAPTURE_ENVIRONMENT_HASH_PREFIX, CaptureAbortError, type CaptureArtifact, type CaptureLeaseOptions, type CaptureLeaseRelease, type CaptureRuntimeIdentity, type CreateCaptureArtifactOptions, DEFAULT_SETTLE_STRATEGY, type LocatorLike, type PageLike, type PlaywrightBindings, type RunDemoOptions, type RuntimeEnvironment, type SettleStrategy, acquireCaptureLeaseLock, cancelCaptureArtifact, canonicalScreenshotFilename, captureNamespace, captureSlug, completeCaptureArtifact, createCaptureArtifact, failCaptureArtifact, isReusableCaptureDirectory, redactCaptureErrorMessage, resolveCaptureEnvironment, resolveLatestCompletedCapture, resolveRecordedScreenshotPath, resolveTarget, runDemo, runDemoWithBindings, screenshotRelativePath, startCaptureArtifact, writeCaptureManifestAtomic };
|