Skip to content

Commit 22c8589

Browse files
committed
feat(screenshot): adds ability to output screenshot to a specific path. Closes #152. Closes #153
1 parent 4202513 commit 22c8589

File tree

2 files changed

+113
-1
lines changed

2 files changed

+113
-1
lines changed

src/tools/screenshot.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
* SPDX-License-Identifier: Apache-2.0
55
*/
66

7+
import {writeFile} from 'node:fs/promises';
78
import type {ElementHandle, Page} from 'puppeteer-core';
89
import z from 'zod';
910

@@ -34,6 +35,12 @@ export const screenshot = defineTool({
3435
.describe(
3536
'If set to true takes a screenshot of the full page instead of the currently visible viewport. Incompatible with uid.',
3637
),
38+
filePath: z
39+
.string()
40+
.optional()
41+
.describe(
42+
'The path to save the screenshot to. If provided, the screenshot will be saved to this path instead of being attached to the response.',
43+
),
3744
},
3845
handler: async (request, response, context) => {
3946
if (request.params.uid && request.params.fullPage) {
@@ -66,7 +73,12 @@ export const screenshot = defineTool({
6673
);
6774
}
6875

69-
if (screenshot.length >= 2_000_000) {
76+
if (request.params.filePath) {
77+
await writeFile(request.params.filePath, screenshot);
78+
response.appendResponseLine(
79+
`Saved screenshot to ${request.params.filePath}.`,
80+
);
81+
} else if (screenshot.length >= 2_000_000) {
7082
const {filename} = await context.saveTemporaryFile(
7183
screenshot,
7284
`image/${request.params.format}`,

tests/tools/screenshot.test.ts

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
* SPDX-License-Identifier: Apache-2.0
55
*/
66
import assert from 'node:assert';
7+
import {rm, stat, mkdir, chmod} from 'node:fs/promises';
8+
import {tmpdir} from 'node:os';
9+
import {join} from 'node:path';
710
import {describe, it} from 'node:test';
811

912
import {screenshot} from '../../src/tools/screenshot.js';
@@ -108,5 +111,102 @@ describe('screenshot', () => {
108111
);
109112
});
110113
});
114+
115+
it('with filePath', async () => {
116+
await withBrowser(async (response, context) => {
117+
const filePath = join(tmpdir(), 'test-screenshot.png');
118+
try {
119+
const fixture = screenshots.basic;
120+
const page = context.getSelectedPage();
121+
await page.setContent(fixture.html);
122+
await screenshot.handler(
123+
{params: {format: 'png', filePath}},
124+
response,
125+
context,
126+
);
127+
128+
assert.equal(response.images.length, 0);
129+
assert.equal(
130+
response.responseLines.at(0),
131+
"Took a screenshot of the current page's viewport.",
132+
);
133+
assert.equal(
134+
response.responseLines.at(1),
135+
`Saved screenshot to ${filePath}.`,
136+
);
137+
138+
const stats = await stat(filePath);
139+
assert.ok(stats.isFile());
140+
assert.ok(stats.size > 0);
141+
} finally {
142+
await rm(filePath, {force: true});
143+
}
144+
});
145+
});
146+
147+
it('with unwritable filePath', async () => {
148+
const dir = join(tmpdir(), 'readonly-dir-for-screenshot-test');
149+
await mkdir(dir, {recursive: true});
150+
await chmod(dir, 0o500);
151+
const filePath = join(dir, 'test-screenshot.png');
152+
153+
try {
154+
await withBrowser(async (response, context) => {
155+
const fixture = screenshots.basic;
156+
const page = context.getSelectedPage();
157+
await page.setContent(fixture.html);
158+
await screenshot.handler(
159+
{params: {format: 'png', filePath}},
160+
response,
161+
context,
162+
);
163+
164+
assert.equal(response.images.length, 0);
165+
assert.equal(
166+
response.responseLines.at(0),
167+
"Took a screenshot of the current page's viewport.",
168+
);
169+
assert.ok(
170+
response.responseLines
171+
.at(1)
172+
?.startsWith(`Could not write screenshot to ${filePath}.`),
173+
`Expected error message for unwritable path, but got: ${response.responseLines.at(
174+
1,
175+
)}`,
176+
);
177+
});
178+
} finally {
179+
await chmod(dir, 0o700);
180+
await rm(dir, {recursive: true, force: true});
181+
}
182+
});
183+
184+
it('with malformed filePath', async () => {
185+
await withBrowser(async (response, context) => {
186+
const filePath = 'malformed\0path.png';
187+
const fixture = screenshots.basic;
188+
const page = context.getSelectedPage();
189+
await page.setContent(fixture.html);
190+
await screenshot.handler(
191+
{params: {format: 'png', filePath}},
192+
response,
193+
context,
194+
);
195+
196+
assert.equal(response.images.length, 0);
197+
assert.equal(
198+
response.responseLines.at(0),
199+
"Took a screenshot of the current page's viewport.",
200+
);
201+
assert.ok(
202+
response.responseLines
203+
.at(1)
204+
?.startsWith(`Could not write screenshot to ${filePath}.`),
205+
`Expected error message for malformed path, but got: ${response.responseLines.at(
206+
1,
207+
)}`,
208+
);
209+
});
210+
});
111211
});
112212
});

0 commit comments

Comments
 (0)