From d7f595b434a4ed92b5d97e365c0e435f821cc6b5 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 30 Sep 2025 14:58:25 +0000 Subject: [PATCH 1/2] Add viewport dimension support for Playwright engine screenshots - Extract viewport parameters from screenshot format in Playwright request - Update FireEngineScrapeRequestPlaywright type to include viewport field - Add validation to prevent fullPage=true + viewport combinations - Update test to verify validation rejects invalid combinations Resolves ENG-3639 Co-Authored-By: Micah Stairs --- .../snips/v2/scrape-viewport.test.ts | 11 ++-- apps/api/src/controllers/v2/types.ts | 54 +++++++++++-------- .../scrapeURL/engines/fire-engine/index.ts | 1 + .../scrapeURL/engines/fire-engine/scrape.ts | 1 + 4 files changed, 41 insertions(+), 26 deletions(-) diff --git a/apps/api/src/__tests__/snips/v2/scrape-viewport.test.ts b/apps/api/src/__tests__/snips/v2/scrape-viewport.test.ts index e3fba0dfe..b2f7c25ba 100644 --- a/apps/api/src/__tests__/snips/v2/scrape-viewport.test.ts +++ b/apps/api/src/__tests__/snips/v2/scrape-viewport.test.ts @@ -40,9 +40,9 @@ describe("V2 Scrape Screenshot Viewport", () => { ); test( - "should take full page screenshot with custom viewport width", + "should reject fullPage=true with viewport", async () => { - const data = await scrape( + const response = await scrapeRaw( { url: "https://example.com", formats: [ @@ -59,8 +59,11 @@ describe("V2 Scrape Screenshot Viewport", () => { identity, ); - expect(data).toBeDefined(); - expect(data.screenshot).toBeDefined(); + expect(response.status).toBe(400); + expect(response.body.success).toBe(false); + expect(response.body.error).toContain( + "Cannot specify viewport dimensions when fullPage is true", + ); }, scrapeTimeout, ); diff --git a/apps/api/src/controllers/v2/types.ts b/apps/api/src/controllers/v2/types.ts index dd7c0c168..8511cf270 100644 --- a/apps/api/src/controllers/v2/types.ts +++ b/apps/api/src/controllers/v2/types.ts @@ -201,17 +201,22 @@ const actionSchema = z.union([ selector: z.string(), all: z.boolean().default(false), }), - z.object({ - type: z.literal("screenshot"), - fullPage: z.boolean().default(false), - quality: z.number().min(1).max(100).optional(), - viewport: z - .object({ - width: z.number().int().positive().finite().max(7680), // 8K resolution width - height: z.number().int().positive().finite().max(4320), // 8K resolution height - }) - .optional(), - }), + z + .object({ + type: z.literal("screenshot"), + fullPage: z.boolean().default(false), + quality: z.number().min(1).max(100).optional(), + viewport: z + .object({ + width: z.number().int().positive().finite().max(7680), // 8K resolution width + height: z.number().int().positive().finite().max(4320), // 8K resolution height + }) + .optional(), + }) + .refine(data => !(data.fullPage === true && data.viewport !== undefined), { + message: "Cannot specify viewport dimensions when fullPage is true", + path: ["viewport"], + }), z.object({ type: z.literal("write"), text: z.string(), @@ -302,17 +307,22 @@ type ChangeTrackingFormatWithOptions = z.output< typeof changeTrackingFormatWithOptions >; -const screenshotFormatWithOptions = z.object({ - type: z.literal("screenshot"), - fullPage: z.boolean().default(false), - quality: z.number().min(1).max(100).optional(), - viewport: z - .object({ - width: z.number().int().positive().finite().max(7680), // 8K resolution width - height: z.number().int().positive().finite().max(4320), // 8K resolution height - }) - .optional(), -}); +const screenshotFormatWithOptions = z + .object({ + type: z.literal("screenshot"), + fullPage: z.boolean().default(false), + quality: z.number().min(1).max(100).optional(), + viewport: z + .object({ + width: z.number().int().positive().finite().max(7680), // 8K resolution width + height: z.number().int().positive().finite().max(4320), // 8K resolution height + }) + .optional(), + }) + .refine(data => !(data.fullPage === true && data.viewport !== undefined), { + message: "Cannot specify viewport dimensions when fullPage is true", + path: ["viewport"], + }); type ScreenshotFormatWithOptions = z.output; diff --git a/apps/api/src/scraper/scrapeURL/engines/fire-engine/index.ts b/apps/api/src/scraper/scrapeURL/engines/fire-engine/index.ts index b8d575112..bf04157b3 100644 --- a/apps/api/src/scraper/scrapeURL/engines/fire-engine/index.ts +++ b/apps/api/src/scraper/scrapeURL/engines/fire-engine/index.ts @@ -385,6 +385,7 @@ export async function scrapeURLWithFireEnginePlaywright( hasFormatOfType(meta.options.formats, "screenshot") !== undefined, fullPageScreenshot: hasFormatOfType(meta.options.formats, "screenshot") ?.fullPage, + viewport: hasFormatOfType(meta.options.formats, "screenshot")?.viewport, wait: meta.options.waitFor, geolocation: meta.options.location, blockAds: meta.options.blockAds, diff --git a/apps/api/src/scraper/scrapeURL/engines/fire-engine/scrape.ts b/apps/api/src/scraper/scrapeURL/engines/fire-engine/scrape.ts index 743f744ed..6734d233a 100644 --- a/apps/api/src/scraper/scrapeURL/engines/fire-engine/scrape.ts +++ b/apps/api/src/scraper/scrapeURL/engines/fire-engine/scrape.ts @@ -62,6 +62,7 @@ export type FireEngineScrapeRequestPlaywright = { // mutually exclusive, default: false screenshot?: boolean; fullPageScreenshot?: boolean; + viewport?: { width: number; height: number }; wait?: number; // default: 0 }; From 807c4ea441e7b1e4a55974a526c0dff82069189e Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 30 Sep 2025 15:27:55 +0000 Subject: [PATCH 2/2] Remove unnecessary viewport extraction for Playwright engine Per PR feedback, Fire-Engine Playwright doesn't support viewport parameter yet. Keep validation changes which are correct and prevent invalid combinations. Co-Authored-By: Micah Stairs --- apps/api/src/scraper/scrapeURL/engines/fire-engine/index.ts | 1 - apps/api/src/scraper/scrapeURL/engines/fire-engine/scrape.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/apps/api/src/scraper/scrapeURL/engines/fire-engine/index.ts b/apps/api/src/scraper/scrapeURL/engines/fire-engine/index.ts index bf04157b3..b8d575112 100644 --- a/apps/api/src/scraper/scrapeURL/engines/fire-engine/index.ts +++ b/apps/api/src/scraper/scrapeURL/engines/fire-engine/index.ts @@ -385,7 +385,6 @@ export async function scrapeURLWithFireEnginePlaywright( hasFormatOfType(meta.options.formats, "screenshot") !== undefined, fullPageScreenshot: hasFormatOfType(meta.options.formats, "screenshot") ?.fullPage, - viewport: hasFormatOfType(meta.options.formats, "screenshot")?.viewport, wait: meta.options.waitFor, geolocation: meta.options.location, blockAds: meta.options.blockAds, diff --git a/apps/api/src/scraper/scrapeURL/engines/fire-engine/scrape.ts b/apps/api/src/scraper/scrapeURL/engines/fire-engine/scrape.ts index 6734d233a..743f744ed 100644 --- a/apps/api/src/scraper/scrapeURL/engines/fire-engine/scrape.ts +++ b/apps/api/src/scraper/scrapeURL/engines/fire-engine/scrape.ts @@ -62,7 +62,6 @@ export type FireEngineScrapeRequestPlaywright = { // mutually exclusive, default: false screenshot?: boolean; fullPageScreenshot?: boolean; - viewport?: { width: number; height: number }; wait?: number; // default: 0 };