Skip to content

Commit a0d741b

Browse files
authored
Feat/improve io docs (#136)
* initial push * remove comment * add code snippets for uploading files * improvements pass * fix alignment * docs fix * update helper function * Move info note to Playwright section Moved beginning playwright note to info section * Remove duplicate language removed duplicate language from playwright section * Update formatting Update formatting + remove config addition step in code snippet for stagehand v3 * Cleaned up a few items - Moved playwright specific callout to the end of the code snippets in Playwright section - Added formatting changes for typescript code snippets ---------
1 parent 8b73729 commit a0d741b

File tree

1 file changed

+264
-17
lines changed

1 file changed

+264
-17
lines changed

browsers/file-io.mdx

Lines changed: 264 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,26 @@ title: "File I/O"
33
description: "Downloads, uploads, and manipulating the browser's filesystem"
44
---
55

6-
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.
7-
86
## Downloads
97

8+
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.
9+
10+
11+
### Playwright
12+
1013
Playwright performs downloads via the browser itself, so there are a few steps:
1114

1215
- Create a browser session
13-
- Configure where the browser saves downloads
16+
- Configure where the browser saves downloads using CDP
1417
- Perform the download
1518
- Retrieve the file from the browser's filesystem
1619

1720
<Info>
18-
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.
21+
The CDP `downloadProgress` event signals when the browser finishes writing a
22+
file, but there may be a brief delay before the file becomes available through
23+
Kernel's File I/O APIs. This is especially true for larger downloads. We
24+
recommend polling `listFiles` to confirm the file exists before attempting to
25+
read it.
1926
</Info>
2027

2128
<CodeGroup>
@@ -126,7 +133,8 @@ async function main() {
126133
}
127134

128135
main();
129-
```
136+
137+
````
130138

131139
```python Python
132140
import asyncio
@@ -221,29 +229,268 @@ async def main():
221229
222230
if __name__ == "__main__":
223231
asyncio.run(main())
224-
```
232+
````
233+
225234
</CodeGroup>
226235
227-
We recommend using the [list files](/api-reference/browsers/list-files-in-a-directory) API to poll for file availability before calling [read file](/api-reference/browsers/read-file-contents), 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.
236+
<Info>We recommend using the [list files](/api-reference/browsers/list-files-in-a-directory) API to poll for file availability before calling [read file](/api-reference/browsers/read-file-contents), 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.</Info>
237+
238+
### Stagehand v3
239+
240+
When using Stagehand with Kernel browsers, you need to configure the download behavior in the `localBrowserLaunchOptions`:
241+
242+
```typescript
243+
const stagehand = new Stagehand({
244+
env: "LOCAL",
245+
verbose: 1,
246+
localBrowserLaunchOptions: {
247+
cdpUrl: kernelBrowser.cdp_ws_url,
248+
downloadsPath: DOWNLOAD_DIR, // Specify where downloads should be saved
249+
acceptDownloads: true, // Enable downloads
250+
},
251+
});
252+
```
253+
254+
Here's a complete example:
255+
256+
```typescript
257+
import { Stagehand } from "@browserbasehq/stagehand";
258+
import Kernel from "@onkernel/sdk";
259+
import fs from "fs";
260+
261+
const DOWNLOAD_DIR = "/tmp/downloads";
262+
263+
// Poll listFiles until any file appears in the directory
264+
async function waitForFile(
265+
kernel: Kernel,
266+
sessionId: string,
267+
dir: string,
268+
timeoutMs = 30_000
269+
) {
270+
const start = Date.now();
271+
while (Date.now() - start < timeoutMs) {
272+
const files = await kernel.browsers.fs.listFiles(sessionId, { path: dir });
273+
if (files.length > 0) {
274+
return files[0];
275+
}
276+
await new Promise((r) => setTimeout(r, 500));
277+
}
278+
throw new Error(`No files found in ${dir} after ${timeoutMs}ms`);
279+
}
280+
281+
async function main() {
282+
const kernel = new Kernel();
283+
284+
console.log("Creating browser via Kernel...");
285+
const kernelBrowser = await kernel.browsers.create({
286+
stealth: true,
287+
});
288+
289+
console.log(`Kernel Browser Session Started`);
290+
console.log(`Session ID: ${kernelBrowser.session_id}`);
291+
console.log(`Watch live: ${kernelBrowser.browser_live_view_url}`);
292+
293+
// Initialize Stagehand with Kernel's CDP URL and download configuration
294+
const stagehand = new Stagehand({
295+
env: "LOCAL",
296+
verbose: 1,
297+
localBrowserLaunchOptions: {
298+
cdpUrl: kernelBrowser.cdp_ws_url,
299+
downloadsPath: DOWNLOAD_DIR,
300+
acceptDownloads: true,
301+
},
302+
});
303+
304+
await stagehand.init();
305+
306+
const page = stagehand.context.pages()[0];
307+
308+
await page.goto("https://browser-tests-alpha.vercel.app/api/download-test");
309+
310+
// Use Stagehand to click the download button
311+
await stagehand.act("Click the download file link");
312+
console.log("Download triggered");
313+
314+
// Wait for the file to be fully available via Kernel's File I/O APIs
315+
console.log("Waiting for file to appear...");
316+
const downloadedFile = await waitForFile(
317+
kernel,
318+
kernelBrowser.session_id,
319+
DOWNLOAD_DIR
320+
);
321+
console.log(`File found: ${downloadedFile.name}`);
322+
323+
const remotePath = `${DOWNLOAD_DIR}/${downloadedFile.name}`;
324+
console.log(`Reading file from: ${remotePath}`);
325+
326+
// Read the file from Kernel browser's filesystem
327+
const resp = await kernel.browsers.fs.readFile(kernelBrowser.session_id, {
328+
path: remotePath,
329+
});
330+
331+
// Save to local filesystem
332+
const bytes = await resp.bytes();
333+
fs.mkdirSync("downloads", { recursive: true });
334+
const localPath = `downloads/${downloadedFile.name}`;
335+
fs.writeFileSync(localPath, bytes);
336+
console.log(`Saved to ${localPath}`);
337+
338+
// Clean up
339+
await stagehand.close();
340+
await kernel.browsers.deleteByID(kernelBrowser.session_id);
341+
console.log("Browser session closed");
342+
}
343+
344+
main().catch((err) => {
345+
console.error(err);
346+
process.exit(1);
347+
});
348+
```
349+
350+
### Browser Use
351+
352+
Browser Use handles downloads automatically when configured properly. Documentation for Browser Use downloads coming soon.
228353

229354
## Uploads
230355

231356
You can upload from your local filesystem into the browser directly using Playwright's file input helpers.
232357

233358
<CodeGroup>
234359
```typescript Typescript/Javascript
235-
const localPath = '/path/to/a/file.txt';
360+
import Kernel from '@onkernel/sdk';
361+
import { chromium } from 'playwright';
362+
import { config } from 'dotenv';
236363

237-
console.log(`Uploading ${localPath}...`);
238-
await page.locator('#fileUpload').setInputFiles(localPath);
239-
console.log('Upload completed');
240-
```
364+
config();
365+
366+
const REMOTE_DIR = '/tmp/downloads';
367+
const FILENAME = 'Kernel-Logo_Accent.png';
368+
const IMAGE_URL = 'https://www.onkernel.com/brand_assets/Kernel-Logo_Accent.png';
369+
370+
const kernel = new Kernel();
371+
372+
async function main() {
373+
// 1. Create Kernel browser session
374+
const kernelBrowser = await kernel.browsers.create();
375+
console.log('Live view:', kernelBrowser.browser_live_view_url);
376+
377+
// 2. Fetch the image from URL
378+
console.log(`Fetching image from ${IMAGE_URL}`);
379+
const response = await fetch(IMAGE_URL);
380+
if (!response.ok) {
381+
throw new Error(`Failed to fetch image: ${response.status}`);
382+
}
383+
const imageBlob = await response.blob();
384+
385+
// 3. Write the fetched image to the remote browser's filesystem
386+
const remotePath = `${REMOTE_DIR}/${FILENAME}`;
387+
console.log(`Writing to remote browser at ${remotePath}`);
388+
await kernel.browsers.fs.writeFile(kernelBrowser.session_id, imageBlob, {
389+
path: remotePath,
390+
});
391+
console.log('File written to remote browser');
392+
393+
// 4. Connect Playwright and navigate to upload test page
394+
const browser = await chromium.connectOverCDP(kernelBrowser.cdp_ws_url);
395+
const context = browser.contexts()[0] || (await browser.newContext());
396+
const page = context.pages()[0] || (await context.newPage());
397+
398+
console.log('Navigating to upload test page');
399+
await page.goto('https://browser-tests-alpha.vercel.app/api/upload-test');
400+
401+
// 5. Upload the file using Playwright's file input helper
402+
console.log(`Uploading ${remotePath} via file input`);
403+
const remoteFile = await kernel.browsers.fs.readFile(kernelBrowser.session_id, { path: remotePath });
404+
const fileBuffer = Buffer.from(await remoteFile.bytes());
405+
await page.locator('#fileUpload').setInputFiles([{
406+
name: FILENAME,
407+
mimeType: 'image/png',
408+
buffer: fileBuffer,
409+
}]);
410+
console.log('Upload completed');
411+
412+
await kernel.browsers.deleteByID(kernelBrowser.session_id);
413+
console.log('Browser deleted');
414+
415+
return null;
416+
}
417+
418+
main();
419+
420+
````
241421

242422
```python Python
243-
local_path = "/path/to/a/file.txt"
423+
import asyncio
424+
import os
425+
from kernel import Kernel
426+
from playwright.async_api import async_playwright
427+
from dotenv import load_dotenv
428+
429+
load_dotenv()
430+
431+
REMOTE_DIR = '/tmp/downloads'
432+
FILENAME = 'Kernel-Logo_Accent.png'
433+
IMAGE_URL = 'https://www.onkernel.com/brand_assets/Kernel-Logo_Accent.png'
434+
435+
kernel = Kernel()
436+
437+
438+
async def main():
439+
# 1. Create Kernel browser session
440+
kernel_browser = kernel.browsers.create()
441+
print(f'Live view: {kernel_browser.browser_live_view_url}')
442+
443+
# 2. Fetch the image from URL
444+
print(f'Fetching image from {IMAGE_URL}')
445+
import aiohttp
446+
async with aiohttp.ClientSession() as session:
447+
async with session.get(IMAGE_URL) as response:
448+
if response.status != 200:
449+
raise Exception(f'Failed to fetch image: {response.status}')
450+
image_bytes = await response.read()
451+
452+
# 3. Write the fetched image to the remote browser's filesystem
453+
remote_path = f'{REMOTE_DIR}/{FILENAME}'
454+
print(f'Writing to remote browser at {remote_path}')
455+
kernel.browsers.fs.write_file(
456+
kernel_browser.session_id,
457+
image_bytes,
458+
path=remote_path
459+
)
460+
print('File written to remote browser')
461+
462+
# 4. Connect Playwright and navigate to upload test page
463+
async with async_playwright() as playwright:
464+
browser = await playwright.chromium.connect_over_cdp(kernel_browser.cdp_ws_url)
465+
context = browser.contexts[0] if browser.contexts else await browser.new_context()
466+
page = context.pages[0] if context.pages else await context.new_page()
467+
468+
print('Navigating to upload test page')
469+
await page.goto('https://browser-tests-alpha.vercel.app/api/upload-test')
470+
471+
# 5. Upload the file using Playwright's file input helper
472+
print(f'Uploading {remote_path} via file input')
473+
remote_file = kernel.browsers.fs.read_file(
474+
kernel_browser.session_id,
475+
path=remote_path
476+
)
477+
file_buffer = remote_file.read()
478+
479+
await page.locator('#fileUpload').set_input_files({
480+
'name': FILENAME,
481+
'mimeType': 'image/png',
482+
'buffer': file_buffer,
483+
})
484+
print('Upload completed')
485+
486+
await browser.close()
487+
488+
kernel.browsers.delete_by_id(kernel_browser.session_id)
489+
print('Browser deleted')
490+
491+
492+
if __name__ == '__main__':
493+
asyncio.run(main())
494+
````
244495
245-
print(f"Uploading {local_path}...")
246-
await page.locator("#fileUpload").set_input_files(str(local_path))
247-
print("Upload completed")
248-
```
249496
</CodeGroup>

0 commit comments

Comments
 (0)