diff --git a/.gitignore b/.gitignore
index 4f8631f..086e74f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,8 +1,9 @@
*/.DS_Store
.DS_Store
+.env
node_modules/
venv
__pycache__/**
test.py
-./kernel/**
+./kernel/**
\ No newline at end of file
diff --git a/browsers/file-io.mdx b/browsers/file-io.mdx
index c9691d4..da24b20 100644
--- a/browsers/file-io.mdx
+++ b/browsers/file-io.mdx
@@ -13,10 +13,14 @@ Kernel browsers run in fully sandboxed environments with writable filesystems. W
Playwright performs downloads via the browser itself, so there are a few steps:
- Create a browser session
-- Configure where the browser saves downloads using CDP
+- 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
@@ -30,18 +34,19 @@ Playwright performs downloads via the browser itself, so there are a few steps:
import Kernel from '@onkernel/sdk';
import { chromium } from 'playwright';
import fs from 'fs';
+import path from 'path';
import pTimeout from 'p-timeout';
-const DOWNLOAD_DIR = '/tmp/downloads';
const kernel = new Kernel();
// Poll listFiles until the expected file appears in the directory
async function waitForFile(
sessionId: string,
- dir: string,
- filename: 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 });
@@ -50,7 +55,7 @@ async function waitForFile(
}
await new Promise((r) => setTimeout(r, 500));
}
- throw new Error(`File ${filename} not found after ${timeoutMs}ms`);
+ throw new Error(`File ${filePath} not found after ${timeoutMs}ms`);
}
async function main() {
@@ -63,13 +68,12 @@ async function main() {
const client = await context.newCDPSession(page);
await client.send('Browser.setDownloadBehavior', {
- behavior: 'allow',
- downloadPath: DOWNLOAD_DIR,
+ behavior: 'default',
eventsEnabled: true,
});
- // Set up CDP listeners to capture download filename and completion
- let downloadFilename: string | undefined;
+ // 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((resolve) => {
@@ -77,13 +81,13 @@ async function main() {
});
client.on('Browser.downloadWillBegin', (event) => {
- downloadFilename = event.suggestedFilename ?? 'unknown';
- console.log('Download started:', downloadFilename);
+ 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();
}
});
@@ -103,28 +107,27 @@ async function main() {
throw err;
}
- if (!downloadFilename) {
- throw new Error('Unable to determine download filename');
- }
-
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: ${downloadFilename}`);
- await waitForFile(kernelBrowser.session_id, DOWNLOAD_DIR, downloadFilename);
+ console.log(`Waiting for file: ${downloadFilePath}`);
+ await waitForFile(kernelBrowser.session_id, downloadFilePath);
- const remotePath = `${DOWNLOAD_DIR}/${downloadFilename}`;
- console.log(`Reading file: ${remotePath}`);
+ console.log(`Reading file: ${downloadFilePath}`);
const resp = await kernel.browsers.fs.readFile(kernelBrowser.session_id, {
- path: remotePath,
+ path: downloadFilePath,
});
const bytes = await resp.bytes();
fs.mkdirSync('downloads', { recursive: true });
- const localPath = `downloads/${downloadFilename}`;
+ const localPath = `downloads/${path.basename(downloadFilePath)}`;
fs.writeFileSync(localPath, bytes);
console.log(`Saved to ${localPath}`);
@@ -139,25 +142,27 @@ main();
```python Python
import asyncio
import os
+from pathlib import Path
import time
from kernel import Kernel
from playwright.async_api import async_playwright
-DOWNLOAD_DIR = "/tmp/downloads"
kernel = Kernel()
# Poll list_files until the expected file appears in the directory
async def wait_for_file(
- session_id: str, dir: str, filename: str, timeout_sec: float = 30
+ session_id: str, file_path: str, timeout_sec: float = 30
):
+ dir_path = str(Path(file_path).parent)
+ filename = Path(file_path).name
start = time.time()
while time.time() - start < timeout_sec:
- files = kernel.browsers.fs.list_files(session_id, path=dir)
+ files = kernel.browsers.fs.list_files(session_id, path=dir_path)
if any(f.name == filename for f in files):
return
await asyncio.sleep(0.5)
- raise TimeoutError(f"File {filename} not found after {timeout_sec}s")
+ raise TimeoutError(f"File {file_path} not found after {timeout_sec}s")
async def main():
@@ -173,25 +178,23 @@ async def main():
await cdp_session.send(
"Browser.setDownloadBehavior",
{
- "behavior": "allow",
- "downloadPath": DOWNLOAD_DIR,
+ "behavior": "default",
"eventsEnabled": True,
},
)
download_completed = asyncio.Event()
- download_filename: str | None = None
+ download_file_path: str | None = None
download_state: str | None = None
def _on_download_begin(event):
- nonlocal download_filename
- download_filename = event.get("suggestedFilename", "unknown")
- print(f"Download started: {download_filename}")
+ print(f"Download started: {event.get('suggestedFilename', 'unknown')}")
def _on_download_progress(event):
- nonlocal download_state
+ nonlocal download_state, download_file_path
if event.get("state") in ["completed", "canceled"]:
download_state = event.get("state")
+ download_file_path = event.get("filePath")
download_completed.set()
cdp_session.on("Browser.downloadWillBegin", _on_download_begin)
@@ -211,14 +214,17 @@ async def main():
if download_state == "canceled":
raise RuntimeError("Download was canceled")
+ if not download_file_path:
+ raise RuntimeError("Unable to determine download file path")
+
# Wait for the file to be available via Kernel's File I/O APIs
- print(f"Waiting for file: {download_filename}")
- await wait_for_file(kernel_browser.session_id, DOWNLOAD_DIR, download_filename)
+ print(f"Waiting for file: {download_file_path}")
+ await wait_for_file(kernel_browser.session_id, download_file_path)
resp = kernel.browsers.fs.read_file(
- kernel_browser.session_id, path=f"{DOWNLOAD_DIR}/{download_filename}"
+ kernel_browser.session_id, path=download_file_path
)
- local_path = f"./downloads/{download_filename}"
+ local_path = f"./downloads/{Path(download_file_path).name}"
os.makedirs("./downloads", exist_ok=True)
resp.write_to_file(local_path)
print(f"Saved to {local_path}")
@@ -353,135 +359,82 @@ Browser Use handles downloads automatically when configured properly. Documentat
## Uploads
-You can upload from your local filesystem into the browser directly using Playwright's file input helpers.
+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()`.
```typescript Typescript/Javascript
import Kernel from '@onkernel/sdk';
import { chromium } from 'playwright';
-import { config } from 'dotenv';
-
-config();
-
-const REMOTE_DIR = '/tmp/downloads';
-const FILENAME = 'Kernel-Logo_Accent.png';
-const IMAGE_URL = 'https://www.onkernel.com/brand_assets/Kernel-Logo_Accent.png';
+const IMAGE_URL = 'https://www.kernel.sh/brand_assets/Kernel-Logo_Accent.png';
const kernel = new Kernel();
async function main() {
- // 1. Create Kernel browser session
+ // Create Kernel browser session
const kernelBrowser = await kernel.browsers.create();
console.log('Live view:', kernelBrowser.browser_live_view_url);
- // 2. Fetch the image from URL
- console.log(`Fetching image from ${IMAGE_URL}`);
- const response = await fetch(IMAGE_URL);
- if (!response.ok) {
- throw new Error(`Failed to fetch image: ${response.status}`);
- }
- const imageBlob = await response.blob();
-
- // 3. Write the fetched image to the remote browser's filesystem
- const remotePath = `${REMOTE_DIR}/${FILENAME}`;
- console.log(`Writing to remote browser at ${remotePath}`);
- await kernel.browsers.fs.writeFile(kernelBrowser.session_id, imageBlob, {
- path: remotePath,
- });
- console.log('File written to remote browser');
-
- // 4. Connect Playwright and navigate to upload test page
+ // 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());
- console.log('Navigating to upload test page');
+ // Navigate to a page with a file input
await page.goto('https://browser-tests-alpha.vercel.app/api/upload-test');
- // 5. Upload the file using Playwright's file input helper
- console.log(`Uploading ${remotePath} via file input`);
- const remoteFile = await kernel.browsers.fs.readFile(kernelBrowser.session_id, { path: remotePath });
- const fileBuffer = Buffer.from(await remoteFile.bytes());
- await page.locator('#fileUpload').setInputFiles([{
- name: FILENAME,
+ // 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: fileBuffer,
+ buffer: buffer,
}]);
- console.log('Upload completed');
+ console.log('File uploaded');
await kernel.browsers.deleteByID(kernelBrowser.session_id);
console.log('Browser deleted');
-
- return null;
}
main();
-
````
```python Python
import asyncio
-import os
+import httpx
from kernel import Kernel
from playwright.async_api import async_playwright
-from dotenv import load_dotenv
-
-load_dotenv()
-
-REMOTE_DIR = '/tmp/downloads'
-FILENAME = 'Kernel-Logo_Accent.png'
-IMAGE_URL = 'https://www.onkernel.com/brand_assets/Kernel-Logo_Accent.png'
+IMAGE_URL = 'https://www.kernel.sh/brand_assets/Kernel-Logo_Accent.png'
kernel = Kernel()
async def main():
- # 1. Create Kernel browser session
+ # Create Kernel browser session
kernel_browser = kernel.browsers.create()
print(f'Live view: {kernel_browser.browser_live_view_url}')
- # 2. Fetch the image from URL
- print(f'Fetching image from {IMAGE_URL}')
- import aiohttp
- async with aiohttp.ClientSession() as session:
- async with session.get(IMAGE_URL) as response:
- if response.status != 200:
- raise Exception(f'Failed to fetch image: {response.status}')
- image_bytes = await response.read()
-
- # 3. Write the fetched image to the remote browser's filesystem
- remote_path = f'{REMOTE_DIR}/{FILENAME}'
- print(f'Writing to remote browser at {remote_path}')
- kernel.browsers.fs.write_file(
- kernel_browser.session_id,
- image_bytes,
- path=remote_path
- )
- print('File written to remote browser')
-
- # 4. Connect Playwright and navigate to upload test page
async with async_playwright() as playwright:
+ # Connect Playwright
browser = await playwright.chromium.connect_over_cdp(kernel_browser.cdp_ws_url)
context = browser.contexts[0] if browser.contexts else await browser.new_context()
page = context.pages[0] if context.pages else await context.new_page()
- print('Navigating to upload test page')
+ # Navigate to a page with a file input
await page.goto('https://browser-tests-alpha.vercel.app/api/upload-test')
- # 5. Upload the file using Playwright's file input helper
- print(f'Uploading {remote_path} via file input')
- remote_file = kernel.browsers.fs.read_file(
- kernel_browser.session_id,
- path=remote_path
- )
- file_buffer = remote_file.read()
+ # Fetch file and pass buffer directly to set_input_files
+ async with httpx.AsyncClient() as client:
+ response = await client.get(IMAGE_URL)
+ buffer = response.content
- await page.locator('#fileUpload').set_input_files({
- 'name': FILENAME,
+ await page.locator('input[type="file"]').set_input_files([{
+ 'name': 'Kernel-Logo_Accent.png',
'mimeType': 'image/png',
- 'buffer': file_buffer,
- })
- print('Upload completed')
+ 'buffer': buffer,
+ }])
+ print('File uploaded')
await browser.close()