This workflow follows the Execute Workflow Trigger → HTTP Request recipe pattern — see all workflows that pair these two integrations.
The workflow JSON
Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →
{
"name": "browser_control",
"nodes": [
{
"parameters": {
"workflowInputs": {
"values": [
{
"name": "action"
},
{
"name": "url"
},
{
"name": "selector"
},
{
"name": "direction"
},
{
"name": "amount"
},
{
"name": "toSelector"
},
{
"name": "fullPage"
},
{
"name": "waitAfter"
}
]
}
},
"id": "d6929b19-45aa-4fad-bc29-0a5c34480b18",
"name": "When Executed by Another Workflow",
"type": "n8n-nodes-base.executeWorkflowTrigger",
"typeVersion": 1.1,
"position": [
0,
0
]
},
{
"parameters": {
"jsCode": "const input = $input.first().json;\nconst action = input.action;\nconst url = input.url || '';\n\n// For goto_and_html, use /unblock endpoint\nif (action === 'goto_and_html') {\n return [{\n json: {\n action,\n url,\n useUnblock: true,\n unblockBody: {\n url: url,\n content: true,\n cookies: false,\n screenshot: false,\n browserWSEndpoint: false\n }\n }\n }];\n}\n\n// For other actions, use /function endpoint with code\nconst selector = input.selector || '';\nconst direction = input.direction || 'down';\nconst amount = input.amount || 500;\nconst toSelector = input.toSelector || '';\nconst fullPage = input.fullPage === true;\n\nconst helpers = `\n const delay = ms => new Promise(r => setTimeout(r, ms + Math.random() * 500));\n const waitForPageReady = async (page, maxWait = 15000) => {\n const startTime = Date.now();\n let lastLength = 0;\n let stableCount = 0;\n await delay(2000);\n while (Date.now() - startTime < maxWait) {\n await delay(500);\n const currentLength = await page.evaluate(() => document.body?.innerHTML?.length || 0);\n if (currentLength === lastLength && currentLength > 5000) {\n stableCount++;\n if (stableCount >= 3) break;\n } else {\n stableCount = 0;\n lastLength = currentLength;\n }\n }\n };\n`;\n\nlet code = '';\n\nif (action === 'goto_and_content') {\n code = `export default async function ({ page }) {\n ${helpers}\n await page.setViewport({ width: 1920, height: 1080 });\n await page.goto('${url}', { waitUntil: 'domcontentloaded', timeout: 30000 });\n await waitForPageReady(page, 8000);\n const currentUrl = page.url();\n const title = await page.title();\n const content = await page.evaluate(() => { document.querySelectorAll('script, style, noscript, iframe, [hidden]').forEach(el => el.remove()); return document.body.innerText; });\n return { data: { success: true, currentUrl, title, content: content.substring(0, 50000), contentLength: content.length }, type: 'application/json' };\n}`;\n} else if (action === 'screenshot') {\n code = `export default async function ({ page }) {\n ${helpers}\n await page.setViewport({ width: 1920, height: 1080 });\n await page.goto('${url}', { waitUntil: 'domcontentloaded', timeout: 30000 });\n await waitForPageReady(page, 8000);\n const screenshot = await page.screenshot({ fullPage: ${fullPage}, encoding: 'base64' });\n return { data: { success: true, screenshot: 'data:image/png;base64,' + screenshot }, type: 'application/json' };\n}`;\n} else {\n code = `export default async function ({ page }) {\n ${helpers}\n await page.setViewport({ width: 1920, height: 1080 });\n await page.goto('${url || 'about:blank'}', { waitUntil: 'domcontentloaded', timeout: 30000 });\n await waitForPageReady(page, 8000);\n const content = await page.evaluate(() => document.body.innerText);\n return { data: { success: true, content: content.substring(0, 50000) }, type: 'application/json' };\n}`;\n}\n\nreturn [{ json: { code, action, url, useUnblock: false } }];"
},
"id": "build-request",
"name": "Build Request",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
256,
0
]
},
{
"parameters": {
"method": "POST",
"url": "={{ $json.useUnblock ? 'https://production-sfo.browserless.io/unblock?token=YOUR_BROWSERLESS_TOKEN' : 'https://production-sfo.browserless.io/function?token=YOUR_BROWSERLESS_TOKEN' }}",
"sendBody": true,
"contentType": "={{ $json.useUnblock ? 'json' : 'raw' }}",
"specifyBody": "={{ $json.useUnblock ? 'json' : 'string' }}",
"bodyParameters": {
"parameters": [
{}
]
},
"jsonBody": "={{ $json.useUnblock ? JSON.stringify($json.unblockBody) : '{}' }}",
"body": "={{ $json.useUnblock ? '' : $json.code }}",
"rawContentType": "application/javascript",
"options": {
"timeout": 120000
}
},
"id": "http-request",
"name": "Browserless Function",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
512,
0
]
},
{
"parameters": {
"jsCode": "const response = $input.first().json;\nconst buildData = $('Build Request').first().json;\n\n// Handle /unblock endpoint response\nif (buildData.useUnblock) {\n // /unblock returns: { content: 'html...', cookies: [], screenshot: null, ttl: ... }\n const html = response.content || '';\n const hasCaptcha = html.includes('captcha-delivery.com') || html.includes('DataDome');\n \n return [{\n json: {\n data: {\n success: !hasCaptcha,\n blocked: hasCaptcha,\n currentUrl: buildData.url,\n title: '',\n html: html,\n htmlLength: html.length\n },\n type: 'application/json'\n }\n }];\n}\n\n// Handle /function endpoint response\nif (response && typeof response === 'object') {\n return [{ json: response }];\n}\nif (typeof response === 'string') {\n try {\n return [{ json: JSON.parse(response) }];\n } catch (e) {\n return [{ json: { success: false, error: 'Failed to parse response', raw: response } }];\n }\n}\nreturn [{ json: { success: false, error: 'Unexpected response format' } }];"
},
"id": "parse-response",
"name": "Parse Response",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
752,
0
]
}
],
"connections": {
"When Executed by Another Workflow": {
"main": [
[
{
"node": "Build Request",
"type": "main",
"index": 0
}
]
]
},
"Build Request": {
"main": [
[
{
"node": "Browserless Function",
"type": "main",
"index": 0
}
]
]
},
"Browserless Function": {
"main": [
[
{
"node": "Parse Response",
"type": "main",
"index": 0
}
]
]
}
},
"active": false,
"settings": {
"executionOrder": "v1",
"executionTimeout": 120,
"callerPolicy": "workflowsFromSameOwner",
"availableInMCP": false
},
"versionId": "af00b1cf-20ad-4453-943f-40180f635c24",
"id": "55qv1MLCvPQ4Cph3",
"tags": []
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
browser_control. Uses executeWorkflowTrigger, httpRequest. Event-driven trigger; 4 nodes.
Source: https://github.com/ndrkltsk/btfopa-hackathon/blob/e83b52e94369793582ce4c9efd2dc67a0ec3c625/n8n-workflows/browser_control.json — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
02_LLM_Pipeline v1.0. Uses executeWorkflowTrigger, httpRequest, seaTable. Event-driven trigger; 65 nodes.
This template is a powerful, reusable utility for managing stateful, long-running processes. It allows a main workflow to be paused indefinitely at "checkpoints" and then be resumed by external, async
Upload files from any source to your account Kommo or AmoCRM with a simple and reusable workflow. It can split a large file into small ones and upload chunks. Works for Kommo and amoCRM There are 3 re
Remixed Backup your workflows to GitHub from Solomon's work. Check out his templates.
Remixed Backup your workflows to GitHub from Solomon's work. Check out his templates.