@buildinternet/uploads 0.12.0 → 0.13.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/dist/commands/screenshot.js +33 -2
- package/dist/github-gh.js +6 -0
- package/dist/mcp/server.d.ts +1 -0
- package/dist/mcp/server.js +6 -1
- package/dist/mcp/tools.js +16 -0
- package/dist/screenshot-local.d.ts +8 -0
- package/dist/screenshot-local.js +20 -2
- package/dist/screenshot-remote.d.ts +8 -0
- package/dist/screenshot.d.ts +34 -0
- package/dist/screenshot.js +48 -0
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { writeFileSync } from "node:fs";
|
|
1
|
+
import { readFileSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { basename } from "node:path";
|
|
3
3
|
import { flagBool, flagInt, flagString, flagValues, parseCommandArgs, UsageError, } from "../cli-args.js";
|
|
4
4
|
import { writeCommandHelp } from "../cli-style.js";
|
|
@@ -10,7 +10,7 @@ import { ghMetadataFromTarget } from "../github.js";
|
|
|
10
10
|
import { execRunner } from "../github-gh.js";
|
|
11
11
|
import { parseMetaFlags, validateMetaMap } from "../metadata.js";
|
|
12
12
|
import { writeJson, writeStdout } from "../io.js";
|
|
13
|
-
import { captureScreenshot, parseViewport, parseWaitUntil, } from "../screenshot.js";
|
|
13
|
+
import { assertHideSelector, captureScreenshot, parseViewport, parseWaitUntil, } from "../screenshot.js";
|
|
14
14
|
const SCREENSHOT_HELP = `uploads screenshot <target> [options]
|
|
15
15
|
|
|
16
16
|
Capture a URL or a local .html file and host it — a hosted, PR-embeddable
|
|
@@ -43,6 +43,12 @@ Options:
|
|
|
43
43
|
own prefers-color-scheme queries won't flip)
|
|
44
44
|
--wait <load|domcontentloaded|networkidle|ms> Settle strategy (default: load); a millisecond
|
|
45
45
|
count is local-only — use --via local
|
|
46
|
+
--hide <css> Hide matching elements before capture (repeatable)
|
|
47
|
+
--no-hide-dev-tools Don't auto-hide framework dev toolbars (auto-hidden on localhost/private)
|
|
48
|
+
--reduced-motion Emulate prefers-reduced-motion: reduce so animations settle (best-effort
|
|
49
|
+
on --via remote — neutralizes animations via injected CSS)
|
|
50
|
+
--eval <js> Run JS in the page after settle, before capture (--via local only)
|
|
51
|
+
--init-script <file> Inject a JS file before navigation (--via local only)
|
|
46
52
|
--out <file> Also write the PNG to a local file
|
|
47
53
|
--no-upload Skip hosting; requires --out (local file only)
|
|
48
54
|
--destination <id> Typed root: screenshots | gh | f
|
|
@@ -129,6 +135,26 @@ captureImpl = captureScreenshot) {
|
|
|
129
135
|
const fullPage = flagBool(parsed.flags, "--full-page");
|
|
130
136
|
const colorScheme = colorSchemeFromFlags(parsed.flags);
|
|
131
137
|
const waitUntil = parseWaitUntil(flagString(parsed.flags, "--wait"));
|
|
138
|
+
const hide = flagValues(parsed.flags, "--hide");
|
|
139
|
+
// Fail fast before capture, using the shared policy (throws UploadsError
|
|
140
|
+
// code USAGE → exit 2, same as UsageError) so there's one source of truth.
|
|
141
|
+
for (const sel of hide)
|
|
142
|
+
assertHideSelector(sel);
|
|
143
|
+
// --no-hide-dev-tools opts out of auto-hiding framework toolbars; undefined
|
|
144
|
+
// lets captureScreenshot apply its localhost-aware default.
|
|
145
|
+
const hideDevTools = flagBool(parsed.flags, "--no-hide-dev-tools") ? false : undefined;
|
|
146
|
+
const reducedMotion = flagBool(parsed.flags, "--reduced-motion");
|
|
147
|
+
const evalJs = flagString(parsed.flags, "--eval");
|
|
148
|
+
const initScriptPath = flagString(parsed.flags, "--init-script");
|
|
149
|
+
let initScript;
|
|
150
|
+
if (initScriptPath !== undefined) {
|
|
151
|
+
try {
|
|
152
|
+
initScript = readFileSync(initScriptPath, "utf8");
|
|
153
|
+
}
|
|
154
|
+
catch (err) {
|
|
155
|
+
throw new UsageError(`could not read --init-script ${initScriptPath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
132
158
|
const outFile = flagString(parsed.flags, "--out");
|
|
133
159
|
const noUpload = flagBool(parsed.flags, "--no-upload");
|
|
134
160
|
if (noUpload && !outFile)
|
|
@@ -207,6 +233,11 @@ captureImpl = captureScreenshot) {
|
|
|
207
233
|
fullPage,
|
|
208
234
|
colorScheme,
|
|
209
235
|
waitUntil,
|
|
236
|
+
hide,
|
|
237
|
+
hideDevTools,
|
|
238
|
+
reducedMotion,
|
|
239
|
+
evalJs,
|
|
240
|
+
initScript,
|
|
210
241
|
apiUrl: ctx.config.apiUrl,
|
|
211
242
|
token: ctx.config.token,
|
|
212
243
|
});
|
package/dist/github-gh.js
CHANGED
|
@@ -42,9 +42,15 @@ export function resolveRepo(explicit, run = execRunner) {
|
|
|
42
42
|
/** Resolve the pull request associated with the current branch. */
|
|
43
43
|
export function resolveCurrentPullRequest(repo, run = execRunner) {
|
|
44
44
|
try {
|
|
45
|
+
// `gh pr view --repo` requires an explicit selector (it refuses to infer
|
|
46
|
+
// from the current branch), so pass the branch name as the selector.
|
|
47
|
+
const branch = run("git", ["rev-parse", "--abbrev-ref", "HEAD"]).trim();
|
|
48
|
+
if (branch === "" || branch === "HEAD")
|
|
49
|
+
throw new Error("detached HEAD");
|
|
45
50
|
const out = run("gh", [
|
|
46
51
|
"pr",
|
|
47
52
|
"view",
|
|
53
|
+
branch,
|
|
48
54
|
"--repo",
|
|
49
55
|
repo,
|
|
50
56
|
"--json",
|
package/dist/mcp/server.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { METADATA_DESCRIPTION, metadataProp, optPosInt, optString, optStringArray, optStringRecord, usage, type ToolArgs, } from "./args.js";
|
|
2
2
|
export { ToolBatchError, batchFailureMessage } from "./batch-error.js";
|
|
3
|
+
export { mapBounded } from "../async.js";
|
|
3
4
|
export interface McpTool {
|
|
4
5
|
name: string;
|
|
5
6
|
description: string;
|
package/dist/mcp/server.js
CHANGED
|
@@ -12,6 +12,7 @@ import { errorCodeFromUnknown, recordEvent } from "../telemetry.js";
|
|
|
12
12
|
import { ToolBatchError } from "./batch-error.js";
|
|
13
13
|
export { METADATA_DESCRIPTION, metadataProp, optPosInt, optString, optStringArray, optStringRecord, usage, } from "./args.js";
|
|
14
14
|
export { ToolBatchError, batchFailureMessage } from "./batch-error.js";
|
|
15
|
+
export { mapBounded } from "../async.js";
|
|
15
16
|
const SUPPORTED_PROTOCOL_VERSIONS = new Set(["2025-06-18", "2025-03-26", "2024-11-05"]);
|
|
16
17
|
const LATEST_PROTOCOL_VERSION = "2025-06-18";
|
|
17
18
|
function response(id, result) {
|
|
@@ -115,7 +116,11 @@ export function createMcpServer(opts) {
|
|
|
115
116
|
const protocolVersion = SUPPORTED_PROTOCOL_VERSIONS.has(requested)
|
|
116
117
|
? requested
|
|
117
118
|
: LATEST_PROTOCOL_VERSION;
|
|
118
|
-
return response(id, {
|
|
119
|
+
return response(id, {
|
|
120
|
+
protocolVersion,
|
|
121
|
+
capabilities: { tools: {} },
|
|
122
|
+
serverInfo,
|
|
123
|
+
});
|
|
119
124
|
}
|
|
120
125
|
case "ping":
|
|
121
126
|
return response(id, {});
|
package/dist/mcp/tools.js
CHANGED
|
@@ -546,6 +546,19 @@ export function createUploadsMcpTools(opts) {
|
|
|
546
546
|
type: "string",
|
|
547
547
|
description: 'Settle strategy: load (default) | domcontentloaded | networkidle | a millisecond count (millisecond counts are local-only — via: "local").',
|
|
548
548
|
},
|
|
549
|
+
hide: {
|
|
550
|
+
type: "array",
|
|
551
|
+
items: { type: "string" },
|
|
552
|
+
description: "CSS selectors to hide (display:none) before capture. Works on both backends.",
|
|
553
|
+
},
|
|
554
|
+
noHideDevTools: {
|
|
555
|
+
type: "boolean",
|
|
556
|
+
description: "Don't auto-hide framework dev toolbars (Astro/Next/Nuxt/Vite), which are hidden by default for localhost/private-network targets.",
|
|
557
|
+
},
|
|
558
|
+
reducedMotion: {
|
|
559
|
+
type: "boolean",
|
|
560
|
+
description: 'Emulate prefers-reduced-motion: reduce so animations settle. Best-effort on via: "remote" (neutralizes animations via injected CSS).',
|
|
561
|
+
},
|
|
549
562
|
key: {
|
|
550
563
|
type: "string",
|
|
551
564
|
description: "Explicit object key (default: <prefix>/<repo>/<ref>/<name>-<hash>.png). Cannot be combined with pr/issue.",
|
|
@@ -684,6 +697,9 @@ export function createUploadsMcpTools(opts) {
|
|
|
684
697
|
fullPage: optBool(args, "fullPage"),
|
|
685
698
|
colorScheme: colorSchemeArg,
|
|
686
699
|
waitUntil: screenshotModule.parseWaitUntil(optString(args, "wait")),
|
|
700
|
+
hide: optStringArray(args, "hide"),
|
|
701
|
+
hideDevTools: optBool(args, "noHideDevTools") ? false : undefined,
|
|
702
|
+
reducedMotion: optBool(args, "reducedMotion"),
|
|
687
703
|
apiUrl: config.apiUrl,
|
|
688
704
|
token: config.token,
|
|
689
705
|
});
|
|
@@ -51,6 +51,14 @@ export interface LocalCaptureOptions {
|
|
|
51
51
|
colorScheme?: "dark" | "light";
|
|
52
52
|
/** "load" | "domcontentloaded" | "networkidle", or a millisecond settle delay. */
|
|
53
53
|
waitUntil: "load" | "domcontentloaded" | "networkidle" | number;
|
|
54
|
+
/** CSS selectors hidden (display:none) just before capture. */
|
|
55
|
+
hide?: string[];
|
|
56
|
+
/** Emulate prefers-reduced-motion: reduce so animations settle deterministically. */
|
|
57
|
+
reducedMotion?: boolean;
|
|
58
|
+
/** JS run via page.evaluate after settle, before capture. */
|
|
59
|
+
evalJs?: string;
|
|
60
|
+
/** JS injected via addInitScript before navigation. */
|
|
61
|
+
initScript?: string;
|
|
54
62
|
timeoutMs?: number;
|
|
55
63
|
detectRoots?: DetectRoots;
|
|
56
64
|
/**
|
package/dist/screenshot-local.js
CHANGED
|
@@ -285,19 +285,37 @@ export async function captureLocal(opts) {
|
|
|
285
285
|
viewport: { width: opts.viewport.width, height: opts.viewport.height },
|
|
286
286
|
deviceScaleFactor: opts.viewport.deviceScaleFactor,
|
|
287
287
|
colorScheme: opts.colorScheme,
|
|
288
|
+
reducedMotion: opts.reducedMotion ? "reduce" : undefined,
|
|
288
289
|
});
|
|
290
|
+
// Runs before every navigation on this context — must be registered before
|
|
291
|
+
// the goto below so it's present when the page's own scripts first run.
|
|
292
|
+
if (opts.initScript)
|
|
293
|
+
await context.addInitScript({ content: opts.initScript });
|
|
289
294
|
const page = usingCdp
|
|
290
295
|
? await context.newPage()
|
|
291
296
|
: (context.pages()[0] ?? (await context.newPage()));
|
|
292
297
|
if (usingCdp) {
|
|
293
298
|
await page.setViewportSize({ width: opts.viewport.width, height: opts.viewport.height });
|
|
294
|
-
|
|
295
|
-
|
|
299
|
+
// deviceScaleFactor + reducedMotion can't be set on an already-launched
|
|
300
|
+
// CDP context, but emulateMedia (colorScheme + reduced-motion) still can.
|
|
301
|
+
if (opts.colorScheme || opts.reducedMotion) {
|
|
302
|
+
await page.emulateMedia({
|
|
303
|
+
colorScheme: opts.colorScheme,
|
|
304
|
+
reducedMotion: opts.reducedMotion ? "reduce" : undefined,
|
|
305
|
+
});
|
|
306
|
+
}
|
|
296
307
|
}
|
|
297
308
|
const waitUntil = typeof opts.waitUntil === "string" ? opts.waitUntil : "load";
|
|
298
309
|
await page.goto(opts.url, { waitUntil, timeout: opts.timeoutMs ?? 30_000 });
|
|
299
310
|
if (typeof opts.waitUntil === "number")
|
|
300
311
|
await page.waitForTimeout(opts.waitUntil);
|
|
312
|
+
// Hide overlays first, then run any user eval (which may depend on, or
|
|
313
|
+
// deliberately override, the hidden state).
|
|
314
|
+
if (opts.hide && opts.hide.length > 0) {
|
|
315
|
+
await page.addStyleTag({ content: `${opts.hide.join(",")}{display:none !important}` });
|
|
316
|
+
}
|
|
317
|
+
if (opts.evalJs)
|
|
318
|
+
await page.evaluate(opts.evalJs);
|
|
301
319
|
const png = opts.selector
|
|
302
320
|
? await page.locator(opts.selector).screenshot({ timeout: opts.timeoutMs ?? 30_000 })
|
|
303
321
|
: await page.screenshot({ fullPage: opts.fullPage === true });
|
|
@@ -12,6 +12,14 @@ export interface RemoteRenderRequest {
|
|
|
12
12
|
fullPage?: boolean;
|
|
13
13
|
colorScheme?: "dark" | "light";
|
|
14
14
|
waitUntil?: "load" | "domcontentloaded" | "networkidle" | number;
|
|
15
|
+
/** CSS selectors hidden (display:none) server-side before capture. */
|
|
16
|
+
hide?: string[];
|
|
17
|
+
/**
|
|
18
|
+
* Best-effort reduced-motion on the remote backend: the render endpoint has
|
|
19
|
+
* no true media-feature emulation, so it neutralizes animations/transitions
|
|
20
|
+
* with an injected stylesheet (documented gap, like colorScheme).
|
|
21
|
+
*/
|
|
22
|
+
reducedMotion?: boolean;
|
|
15
23
|
}
|
|
16
24
|
export interface RemoteRenderOptions {
|
|
17
25
|
apiUrl: string;
|
package/dist/screenshot.d.ts
CHANGED
|
@@ -2,6 +2,23 @@ import { captureRemote } from "./screenshot-remote.js";
|
|
|
2
2
|
import type { DetectRoots } from "./screenshot-local.js";
|
|
3
3
|
export type ScreenshotBackend = "auto" | "local" | "remote";
|
|
4
4
|
export type WaitUntil = "load" | "domcontentloaded" | "networkidle" | number;
|
|
5
|
+
/**
|
|
6
|
+
* Host selectors for framework dev toolbars/overlays that otherwise pollute a
|
|
7
|
+
* screenshot of a running dev server. Hidden (display:none) automatically when
|
|
8
|
+
* the target is a localhost/private URL, unless `--no-hide-dev-tools`. Hiding
|
|
9
|
+
* the custom-element host also hides its shadow-DOM contents, so a single
|
|
10
|
+
* host-level rule is enough for the web-component toolbars.
|
|
11
|
+
*/
|
|
12
|
+
export declare const DEV_TOOLBAR_SELECTORS: readonly string[];
|
|
13
|
+
/**
|
|
14
|
+
* Reject a `--hide` selector that could break out of the generated
|
|
15
|
+
* `selector{display:none}` rule (or inject markup server-side). The selectors
|
|
16
|
+
* are the caller's own, so this guards against footguns, not a trust boundary.
|
|
17
|
+
* `@` is rejected too: a leading at-rule (e.g. `@import url(...);*`) needs no
|
|
18
|
+
* braces to smuggle an `@import` into the injected stylesheet — and `@` is
|
|
19
|
+
* never valid in a CSS selector anyway.
|
|
20
|
+
*/
|
|
21
|
+
export declare function assertHideSelector(selector: string): void;
|
|
5
22
|
export interface ScreenshotViewport {
|
|
6
23
|
width: number;
|
|
7
24
|
height: number;
|
|
@@ -41,6 +58,19 @@ export interface CaptureScreenshotOptions {
|
|
|
41
58
|
fullPage?: boolean;
|
|
42
59
|
colorScheme?: "dark" | "light";
|
|
43
60
|
waitUntil?: WaitUntil;
|
|
61
|
+
/** Extra CSS selectors to hide (display:none) before capture. */
|
|
62
|
+
hide?: string[];
|
|
63
|
+
/**
|
|
64
|
+
* Auto-hide known framework dev toolbars. Defaults to on for localhost/
|
|
65
|
+
* private-network targets and off otherwise; pass `false` to opt out.
|
|
66
|
+
*/
|
|
67
|
+
hideDevTools?: boolean;
|
|
68
|
+
/** Emulate prefers-reduced-motion: reduce so CSS/JS animations settle. */
|
|
69
|
+
reducedMotion?: boolean;
|
|
70
|
+
/** Run this JS in the page after settle, before capture (local backend only). */
|
|
71
|
+
evalJs?: string;
|
|
72
|
+
/** Inject this JS as an init script before navigation (local backend only). */
|
|
73
|
+
initScript?: string;
|
|
44
74
|
apiUrl: string;
|
|
45
75
|
token: string;
|
|
46
76
|
/** Injectable for tests; forwarded to detectLocalBrowser. */
|
|
@@ -55,6 +85,10 @@ export interface CaptureScreenshotOptions {
|
|
|
55
85
|
fullPage?: boolean;
|
|
56
86
|
colorScheme?: "dark" | "light";
|
|
57
87
|
waitUntil: WaitUntil;
|
|
88
|
+
hide?: string[];
|
|
89
|
+
reducedMotion?: boolean;
|
|
90
|
+
evalJs?: string;
|
|
91
|
+
initScript?: string;
|
|
58
92
|
detectRoots?: DetectRoots;
|
|
59
93
|
/** Pre-computed detection result from auto-routing, to avoid a second fs scan. */
|
|
60
94
|
detectResult?: import("./screenshot-local.js").DetectResult;
|
package/dist/screenshot.js
CHANGED
|
@@ -13,6 +13,35 @@ import { basename, resolve as resolvePath } from "node:path";
|
|
|
13
13
|
import { pathToFileURL } from "node:url";
|
|
14
14
|
import { UploadsError } from "./errors.js";
|
|
15
15
|
import { captureRemote, MAX_REMOTE_HTML_BYTES } from "./screenshot-remote.js";
|
|
16
|
+
/**
|
|
17
|
+
* Host selectors for framework dev toolbars/overlays that otherwise pollute a
|
|
18
|
+
* screenshot of a running dev server. Hidden (display:none) automatically when
|
|
19
|
+
* the target is a localhost/private URL, unless `--no-hide-dev-tools`. Hiding
|
|
20
|
+
* the custom-element host also hides its shadow-DOM contents, so a single
|
|
21
|
+
* host-level rule is enough for the web-component toolbars.
|
|
22
|
+
*/
|
|
23
|
+
export const DEV_TOOLBAR_SELECTORS = [
|
|
24
|
+
"astro-dev-toolbar", // Astro
|
|
25
|
+
"#__next-build-watcher", // Next.js (legacy build-activity indicator)
|
|
26
|
+
"nextjs-portal", // Next.js dev overlay / indicators (App Router)
|
|
27
|
+
"#nuxt-devtools-anchor", // Nuxt DevTools launcher
|
|
28
|
+
"#nuxt-devtools-container",
|
|
29
|
+
"vite-plugin-checker-error-overlay", // vite-plugin-checker overlay
|
|
30
|
+
"vite-error-overlay", // Vite HMR error overlay
|
|
31
|
+
];
|
|
32
|
+
/**
|
|
33
|
+
* Reject a `--hide` selector that could break out of the generated
|
|
34
|
+
* `selector{display:none}` rule (or inject markup server-side). The selectors
|
|
35
|
+
* are the caller's own, so this guards against footguns, not a trust boundary.
|
|
36
|
+
* `@` is rejected too: a leading at-rule (e.g. `@import url(...);*`) needs no
|
|
37
|
+
* braces to smuggle an `@import` into the injected stylesheet — and `@` is
|
|
38
|
+
* never valid in a CSS selector anyway.
|
|
39
|
+
*/
|
|
40
|
+
export function assertHideSelector(selector) {
|
|
41
|
+
if (selector.length === 0 || /[@{}<>]/.test(selector)) {
|
|
42
|
+
throw new UploadsError(`invalid hide selector: ${JSON.stringify(selector)} (a CSS selector, no @, {, }, <, or >)`, "USAGE");
|
|
43
|
+
}
|
|
44
|
+
}
|
|
16
45
|
export const DEFAULT_SCREENSHOT_VIEWPORT = {
|
|
17
46
|
width: 1280,
|
|
18
47
|
height: 800,
|
|
@@ -159,6 +188,13 @@ export async function captureScreenshot(opts) {
|
|
|
159
188
|
// is sent to the remote backend as an inline `html` body (though anything
|
|
160
189
|
// it references via file:// or relative paths won't resolve there).
|
|
161
190
|
const localOnly = target.kind === "url" && target.localOnly;
|
|
191
|
+
// Auto-hide framework dev toolbars only makes sense for a running dev server
|
|
192
|
+
// (a localhost/private URL); default off elsewhere. Combine with any
|
|
193
|
+
// explicit --hide selectors into one list shared by both backends.
|
|
194
|
+
const autoHideDevTools = opts.hideDevTools ?? localOnly;
|
|
195
|
+
for (const sel of opts.hide ?? [])
|
|
196
|
+
assertHideSelector(sel);
|
|
197
|
+
const hide = [...(opts.hide ?? []), ...(autoHideDevTools ? DEV_TOOLBAR_SELECTORS : [])];
|
|
162
198
|
// Populated only when auto-routing actually probes the filesystem, so it
|
|
163
199
|
// can be threaded into captureLocalImpl below to avoid a second scan.
|
|
164
200
|
let detected;
|
|
@@ -192,6 +228,12 @@ export async function captureScreenshot(opts) {
|
|
|
192
228
|
if (backend === "remote" && typeof waitUntil === "number") {
|
|
193
229
|
throw new UploadsError(`numeric --wait (${waitUntil}ms) is local-only — use --via local, or one of load/domcontentloaded/networkidle for the remote backend`, "USAGE");
|
|
194
230
|
}
|
|
231
|
+
// Arbitrary pre-capture JS runs only on the local backend — the shared
|
|
232
|
+
// remote renderer intentionally has no eval escape hatch (different security
|
|
233
|
+
// posture). Fail fast rather than silently dropping it.
|
|
234
|
+
if (backend === "remote" && (opts.evalJs !== undefined || opts.initScript !== undefined)) {
|
|
235
|
+
throw new UploadsError("--eval and --init-script are local-only — use --via local", "USAGE");
|
|
236
|
+
}
|
|
195
237
|
if (backend === "local") {
|
|
196
238
|
const captureLocalImpl = opts.captureLocalImpl ??
|
|
197
239
|
(async (localOpts) => {
|
|
@@ -207,6 +249,10 @@ export async function captureScreenshot(opts) {
|
|
|
207
249
|
fullPage: opts.fullPage,
|
|
208
250
|
colorScheme: opts.colorScheme,
|
|
209
251
|
waitUntil,
|
|
252
|
+
hide,
|
|
253
|
+
reducedMotion: opts.reducedMotion,
|
|
254
|
+
evalJs: opts.evalJs,
|
|
255
|
+
initScript: opts.initScript,
|
|
210
256
|
detectRoots: opts.detectRoots,
|
|
211
257
|
detectResult: detected,
|
|
212
258
|
});
|
|
@@ -226,6 +272,8 @@ export async function captureScreenshot(opts) {
|
|
|
226
272
|
fullPage: opts.fullPage,
|
|
227
273
|
colorScheme: opts.colorScheme,
|
|
228
274
|
waitUntil,
|
|
275
|
+
...(hide.length > 0 ? { hide } : {}),
|
|
276
|
+
...(opts.reducedMotion ? { reducedMotion: true } : {}),
|
|
229
277
|
}, { apiUrl: opts.apiUrl, token: opts.token });
|
|
230
278
|
return { png, filename, backend };
|
|
231
279
|
}
|