-
Notifications
You must be signed in to change notification settings - Fork 42
ad hoc playwright code exec API #84
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
rgarcia
merged 19 commits into
main
from
raf/kernel-458-ad-hoc-playwright-code-exec-api
Oct 27, 2025
Merged
Changes from all commits
Commits
Show all changes
19 commits
Select commit
Hold shift + click to select a range
5e8c82f
feat: Add dynamic Playwright code execution API
tembo[bot] cd243de
feat(node): upgrade Node.js from 20.x to 22.x in Docker images
tembo[bot] 4ffd35b
fix: remove playwright install-deps from Docker images
tembo[bot] 2acc2b4
fix(api): update Playwright timeout default to 60 seconds
tembo[bot] 7cb361f
fix(api): correct type name in playwright test
tembo[bot] bb770b7
fix(playwright): remove unused temp directory creation
tembo[bot] b850e0f
fix(test): improve error reporting in playwright e2e test
tembo[bot] f062258
fix(runtime): use require instead of import for playwright
tembo[bot] 41833cd
fix(playwright): add NODE_PATH to resolve global playwright module
tembo[bot] e0a36b1
fix(playwright): use ws protocol and ES module imports
tembo[bot] 2a6faf7
fix(container): set NODE_PATH to resolve global node modules
tembo[bot] 662247d
lighter weight node install
rgarcia 6f9e959
remove node path
rgarcia 42b1499
tweaks
rgarcia ecd8f14
use 127.0.0.1 (don't think we set hostname on unikernel)
rgarcia e809a85
name the node image differently from neko client
rgarcia 61dac42
Merge branch 'main' into raf/kernel-458-ad-hoc-playwright-code-exec-api
rgarcia 8251b07
Merge branch 'main' into raf/kernel-458-ad-hoc-playwright-code-exec-api
rgarcia aaccaeb
mutex on playwright exec endpoint
rgarcia File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 | ||
| } | ||
rgarcia marked this conversation as resolved.
Show resolved
Hide resolved
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just curious, does it make the image bigger?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes by 200-300 MB