> ## Documentation Index
> Fetch the complete documentation index at: https://tbd-6fc993ce-mason-add-copy-page-to-context-menu.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# File I/O

> Downloads, uploads, and manipulating the browser's filesystem

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

<CodeGroup>
  ```typescript Typescript/Javascript theme={null}
  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();
  ```

  ```python Python theme={null}
  import asyncio
  import os

  from kernel import AsyncKernel
  from playwright.async_api import async_playwright

  DOWNLOAD_DIR = "/tmp/downloads"
  client = AsyncKernel()


  async def main():
      # Create a new browser via Kernel
      kbrowser = await client.browsers.create()
      print("Kernel browser live view url:", kbrowser.browser_live_view_url)

      async with async_playwright() as playwright:
          browser = await playwright.chromium.connect_over_cdp(kbrowser.cdp_ws_url)
          context = browser.contexts[0]
          page = context.pages[0] if len(context.pages) > 0 else await context.new_page()

          # Required to prevent Playwright from overriding the location of the downloaded file
          cdp_session = await context.new_cdp_session(page)
          await cdp_session.send(
              "Browser.setDownloadBehavior",
              {
                  "behavior": "allow",
                  "downloadPath": DOWNLOAD_DIR,
                  "eventsEnabled": True,
              },
          )

          # Set up CDP listeners to capture download filename and completion
          download_completed = asyncio.Event()
          download_filename: str | None = None

          def _on_download_begin(event):
              nonlocal download_filename
              download_filename = event.get("suggestedFilename", "unknown")
              print(f"Download started: {download_filename}")

          def _on_download_progress(event):
              if event.get("state") in ["completed", "canceled"]:
                  download_completed.set()

          cdp_session.on("Browser.downloadWillBegin", _on_download_begin)
          cdp_session.on("Browser.downloadProgress", _on_download_progress)

          # Trigger the download in the page
          print("Navigating to download test page")
          await page.goto("https://browser-tests-alpha.vercel.app/api/download-test")
          await page.get_by_role("link", name="Download File").click()
          try:
              await asyncio.wait_for(download_completed.wait(), timeout=10)
              print("Download completed")
          except asyncio.TimeoutError:
              print("Download timed out after 10 seconds")

          # Download the file directly from the browser instance
          resp = await client.browsers.fs.read_file(
              kbrowser.session_id, path=f"{DOWNLOAD_DIR}/{download_filename}"
          )
          local_path = f"./downloads/{download_filename}"
          os.makedirs("./downloads", exist_ok=True)
          await resp.write_to_file(local_path)  # streaming; file never in memory
          print(f"Saved to {local_path}")

          await browser.close()


  if __name__ == "__main__":
      asyncio.run(main())
  ```
</CodeGroup>

For more complex scenarios, you can also use the [list files](/api-reference/browsers/list-files-in-a-directory) API together with [read file](/api-reference/browsers/read-file-contents) 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.

<CodeGroup>
  ```typescript Typescript/Javascript theme={null}
  const localPath = "/path/to/a/file.txt";
  console.log(`Uploading ${localPath} ...`);
  await page.locator("#fileUpload").setInputFiles(localPath);
  console.log("Upload completed");
  ```

  ```python Python theme={null}
  local_path = "/path/to/a/file.txt"
  print(f"Uploading {local_path} ...")
  await page.locator("#fileUpload").set_input_files(str(local_path))
  print("Upload completed")
  ```
</CodeGroup>
