Kernel browsers run in fully sandboxed environments with writable filesystems. When your automation downloads a file, it’s saved inside the browser’s filesystem and can be retrieved using Kernel’s File I/O APIs.
Playwright performs downloads via the browser itself, so there are a few steps:
Create a browser session
Configure browser download behavior using CDP
Perform the download
Retrieve the file from the browser’s filesystem
With behavior: 'default', downloads are saved to the browser’s default download directory. The CDP downloadProgress event includes a filePath field when the download completes, which tells you exactly where the file was saved. Use this path with Kernel’s File I/O APIs to retrieve the file.
The CDP downloadProgress event signals when the browser finishes writing a
file, but there may be a brief delay before the file becomes available through
Kernel’s File I/O APIs. This is especially true for larger downloads. We
recommend polling listFiles to confirm the file exists before attempting to
read it.
Copy
Ask AI
import Kernel from '@onkernel/sdk';import { chromium } from 'playwright';import fs from 'fs';import path from 'path';import pTimeout from 'p-timeout';const kernel = new Kernel();// Poll listFiles until the expected file appears in the directoryasync function waitForFile( sessionId: string, filePath: string, timeoutMs = 30_000) { const dir = path.dirname(filePath); const filename = path.basename(filePath); const start = Date.now(); while (Date.now() - start < timeoutMs) { const files = await kernel.browsers.fs.listFiles(sessionId, { path: dir }); if (files.some((f) => f.name === filename)) { return; } await new Promise((r) => setTimeout(r, 500)); } throw new Error(`File ${filePath} not found after ${timeoutMs}ms`);}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: 'default', eventsEnabled: true, }); // Set up CDP listeners to capture download path and completion let downloadFilePath: string | undefined; let downloadState: string | undefined; let downloadCompletedResolve!: () => void; const downloadCompleted = new Promise<void>((resolve) => { downloadCompletedResolve = resolve; }); client.on('Browser.downloadWillBegin', (event) => { console.log('Download started:', event.suggestedFilename); }); client.on('Browser.downloadProgress', (event) => { if (event.state === 'completed' || event.state === 'canceled') { downloadState = event.state; downloadFilePath = event.filePath; 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 (downloadState === 'canceled') { throw new Error('Download was canceled'); } if (!downloadFilePath) { throw new Error('Unable to determine download file path'); } // Wait for the file to be available via Kernel's File I/O APIs console.log(`Waiting for file: ${downloadFilePath}`); await waitForFile(kernelBrowser.session_id, downloadFilePath); console.log(`Reading file: ${downloadFilePath}`); const resp = await kernel.browsers.fs.readFile(kernelBrowser.session_id, { path: downloadFilePath, }); const bytes = await resp.bytes(); fs.mkdirSync('downloads', { recursive: true }); const localPath = `downloads/${path.basename(downloadFilePath)}`; fs.writeFileSync(localPath, bytes); console.log(`Saved to ${localPath}`); await kernel.browsers.deleteByID(kernelBrowser.session_id); console.log('Kernel browser deleted successfully.');}main();
We recommend using the list files API to poll for file availability before calling read file, as shown in the examples above. This approach ensures reliable downloads, especially for larger files. You can also use listFiles to enumerate and save all downloads at the end of a session.
Playwright’s setInputFiles() method allows you to upload files directly to file input elements. You can fetch a file from a URL and pass the buffer directly to setInputFiles().
Copy
Ask AI
import Kernel from '@onkernel/sdk';import { chromium } from 'playwright';const IMAGE_URL = 'https://www.kernel.sh/brand_assets/Kernel-Logo_Accent.png';const kernel = new Kernel();async function main() { // Create Kernel browser session const kernelBrowser = await kernel.browsers.create(); console.log('Live view:', kernelBrowser.browser_live_view_url); // Connect Playwright 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()); // Navigate to a page with a file input await page.goto('https://browser-tests-alpha.vercel.app/api/upload-test'); // Fetch file and pass buffer directly to setInputFiles const response = await fetch(IMAGE_URL); const buffer = Buffer.from(await response.arrayBuffer()); await page.locator('input[type="file"]').setInputFiles([{ name: 'Kernel-Logo_Accent.png', mimeType: 'image/png', buffer: buffer, }]); console.log('File uploaded'); await kernel.browsers.deleteByID(kernelBrowser.session_id); console.log('Browser deleted');}main();