File Transfers
File uploads and downloads require specific handling when the browser runs on a remote server instead of your local machine. This page covers both directions.
Uploading Files
Puppeteer's Page.uploadFile() reads from the server filesystem, not the client. To upload client-side data, create a virtual file in the browser context using DataTransfer and page.evaluate().
import puppeteer from "puppeteer-core";
import path from "path";
import fs from "fs";
const sleep = (ms) => new Promise((res) => setTimeout(res, ms));
const API_TOKEN = "YOUR_API_TOKEN_HERE";
const BROWSER_WS_ENDPOINT = `wss://production-sfo.browserless.io?token=${API_TOKEN}`;
const fileToUpload = {
name: "image.png",
content: fs
.readFileSync(path.join(process.cwd(), "image.png"))
.toString("base64"),
mimeType: "image/png",
};
const uploadFile = async (page, fileToUpload) => {
await page.evaluate(
({ selector, fileName, mimeType, base64Data }) => {
function b64ToUint8Array(b64) {
const binary = atob(b64);
const bytes = new Uint8Array(binary.length);
for (let i = 0; i < binary.length; i++) {
bytes[i] = binary.charCodeAt(i);
}
return bytes;
}
const input = document.querySelector(selector);
const file = new File([b64ToUint8Array(base64Data)], fileName, {
type: mimeType,
});
const dataTransfer = new DataTransfer();
dataTransfer.items.add(file);
input.files = dataTransfer.files;
const event = new Event("change", { bubbles: true });
input.dispatchEvent(event);
},
{
selector: "input[type=file]",
fileName: fileToUpload.name,
mimeType: fileToUpload.mimeType,
base64Data: fileToUpload.content,
}
);
};
(async () => {
const browser = await puppeteer.connect({
browserWSEndpoint: BROWSER_WS_ENDPOINT,
});
const page = await browser.newPage();
console.log("Navigating to jimpl.com...");
await page.goto("https://jimpl.com/");
console.log("Waiting for file input...");
await page.waitForSelector("input[type=file]");
await uploadFile(page, fileToUpload);
console.log("Waiting 5 seconds to allow upload...");
await sleep(5000);
await page.screenshot({ path: "imagecompressor_upload_result.png" });
console.log("Screenshot taken: imagecompressor_upload_result.png");
await browser.close();
})().catch((e) => {
console.error("Error:", e);
});
Always trigger a change event after setting files on the input element, and set the correct mimeType for your file data.
Downloading Files
Downloads behave differently in local and remote browser environments. Playwright handles this natively. Puppeteer requires CDP network interception to capture file data before it hits the remote disk.
Playwright
Playwright's Download.saveAs() works seamlessly with remote browsers.
import playwright from "playwright-core";
// Establish remote connection
const browserWSEndpoint = `wss://production-sfo.browserless.io/chromium/playwright?token=${process.env.BROWSERLESS_TOKEN}`;
const browser = await playwright.chromium.connect(browserWSEndpoint);
const page = await browser.newPage();
// Navigate to target site and login if needed
await page.goto("https://slackmojis.com/", { waitUntil: "networkidle" });
page.on("download", (download) => {
download.saveAs(`${download.suggestedFilename()}`);
});
// Trigger the downloads
await page.click("li.emoji.alert a.downloader");
browser.close();
Puppeteer
Puppeteer requires CDP network interception and IO.read to stream file data from the remote browser.
import puppeteer from 'puppeteer-core';
import fs from 'fs';
// Establish remote connection
const browserWSEndpoint = `wss://production-sfo.browserless.io/chromium/stealth?token=${process.env.BROWSERLESS_TOKEN}`;
const browser = await puppeteer.connect({
browserWSEndpoint
});
const [page] = await browser.pages();
// Create cdp session
const cdp = await page.createCDPSession();
// Navigate to target site and login if needed
await page.goto("https://slackmojis.com/", { waitUntil: "networkidle0" });
// Configure Network Interception
await cdp.send('Network.enable');
await cdp.send('Network.setRequestInterception', {
patterns: [
{
urlPattern: '*',
interceptionStage: 'HeadersReceived',
},
],
});
// Function to download file once the response was intercepted
const downloadFileFromInterceptedResponse = async (interceptionId, fileName) => {
const { stream: streamHandle } = await cdp.send('Network.takeResponseBodyForInterceptionAsStream', {
interceptionId: interceptionId,
});
const writer = fs.createWriteStream(`${fileName}`, { encoding: 'base64' });
while (true) {
const read = await cdp.send('IO.read', {
handle: streamHandle,
});
if (read.eof)
break;
writer.write(read.data);
}
// After file is saved, we need to abort the request so that the browser doesn't wait for the response.
cdp.send('Network.continueInterceptedRequest', {
interceptionId: interceptionId,
errorReason: 'Aborted',
});
};
// Listen for intercepted events events
const downloadPromises = [];
await cdp.on('Network.requestIntercepted', async (event) => {
if (event.isDownload) {
// When event is a download we call our download function
const fileName = event.request.url.split('/').pop();
downloadPromises.push(downloadFileFromInterceptedResponse(event.interceptionId, fileName));
} else {
await cdp.send('Network.continueInterceptedRequest', {
interceptionId: event.interceptionId,
});
}
});
// Trigger the downloads
await page.click('li.emoji.alert a.downloader');
// Wait for downloads to finish
await new Promise(r => setTimeout(r, 3000));
await Promise.all(downloadPromises);
browser.close();
Next Steps
- Session Management - Manage browser sessions and persisted state
- Bot Detection - Bypass anti-bot protections with stealth and unblock modes
- Hybrid Automation - Combine automated scripts with manual browser interaction