diff --git a/sdk/node/src/index.ts b/sdk/node/src/index.ts index 662c75e..b70414a 100644 --- a/sdk/node/src/index.ts +++ b/sdk/node/src/index.ts @@ -33,6 +33,10 @@ export interface ScreenshotOptions { delay?: number; /** Page load event to wait for (default: domcontentloaded) */ waitUntil?: "load" | "domcontentloaded" | "networkidle0" | "networkidle2"; + /** Emulate dark mode (prefers-color-scheme: dark) */ + darkMode?: boolean; + /** CSS selectors to hide before capture (max 10, each max 200 chars) */ + hideSelectors?: string | string[]; } export interface SnapAPIConfig { @@ -108,6 +112,18 @@ export class SnapAPI { * format: 'png', * deviceScale: 2 * }); + * + * // Dark mode with hidden elements + * const darkScreenshot = await snap.capture({ + * url: 'https://example.com', + * darkMode: true, + * hideSelectors: ['.ads', '.popup', '.cookie-banner'] + * }); + * + * // Hide single selector + * const clean = await snap.capture('https://example.com', { + * hideSelectors: '.advertisement' + * }); * ``` * * @throws {SnapAPIError} When the API returns an error response diff --git a/sdk/node/test/snapapi.test.ts b/sdk/node/test/snapapi.test.ts index d87c545..74e2cd1 100644 --- a/sdk/node/test/snapapi.test.ts +++ b/sdk/node/test/snapapi.test.ts @@ -130,6 +130,98 @@ describe('SnapAPI', () => { ); }); + it('sends darkMode parameter correctly', async () => { + const mockArrayBuffer = new ArrayBuffer(100); + mockFetch.mockResolvedValueOnce({ + ok: true, + arrayBuffer: () => Promise.resolve(mockArrayBuffer), + }); + + await snap.capture('https://example.com', { + darkMode: true, + }); + + expect(mockFetch).toHaveBeenCalledWith( + 'https://snapapi.eu/v1/screenshot', + expect.objectContaining({ + body: JSON.stringify({ + url: 'https://example.com', + darkMode: true, + }), + }) + ); + }); + + it('sends hideSelectors as string correctly', async () => { + const mockArrayBuffer = new ArrayBuffer(100); + mockFetch.mockResolvedValueOnce({ + ok: true, + arrayBuffer: () => Promise.resolve(mockArrayBuffer), + }); + + await snap.capture('https://example.com', { + hideSelectors: '.ads', + }); + + expect(mockFetch).toHaveBeenCalledWith( + 'https://snapapi.eu/v1/screenshot', + expect.objectContaining({ + body: JSON.stringify({ + url: 'https://example.com', + hideSelectors: '.ads', + }), + }) + ); + }); + + it('sends hideSelectors as array correctly', async () => { + const mockArrayBuffer = new ArrayBuffer(100); + mockFetch.mockResolvedValueOnce({ + ok: true, + arrayBuffer: () => Promise.resolve(mockArrayBuffer), + }); + + await snap.capture('https://example.com', { + hideSelectors: ['.ads', '.popup', '.banner'], + }); + + expect(mockFetch).toHaveBeenCalledWith( + 'https://snapapi.eu/v1/screenshot', + expect.objectContaining({ + body: JSON.stringify({ + url: 'https://example.com', + hideSelectors: ['.ads', '.popup', '.banner'], + }), + }) + ); + }); + + it('sends both darkMode and hideSelectors together', async () => { + const mockArrayBuffer = new ArrayBuffer(100); + mockFetch.mockResolvedValueOnce({ + ok: true, + arrayBuffer: () => Promise.resolve(mockArrayBuffer), + }); + + await snap.capture('https://example.com', { + darkMode: true, + hideSelectors: ['.ads', '.tracking'], + format: 'png', + }); + + expect(mockFetch).toHaveBeenCalledWith( + 'https://snapapi.eu/v1/screenshot', + expect.objectContaining({ + body: JSON.stringify({ + url: 'https://example.com', + darkMode: true, + hideSelectors: ['.ads', '.tracking'], + format: 'png', + }), + }) + ); + }); + it('works with ScreenshotOptions object form', async () => { const mockArrayBuffer = new ArrayBuffer(100); mockFetch.mockResolvedValueOnce({ diff --git a/sdk/python/src/snapapi/client.py b/sdk/python/src/snapapi/client.py index fb2fa0e..dc9e877 100644 --- a/sdk/python/src/snapapi/client.py +++ b/sdk/python/src/snapapi/client.py @@ -32,6 +32,8 @@ class ScreenshotOptions: device_scale: Optional[float] = None delay: Optional[int] = None wait_until: Optional[str] = None + dark_mode: Optional[bool] = None + hide_selectors: Optional[list] = None def to_dict(self) -> dict: """Convert to API request body (camelCase keys).""" @@ -46,6 +48,8 @@ class ScreenshotOptions: "device_scale": "deviceScale", "delay": "delay", "wait_until": "waitUntil", + "dark_mode": "darkMode", + "hide_selectors": "hideSelectors", } return { mapping[k]: v @@ -98,6 +102,8 @@ class SnapAPI: device_scale: Optional[float] = None, delay: Optional[int] = None, wait_until: Optional[str] = None, + dark_mode: Optional[bool] = None, + hide_selectors: Optional[list] = None, options: Optional[ScreenshotOptions] = None, ) -> bytes: """Capture a screenshot. @@ -113,6 +119,8 @@ class SnapAPI: device_scale: Device pixel ratio (1-3). delay: Extra delay in ms (0-5000). wait_until: Load event (domcontentloaded, load, networkidle0, networkidle2). + dark_mode: Emulate dark mode (prefers-color-scheme: dark). + hide_selectors: CSS selectors to hide before capture (max 10, each max 200 chars). options: ScreenshotOptions object (alternative to keyword args). Returns: @@ -141,6 +149,13 @@ class SnapAPI: full_page=True, device_scale=2, ) + + # Dark mode with hidden elements + dark_screenshot = snap.capture( + "https://example.com", + dark_mode=True, + hide_selectors=['.ads', '.popup', '.cookie-banner'] + ) """ if options: body = options.to_dict() @@ -160,6 +175,8 @@ class SnapAPI: device_scale=device_scale, delay=delay, wait_until=wait_until, + dark_mode=dark_mode, + hide_selectors=hide_selectors, ) body = opts.to_dict()