Skip to content

Commit bed52e3

Browse files
authored
playwright code exec (#30)
adds `kernel browsers playwright execute...`
1 parent b12b4d3 commit bed52e3

File tree

2 files changed

+135
-6
lines changed

2 files changed

+135
-6
lines changed

README.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,12 @@ Create an API key from the [Kernel dashboard](https://dashboard.onkernel.com).
285285
- `--button <button>` - Mouse button: left, middle, right (default: left)
286286
- `--hold-key <key>` - Modifier keys to hold (repeatable)
287287

288+
### Browser Playwright
289+
290+
- `kernel browsers playwright execute <id or persistent id> [code]` - Execute Playwright/TypeScript code against the browser
291+
- `--timeout <seconds>` - Maximum execution time in seconds (defaults server-side)
292+
- If `[code]` is omitted, code is read from stdin
293+
288294
### Extension Management
289295

290296
- `kernel extensions list` - List all uploaded extensions
@@ -395,6 +401,37 @@ kernel browsers computer type my-browser --text "Hello, World!"
395401

396402
# Type text with a 100ms delay between keystrokes
397403
kernel browsers computer type my-browser --text "Slow typing..." --delay 100
404+
405+
```
406+
407+
### Playwright execution
408+
409+
```bash
410+
# Execute inline Playwright (TypeScript) code
411+
kernel browsers playwright execute my-browser 'await page.goto("https://example.com"); const title = await page.title(); return title;'
412+
413+
# Or pipe code from stdin
414+
cat <<'TS' | kernel browsers playwright execute my-browser
415+
await page.goto("https://example.com");
416+
const title = await page.title();
417+
return { title };
418+
TS
419+
420+
# With a timeout in seconds
421+
kernel browsers playwright execute my-browser --timeout 30 'await (await context.newPage()).goto("https://example.com")'
422+
423+
# Mini CDP connection load test (10s)
424+
cat <<'TS' | kernel browsers playwright execute my-browser
425+
const start = Date.now();
426+
let ops = 0;
427+
while (Date.now() - start < 10_000) {
428+
await page.evaluate("new Date();");
429+
ops++;
430+
}
431+
const durationMs = Date.now() - start;
432+
const opsPerSec = ops / (durationMs / 1000);
433+
return { opsPerSec, ops, durationMs };
434+
TS
398435
```
399436

400437
### Extension management

cmd/browsers.go

Lines changed: 98 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,11 @@ type BrowserLogService interface {
7373
StreamStreaming(ctx context.Context, id string, query kernel.BrowserLogStreamParams, opts ...option.RequestOption) (stream *ssestream.Stream[shared.LogEvent])
7474
}
7575

76+
// BrowserPlaywrightService defines the subset we use for Playwright execution.
77+
type BrowserPlaywrightService interface {
78+
Execute(ctx context.Context, id string, body kernel.BrowserPlaywrightExecuteParams, opts ...option.RequestOption) (res *kernel.BrowserPlaywrightExecuteResponse, err error)
79+
}
80+
7681
// BrowserComputerService defines the subset we use for OS-level mouse & screen.
7782
type BrowserComputerService interface {
7883
CaptureScreenshot(ctx context.Context, id string, body kernel.BrowserComputerCaptureScreenshotParams, opts ...option.RequestOption) (res *http.Response, err error)
@@ -166,12 +171,13 @@ type BrowsersViewInput struct {
166171

167172
// BrowsersCmd is a cobra-independent command handler for browsers operations.
168173
type BrowsersCmd struct {
169-
browsers BrowsersService
170-
replays BrowserReplaysService
171-
fs BrowserFSService
172-
process BrowserProcessService
173-
logs BrowserLogService
174-
computer BrowserComputerService
174+
browsers BrowsersService
175+
replays BrowserReplaysService
176+
fs BrowserFSService
177+
process BrowserProcessService
178+
logs BrowserLogService
179+
computer BrowserComputerService
180+
playwright BrowserPlaywrightService
175181
}
176182

177183
type BrowsersListInput struct {
@@ -940,6 +946,59 @@ type BrowsersProcessStdoutStreamInput struct {
940946
ProcessID string
941947
}
942948

949+
// Playwright
950+
type BrowsersPlaywrightExecuteInput struct {
951+
Identifier string
952+
Code string
953+
Timeout int64
954+
}
955+
956+
func (b BrowsersCmd) PlaywrightExecute(ctx context.Context, in BrowsersPlaywrightExecuteInput) error {
957+
if b.playwright == nil {
958+
pterm.Error.Println("playwright service not available")
959+
return nil
960+
}
961+
br, err := b.resolveBrowserByIdentifier(ctx, in.Identifier)
962+
if err != nil {
963+
return util.CleanedUpSdkError{Err: err}
964+
}
965+
if br == nil {
966+
pterm.Error.Printf("Browser '%s' not found\n", in.Identifier)
967+
return nil
968+
}
969+
params := kernel.BrowserPlaywrightExecuteParams{Code: in.Code}
970+
if in.Timeout > 0 {
971+
params.TimeoutSec = kernel.Opt(in.Timeout)
972+
}
973+
res, err := b.playwright.Execute(ctx, br.SessionID, params)
974+
if err != nil {
975+
return util.CleanedUpSdkError{Err: err}
976+
}
977+
978+
rows := pterm.TableData{{"Property", "Value"}, {"Success", fmt.Sprintf("%t", res.Success)}}
979+
PrintTableNoPad(rows, true)
980+
981+
if res.Stdout != "" {
982+
pterm.Info.Println("stdout:")
983+
fmt.Println(res.Stdout)
984+
}
985+
if res.Stderr != "" {
986+
pterm.Info.Println("stderr:")
987+
fmt.Fprintln(os.Stderr, res.Stderr)
988+
}
989+
if res.Result != nil {
990+
bs, err := json.MarshalIndent(res.Result, "", " ")
991+
if err == nil {
992+
pterm.Info.Println("result:")
993+
fmt.Println(string(bs))
994+
}
995+
}
996+
if !res.Success && res.Error != "" {
997+
pterm.Error.Printf("error: %s\n", res.Error)
998+
}
999+
return nil
1000+
}
1001+
9431002
func (b BrowsersCmd) ProcessExec(ctx context.Context, in BrowsersProcessExecInput) error {
9441003
if b.process == nil {
9451004
pterm.Error.Println("process service not available")
@@ -1898,6 +1957,13 @@ func init() {
18981957
computerRoot.AddCommand(computerClick, computerMove, computerScreenshot, computerType, computerPressKey, computerScroll, computerDrag)
18991958
browsersCmd.AddCommand(computerRoot)
19001959

1960+
// playwright
1961+
playwrightRoot := &cobra.Command{Use: "playwright", Short: "Playwright operations"}
1962+
playwrightExecute := &cobra.Command{Use: "execute <id|persistent-id> [code]", Short: "Execute Playwright/TypeScript code against the browser", Args: cobra.MinimumNArgs(1), RunE: runBrowsersPlaywrightExecute}
1963+
playwrightExecute.Flags().Int64("timeout", 0, "Maximum execution time in seconds (default per server)")
1964+
playwrightRoot.AddCommand(playwrightExecute)
1965+
browsersCmd.AddCommand(playwrightRoot)
1966+
19011967
// Add flags for create command
19021968
browsersCreateCmd.Flags().StringP("persistent-id", "p", "", "Unique identifier for browser session persistence")
19031969
browsersCreateCmd.Flags().BoolP("stealth", "s", false, "Launch browser in stealth mode to avoid detection")
@@ -2120,6 +2186,32 @@ func runBrowsersProcessStdoutStream(cmd *cobra.Command, args []string) error {
21202186
return b.ProcessStdoutStream(cmd.Context(), BrowsersProcessStdoutStreamInput{Identifier: args[0], ProcessID: args[1]})
21212187
}
21222188

2189+
func runBrowsersPlaywrightExecute(cmd *cobra.Command, args []string) error {
2190+
client := getKernelClient(cmd)
2191+
svc := client.Browsers
2192+
2193+
var code string
2194+
if len(args) >= 2 {
2195+
code = strings.Join(args[1:], " ")
2196+
} else {
2197+
// Read code from stdin
2198+
stat, _ := os.Stdin.Stat()
2199+
if (stat.Mode() & os.ModeCharDevice) != 0 {
2200+
pterm.Error.Println("no code provided. Provide code as an argument or pipe via stdin")
2201+
return nil
2202+
}
2203+
data, err := io.ReadAll(os.Stdin)
2204+
if err != nil {
2205+
pterm.Error.Printf("failed to read stdin: %v\n", err)
2206+
return nil
2207+
}
2208+
code = string(data)
2209+
}
2210+
timeout, _ := cmd.Flags().GetInt64("timeout")
2211+
b := BrowsersCmd{browsers: &svc, playwright: &svc.Playwright}
2212+
return b.PlaywrightExecute(cmd.Context(), BrowsersPlaywrightExecuteInput{Identifier: args[0], Code: strings.TrimSpace(code), Timeout: timeout})
2213+
}
2214+
21232215
func runBrowsersFSNewDirectory(cmd *cobra.Command, args []string) error {
21242216
client := getKernelClient(cmd)
21252217
svc := client.Browsers

0 commit comments

Comments
 (0)