Playwright tooling for Godot web exports.
This repo contains the Godot addon that emits test metadata from a web export, the read-only gdpw CLI that queries that metadata and generates Playwright actions, and JavaScript helpers for controlling active games from a persistent Playwright process.
Use it when browser-based tests need stable names like start_button instead of hardcoded screen coordinates.
gd/: Godot addon that exposes browser events, element positions, viewport state, and test state.cli/: Gogdpwcommand-line helper for querying exported games through CDP.js/: dependency-free Playwright helpers for live element resolution, confirmed actions, and retries.
gdam install @aviorstudio/gd-playwrightCopy gd/addon/ into res://addons/@aviorstudio_gd-playwright/ and enable GD Playwright Client in Project Settings -> Plugins.
The plugin installs an autoload named PlaywrightService. You can also add autoload.gd manually as an autoload with that name.
const PlaywrightServiceModule = preload("res://addons/@aviorstudio_gd-playwright/src/playwright_service.gd")
func _ready() -> void:
PlaywrightService.configure(PlaywrightServiceModule.PlaywrightConfig.new(true, true, true, 1000))
PlaywrightService.emit_event("route_loaded", {"route": "home"})
PlaywrightService.set_test_state("menu", {"route": "home"})Add a visible PlaywrightTag child under any Control or Node2D that tests need to click or inspect:
StartButton
PlaywrightTag
tag_key = "start_button"
When the web export runs with gd-playwright enabled, the tag writes center-point positions to window.godotElements:
{
"start_button": { "x": 360, "y": 800, "w": 280, "h": 72, "visible": true }
}If your game wraps the addon service with its own autoload, set service_path on the helper nodes:
PlaywrightTag
tag_key = "start_button"
service_path = "/root/MyPlaywrightService"
Legacy metadata tags still work:
button.set_meta("playwright", "start_button")
PlaywrightService.scan_scene()The editor also includes tool menu actions:
GD Playwright: Scan Scene For MetadataGD Playwright: Convert Metadata To Tags
Use PlaywrightEventEmitter when a scene should author a named event in the Inspector:
HomeScreen
RouteLoadedEvent
script = PlaywrightEventEmitter
event_name = "route_loaded"
payload = { "route": "home" }
Game code can call the node when the event occurs:
$RouteLoadedEvent.emit_playwright_event({"screen": "HomeScreen"})Use PlaywrightStatePublisher for namespaced test-observable state:
GameScreen
GameStatePublisher
script = PlaywrightStatePublisher
state_namespace = "game"
$GameStatePublisher.publish({
"level": "level_01",
"solved": false
})An installable example scene is included at:
res://addons/@aviorstudio_gd-playwright/examples/app_shell/playwright_example_screen.tscn
For runtime-created objects, use the generic service APIs:
PlaywrightService.register_element("unit_0", center_pos, size, true)
PlaywrightService.unregister_element("unit_0")
PlaywrightService.emit_namespaced_event("combat", "turn_started", {"turn": 1})
PlaywrightService.set_state("combat", {"turn": 1})For dynamic objects, register positions manually:
var element_map = PlaywrightService.get_element_map()
if element_map:
element_map.register("unit_0", center_pos, size, true)Events are appended to window.godotEvents and dispatched as browser CustomEvent("godot-event") events:
PlaywrightService.emit_event("battle_started", {"round": 1})State is exposed at window.godotTestState.<namespace>:
PlaywrightService.set_test_state("puzzle", {"moves": 4, "solved": false})
PlaywrightService.clear_test_state("puzzle")Requires Go 1.24+.
go install github.com/aviorstudio/gd-playwright/cli/cmd/gdpw@latestOr build from source:
cd cli
mkdir -p bin
go build -o bin/gdpw ./cmd/gdpw/Or use the helper script:
cd cli
./build.sh# Open the game in a browser.
playwright-cli open http://localhost:3000 --headed
# See what the addon exposed. gdpw discovers playwright-cli's CDP port.
gdpw list --visible
# Get coordinates for an element.
gdpw get start_button
# Click it via playwright-cli.
playwright-cli mousemove 360 640
playwright-cli mousedown
playwright-cli mouseup
# Wait for a new event.
gdpw wait route_loadedMoving game objects should be resolved immediately before browser input. gdpw remains read-only: these commands print one playwright-cli run-code command, and Playwright performs the input when the output is executed.
# Atomic browser-local click.
gdpw get play_button --script | sh
# Retry a moving-target drag only when the expected fresh event is missing.
gdpw script drag enemy_1 \
--to 1000,260 \
--expect-event enemy_released \
--filter id=1 \
--retries 3 | shEvery attempt re-reads window.godotElements, viewport scaling, and the canvas rectangle. Confirmation listeners are armed before input, and drag failures always attempt to release the mouse.
For sustained active-game tests, import the JavaScript helper package in a Playwright test so decisions and input stay in one process:
import { dragElement } from "@aviorstudio/gd-playwright";
await dragElement(page, "enemy_1", {
to: { x: 1000, y: 260 },
confirm: { eventName: "enemy_released", filters: { id: 1 } },
retries: 3,
});Retries are not exactly-once delivery. Keep them disabled for non-idempotent actions unless the confirmation event uniquely identifies success.
| Command | Description |
|---|---|
get <key> [key2...] |
Get canvas-scaled center coordinates for elements. |
list |
List all registered element keys. |
status |
Check CDP connection and gd-playwright state. |
events |
Show recent game events from window.godotEvents. |
wait <event> |
Wait for a new event to appear. |
watch |
Stream events in real time. |
state |
Show aggregated state: test state, elements, viewport, and latest events by type. |
script click/drag |
Generate atomic Playwright actions with optional fresh-event confirmation and retries. |
gdpw resolves its browser connection in this order:
--cdp ws://...--port <N>GDPW_CDPGDPW_PORT- Auto-discovery from default ports and local browser
--remote-debugging-portprocess arguments
Auto-discovery checks every page target and selects the one exposing gd-playwright globals. This avoids attaching to a stale non-game tab when multiple Playwright or Chrome targets exist. Explicit --cdp remains available when process inspection is unavailable or the browser is remote.
Settings use the gd_playwright/ prefix:
| Setting | Type | Default | Description |
|---|---|---|---|
enabled |
bool | false |
Force-enable event emission. |
test_mode |
bool | false |
Enable emission and element maps in non-debug builds. |
log_events |
bool | true |
Log emitted events in the browser console. |
event_buffer_max |
int | 1000 |
Max events in window.godotEvents; 0 means no limit. |
event_buffer_trim |
int | 500 |
Events kept after trimming; 0 means no trim. |
The Godot addon writes generic browser globals during enabled web runs:
window.godotElements: element keys mapped to{x, y, w, h, visible}in Godot viewport space.window.godotElementsViewport: viewport information used to scale coordinates to the canvas.window.godotEvents: buffered event records emitted by game code.window.godotTestState: namespaced test state dictionaries.
gdpw reads those globals through CDP. It never mutates browser or game state and it never calls playwright-cli; it only provides data that another tool can use for input.
- Features only run in web builds when debug mode,
enabled, ortest_modeis active. - Calls are safe to leave in game code because disabled features no-op.
- Do not expose private player data through test state or event payloads.
- Game-specific knowledge belongs in game docs or skills, not in
gdpw.
gd/addon/: Godot plugin source packaged for GDAM and manual installation.gd/tests/: Godot test project/scripts for addon behavior.cli/: GogdpwCLI source and build scripts.js/: JavaScript Playwright helpers and tests..github/workflows/ci.yml: runs Godot addon tests and Go CLI tests..github/workflows/release.yml: creates addon and CLI GitHub releases.
This repo currently has two automated release targets:
gd: usesgd-v*tags, verifiesgd/addon/plugin.cfg, builds@aviorstudio_gd-playwright.zip, and publishes@aviorstudio/gd-playwrightto GDAM.cli: usescli-v*tags, runs Go tests, buildsgdpwbinaries for Linux, macOS, and Windows, and attaches checksums.
The implemented js/ package does not yet have an automated release target. The release workflow is manual and must be run from main with a patch, minor, or major bump.
Run locally with:
mise exec -- ./gd/tests/test.sh
cd cli && mise exec -- go test ./...
cd js && mise exec -- bun testCI runs all three test suites.
MIT