diff --git a/images/chromium-headful/Dockerfile b/images/chromium-headful/Dockerfile index 0aaa21c6..5f6aab3d 100644 --- a/images/chromium-headful/Dockerfile +++ b/images/chromium-headful/Dockerfile @@ -55,6 +55,7 @@ RUN set -eux; \ FROM ghcr.io/onkernel/neko/base:3.0.8-v1.3.0 AS neko # ^--- now has event.SYSTEM_PONG with legacy support to keepalive +FROM node:22-bullseye-slim AS node-22 FROM docker.io/ubuntu:22.04 ENV DEBIAN_FRONTEND=noninteractive @@ -161,6 +162,20 @@ RUN set -eux; \ RUN add-apt-repository -y ppa:xtradeb/apps RUN apt update -y && apt install -y chromium sqlite3 +# install Node.js 22.x by copying from the node:22-bullseye-slim stage +COPY --from=node-22 /usr/local/bin/node /usr/local/bin/node +COPY --from=node-22 /usr/local/lib/node_modules /usr/local/lib/node_modules +# Recreate symlinks for npm/npx/corepack to point into node_modules +RUN set -eux; \ + ln -sf /usr/local/lib/node_modules/npm/bin/npm-cli.js /usr/local/bin/npm; \ + ln -sf /usr/local/lib/node_modules/npm/bin/npx-cli.js /usr/local/bin/npx; \ + if [ -e /usr/local/lib/node_modules/corepack/dist/corepack.js ]; then \ + ln -sf /usr/local/lib/node_modules/corepack/dist/corepack.js /usr/local/bin/corepack; \ + fi + +# Install TypeScript and Playwright globally +RUN npm install -g typescript playwright-core tsx + # setup desktop env & app ENV DISPLAY_NUM=1 ENV HEIGHT=768 @@ -185,6 +200,9 @@ COPY images/chromium-headful/supervisor/services/ /etc/supervisor/conf.d/service COPY --from=server-builder /out/kernel-images-api /usr/local/bin/kernel-images-api COPY --from=server-builder /out/chromium-launcher /usr/local/bin/chromium-launcher +# Copy the Playwright executor runtime +COPY server/runtime/playwright-executor.ts /usr/local/lib/playwright-executor.ts + RUN useradd -m -s /bin/bash kernel ENTRYPOINT [ "/wrapper.sh" ] diff --git a/images/chromium-headless/image/Dockerfile b/images/chromium-headless/image/Dockerfile index 144b4893..dbf63b9c 100644 --- a/images/chromium-headless/image/Dockerfile +++ b/images/chromium-headless/image/Dockerfile @@ -21,6 +21,7 @@ RUN GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH:-amd64} \ RUN GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH:-amd64} \ go build -ldflags="-s -w" -o /out/chromium-launcher ./cmd/chromium-launcher +FROM node:22-bullseye-slim AS node-22 FROM docker.io/ubuntu:22.04 RUN set -xe; \ @@ -73,6 +74,20 @@ RUN set -eux; \ # Remove upower to prevent spurious D-Bus activations and logs RUN apt-get -yqq purge upower || true && rm -rf /var/lib/apt/lists/* +# install Node.js 22.x by copying from the node:22-bullseye-slim stage +COPY --from=node-22 /usr/local/bin/node /usr/local/bin/node +COPY --from=node-22 /usr/local/lib/node_modules /usr/local/lib/node_modules +# Recreate symlinks for npm/npx/corepack to point into node_modules +RUN set -eux; \ + ln -sf /usr/local/lib/node_modules/npm/bin/npm-cli.js /usr/local/bin/npm; \ + ln -sf /usr/local/lib/node_modules/npm/bin/npx-cli.js /usr/local/bin/npx; \ + if [ -e /usr/local/lib/node_modules/corepack/dist/corepack.js ]; then \ + ln -sf /usr/local/lib/node_modules/corepack/dist/corepack.js /usr/local/bin/corepack; \ + fi + +# Install TypeScript and Playwright globally +RUN npm install -g typescript playwright-core tsx + ENV WITHDOCKER=true # Create a non-root user with a home directory @@ -93,4 +108,7 @@ COPY images/chromium-headless/image/supervisor/services/ /etc/supervisor/conf.d/ COPY --from=server-builder /out/kernel-images-api /usr/local/bin/kernel-images-api COPY --from=server-builder /out/chromium-launcher /usr/local/bin/chromium-launcher +# Copy the Playwright executor runtime +COPY server/runtime/playwright-executor.ts /usr/local/lib/playwright-executor.ts + ENTRYPOINT [ "/usr/bin/wrapper.sh" ] diff --git a/server/cmd/api/api/api.go b/server/cmd/api/api/api.go index f29edf1a..f5d8084d 100644 --- a/server/cmd/api/api/api.go +++ b/server/cmd/api/api/api.go @@ -39,6 +39,9 @@ type ApiService struct { // inputMu serializes input-related operations (mouse, keyboard, screenshot) inputMu sync.Mutex + + // playwrightMu serializes Playwright code execution (only one execution at a time) + playwrightMu sync.Mutex } var _ oapi.StrictServerInterface = (*ApiService)(nil) diff --git a/server/cmd/api/api/playwright.go b/server/cmd/api/api/playwright.go new file mode 100644 index 00000000..61669333 --- /dev/null +++ b/server/cmd/api/api/playwright.go @@ -0,0 +1,135 @@ +package api + +import ( + "context" + "encoding/json" + "fmt" + "os" + "os/exec" + "time" + + "github.com/onkernel/kernel-images/server/lib/logger" + "github.com/onkernel/kernel-images/server/lib/oapi" +) + +// ExecutePlaywrightCode implements the Playwright code execution endpoint +func (s *ApiService) ExecutePlaywrightCode(ctx context.Context, request oapi.ExecutePlaywrightCodeRequestObject) (oapi.ExecutePlaywrightCodeResponseObject, error) { + // Serialize Playwright execution - only one execution at a time + s.playwrightMu.Lock() + defer s.playwrightMu.Unlock() + + log := logger.FromContext(ctx) + + // Validate request + if request.Body == nil || request.Body.Code == "" { + return oapi.ExecutePlaywrightCode400JSONResponse{ + BadRequestErrorJSONResponse: oapi.BadRequestErrorJSONResponse{ + Message: "code is required", + }, + }, nil + } + + // Determine timeout (default to 60 seconds) + timeout := 60 * time.Second + if request.Body.TimeoutSec != nil && *request.Body.TimeoutSec > 0 { + timeout = time.Duration(*request.Body.TimeoutSec) * time.Second + } + + // Create a temporary file for the user code + tmpFile, err := os.CreateTemp("", "playwright-code-*.ts") + if err != nil { + log.Error("failed to create temp file", "error", err) + return oapi.ExecutePlaywrightCode500JSONResponse{ + InternalErrorJSONResponse: oapi.InternalErrorJSONResponse{ + Message: fmt.Sprintf("failed to create temp file: %v", err), + }, + }, nil + } + tmpFilePath := tmpFile.Name() + defer os.Remove(tmpFilePath) // Clean up the temp file + + // Write the user code to the temp file + if _, err := tmpFile.WriteString(request.Body.Code); err != nil { + tmpFile.Close() + log.Error("failed to write code to temp file", "error", err) + return oapi.ExecutePlaywrightCode500JSONResponse{ + InternalErrorJSONResponse: oapi.InternalErrorJSONResponse{ + Message: fmt.Sprintf("failed to write code to temp file: %v", err), + }, + }, nil + } + tmpFile.Close() + + // Create context with timeout + execCtx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + + // Execute the Playwright code via the executor script + cmd := exec.CommandContext(execCtx, "tsx", "/usr/local/lib/playwright-executor.ts", tmpFilePath) + + output, err := cmd.CombinedOutput() + + if err != nil { + if execCtx.Err() == context.DeadlineExceeded { + log.Error("playwright execution timed out", "timeout", timeout) + success := false + errorMsg := fmt.Sprintf("execution timed out after %v", timeout) + return oapi.ExecutePlaywrightCode200JSONResponse{ + Success: success, + Error: &errorMsg, + }, nil + } + + log.Error("playwright execution failed", "error", err, "output", string(output)) + + // Try to parse the error output as JSON + var result struct { + Success bool `json:"success"` + Result interface{} `json:"result,omitempty"` + Error string `json:"error,omitempty"` + Stack string `json:"stack,omitempty"` + } + if jsonErr := json.Unmarshal(output, &result); jsonErr == nil { + success := result.Success + errorMsg := result.Error + stderr := string(output) + return oapi.ExecutePlaywrightCode200JSONResponse{ + Success: success, + Error: &errorMsg, + Stderr: &stderr, + }, nil + } + + // If we can't parse the output, return a generic error + success := false + errorMsg := fmt.Sprintf("execution failed: %v", err) + stderr := string(output) + return oapi.ExecutePlaywrightCode200JSONResponse{ + Success: success, + Error: &errorMsg, + Stderr: &stderr, + }, nil + } + + // Parse successful output + var result struct { + Success bool `json:"success"` + Result interface{} `json:"result,omitempty"` + } + if err := json.Unmarshal(output, &result); err != nil { + log.Error("failed to parse playwright output", "error", err, "output", string(output)) + success := false + errorMsg := fmt.Sprintf("failed to parse output: %v", err) + stdout := string(output) + return oapi.ExecutePlaywrightCode200JSONResponse{ + Success: success, + Error: &errorMsg, + Stdout: &stdout, + }, nil + } + + return oapi.ExecutePlaywrightCode200JSONResponse{ + Success: result.Success, + Result: &result.Result, + }, nil +} diff --git a/server/e2e/e2e_chromium_test.go b/server/e2e/e2e_chromium_test.go index 4c9029ec..4c91f925 100644 --- a/server/e2e/e2e_chromium_test.go +++ b/server/e2e/e2e_chromium_test.go @@ -5,6 +5,7 @@ import ( "bytes" "context" "encoding/base64" + "encoding/json" "fmt" "io" "mime/multipart" @@ -729,3 +730,83 @@ func getXvfbResolution(ctx context.Context) (width, height int, err error) { return 0, 0, fmt.Errorf("Xvfb process not found in ps aux output") } + +func TestPlaywrightExecuteAPI(t *testing.T) { + image := headlessImage + name := containerName + "-playwright-api" + + logger := slog.New(slog.NewTextHandler(t.Output(), &slog.HandlerOptions{Level: slog.LevelInfo})) + baseCtx := logctx.AddToContext(context.Background(), logger) + + if _, err := exec.LookPath("docker"); err != nil { + require.NoError(t, err, "docker not available: %v", err) + } + + // Clean slate + _ = stopContainer(baseCtx, name) + + env := map[string]string{} + + // Start container + _, exitCh, err := runContainer(baseCtx, image, name, env) + require.NoError(t, err, "failed to start container: %v", err) + defer stopContainer(baseCtx, name) + + ctx, cancel := context.WithTimeout(baseCtx, 2*time.Minute) + defer cancel() + + require.NoError(t, waitHTTPOrExit(ctx, apiBaseURL+"/spec.yaml", exitCh), "api not ready: %v", err) + + client, err := apiClient() + require.NoError(t, err) + + // Test simple Playwright script that navigates to a page and returns the title + playwrightCode := ` + await page.goto('https://example.com'); + const title = await page.title(); + return title; + ` + + logger.Info("[test]", "action", "executing playwright code") + req := instanceoapi.ExecutePlaywrightCodeJSONRequestBody{ + Code: playwrightCode, + } + + rsp, err := client.ExecutePlaywrightCodeWithResponse(ctx, req) + require.NoError(t, err, "playwright execute request error: %v", err) + require.Equal(t, http.StatusOK, rsp.StatusCode(), "unexpected status for playwright execute: %s body=%s", rsp.Status(), string(rsp.Body)) + require.NotNil(t, rsp.JSON200, "expected JSON200 response, got nil") + + // Log the full response for debugging + if !rsp.JSON200.Success { + var errorMsg string + if rsp.JSON200.Error != nil { + errorMsg = *rsp.JSON200.Error + } + var stdout, stderr string + if rsp.JSON200.Stdout != nil { + stdout = *rsp.JSON200.Stdout + } + if rsp.JSON200.Stderr != nil { + stderr = *rsp.JSON200.Stderr + } + logger.Error("[test]", "error", errorMsg, "stdout", stdout, "stderr", stderr) + } + + require.True(t, rsp.JSON200.Success, "expected success=true, got success=false. Error: %s", func() string { + if rsp.JSON200.Error != nil { + return *rsp.JSON200.Error + } + return "nil" + }()) + require.NotNil(t, rsp.JSON200.Result, "expected result to be non-nil") + + // Verify the result contains "Example Domain" (the title of example.com) + resultBytes, err := json.Marshal(rsp.JSON200.Result) + require.NoError(t, err, "failed to marshal result: %v", err) + resultStr := string(resultBytes) + logger.Info("[test]", "result", resultStr) + require.Contains(t, resultStr, "Example Domain", "expected result to contain 'Example Domain'") + + logger.Info("[test]", "result", "playwright execute API test passed") +} diff --git a/server/lib/oapi/oapi.go b/server/lib/oapi/oapi.go index a9c5cf7c..e9252d0e 100644 --- a/server/lib/oapi/oapi.go +++ b/server/lib/oapi/oapi.go @@ -184,6 +184,34 @@ type Error struct { Message string `json:"message"` } +// ExecutePlaywrightRequest Request to execute Playwright code +type ExecutePlaywrightRequest struct { + // Code TypeScript/JavaScript code to execute. The code has access to 'page', 'context', and 'browser' variables. + // Example: "await page.goto('https://example.com'); return await page.title();" + Code string `json:"code"` + + // TimeoutSec Maximum execution time in seconds. Default is 30. + TimeoutSec *int `json:"timeout_sec,omitempty"` +} + +// ExecutePlaywrightResult Result of Playwright code execution +type ExecutePlaywrightResult struct { + // Error Error message if execution failed + Error *string `json:"error,omitempty"` + + // Result The value returned by the code (if any) + Result interface{} `json:"result,omitempty"` + + // Stderr Standard error from the execution + Stderr *string `json:"stderr,omitempty"` + + // Stdout Standard output from the execution + Stdout *string `json:"stdout,omitempty"` + + // Success Whether the code executed successfully + Success bool `json:"success"` +} + // FileInfo defines model for FileInfo. type FileInfo struct { // IsDir Whether the path is a directory. @@ -293,6 +321,7 @@ type PressKeyRequest struct { // Keys List of key symbols to press. Each item should be a key symbol supported by xdotool // (see X11 keysym definitions). Examples include "Return", "Shift", "Ctrl", "Alt", "F5". + // Items in this list could also be combinations, e.g. "Ctrl+t" or "Ctrl+Shift+Tab". Keys []string `json:"keys"` } @@ -670,6 +699,9 @@ type UploadZipMultipartRequestBody UploadZipMultipartBody // StartFsWatchJSONRequestBody defines body for StartFsWatch for application/json ContentType. type StartFsWatchJSONRequestBody = StartFsWatchRequest +// ExecutePlaywrightCodeJSONRequestBody defines body for ExecutePlaywrightCode for application/json ContentType. +type ExecutePlaywrightCodeJSONRequestBody = ExecutePlaywrightRequest + // ProcessExecJSONRequestBody defines body for ProcessExec for application/json ContentType. type ProcessExecJSONRequestBody = ProcessExecRequest @@ -872,6 +904,11 @@ type ClientInterface interface { // LogsStream request LogsStream(ctx context.Context, params *LogsStreamParams, reqEditors ...RequestEditorFn) (*http.Response, error) + // ExecutePlaywrightCodeWithBody request with any body + ExecutePlaywrightCodeWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + ExecutePlaywrightCode(ctx context.Context, body ExecutePlaywrightCodeJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + // ProcessExecWithBody request with any body ProcessExecWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -1412,6 +1449,30 @@ func (c *Client) LogsStream(ctx context.Context, params *LogsStreamParams, reqEd return c.Client.Do(req) } +func (c *Client) ExecutePlaywrightCodeWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewExecutePlaywrightCodeRequestWithBody(c.Server, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) ExecutePlaywrightCode(ctx context.Context, body ExecutePlaywrightCodeJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewExecutePlaywrightCodeRequest(c.Server, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + func (c *Client) ProcessExecWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { req, err := NewProcessExecRequestWithBody(c.Server, contentType, body) if err != nil { @@ -2719,6 +2780,46 @@ func NewLogsStreamRequest(server string, params *LogsStreamParams) (*http.Reques return req, nil } +// NewExecutePlaywrightCodeRequest calls the generic ExecutePlaywrightCode builder with application/json body +func NewExecutePlaywrightCodeRequest(server string, body ExecutePlaywrightCodeJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewExecutePlaywrightCodeRequestWithBody(server, "application/json", bodyReader) +} + +// NewExecutePlaywrightCodeRequestWithBody generates requests for ExecutePlaywrightCode with any type of body +func NewExecutePlaywrightCodeRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/playwright/execute") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + // NewProcessExecRequest calls the generic ProcessExec builder with application/json body func NewProcessExecRequest(server string, body ProcessExecJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader @@ -3308,6 +3409,11 @@ type ClientWithResponsesInterface interface { // LogsStreamWithResponse request LogsStreamWithResponse(ctx context.Context, params *LogsStreamParams, reqEditors ...RequestEditorFn) (*LogsStreamResponse, error) + // ExecutePlaywrightCodeWithBodyWithResponse request with any body + ExecutePlaywrightCodeWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*ExecutePlaywrightCodeResponse, error) + + ExecutePlaywrightCodeWithResponse(ctx context.Context, body ExecutePlaywrightCodeJSONRequestBody, reqEditors ...RequestEditorFn) (*ExecutePlaywrightCodeResponse, error) + // ProcessExecWithBodyWithResponse request with any body ProcessExecWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*ProcessExecResponse, error) @@ -3974,6 +4080,30 @@ func (r LogsStreamResponse) StatusCode() int { return 0 } +type ExecutePlaywrightCodeResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *ExecutePlaywrightResult + JSON400 *BadRequestError + JSON500 *InternalError +} + +// Status returns HTTPResponse.Status +func (r ExecutePlaywrightCodeResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r ExecutePlaywrightCodeResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + type ProcessExecResponse struct { Body []byte HTTPResponse *http.Response @@ -4593,6 +4723,23 @@ func (c *ClientWithResponses) LogsStreamWithResponse(ctx context.Context, params return ParseLogsStreamResponse(rsp) } +// ExecutePlaywrightCodeWithBodyWithResponse request with arbitrary body returning *ExecutePlaywrightCodeResponse +func (c *ClientWithResponses) ExecutePlaywrightCodeWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*ExecutePlaywrightCodeResponse, error) { + rsp, err := c.ExecutePlaywrightCodeWithBody(ctx, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseExecutePlaywrightCodeResponse(rsp) +} + +func (c *ClientWithResponses) ExecutePlaywrightCodeWithResponse(ctx context.Context, body ExecutePlaywrightCodeJSONRequestBody, reqEditors ...RequestEditorFn) (*ExecutePlaywrightCodeResponse, error) { + rsp, err := c.ExecutePlaywrightCode(ctx, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseExecutePlaywrightCodeResponse(rsp) +} + // ProcessExecWithBodyWithResponse request with arbitrary body returning *ProcessExecResponse func (c *ClientWithResponses) ProcessExecWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*ProcessExecResponse, error) { rsp, err := c.ProcessExecWithBody(ctx, contentType, body, reqEditors...) @@ -5725,6 +5872,46 @@ func ParseLogsStreamResponse(rsp *http.Response) (*LogsStreamResponse, error) { return response, nil } +// ParseExecutePlaywrightCodeResponse parses an HTTP response from a ExecutePlaywrightCodeWithResponse call +func ParseExecutePlaywrightCodeResponse(rsp *http.Response) (*ExecutePlaywrightCodeResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &ExecutePlaywrightCodeResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest ExecutePlaywrightResult + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: + var dest BadRequestError + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON400 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: + var dest InternalError + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON500 = &dest + + } + + return response, nil +} + // ParseProcessExecResponse parses an HTTP response from a ProcessExecWithResponse call func ParseProcessExecResponse(rsp *http.Response) (*ProcessExecResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) @@ -6252,6 +6439,9 @@ type ServerInterface interface { // Stream logs over SSE // (GET /logs/stream) LogsStream(w http.ResponseWriter, r *http.Request, params LogsStreamParams) + // Execute Playwright/TypeScript code against the browser + // (POST /playwright/execute) + ExecutePlaywrightCode(w http.ResponseWriter, r *http.Request) // Execute a command synchronously // (POST /process/exec) ProcessExec(w http.ResponseWriter, r *http.Request) @@ -6447,6 +6637,12 @@ func (_ Unimplemented) LogsStream(w http.ResponseWriter, r *http.Request, params w.WriteHeader(http.StatusNotImplemented) } +// Execute Playwright/TypeScript code against the browser +// (POST /playwright/execute) +func (_ Unimplemented) ExecutePlaywrightCode(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + // Execute a command synchronously // (POST /process/exec) func (_ Unimplemented) ProcessExec(w http.ResponseWriter, r *http.Request) { @@ -7060,6 +7256,20 @@ func (siw *ServerInterfaceWrapper) LogsStream(w http.ResponseWriter, r *http.Req handler.ServeHTTP(w, r) } +// ExecutePlaywrightCode operation middleware +func (siw *ServerInterfaceWrapper) ExecutePlaywrightCode(w http.ResponseWriter, r *http.Request) { + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.ExecutePlaywrightCode(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + // ProcessExec operation middleware func (siw *ServerInterfaceWrapper) ProcessExec(w http.ResponseWriter, r *http.Request) { @@ -7462,6 +7672,9 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl r.Group(func(r chi.Router) { r.Get(options.BaseURL+"/logs/stream", wrapper.LogsStream) }) + r.Group(func(r chi.Router) { + r.Post(options.BaseURL+"/playwright/execute", wrapper.ExecutePlaywrightCode) + }) r.Group(func(r chi.Router) { r.Post(options.BaseURL+"/process/exec", wrapper.ProcessExec) }) @@ -8627,6 +8840,41 @@ func (response LogsStream200TexteventStreamResponse) VisitLogsStreamResponse(w h } } +type ExecutePlaywrightCodeRequestObject struct { + Body *ExecutePlaywrightCodeJSONRequestBody +} + +type ExecutePlaywrightCodeResponseObject interface { + VisitExecutePlaywrightCodeResponse(w http.ResponseWriter) error +} + +type ExecutePlaywrightCode200JSONResponse ExecutePlaywrightResult + +func (response ExecutePlaywrightCode200JSONResponse) VisitExecutePlaywrightCodeResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(200) + + return json.NewEncoder(w).Encode(response) +} + +type ExecutePlaywrightCode400JSONResponse struct{ BadRequestErrorJSONResponse } + +func (response ExecutePlaywrightCode400JSONResponse) VisitExecutePlaywrightCodeResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(400) + + return json.NewEncoder(w).Encode(response) +} + +type ExecutePlaywrightCode500JSONResponse struct{ InternalErrorJSONResponse } + +func (response ExecutePlaywrightCode500JSONResponse) VisitExecutePlaywrightCodeResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(500) + + return json.NewEncoder(w).Encode(response) +} + type ProcessExecRequestObject struct { Body *ProcessExecJSONRequestBody } @@ -9214,6 +9462,9 @@ type StrictServerInterface interface { // Stream logs over SSE // (GET /logs/stream) LogsStream(ctx context.Context, request LogsStreamRequestObject) (LogsStreamResponseObject, error) + // Execute Playwright/TypeScript code against the browser + // (POST /playwright/execute) + ExecutePlaywrightCode(ctx context.Context, request ExecutePlaywrightCodeRequestObject) (ExecutePlaywrightCodeResponseObject, error) // Execute a command synchronously // (POST /process/exec) ProcessExec(ctx context.Context, request ProcessExecRequestObject) (ProcessExecResponseObject, error) @@ -10046,6 +10297,37 @@ func (sh *strictHandler) LogsStream(w http.ResponseWriter, r *http.Request, para } } +// ExecutePlaywrightCode operation middleware +func (sh *strictHandler) ExecutePlaywrightCode(w http.ResponseWriter, r *http.Request) { + var request ExecutePlaywrightCodeRequestObject + + var body ExecutePlaywrightCodeJSONRequestBody + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode JSON body: %w", err)) + return + } + request.Body = &body + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.ExecutePlaywrightCode(ctx, request.(ExecutePlaywrightCodeRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "ExecutePlaywrightCode") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(ExecutePlaywrightCodeResponseObject); ok { + if err := validResponse.VisitExecutePlaywrightCodeResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + // ProcessExec operation middleware func (sh *strictHandler) ProcessExec(w http.ResponseWriter, r *http.Request) { var request ProcessExecRequestObject @@ -10372,115 +10654,122 @@ func (sh *strictHandler) StopRecording(w http.ResponseWriter, r *http.Request) { // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "jQAA", + "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", + "9uCwKl9WP7SR/wkAAP//KSSqVIyVAAA=", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/server/openapi.yaml b/server/openapi.yaml index 1ca3cfd6..62b818ae 100644 --- a/server/openapi.yaml +++ b/server/openapi.yaml @@ -930,6 +930,31 @@ paths: $ref: "#/components/responses/BadRequestError" "500": $ref: "#/components/responses/InternalError" + /playwright/execute: + post: + summary: Execute Playwright/TypeScript code against the browser + description: | + Execute arbitrary Playwright code in a fresh execution context against the browser running + on localhost:9222. The code has access to 'page', 'context', and 'browser' variables. + The result of the code execution is returned in the response. + operationId: executePlaywrightCode + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/ExecutePlaywrightRequest" + responses: + "200": + description: Code executed successfully + content: + application/json: + schema: + $ref: "#/components/schemas/ExecutePlaywrightResult" + "400": + $ref: "#/components/responses/BadRequestError" + "500": + $ref: "#/components/responses/InternalError" components: schemas: StartRecordingRequest: @@ -1487,6 +1512,43 @@ components: description: Indicates success. default: true additionalProperties: false + ExecutePlaywrightRequest: + type: object + description: Request to execute Playwright code + required: [code] + properties: + code: + type: string + description: | + TypeScript/JavaScript code to execute. The code has access to 'page', 'context', and 'browser' variables. + Example: "await page.goto('https://example.com'); return await page.title();" + timeout_sec: + type: integer + description: Maximum execution time in seconds. Default is 60. + default: 60 + minimum: 1 + maximum: 300 + additionalProperties: false + ExecutePlaywrightResult: + type: object + description: Result of Playwright code execution + required: [success] + properties: + success: + type: boolean + description: Whether the code executed successfully + result: + description: The value returned by the code (if any) + error: + type: string + description: Error message if execution failed + stdout: + type: string + description: Standard output from the execution + stderr: + type: string + description: Standard error from the execution + additionalProperties: false responses: BadRequestError: description: Bad Request diff --git a/server/runtime/playwright-executor.ts b/server/runtime/playwright-executor.ts new file mode 100644 index 00000000..5246eab4 --- /dev/null +++ b/server/runtime/playwright-executor.ts @@ -0,0 +1,60 @@ +import { readFileSync } from 'fs'; +import { chromium } from 'playwright-core'; + +async function main() { + const codeFilePath = process.argv[2]; + + if (!codeFilePath) { + console.error('Usage: tsx playwright-executor.ts '); + process.exit(1); + } + + let userCode: string; + try { + userCode = readFileSync(codeFilePath, 'utf-8'); + } catch (error: any) { + console.error(JSON.stringify({ + success: false, + error: `Failed to read code file: ${error.message}` + })); + process.exit(1); + } + + let browser; + let result; + + try { + browser = await chromium.connectOverCDP('ws://127.0.0.1:9222'); + const contexts = browser.contexts(); + const context = contexts.length > 0 ? contexts[0] : await browser.newContext(); + const pages = context.pages(); + const page = pages.length > 0 ? pages[0] : await context.newPage(); + + const AsyncFunction = Object.getPrototypeOf(async function () { }).constructor; + const userFunction = new AsyncFunction('page', 'context', 'browser', userCode); + result = await userFunction(page, context, browser); + + if (result !== undefined) { + console.log(JSON.stringify({ success: true, result: result })); + } else { + console.log(JSON.stringify({ success: true, result: null })); + } + } catch (error: any) { + console.error(JSON.stringify({ + success: false, + error: error.message, + stack: error.stack + })); + process.exit(1); + } finally { + if (browser) { + try { + await browser.close(); + } catch (e) { + // Ignore errors when closing CDP connection + } + } + } +} + +main();