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 fs from "fs";
import pTimeout from "p-timeout";
import { chromium } from "playwright";

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 = (await browser.contexts()[0]) || (await browser.newContext());
  const page = (await context.pages()[0]) || (await context.newPage());

  // Required to prevent Playwright from overriding the location of the downloaded file
  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();
    }
  });

  // Trigger the download in the page
  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");
  }

  // Download the file directly from the browser instance
  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}`);
  // Alternatively, stream directly to disk:
  // import { pipeline } from 'node:stream/promises';
  // import { createWriteStream } from 'node:fs';
  // import { Readable } from 'node:stream';
  // await pipeline(Readable.fromWeb(resp.body!), createWriteStream(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");