feat(examples): Introduce Chromium CDP - #151
Conversation
e1b1e5b to
7ba0200
Compare
Introduce Chromium running as a browser service using CDP (Chrome DevTools Protocol). It uses Node Playwright to start the browser and the Node `http-proxy` module to proxy websocket communication. Add: * `Kraftfile`: build / run rules * `Dockerfile`: placeholder to extract the filesystem * `package.json` / `package-lock.json`: Node package requirements * `server.js`: Node-based websocket proxy * `wrapper.sh`: helper script to start the Node proxy service (and the browser) * `README.md`: document how to use * `.dockerignore` / `.gitignore`: ignore generated files * `test/`: CDP test client (in Python using Playwright) Signed-off-by: Razvan Deaconescu <razvand@unikraft.io>
7ba0200 to
5792db8
Compare
Introduce an echo-reply WebSocket server to be deployed on Unikraft Cloud with Node. It uses the `node:21` image. Add: * `Kraftfile`: build / run rules * `Dockerfile`: placeholder to extract the filesystem * `package.json` / `package-lock.json`: NPM configuration file * `server.js`: implementation of Node WebSocket server * `README.md`: document how to use * `.dockerignore` / `.gitignore`: ignore generated files * workflow files in `../.github/workflows/` Update top-level `README.md` to feature the example. Signed-off-by: Razvan Deaconescu <razvand@unikraft.io>
Install `wscat` globally using `npm install -g wscat`, as instructed: https://www.npmjs.com/package/wscat Signed-off-by: Razvan Deaconescu <razvand@unikraft.io>
Use `websocat` instead of `wscat` as the WebSocket client, as it can be used non-interactively. Signed-off-by: Razvan Deaconescu <razvand@unikraft.io>
|
@razvand can you rebase this just in case so I can get to reviewing? I can see that the tests failed but those were because of the missing token and/or metro on the fork. If that is the case, we can merge and see afterwards if tests pass |
There was a problem hiding this comment.
Pull request overview
This PR adds a new chromium-cdp example that runs Chromium as a remote browser service on Unikraft Cloud, exposing a CDP endpoint via a Node-based proxy (Playwright + http-proxy). It also introduces a separate node21-websocket echo server example and wires it into the top-level examples list and CI workflows.
Changes:
- Add
chromium-cdp/example: container rootfs build, Node proxy server, wrapper script, and Python Playwright CDP client. - Add
node21-websocket/example: minimalwsecho server with Kraft/Docker packaging and docs. - Add GitHub Actions workflows for
node21-websocketstable/staging integration tests and list the example in the root README.
Reviewed changes
Copilot reviewed 20 out of 23 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| README.md | Adds node21-websocket to the examples table (and its CI badges). |
| node21-websocket/server.js | Implements a simple WebSocket echo server. |
| node21-websocket/README.md | Deployment + usage instructions for the WebSocket example. |
| node21-websocket/package.json | Declares ws dependency and ESM mode. |
| node21-websocket/package-lock.json | Locks ws dependency version. |
| node21-websocket/Kraftfile | Unikraft runtime/config for the WebSocket example. |
| node21-websocket/Dockerfile | Builds a minimal rootfs containing Node deps and server script. |
| chromium-cdp/README.md | Documents deploying the Chromium CDP browser service. |
| chromium-cdp/proxy.js | Starts Chromium and proxies HTTP/WS traffic, rewriting CDP WS URLs. |
| chromium-cdp/wrapper.sh | Wrapper to set HOME, cd into /app, and exec the server process. |
| chromium-cdp/package.json | Node dependencies for Playwright + proxying/JSON rewrite. |
| chromium-cdp/package-lock.json | Locks Node dependencies for chromium-cdp. |
| chromium-cdp/Kraftfile | Unikraft runtime/config for the Chromium CDP service. |
| chromium-cdp/Dockerfile | Builds a scratch rootfs containing Node, Playwright Chromium, and required libs. |
| chromium-cdp/.gitignore | Ignores node_modules/.unikraft artifacts and generated PNGs. |
| chromium-cdp/.dockerignore | Avoids copying node_modules/.unikraft artifacts and generated PNGs into builds. |
| chromium-cdp/test/README.md | Instructions for setting up/running the Python CDP client. |
| chromium-cdp/test/pyproject.toml | Poetry project config for the Python test client. |
| chromium-cdp/test/poetry.lock | Locked Python dependencies for the CDP client. |
| chromium-cdp/test/cdp-screenshot.py | Python Playwright client that connects over CDP and takes a screenshot. |
| chromium-cdp/test/.gitignore | Ignores venv and generated screenshots. |
| .github/workflows/example-node21-websocket-stable.yaml | Stable integration workflow for node21-websocket. |
| .github/workflows/example-node21-websocket-staging.yaml | Staging integration workflow for node21-websocket. |
Files not reviewed (2)
- chromium-cdp/package-lock.json: Generated file
- node21-websocket/package-lock.json: Generated file
Suppressed comments (1)
.github/workflows/example-node21-websocket-staging.yaml:107
- In the debug re-test, the workflow deploys
node21-websocket-staging-${GITHUB_RUN_ID}-dbgbut connects to the stable subdomain. This should connect to the-dbgstaging instance that was just started.
echo "hello" | ./websocat.x86_64-unknown-linux-musl wss://node21-websocket-stable-${GITHUB_RUN_ID}.${UKC_METRO}.kraft.host | grep "hello" > /dev/null
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| kraft cloud vm start -w 60s node21-websocket-staging-${GITHUB_RUN_ID}; | ||
| sleep 5; | ||
|
|
||
| echo "hello" | ./websocat.x86_64-unknown-linux-musl wss://node21-websocket-stable-${GITHUB_RUN_ID}.${UKC_METRO}.kraft.host | grep "hello" > /dev/null |
|
|
||
| export HOME=/root | ||
| cd /app | ||
| exec $@ |
| // | ||
| // Launch Chromium browser with CDP enabled. | ||
| // | ||
| chromium.launch({headless: true, args: [`--remote-debugging-port=${cdp_port}`]}) | ||
|
|
| proxy.on('proxyRes', function (proxyRes, req, res) { | ||
| if (res.req.url.startsWith("/json")) { | ||
| const isHost = (element) => element == 'Host'; | ||
| host = res.req.rawHeaders[res.req.rawHeaders.findIndex(isHost)+1]; | ||
|
|
||
| modifyResponse(res, proxyRes, function (body) { | ||
| if (body) { | ||
| body.webSocketDebuggerUrl = body.webSocketDebuggerUrl.replace(`${cdp_host}:${cdp_port}`, host); | ||
| body.webSocketDebuggerUrl = body.webSocketDebuggerUrl.replace("ws://", "wss://"); | ||
| } | ||
| return body; // return value can be a promise | ||
| }); | ||
|
|
||
| res.setHeader('Host', host); | ||
| } | ||
| }); |
| # Take a single screenshot of the entire page | ||
| screenshot = new_page.screenshot() | ||
|
|
||
| with open("screenshot.png", "wb") as stream: | ||
| stream.write(screenshot) |
| @@ -0,0 +1,18 @@ | |||
| # CDP Screenshot Test / Client | |||
|
|
|||
| This is Python client using [CDP (Chrome DevTools Protocol)](https://chromedevtools.github.io/devtools-protocol/) to create a screenshot from an existing Chromium instances. | |||
| [`node18-nextjs`](https://github.com/unikraft-cloud/examples/tree/main/node21-nextjs) | [](https://github.com/unikraft-cloud/examples/actions/workflows/example-node18-nextjs-stable.yaml) | [](https://github.com/unikraft-cloud/examples/actions/workflows/example-node18-nextjs-staging.yaml) | | ||
| [`node21-solidstart`](https://github.com/unikraft-cloud/examples/tree/main/node21-solid-start) | [](https://github.com/unikraft-cloud/examples/actions/workflows/example-node21-solidstart-stable.yaml) | [](https://github.com/unikraft-cloud/examples/actions/workflows/example-node21-solidstart-staging.yaml) | | ||
| [`node21-remix`](https://github.com/unikraft-cloud/examples/tree/main/node21-remix) | [](https://github.com/unikraft-cloud/examples/actions/workflows/example-node21-remix-stable.yaml) | [](https://github.com/unikraft-cloud/examples/actions/workflows/example-node21-remix-staging.yaml) | | ||
| [`node21-websocket`](https://github.com/unikraft-cloud/examples/tree/main/node21-websocket) | [](https://github.com/unikraft-cloud/examples/actions/workflows/example-node21-websocket-stable.yaml) | [](https://github.com/unikraft-cloud/examples/actions/workflows/example-node21-websocket-staging.yaml) | |
| if len(sys.argv) != 2: | ||
| print(f"Usage: {sys.argv[0]} wss://...", file=sys.stderr) | ||
| sys.exit(1) |
|
|
||
| ## Learn more | ||
|
|
||
| - [WebSocket documentation](https://nextjs.org/docs) |
Introduce Chromium running as a browser service using CDP (Chrome DevTools Protocol). It uses Node Playwright to start the browser and the Node
http-proxymodule to proxy websocket communication.Add:
Kraftfile: build / run rulesDockerfile: placeholder to extract the filesystempackage.json/package-lock.json: Node package requirementsproxy.js: Node-based websocket proxywrapper.sh: helper script to start the Node proxy service (and the browser)README.md: document how to use.dockerignore/.gitignore: ignore generated filestest/: CDP test client (in Python using Playwright)