diff --git a/sdk/node/README.md b/sdk/node/README.md
index 187f0bc..ac9377f 100644
--- a/sdk/node/README.md
+++ b/sdk/node/README.md
@@ -121,37 +121,6 @@ const combined = await snap.capture({
});
```
-### JavaScript Injection
-
-```typescript
-// Execute custom JavaScript before capture
-const interactiveScreenshot = await snap.capture({
- url: 'https://example.com',
- js: `
- // Dismiss modal popup
- document.querySelector('.modal-overlay')?.remove();
-
- // Scroll to specific content
- window.scrollTo(0, 500);
-
- // Click button to reveal content
- document.querySelector('#show-more-btn')?.click();
-
- // Wait for animation to complete
- await new Promise(resolve => setTimeout(resolve, 1000));
- `,
-});
-
-// Combine with other options for complex scenarios
-const complexCapture = await snap.capture({
- url: 'https://example.com/app',
- js: 'document.querySelector(".sidebar").style.display = "none";',
- css: 'body { zoom: 0.8 }',
- waitForSelector: '#content-loaded',
- hideSelectors: ['.ad-banner', '.cookie-notice'],
-});
-```
-
## API Reference
### `new SnapAPI(apiKey, config?)`
diff --git a/sdk/python/README.md b/sdk/python/README.md
index b314da0..1874b4b 100644
--- a/sdk/python/README.md
+++ b/sdk/python/README.md
@@ -125,37 +125,6 @@ combined = snap.capture(
)
```
-### JavaScript Injection
-
-```python
-# Execute custom JavaScript before capture
-interactive_screenshot = snap.capture(
- "https://example.com",
- js="""
- // Dismiss modal popup
- document.querySelector('.modal-overlay')?.remove();
-
- // Scroll to specific content
- window.scrollTo(0, 500);
-
- // Click button to reveal content
- document.querySelector('#show-more-btn')?.click();
-
- // Wait for animation to complete
- await new Promise(resolve => setTimeout(resolve, 1000));
- """,
-)
-
-# Combine with other options for complex scenarios
-complex_capture = snap.capture(
- "https://example.com/app",
- js='document.querySelector(".sidebar").style.display = "none";',
- css="body { zoom: 0.8 }",
- wait_for_selector="#content-loaded",
- hide_selectors=[".ad-banner", ".cookie-notice"],
-)
-```
-
### Error Handling
```python
diff --git a/src/routes/__tests__/screenshot.test.ts b/src/routes/__tests__/screenshot.test.ts
index e6e47c1..cc43b64 100644
--- a/src/routes/__tests__/screenshot.test.ts
+++ b/src/routes/__tests__/screenshot.test.ts
@@ -624,249 +624,4 @@ describe('Screenshot Route', () => {
}))
})
})
-
- describe('JavaScript injection (js parameter)', () => {
- it('should accept js parameter in POST request', async () => {
- const mockBuffer = Buffer.from('fake-screenshot-data')
- mockTakeScreenshot.mockResolvedValueOnce({
- buffer: mockBuffer,
- contentType: 'image/png'
- })
-
- const jsCode = 'document.body.style.background = "red";'
- const req = createMockRequest({
- url: "https://example.com",
- js: jsCode
- })
- const res = createMockResponse()
-
- const handler = screenshotRouter.stack.find(layer =>
- layer.route?.methods.post
- )?.route.stack[0].handle
- await handler(req, res, vi.fn())
-
- expect(mockTakeScreenshot).toHaveBeenCalledWith(expect.objectContaining({
- js: jsCode
- }))
- })
-
- it('should accept js parameter in GET request', async () => {
- const mockBuffer = Buffer.from('fake-screenshot-data')
- mockTakeScreenshot.mockResolvedValueOnce({
- buffer: mockBuffer,
- contentType: 'image/png'
- })
-
- const jsCode = 'window.scrollTo(0, 500);'
- const req = createMockRequest({
- url: "https://example.com",
- js: jsCode
- }, { method: 'GET' })
- const res = createMockResponse()
-
- const handler = screenshotRouter.stack.find(layer =>
- layer.route?.methods.get
- )?.route.stack[0].handle
- await handler(req, res, vi.fn())
-
- expect(mockTakeScreenshot).toHaveBeenCalledWith(expect.objectContaining({
- js: jsCode
- }))
- })
-
- it('should reject js parameter longer than 5000 characters', async () => {
- const longJs = 'a'.repeat(5001)
- const req = createMockRequest({
- url: "https://example.com",
- js: longJs
- })
- const res = createMockResponse()
-
- const handler = screenshotRouter.stack.find(layer =>
- layer.route?.methods.post
- )?.route.stack[0].handle
- await handler(req, res, vi.fn())
-
- expect(res.status).toHaveBeenCalledWith(400)
- expect(res.json).toHaveBeenCalledWith({
- error: "js: maximum 5000 characters allowed"
- })
- })
-
- it('should handle JS_EXECUTION_ERROR from service', async () => {
- mockTakeScreenshot.mockRejectedValueOnce(new Error('JS_EXECUTION_ERROR: Script failed'))
-
- const req = createMockRequest({
- url: "https://example.com",
- js: "throw new Error('test error');"
- })
- const res = createMockResponse()
-
- const handler = screenshotRouter.stack.find(layer =>
- layer.route?.methods.post
- )?.route.stack[0].handle
- await handler(req, res, vi.fn())
-
- expect(res.status).toHaveBeenCalledWith(400)
- expect(res.json).toHaveBeenCalledWith({
- error: "JS_EXECUTION_ERROR: Script failed"
- })
- })
-
- it('should handle JS_TIMEOUT error from service', async () => {
- mockTakeScreenshot.mockRejectedValueOnce(new Error('JS_EXECUTION_ERROR: JS_TIMEOUT'))
-
- const req = createMockRequest({
- url: "https://example.com",
- js: "while(true) {}"
- })
- const res = createMockResponse()
-
- const handler = screenshotRouter.stack.find(layer =>
- layer.route?.methods.post
- )?.route.stack[0].handle
- await handler(req, res, vi.fn())
-
- expect(res.status).toHaveBeenCalledWith(400)
- expect(res.json).toHaveBeenCalledWith({
- error: "JS_EXECUTION_ERROR: JS_TIMEOUT"
- })
- })
-
- describe('selector parameter', () => {
- it('should pass selector parameter to takeScreenshot service', async () => {
- const mockBuffer = Buffer.from('element-screenshot')
- mockTakeScreenshot.mockResolvedValueOnce({
- buffer: mockBuffer,
- contentType: 'image/png'
- })
-
- const req = createMockRequest({
- url: "https://example.com",
- selector: "#main-content"
- })
- const res = createMockResponse()
-
- const handler = screenshotRouter.stack.find(layer =>
- layer.route?.methods.post
- )?.route.stack[0].handle
- await handler(req, res, vi.fn())
-
- expect(mockTakeScreenshot).toHaveBeenCalledWith(
- expect.objectContaining({
- url: "https://example.com",
- selector: "#main-content"
- })
- )
- expect(res.send).toHaveBeenCalledWith(mockBuffer)
- })
-
- it('should return 400 when selector and fullPage are both provided', async () => {
- const req = createMockRequest({
- url: "https://example.com",
- selector: "#content",
- fullPage: true
- })
- const res = createMockResponse()
-
- const handler = screenshotRouter.stack.find(layer =>
- layer.route?.methods.post
- )?.route.stack[0].handle
- await handler(req, res, vi.fn())
-
- expect(res.status).toHaveBeenCalledWith(400)
- expect(res.json).toHaveBeenCalledWith({
- error: "selector and fullPage are mutually exclusive"
- })
- })
-
- it('should handle SELECTOR_NOT_FOUND error from service', async () => {
- mockTakeScreenshot.mockRejectedValueOnce(new Error('SELECTOR_NOT_FOUND'))
-
- const req = createMockRequest({
- url: "https://example.com",
- selector: "#nonexistent"
- })
- const res = createMockResponse()
-
- const handler = screenshotRouter.stack.find(layer =>
- layer.route?.methods.post
- )?.route.stack[0].handle
- await handler(req, res, vi.fn())
-
- expect(res.status).toHaveBeenCalledWith(400)
- expect(res.json).toHaveBeenCalledWith({
- error: "Element not found: #nonexistent"
- })
- })
- })
- })
-
- describe('GET /v1/screenshot selector parameter', () => {
- it('should pass selector from query string to takeScreenshot service', async () => {
- const mockBuffer = Buffer.from('element-screenshot')
- mockTakeScreenshot.mockResolvedValueOnce({
- buffer: mockBuffer,
- contentType: 'image/png'
- })
-
- const req = createMockRequest({
- url: "https://example.com",
- selector: ".main-section"
- }, { method: 'GET' })
- const res = createMockResponse()
-
- const handler = screenshotRouter.stack.find(layer =>
- layer.route?.methods.get
- )?.route.stack[0].handle
- await handler(req, res, vi.fn())
-
- expect(mockTakeScreenshot).toHaveBeenCalledWith(
- expect.objectContaining({
- url: "https://example.com",
- selector: ".main-section"
- })
- )
- expect(res.send).toHaveBeenCalledWith(mockBuffer)
- })
-
- it('should return 400 when selector and fullPage are both provided in GET request', async () => {
- const req = createMockRequest({
- url: "https://example.com",
- selector: "#content",
- fullPage: "true"
- }, { method: 'GET' })
- const res = createMockResponse()
-
- const handler = screenshotRouter.stack.find(layer =>
- layer.route?.methods.get
- )?.route.stack[0].handle
- await handler(req, res, vi.fn())
-
- expect(res.status).toHaveBeenCalledWith(400)
- expect(res.json).toHaveBeenCalledWith({
- error: "selector and fullPage are mutually exclusive"
- })
- })
-
- it('should handle SELECTOR_NOT_FOUND error from service in GET request', async () => {
- mockTakeScreenshot.mockRejectedValueOnce(new Error('SELECTOR_NOT_FOUND'))
-
- const req = createMockRequest({
- url: "https://example.com",
- selector: "#missing"
- }, { method: 'GET' })
- const res = createMockResponse()
-
- const handler = screenshotRouter.stack.find(layer =>
- layer.route?.methods.get
- )?.route.stack[0].handle
- await handler(req, res, vi.fn())
-
- expect(res.status).toHaveBeenCalledWith(400)
- expect(res.json).toHaveBeenCalledWith({
- error: "Element not found: #missing"
- })
- })
- })
})
\ No newline at end of file
diff --git a/src/routes/screenshot.ts b/src/routes/screenshot.ts
index ee3a7a4..bc167bb 100644
--- a/src/routes/screenshot.ts
+++ b/src/routes/screenshot.ts
@@ -80,16 +80,6 @@ export const screenshotRouter = Router();
* maxLength: 5000
* description: Custom CSS to inject into the page before capture (max 5000 chars)
* example: "body { background: #1a1a2e !important; color: #eee !important }"
- * js:
- * type: string
- * maxLength: 5000
- * description: Custom JavaScript code to execute on the page before capture (max 5000 chars, 5-second timeout)
- * example: "document.querySelector('.modal').remove(); window.scrollTo(0, 500);"
- * selector:
- * type: string
- * maxLength: 200
- * description: CSS selector for element to capture instead of full page/viewport (max 200 chars)
- * example: "#main-content"
* hideSelectors:
* oneOf:
* - type: string
@@ -117,9 +107,6 @@ export const screenshotRouter = Router();
* mobile:
* summary: Mobile viewport
* value: { "url": "https://example.com", "width": 375, "height": 812, "deviceScale": 2 }
- * element:
- * summary: Element screenshot
- * value: { "url": "https://github.com", "selector": "#readme" }
* responses:
* 200:
* description: Screenshot image binary
@@ -259,20 +246,6 @@ export const screenshotRouter = Router();
* maxLength: 5000
* description: Custom CSS to inject into the page before capture (max 5000 chars)
* example: "body { background: #1a1a2e !important }"
- * - name: js
- * in: query
- * schema:
- * type: string
- * maxLength: 5000
- * description: Custom JavaScript code to execute on the page before capture (max 5000 chars, 5-second timeout)
- * example: "document.querySelector('.modal').remove();"
- * - name: selector
- * in: query
- * schema:
- * type: string
- * maxLength: 200
- * description: CSS selector for element to capture instead of full page/viewport (max 200 chars)
- * example: "#main-content"
* - name: darkMode
* in: query
* schema:
@@ -358,8 +331,6 @@ async function handleScreenshotRequest(req: any, res: any) {
darkMode,
hideSelectors,
css,
- js,
- selector,
} = source;
if (!url || typeof url !== "string") {
@@ -373,12 +344,6 @@ async function handleScreenshotRequest(req: any, res: any) {
return;
}
- // Validate js parameter
- if (js && typeof js === 'string' && js.length > 5000) {
- res.status(400).json({ error: "js: maximum 5000 characters allowed" });
- return;
- }
-
// Normalize hideSelectors: string | string[] → string[]
let normalizedHideSelectors: string[] | undefined;
if (hideSelectors) {
@@ -401,12 +366,6 @@ async function handleScreenshotRequest(req: any, res: any) {
}
}
- // Check mutual exclusivity of selector and fullPage
- if (selector && (fullPage === true || fullPage === "true")) {
- res.status(400).json({ error: "selector and fullPage are mutually exclusive" });
- return;
- }
-
// Normalize parameters
const params = {
url,
@@ -423,8 +382,6 @@ async function handleScreenshotRequest(req: any, res: any) {
darkMode: darkMode === true || darkMode === "true",
hideSelectors: normalizedHideSelectors,
css: css || undefined,
- js: js || undefined,
- selector: selector || undefined,
};
try {
@@ -466,11 +423,7 @@ async function handleScreenshotRequest(req: any, res: any) {
res.status(504).json({ error: "Screenshot timed out. The page may be too slow to load." });
return;
}
- if (err.message === "SELECTOR_NOT_FOUND") {
- res.status(400).json({ error: `Element not found: ${selector}` });
- return;
- }
- if (err.message.includes("blocked") || err.message.includes("not allowed") || err.message.includes("Invalid URL") || err.message.includes("Could not resolve") || err.message.includes("JS_EXECUTION_ERROR")) {
+ if (err.message.includes("blocked") || err.message.includes("not allowed") || err.message.includes("Invalid URL") || err.message.includes("Could not resolve")) {
res.status(400).json({ error: err.message });
return;
}
diff --git a/src/services/__tests__/screenshot.test.ts b/src/services/__tests__/screenshot.test.ts
index 2e7b77b..e9ae600 100644
--- a/src/services/__tests__/screenshot.test.ts
+++ b/src/services/__tests__/screenshot.test.ts
@@ -410,184 +410,4 @@ describe('Screenshot Service', () => {
expect(releasePage).not.toHaveBeenCalled()
})
})
-
- describe('JavaScript injection (js parameter)', () => {
- beforeEach(() => {
- mockPage.evaluate = vi.fn().mockResolvedValue(undefined)
- })
-
- it('executes custom JavaScript when js parameter is provided', async () => {
- const jsCode = 'document.body.style.background = "red";'
-
- await takeScreenshot({
- url: 'https://example.com',
- js: jsCode
- })
-
- expect(mockPage.evaluate).toHaveBeenCalledWith(jsCode)
- })
-
- it('does not call page.evaluate when js parameter is not provided', async () => {
- await takeScreenshot({ url: 'https://example.com' })
-
- expect(mockPage.evaluate).not.toHaveBeenCalled()
- })
-
- it('executes JavaScript after delay but before CSS injection', async () => {
- const jsCode = 'window.scrollTo(0, 100);'
- const cssCode = 'body { color: red; }'
-
- await takeScreenshot({
- url: 'https://example.com',
- delay: 1000,
- js: jsCode,
- css: cssCode
- })
-
- // Check that methods were called in the right order
- expect(mockPage.goto).toHaveBeenCalledBefore(mockPage.evaluate as any)
- expect(mockPage.evaluate).toHaveBeenCalledBefore(mockPage.addStyleTag as any)
- })
-
- it('throws JS_EXECUTION_ERROR when JavaScript execution fails', async () => {
- mockPage.evaluate = vi.fn().mockRejectedValueOnce(new Error('ReferenceError: foo is not defined'))
-
- await expect(takeScreenshot({
- url: 'https://example.com',
- js: 'console.log(foo);'
- })).rejects.toThrow('JS_EXECUTION_ERROR: ReferenceError: foo is not defined')
- })
-
- it('throws JS_TIMEOUT error when JavaScript execution takes too long', async () => {
- // Mock a long-running script that never resolves
- mockPage.evaluate = vi.fn().mockReturnValue(new Promise(() => {}))
-
- await expect(takeScreenshot({
- url: 'https://example.com',
- js: 'while(true) {}'
- })).rejects.toThrow('JS_EXECUTION_ERROR: JS_TIMEOUT')
- })
-
- it('handles JavaScript execution with hideSelectors and CSS', async () => {
- const jsCode = 'document.querySelector(".modal").remove();'
-
- await takeScreenshot({
- url: 'https://example.com',
- js: jsCode,
- hideSelectors: ['.popup'],
- css: 'body { font-size: 16px; }'
- })
-
- expect(mockPage.evaluate).toHaveBeenCalledWith(jsCode)
- expect(mockPage.addStyleTag).toHaveBeenCalledTimes(2) // Once for CSS, once for hideSelectors
- })
- })
-
- describe('selector parameter for element screenshots', () => {
- let mockElement: any
-
- beforeEach(() => {
- mockElement = {
- screenshot: vi.fn().mockResolvedValue(Buffer.from('element-screenshot'))
- }
- mockPage.$ = vi.fn().mockResolvedValue(mockElement)
- })
-
- it('uses element.screenshot() when selector is provided', async () => {
- await takeScreenshot({ url: 'https://example.com', selector: '#main-content' })
- expect(mockPage.$).toHaveBeenCalledWith('#main-content')
- expect(mockElement.screenshot).toHaveBeenCalledWith(
- expect.objectContaining({
- type: 'png',
- encoding: 'binary'
- })
- )
- expect(mockPage.screenshot).not.toHaveBeenCalled()
- })
-
- it('passes through format and quality to element.screenshot()', async () => {
- await takeScreenshot({
- url: 'https://example.com',
- selector: '.widget',
- format: 'jpeg',
- quality: 85
- })
- expect(mockElement.screenshot).toHaveBeenCalledWith(
- expect.objectContaining({
- type: 'jpeg',
- quality: 85,
- encoding: 'binary'
- })
- )
- })
-
- it('throws SELECTOR_NOT_FOUND when element is not found', async () => {
- mockPage.$.mockResolvedValueOnce(null)
- await expect(takeScreenshot({
- url: 'https://example.com',
- selector: '#nonexistent'
- })).rejects.toThrow('SELECTOR_NOT_FOUND')
- })
-
- it('validates selector length (max 200 chars)', async () => {
- const longSelector = 'div'.repeat(100) // 300 chars
- await expect(takeScreenshot({
- url: 'https://example.com',
- selector: longSelector
- })).rejects.toThrow('selector is too long')
- })
-
- it('validates selector for dangerous content (javascript:)', async () => {
- await expect(takeScreenshot({
- url: 'https://example.com',
- selector: 'javascript:alert(1)'
- })).rejects.toThrow('selector contains dangerous content')
- })
-
- it('validates selector for dangerous content (script tag)', async () => {
- await expect(takeScreenshot({
- url: 'https://example.com',
- selector: ''
- })).rejects.toThrow('selector contains dangerous content')
- })
-
- it('rejects selector and fullPage used together', async () => {
- await expect(takeScreenshot({
- url: 'https://example.com',
- selector: '#content',
- fullPage: true
- })).rejects.toThrow('selector and fullPage are mutually exclusive')
- })
-
- it('works with all other parameters (delay, css, hideSelectors, etc.)', async () => {
- await takeScreenshot({
- url: 'https://example.com',
- selector: '#main',
- delay: 1000,
- css: 'body { color: red; }',
- hideSelectors: ['.ad'],
- darkMode: true
- })
-
- expect(mockPage.emulateMediaFeatures).toHaveBeenCalledWith([
- { name: 'prefers-color-scheme', value: 'dark' }
- ])
- expect(mockPage.addStyleTag).toHaveBeenCalledWith({ content: 'body { color: red; }' })
- expect(mockPage.addStyleTag).toHaveBeenCalledWith({ content: '.ad { display: none !important }' })
- expect(mockPage.$).toHaveBeenCalledWith('#main')
- expect(mockElement.screenshot).toHaveBeenCalled()
- })
-
- it('does not use page.screenshot() when selector is provided', async () => {
- await takeScreenshot({ url: 'https://example.com', selector: '.element' })
- expect(mockPage.screenshot).not.toHaveBeenCalled()
- expect(mockElement.screenshot).toHaveBeenCalled()
- })
-
- it('uses page.screenshot() when selector is not provided', async () => {
- await takeScreenshot({ url: 'https://example.com' })
- expect(mockPage.screenshot).toHaveBeenCalled()
- expect(mockPage.$).not.toHaveBeenCalled()
- })
- })
})
diff --git a/src/services/screenshot.ts b/src/services/screenshot.ts
index 3eb85f6..eaa50e3 100644
--- a/src/services/screenshot.ts
+++ b/src/services/screenshot.ts
@@ -17,8 +17,6 @@ export interface ScreenshotOptions {
darkMode?: boolean;
hideSelectors?: string[];
css?: string;
- js?: string;
- selector?: string;
}
export interface ScreenshotResult {
@@ -62,15 +60,6 @@ function validateCSS(css: string): void {
}
}
-function validateSelector(selector: string): void {
- if (selector.length > 200) {
- throw new Error("selector is too long");
- }
- if (selector.includes("javascript:") || selector.includes("