Skip to main content
Kernel browsers run in fully sandboxed environments with a writable filesystem that you control. Anything your automation downloads during a session is saved inside this filesystem and can be retrieved directly while the session is running.

Downloads

Playwright performs downloads via the browser itself, so there are a few steps:
  • Create a browser session
  • Configure where the browser saves downloads
  • Perform the download
  • Retrieve the file from the browser’s filesystem
import Kernel from '@onkernel/sdk';
import { chromium } from 'playwright';
import fs from 'fs';
import pTimeout from 'p-timeout';

const DOWNLOAD_DIR = '/tmp/downloads';
const kernel = new Kernel();

async function main() {
  const kernelBrowser = await kernel.browsers.create();
  console.log('live view:', kernelBrowser.browser_live_view_url);

  const browser = await chromium.connectOverCDP(kernelBrowser.cdp_ws_url);
  const context = browser.contexts()[0] || (await browser.newContext());
  const page = context.pages()[0] || (await context.newPage());

  const client = await context.newCDPSession(page);
  await client.send('Browser.setDownloadBehavior', {
    behavior: 'allow',
    downloadPath: DOWNLOAD_DIR,
    eventsEnabled: true,
  });

  // Set up CDP listeners to capture download filename and completion
  let downloadFilename: string | undefined;
  let downloadCompletedResolve!: () => void;
  const downloadCompleted = new Promise<void>((resolve) => {
    downloadCompletedResolve = resolve;
  });

  client.on('Browser.downloadWillBegin', (event) => {
    downloadFilename = event.suggestedFilename ?? 'unknown';
    console.log('Download started:', downloadFilename);
  });

  client.on('Browser.downloadProgress', (event) => {
    if (event.state === 'completed' || event.state === 'canceled') {
      downloadCompletedResolve();
    }
  });

  console.log('Navigating to download test page');
  await page.goto('https://browser-tests-alpha.vercel.app/api/download-test');
  await page.getByRole('link', { name: 'Download File' }).click();

  try {
    await pTimeout(downloadCompleted, {
      milliseconds: 10_000,
      message: new Error('Download timed out after 10 seconds'),
    });
    console.log('Download completed');
  } catch (err) {
    console.error(err);
    throw err;
  }

  if (!downloadFilename) {
    throw new Error('Unable to determine download filename');
  }

  const remotePath = `${DOWNLOAD_DIR}/${downloadFilename}`;
  console.log(`Reading file: ${remotePath}`);

  const resp = await kernel.browsers.fs.readFile(kernelBrowser.session_id, {
    path: remotePath,
  });

  const bytes = await resp.bytes();
  fs.mkdirSync('downloads', { recursive: true });
  const localPath = `downloads/${downloadFilename}`;
  fs.writeFileSync(localPath, bytes);
  console.log(`Saved to ${localPath}`);

  await browser.close();
}

main();
For more complex scenarios, you can also use the list files API together with read file to enumerate and save all downloads at the end of a session.

Uploads

You can upload from your local filesystem into the browser directly using Playwright’s file input helpers.
const localPath = '/path/to/a/file.txt';

console.log(`Uploading ${localPath}...`);
await page.locator('#fileUpload').setInputFiles(localPath);
console.log('Upload completed');