From 33e3f7d5d5ba26cebc531de2724a26522ec90af5 Mon Sep 17 00:00:00 2001 From: breken-ai <312387581+breken-ai@users.noreply.github.com> Date: Fri, 25 Sep 2026 20:07:53 -0700 Subject: [PATCH 1/2] fix(scrape): treat --full-page-screenshot as the screenshot format `--full-page-screenshot` was added to the request but not to the output formats, so `firecrawl scrape --full-page-screenshot` printed only the markdown and dropped the screenshot URL. Combined with `--format screenshot` it sent two screenshot formats, which the API rejects with "You may only specify one screenshot format". The full-page screenshot now replaces a plain `screenshot` format and counts as a screenshot when choosing the output shape, the same way `--screenshot` already does. Co-Authored-By: Claude Opus 5.5 (1M context) --- src/__tests__/commands/scrape.test.ts | 47 ++++++++++++++++++++++++++- src/commands/scrape.ts | 11 +++++-- 2 files changed, 55 insertions(+), 3 deletions(-) diff --git a/src/__tests__/commands/scrape.test.ts b/src/__tests__/commands/scrape.test.ts index b9c9606882..53b951e095 100644 --- a/src/__tests__/commands/scrape.test.ts +++ b/src/__tests__/commands/scrape.test.ts @@ -3,7 +3,7 @@ */ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; -import { executeScrape } from '../../commands/scrape'; +import { executeScrape, handleScrapeCommand } from '../../commands/scrape'; import { getClient, isKeylessMode, keylessRequest } from '../../utils/client'; import { initializeConfig } from '../../utils/config'; import { setupTest, teardownTest } from '../utils/mock-client'; @@ -192,6 +192,22 @@ describe('executeScrape', () => { }); }); + it('sends a single screenshot format when --full-page-screenshot is combined with --format screenshot', async () => { + mockClient.scrape.mockResolvedValue({ screenshot: 'https://x/s.png' }); + + await executeScrape({ + url: 'https://example.com', + formats: ['markdown', 'screenshot'], + fullPageScreenshot: true, + }); + + // The API rejects more than one screenshot format. + expect(mockClient.scrape).toHaveBeenCalledWith('https://example.com', { + formats: ['markdown', { type: 'screenshot', fullPage: true }], + integration: 'cli', + }); + }); + it('should include onlyMainContent parameter when provided', async () => { const mockResponse = { markdown: '# Test' }; mockClient.scrape.mockResolvedValue(mockResponse); @@ -519,4 +535,33 @@ describe('executeScrape', () => { }); }); }); + + describe('Full-page screenshot output', () => { + it('prints the screenshot URL alongside markdown for --full-page-screenshot', async () => { + mockClient.scrape.mockResolvedValue({ + markdown: '# Test', + screenshot: 'https://cdn.example.com/full.png', + }); + const write = vi + .spyOn(process.stdout, 'write') + .mockImplementation(() => true); + + try { + await handleScrapeCommand({ + url: 'https://example.com', + formats: ['markdown'], + fullPageScreenshot: true, + }); + const printed = write.mock.calls + .map((call) => String(call[0])) + .join(''); + expect(JSON.parse(printed)).toEqual({ + markdown: '# Test', + screenshot: 'https://cdn.example.com/full.png', + }); + } finally { + write.mockRestore(); + } + }); + }); }); diff --git a/src/commands/scrape.ts b/src/commands/scrape.ts index 5407275df6..706429fac8 100644 --- a/src/commands/scrape.ts +++ b/src/commands/scrape.ts @@ -66,8 +66,12 @@ export async function executeScrape( formats.push(...options.formats); } - // Add screenshot format if requested and not already included + // Add screenshot format if requested and not already included. The API + // accepts only one screenshot format, so a full-page screenshot replaces a + // plain `screenshot` from --format instead of being added next to it. if (options.fullPageScreenshot) { + const plain = formats.indexOf('screenshot'); + if (plain !== -1) formats.splice(plain, 1); formats.push({ type: 'screenshot', fullPage: true }); } else if (options.screenshot && !formats.includes('screenshot')) { formats.push('screenshot'); @@ -225,7 +229,10 @@ export async function handleScrapeCommand( : ['markdown']; // Add screenshot to effective formats if it was requested separately - if (options.screenshot && !effectiveFormats.includes('screenshot')) { + if ( + (options.screenshot || options.fullPageScreenshot) && + !effectiveFormats.includes('screenshot') + ) { effectiveFormats.push('screenshot'); } From 0140582bb725bb07ecd565a72fcc448e123df652 Mon Sep 17 00:00:00 2001 From: breken-ai <312387581+breken-ai@users.noreply.github.com> Date: Fri, 25 Sep 2026 20:20:57 -0700 Subject: [PATCH 2/2] fix(scrape): drop every plain screenshot before adding the full-page one Also cover --screenshot together with --full-page-screenshot. Co-Authored-By: Claude Opus 5.5 (1M context) --- src/__tests__/commands/scrape.test.ts | 31 +++++++++++++++++++++++++++ src/commands/scrape.ts | 5 +++-- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/src/__tests__/commands/scrape.test.ts b/src/__tests__/commands/scrape.test.ts index 53b951e095..9389e31eb7 100644 --- a/src/__tests__/commands/scrape.test.ts +++ b/src/__tests__/commands/scrape.test.ts @@ -192,6 +192,37 @@ describe('executeScrape', () => { }); }); + it('sends a single screenshot format when --screenshot and --full-page-screenshot are both set', async () => { + mockClient.scrape.mockResolvedValue({ screenshot: 'https://x/s.png' }); + + await executeScrape({ + url: 'https://example.com', + formats: ['markdown'], + screenshot: true, + fullPageScreenshot: true, + }); + + expect(mockClient.scrape).toHaveBeenCalledWith('https://example.com', { + formats: ['markdown', { type: 'screenshot', fullPage: true }], + integration: 'cli', + }); + }); + + it('drops every plain screenshot format when a full-page screenshot is requested', async () => { + mockClient.scrape.mockResolvedValue({ screenshot: 'https://x/s.png' }); + + await executeScrape({ + url: 'https://example.com', + formats: ['screenshot', 'markdown', 'screenshot'], + fullPageScreenshot: true, + }); + + expect(mockClient.scrape).toHaveBeenCalledWith('https://example.com', { + formats: ['markdown', { type: 'screenshot', fullPage: true }], + integration: 'cli', + }); + }); + it('sends a single screenshot format when --full-page-screenshot is combined with --format screenshot', async () => { mockClient.scrape.mockResolvedValue({ screenshot: 'https://x/s.png' }); diff --git a/src/commands/scrape.ts b/src/commands/scrape.ts index 706429fac8..df2cc0e806 100644 --- a/src/commands/scrape.ts +++ b/src/commands/scrape.ts @@ -70,8 +70,9 @@ export async function executeScrape( // accepts only one screenshot format, so a full-page screenshot replaces a // plain `screenshot` from --format instead of being added next to it. if (options.fullPageScreenshot) { - const plain = formats.indexOf('screenshot'); - if (plain !== -1) formats.splice(plain, 1); + for (let i = formats.length - 1; i >= 0; i--) { + if (formats[i] === 'screenshot') formats.splice(i, 1); + } formats.push({ type: 'screenshot', fullPage: true }); } else if (options.screenshot && !formats.includes('screenshot')) { formats.push('screenshot');