How To Streamline Web Tasks by Integrating Browserless, Playwright and ChangeDetection.io with n8n
I recently self-hosted ChangeDetection.io using Browserless and Playwright in Docker. My initial goal was straightforward: set up basic website monitoring with ChangeDetection.io's GUI. I figured I might find it useful to have a way to monitor websites for changes and get alerts on restocks and price drops.
After setting it up, I realized that I had an environment capable of executing Browserless API calls and running Playwright scripts. This opened the door to advanced web automation tasks like generating PDFs, capturing screenshots, and more—all integrated seamlessly with n8n.
Why Integrate ChangeDetection.io, Browserless, Playwright and n8n?
- ChangeDetection.io provides a simple frontend for monitoring website changes.
- Browserless offers a hosted browser solution, enabling headless Chrome automation.
- Playwright is a powerful library for automating browser interactions.
- n8n is a workflow automation tool that connects various services and APIs.
By integrating these tools, it's trivial to have a self-hosted system for web monitoring and automation.
Setting Up ChangeDetection.io with Browserless and Playwright
I stumbled upon a Reddit post that linked to this Gist containing a useful docker-compose.yml
file. This configuration integrates Browserless with Playwright, making it easy to get started.
Securing Your Setup with a Custom Token
Security is important when exposing services like Browserless and Playwright. I modified the Docker Compose file to include a custom token for authentication. Here's how you can do it:
- Generate a Token: Create a secure token that you'll use for authentication.
- Update
docker-compose.yml
: ReplaceYOUR_PLAYWRIGHT_TOKEN
with your generated token in the environment variables.
Docker Compose Configuration
Below is the modified docker-compose.yml
file with a with a Custom Token:
changedetection:
image: ghcr.io/dgtlmoon/changedetection.io:latest
container_name: changedetection
hostname: changedetection
volumes:
- /home/path/files:/datastore # Update path to your data volume
environment:
- PORT=20400 # Port for changedetection
- PUID=1000 # Preferred user ID (avoid root/0 unless necessary in a managed environment)
- PGID=1000 # Preferred group ID (avoid root/0 unless necessary in a managed environment)
- PLAYWRIGHT_DRIVER_URL=ws://playwright-chrome:3000/chrome?token=YOUR_PLAYWRIGHT_TOKEN&launch={"headless":false} # WebSocket connection for Playwright
ports:
- 20400:20400 # Port mapping for changedetection
restart: unless-stopped
depends_on:
- playwright-chrome
playwright-chrome:
hostname: playwright-chrome
image: ghcr.io/browserless/chrome
restart: unless-stopped
environment:
- TOKEN=YOUR_PLAYWRIGHT_TOKEN # Update using a secure token
- SCREEN_WIDTH=1920
- SCREEN_HEIGHT=1024
- SCREEN_DEPTH=16
- ENABLE_DEBUGGER=true
- TIMEOUT=600000
- CONCURRENT=15
ports:
- 20450:3000 # Port mapping for Playwright WebSocket connection
Exploring Browserless API Capabilities
With Browserless and Playwright set up, I explored the Browserless API documentation to see what was possible. Here are some of the key endpoints and their capabilities:
- Load and Render HTML: Use
/content
to load a URL or HTML content and retrieve the fully rendered HTML after JavaScript execution. - Download Files: Use
/download
to execute browser interactions and download files. - Execute Code in Browser: Use
/function
to run custom Playwright code in a browser context, perfect for web scraping tasks. - Generate PDFs: Use
/pdf
to create PDF documents from URLs or HTML content with customizable options. - Capture Screenshots: Use
/screenshot
to take screenshots of webpages, supporting full-page captures and customizations. - Scrape Data: Use
/scrape
to extract specific data using CSS or XPath selectors.
Integrating with n8n for Workflow Automation
In a previous post, I detailed how to self-host n8n on Google Cloud. Building on that, I connected n8n with a self-hosted Browserless and Playwright setup to automate web tasks.
Setting Up Credentials in n8n
To authenticate with your Browserless instance in n8n:
- Create New Credentials:
- Go to Credentials in n8n.
- Choose Header Auth.
- Set the name to
Authorization
and the value toYOUR_PLAYWRIGHT_TOKEN
.
- Use Credentials in Workflows:
- In your HTTP Request nodes, select the Browserless credentials you just created.
Example n8n Workflows
I've created several workflows utilizing different Browserless API endpoints:
- Screenshotting: Capture a screenshot of a URL.
- PDF Generation: Convert a web page into a PDF.
- Content Retrieval: Extract the HTML content from a specific URL.
- Downloading Files: Download files (e.g., ZIPs) from provided links.
- Scraping: Extract elements using CSS or XPath selectors.
You can copy and paste the following workflow template to import all the examples into your n8n instance (also shared on the n8n forum).
{
"name": "Browserless Examples [Shared]",
"nodes": [
{
"parameters": {
"content": "## /Screenshot URL\nhttps://docs.browserless.io/HTTP-APIs/screenshot\n\nHardcoded URL Example.com\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nPassing URL Example.com as a variable",
"height": 552.0382521592817,
"width": 414.07950814059393
},
"id": "3b89c6bb-b9cd-4bcd-8958-4dfbf87e39aa",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-53.09026656387687,
-60
]
},
{
"parameters": {
"content": "## /content return HTML\nhttps://docs.browserless.io/HTTP-APIs/content\n\nHardcoded URL Example.com\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nPassing URL Example.com as a variable",
"height": 549.8248303020775,
"width": 415.7157914610786
},
"id": "e069f9c9-a524-4da0-b0e7-9ddb8f7f2317",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
400,
-60
]
},
{
"parameters": {
"content": "## /download example\n\nHardcoded URL https://getsamplefiles.com/sample-archive-files/zip and downloads the first ZIP file\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nPassing URL, file type and first/last/all as a variable",
"height": 543.2718166583338,
"width": 436.9874746273785
},
"id": "5b5bef89-7cb2-4932-86cf-1cb695e1e46c",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
860,
-60
]
},
{
"parameters": {
"content": "## /function examples",
"height": 543.1338634894774,
"width": 404.2618082176863
},
"id": "f2a40685-047a-4a71-b314-7409ac83cfa3",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-56.54071590859172,
580
]
},
{
"parameters": {
"content": "## /scrape example\nhttps://docs.browserless.io/HTTP-APIs/scrape\n\nHardcoded URL Example.com\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nPassing URL Example.com as a variable",
"height": 549.7523374008159,
"width": 454.9865911527092
},
"id": "1bbb57a1-87cc-48bb-8aa4-e7705d6d434f",
"name": "Sticky Note5",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
860,
580
]
},
{
"parameters": {
"method": "POST",
"url": "http://yourdomain:port/chrome/function",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"sendBody": true,
"contentType": "raw",
"rawContentType": "application/json",
"body": "{\n \"code\": \"export default async function ({ page }) {\\n try {\\n await page.goto('https://www.example.com', { \\n waitUntil: 'networkidle2',\\n timeout: 60000 // 60 seconds timeout\\n });\\n const screenshot = await page.screenshot({ fullPage: true });\\n return {\\n data: screenshot.toString('base64'),\\n type: 'image/png'\\n };\\n } catch (error) {\\n console.error('Error:', error);\\n return {\\n data: `Error: ${error.message}`,\\n type: 'text/plain'\\n };\\n }\\n }\\n\"\n}",
"options": {
}
},
"id": "8a91be2b-03f6-4060-93c7-bcd4e3c0e06f",
"name": "HTTP Request5",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
120,
860
],
"credentials": {
"httpHeaderAuth": {
"id": "NXo0Bj93cIusdtnD",
"name": "Browserless: Header Auth Account"
}
}
},
{
"parameters": {
"jsCode": "// Set the URL in a variable\nconst url = 'https://www.example.com';\n\n// Loop over input items and add the URL to the JSON of each one\nfor (const item of $input.all()) {\n item.json.url = url;\n}\n\n// Return the updated items\nreturn $input.all();"
},
"id": "1233d7c8-6218-4e3c-92ad-b3ed33fde0ba",
"name": "Code",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
0,
320
]
},
{
"parameters": {
"jsCode": "// Set the URL in a variable\nconst url = 'https://www.example.com';\n\n// Loop over input items and add the URL to the JSON of each one\nfor (const item of $input.all()) {\n item.json.url = url;\n}\n\n// Return the updated items\nreturn $input.all();"
},
"id": "be478e2d-0964-416d-9641-9deda9d92dfb",
"name": "Code1",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
440,
320
]
},
{
"parameters": {
"jsCode": "// Set the URL in a variable\nconst url = 'https://www.example.com/';\n\n// Loop over input items and add the URL to the JSON of each one\nfor (const item of $input.all()) {\n item.json.url = url;\n}\n\n// Return the updated items\nreturn $input.all();"
},
"id": "cc78803b-e207-4fd1-b650-1ff8f92054f1",
"name": "Set URL",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
920,
960
]
},
{
"parameters": {
"jsCode": "// Set the URL in a variable\nconst url = 'https://www.example.com';\n\n// Loop over input items and add the URL to the JSON of each one\nfor (const item of $input.all()) {\n item.json.url = url;\n}\n\n// Return the updated items\nreturn $input.all();"
},
"id": "31a1b79b-b10f-48c4-8f43-c30658976ae8",
"name": "Set URL1",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
420,
960
]
},
{
"parameters": {
"content": "## /pdf example\nhttps://docs.browserless.io/HTTP-APIs/pdf\n\nHardcoded URL Example.com\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nPassing URL Example.com as a variable",
"height": 549.7523374008159,
"width": 454.9865911527092
},
"id": "5bd19d9b-258b-4a9d-9664-f648d9cbb8af",
"name": "Sticky Note6",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
380,
580
]
},
{
"parameters": {
"method": "POST",
"url": "http://yourdomain:port/chrome/screenshot",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"sendBody": true,
"contentType": "raw",
"rawContentType": "application/json",
"body": "{ \"url\": \"https://www.example.com\", \"options\": { \"fullPage\": true } }",
"options": {
}
},
"id": "48668f58-fed4-4a74-8b50-57d8dec68eef",
"name": "HTTP Request: Screenshot",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
0,
80
],
"credentials": {
"httpHeaderAuth": {
"id": "NXo0Bj93cIusdtnD",
"name": "Browserless: Header Auth Account"
}
}
},
{
"parameters": {
"method": "POST",
"url": "http://yourdomain:port/chrome/content",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"sendBody": true,
"contentType": "raw",
"rawContentType": "application/json",
"body": "{\n \"url\": \"https://www.example.com\"\n}",
"options": {
}
},
"id": "c473afb5-3b38-4d8a-9bee-4fbb95584a26",
"name": "HTTP Request: Content",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
440,
80
],
"credentials": {
"httpHeaderAuth": {
"id": "NXo0Bj93cIusdtnD",
"name": "Browserless: Header Auth Account"
}
}
},
{
"parameters": {
"method": "POST",
"url": "http://yourdomain:port/chrome/download",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"sendBody": true,
"contentType": "raw",
"rawContentType": "application/json",
"body": "{\n \"code\": \"export default async function ({ page }) {\\n try {\\n await page.goto('https://getsamplefiles.com/sample-archive-files/zip', { waitUntil: 'networkidle2', timeout: 120000 });\\n console.log('Page loaded successfully');\\n const zipLink = await page.$eval('a[href$=\\\\\\\".zip\\\\\\\"]', link => link.href);\\n console.log(`Processing first ZIP link: ${zipLink}`);\\n const response = await page.goto(zipLink, { waitUntil: 'networkidle2', timeout: 120000 });\\n if (response.ok()) {\\n const buffer = await response.buffer();\\n return [{\\n filename: zipLink.split('/').pop(),\\n data: buffer.toString('base64'),\\n type: 'application/zip'\\n }];\\n } else {\\n console.error(`Failed to download: ${response.status()} ${response.statusText()}`);\\n return {\\\"error\\\": `Failed to download: ${response.status()} ${response.statusText()}` };\\n }\\n } catch (error) {\\n console.error('No matching ZIP file found or an error occurred:', error);\\n return {\\\"error\\\": 'No matching ZIP file found or an error occurred' };\\n }\\n}\\n\",\n \"context\": {}\n}",
"options": {
}
},
"id": "15cf6c0b-458c-4a7a-8fc3-6f7cb666b307",
"name": "HTTP Request: Download",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
920,
80
],
"credentials": {
"httpHeaderAuth": {
"id": "NXo0Bj93cIusdtnD",
"name": "Browserless: Header Auth Account"
}
}
},
{
"parameters": {
"method": "POST",
"url": "http://yourdomain:port/chrome/function",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"sendBody": true,
"contentType": "raw",
"rawContentType": "application/json",
"body": "{\n \"code\": \"export default async function ({ page }) {\\n try {\\n const response = await page.goto('https://www.dundeecity.gov.uk/sites/default/files/publications/civic_renewal_forms.zip', { \\n waitUntil: 'networkidle2',\\n timeout: 60000 // 60 seconds timeout\\n });\\n\\n if (response.ok()) {\\n const buffer = await response.buffer();\\n return {\\n data: buffer.toString('base64'),\\n type: 'application/zip'\\n };\\n } else {\\n throw new Error(`Failed to download: ${response.status()} ${response.statusText()}`);\\n }\\n } catch (error) {\\n console.error('Error:', error);\\n return {\\n data: `Error: ${error.message}`,\\n type: 'text/plain'\\n };\\n }\\n}\\n\"\n}",
"options": {
}
},
"id": "92c67358-e23b-4954-bd21-01430b255183",
"name": "HTTP Request: Function",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
120,
680
],
"credentials": {
"httpHeaderAuth": {
"id": "NXo0Bj93cIusdtnD",
"name": "Browserless: Header Auth Account"
}
}
},
{
"parameters": {
"method": "POST",
"url": "http://yourdomain:port/chrome/pdf",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"sendBody": true,
"contentType": "raw",
"rawContentType": "application/json",
"body": "{\n \"url\": \"https://www.example.com\",\n \"options\": {\n \"displayHeaderFooter\": true,\n \"printBackground\": false,\n \"format\": \"A4\"\n }\n}",
"options": {
}
},
"id": "2996a1d2-70c1-469f-8610-c122f0cfc95c",
"name": "HTTP Request: PDF",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
420,
720
],
"credentials": {
"httpHeaderAuth": {
"id": "NXo0Bj93cIusdtnD",
"name": "Browserless: Header Auth Account"
}
}
},
{
"parameters": {
"method": "POST",
"url": "http://yourdomain:port/chrome/scrape",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"sendBody": true,
"contentType": "raw",
"rawContentType": "application/json",
"body": "{\n \"url\": \"https://www.example.com/\",\n \"elements\": [\n { \"selector\": \"h1\" }\n ],\n \"gotoOptions\": {\n \"timeout\": 10000,\n \"waitUntil\": \"networkidle2\"\n }\n}",
"options": {
}
},
"id": "e6b2daae-b3ba-4cc4-b1f5-b58577c3b4f3",
"name": "HTTP Request: Scrape",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
920,
720
],
"credentials": {
"httpHeaderAuth": {
"id": "NXo0Bj93cIusdtnD",
"name": "Browserless: Header Auth Account"
}
}
},
{
"parameters": {
"method": "POST",
"url": "http://yourdomain:port/chrome/screenshot",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"sendBody": true,
"contentType": "raw",
"rawContentType": "application/json",
"body": "={\n \"url\": \"{{$json[\"url\"]}}\",\n \"options\": { \"fullPage\": true }\n}",
"options": {
}
},
"id": "9513a03e-e37c-4d4d-80cb-076e212fcdb9",
"name": "HTTP Request: Screenshot1",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
160,
320
],
"credentials": {
"httpHeaderAuth": {
"id": "NXo0Bj93cIusdtnD",
"name": "Browserless: Header Auth Account"
}
}
},
{
"parameters": {
"method": "POST",
"url": "http://yourdomain:port/chrome/pdf",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"sendBody": true,
"contentType": "raw",
"rawContentType": "application/json",
"body": "={\n \"url\": \"{{$json[\"url\"]}}\",\n \"options\": {\n \"displayHeaderFooter\": true,\n \"printBackground\": false,\n \"format\": \"A4\"\n }\n}",
"options": {
}
},
"id": "4bed427f-0c4e-4c5e-8fb5-611f483e86ed",
"name": "HTTP Request: PDF1",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
580,
960
],
"credentials": {
"httpHeaderAuth": {
"id": "NXo0Bj93cIusdtnD",
"name": "Browserless: Header Auth Account"
}
}
},
{
"parameters": {
"method": "POST",
"url": "http://yourdomain:port/chrome/content",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"sendBody": true,
"contentType": "raw",
"rawContentType": "application/json",
"body": "={\n \"url\": \"{{$json[\"url\"]}}\"\n}",
"options": {
}
},
"id": "73836d1e-638c-4937-a89a-b0c923bbcd52",
"name": "HTTP Request: Content1",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
600,
320
],
"credentials": {
"httpHeaderAuth": {
"id": "NXo0Bj93cIusdtnD",
"name": "Browserless: Header Auth Account"
}
}
},
{
"parameters": {
"method": "POST",
"url": "http://yourdomain:port/chrome/scrape",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"sendBody": true,
"contentType": "raw",
"rawContentType": "application/json",
"body": "={\n \"url\": \"{{$json[\"url\"]}}\",\n \"elements\": [\n { \"selector\": \"h1\" }\n ],\n \"gotoOptions\": {\n \"timeout\": 10000,\n \"waitUntil\": \"networkidle2\"\n }\n}",
"options": {
}
},
"id": "bd0e9889-68d5-4ae0-95b1-96fb937b592d",
"name": "HTTP Request: Scrape1",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
1080,
960
],
"credentials": {
"httpHeaderAuth": {
"id": "NXo0Bj93cIusdtnD",
"name": "Browserless: Header Auth Account"
}
}
},
{
"parameters": {
"method": "POST",
"url": "http://yourdomain:port/chrome/download",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"sendBody": true,
"contentType": "raw",
"rawContentType": "application/json",
"body": "={\n \"code\": \"export default async function ({ page }) {\\n try {\\n await page.goto('{{ $json[\"url\"] }}', { waitUntil: 'networkidle2', timeout: 120000 });\\n console.log('Page loaded successfully');\\n const links = await page.$eval('a[href$=\\\\\\\".{{ $json[\"fileType\"] }}\\\\\\\"]', links => links.map(link => link.href));\\n let selectedLinks = [];\\n if ('{{ $json[\"downloadOption\"] }}' === 'all') {\\n selectedLinks = links;\\n } else if ('{{ $json[\"downloadOption\"] }}' === 'first') {\\n selectedLinks = links.length > 0 ? [links[0]] : [];\\n } else if ('{{ $json[\"downloadOption\"] }}' === 'last') {\\n selectedLinks = links.length > 0 ? [links[links.length - 1]] : [];\\n }\\n const results = [];\\n for (const zipLink of selectedLinks) {\\n console.log(`Processing ${zipLink}`);\\n const response = await page.goto(zipLink, { waitUntil: 'networkidle2', timeout: 120000 });\\n if (response.ok()) {\\n const buffer = await response.buffer();\\n results.push({\\n filename: zipLink.split('/').pop(),\\n data: buffer.toString('base64'),\\n type: 'application/{{ $json[\"fileType\"] }}'\\n });\\n } else {\\n console.error(`Failed to download: ${response.status()} ${response.statusText()}`);\\n results.push({\\\"error\\\": `Failed to download: ${response.status()} ${response.statusText()}` });\\n }\\n }\\n return results;\\n } catch (error) {\\n console.error('No matching {{ $json[\"fileType\"] }} file found or an error occurred:', error);\\n return {\\\"error\\\": 'No matching {{ $json[\"fileType\"] }} file found or an error occurred' };\\n }\\n}\\n\",\n \"context\": {}\n}",
"options": {
}
},
"name": "HTTP Request: Download4",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
1120,
320
],
"id": "8ba0010f-fa97-45cd-928e-01751688705d",
"credentials": {
"httpHeaderAuth": {
"id": "NXo0Bj93cIusdtnD",
"name": "Browserless: Header Auth Account"
}
}
},
{
"parameters": {
"jsCode": "return [\n {\n json: {\n url: 'https://getsamplefiles.com/sample-archive-files/zip',\n fileType: 'zip',\n downloadOption: 'first' // Options: 'all', 'first', 'last'\n }\n }\n];"
},
"id": "8ecdcfac-2d65-45ec-a604-5f49ae66383f",
"name": "Code4",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
920,
320
]
},
{
"parameters": {
"content": "## Note about the URL field\nReplace yourdomain:port with your actual domain.com:portt# like something.com:24000",
"height": 80,
"width": 1341.4849931447006
},
"id": "e4e9705d-4b08-447c-85fa-57d260697ad8",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-60,
-180
]
},
{
"parameters": {
},
"id": "2e4045a9-cb47-4a65-af34-aabaa47ba16c",
"name": "When clicking ‘Test workflow’",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [
1440,
940
]
}
],
"pinData": {
},
"connections": {
"Code": {
"main": [
[
{
"node": "HTTP Request: Screenshot1",
"type": "main",
"index": 0
}
]
]
},
"Code1": {
"main": [
[
{
"node": "HTTP Request: Content1",
"type": "main",
"index": 0
}
]
]
},
"Set URL": {
"main": [
[
{
"node": "HTTP Request: Scrape1",
"type": "main",
"index": 0
}
]
]
},
"Set URL1": {
"main": [
[
{
"node": "HTTP Request: PDF1",
"type": "main",
"index": 0
}
]
]
},
"Code4": {
"main": [
[
{
"node": "HTTP Request: Download4",
"type": "main",
"index": 0
}
]
]
}
},
"active": false,
"settings": {
"executionOrder": "v1"
},
"versionId": "186e16fa-c115-441f-8517-1ce16f3f2695",
"meta": {
"templateCredsSetupCompleted": true,
"instanceId": "84c8cadeffb0e45ffb93507bd03ee1ba65b1274dc2bab04cc058f9e6a2a130e1"
},
"id": "cqCiCDGyUb37qXGd",
"tags": [
]
}
Note: Aside from adding your header authentication credentials, the only other change you’ll need to make to the above workflow is to update the domain and port of your Changedetection.io (or Browserless/Playwright) instance in each node, as mentioned in the sticky note within the template.
Benefits of This Setup
For those relying on third-party services for web automation tasks, this setup offers a self-hosted, cost-effective solution to capture screenshots, generate PDFs, extract HTML content, automate file downloads, and scrape elements from web pages, reducing reliance on third-party services. I hope these tips are helpful for others looking to automate mundane tasks possible with Browserless, Playwright, and n8n.