/godot-e2e
Write and run E2E (end-to-end) game tests using the godot-e2e framework. Python controls a live Godot game over TCP — Locator-based semantic queries, expect() auto-retry assertions, and engine log capture make failures self-diagnosing. Use this skill whenever you need to: - Test
$ npx -y skills add RandallLiuXin/GodotMaker --skill godot-e2e --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
- Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
- You can call itInvoke it directly when you want it.
- Slash command
/godot-e2e
Context preview
The summary Claude sees to decide when to auto-load this skill.
Write and run E2E (end-to-end) game tests using the godot-e2e framework. Python controls a live Godot game over TCP — Locator-based semantic queries, expect() auto-retry assertions, and engine log capture make failures self-diagnosing. Use this skill whenever you need to: - Test
SKILL.md
godot-e2e.SKILL.mdname: godot-e2e
description: |
Write and run E2E (end-to-end) game tests using the godot-e2e
framework. Python controls a live Godot game over TCP — Locator-based
semantic queries, expect() auto-retry assertions, and engine log
capture make failures self-diagnosing.
Use this skill whenever you need to:
- Test actual gameplay: player movement, collisions, scoring, scene transitions
- Verify UI interactions: button clicks, label text, menu navigation
- Write integration tests that run the real game (not mocked unit tests)
- Debug E2E test failures or set up E2E test infrastructure
Triggers: "E2E test", "end-to-end test", "gameplay test", "test the game running",
"simulate input", "test player movement", "test UI clicks", "godot-e2e",
"integration test for game", "test scene transitions".
godot-e2e — E2E Testing for Godot
$ARGUMENTS
godot-e2e is a custom framework with **zero LLM training data coverage**. Everything the model needs is in this skill (with deeper detail in `references/`). Do not guess — follow these docs exactly.
Architecture
The `godot-e2e` CLI launches a Godot process and communicates over TCP (localhost). Enabling the GodotE2E plugin in Project Settings auto-registers an `AutomationServer` autoload that receives JSON commands, executes them on the main thread, and sends back results. The game runs unmodified — the server is dormant unless launched with `--e2e`. Multiple instances can run in parallel (each auto-allocates a unique port). The framework rests on three pillars: `Locator` for semantic node queries, `expect()` for auto-retry assertions, and engine log capture so every error carries the Godot logs that preceded it.
Quick Start — conftest.py + Test File
# conftest.py (per test directory — explicit project path control;
# alternatively set GODOT_E2E_PROJECT_PATH env or pytest.ini
# `godot_e2e_project_path` and use the auto-registered `game` fixture).
# Replace "/root/Main" below with your project's entry-scene root —
# read it from `project.godot`'s `run/main_scene`.
import os
import pytest
from godot_e2e import GodotE2E
GODOT_PROJECT = os.path.join(os.path.dirname(__file__), "..")
GODOT_CONFIG = os.path.join(GODOT_PROJECT, ".claude", "godotmaker.yaml")
def _read_godot_path():
try:
with open(GODOT_CONFIG, "r", encoding="utf-8") as f:
for line in f:
line = line.split("#", 1)[0].strip()
if line.startswith("godot_path:"):
value = line.split(":", 1)[1].strip().strip("\"'")
return value or None
except OSError:
return None
return None
GODOT_PATH = _read_godot_path()
@pytest.fixture(scope="module")
def _game_process():
with GodotE2E.launch(
GODOT_PROJECT,
godot_path=GODOT_PATH,
timeout=15.0,
) as game:
game.wait_for_node("/root/Main", timeout=10.0)
yield game
@pytest.fixture(scope="function")
def game(_game_process):
_game_process.reload_scene()
_game_process.wait_for_node("/root/Main", timeout=5.0)
yield _game_process# test_player.py
from godot_e2e import expect
def test_player_moves_right(game):
player = game.locator(group="player") # Locator query
initial_x = player.get_property("position:x")
game.input_action("ui_right", True)
game.wait_physics_frames(10)
game.input_action("ui_right", False)
expect(player).to_satisfy(
lambda l: l.get_property("position:x") > initial_x,
description="player moved right",
)
def test_button_starts_game(game):
game.get_by_button("Start").click() # auto-waits actionability
expect(game.locator(name="GameStatus")).to_have_text("Playing")
errors = [e for e in game.collected_logs if e.level == "error"]
assert not errors, f"errors during click: {errors}"godot-e2e e2e/ -v
API Quick Reference
Launch / Lifecycle
| Method | Description | |---|---| | `GodotE2E.launch(project_path, godot_path=None, port=0, timeout=10.0, extra_args=None, log_verbosity=None)` | Context manager. Launch Godot + connect. `port=0` auto-allocates. `log_verbosity` ∈ `"error"`/`"warning"`/`"info"`. | | `GodotE2E.connect(host="127.0.0.1", port=6008, token="")` | Connect to already-running Godot. | | `game.close()` | Kill Godot process and close connection. |
Locator — Semantic Queries
`Locator` is lazy: queries re-resolve on every action, so a Locator created before `reload_scene()` still works after.
| Constructor | Description | |---|---| | `game.locator(path=, name=, group=, text=, type=, script=)` | At least one kwarg required; AND-composed. `name` / `text` accept glob (`*`, `?`). `type` matches via `is X` (descendants included, e.g. `type="BaseButton"` covers `Button`/`CheckBox`). | | `game.get_by_text(text)` | Sugar for `locator(text=text)`. | | `game.get_by_button(text)` | Sugar for `locator(type="BaseButton", text=text)`. |
| Refinement | Returns | Description | |---|---|---| | `loc.filter(**kwargs)` | `Locator` | Add AND-composed predicates. | | `loc.first()` / `loc.nth(i)` | `Locator` | Pick first / i-th match. | | `loc.all()` | `list[Locator]` | Snapshot of all matches; `[]` if none (no raise). | | `loc.locator(**kwargs)` | `Locator` | Sub-query under this Locator's resolved node (parent resolved at action time). |
| Inspection (no raise on miss) | Returns | |---|---| | `loc.exists()` / `loc.count()` | `bool` / `int` | | `loc.is_visible()` / `loc.is_actionable()` | `bool` (raises on multi-match / missing) |
| Action (re-resolves; requires exactly one match) | Notes | |---|---| | `loc.click(*, force=False, timeout=5.0)` | Auto-waits actionability for `Control` (visible + mouse_filter + in viewport); `Node2D` only checks visibility. `force=True` skips check. Raises `NotActionableError` on timeout. | | `loc.hover()` | Inject `InputEventMouseMotion` at node's screen position. | | `loc.get_property(prop)
Read more
name: godot-e2e description: | Write and run E2E (end-to-end) game tests using the godot-e2e framework. Python controls a live Godot game over TCP — Locator-based semantic queries, expect() auto-retry assertions, and engine log capture make failures self-diagnosing. Use this skill whenever you need to: - Test actual gameplay: player movement, collisions, scoring, scene transitions - Verify UI interactions: button clicks, label text, menu navigation - Write integration tests that run the real game (not mocked unit tests) - Debug E2E test failures or set up E2E test infrastructure Triggers: "E2E test", "end-to-end test", "gameplay test", "test the game running", "simulate input", "test player movement", "test UI clicks", "godot-e2e", "integration test for game", "test scene transitions".
godot-e2e — E2E Testing for Godot
$ARGUMENTS
godot-e2e is a custom framework with **zero LLM training data coverage**. Everything the model needs is in this skill (with deeper detail in `references/`). Do not guess — follow these docs exactly.
Architecture
The `godot-e2e` CLI launches a Godot process and communicates over TCP (localhost). Enabling the GodotE2E plugin in Project Settings auto-registers an `AutomationServer` autoload that receives JSON commands, executes them on the main thread, and sends back results. The game runs unmodified — the server is dormant unless launched with `--e2e`. Multiple instances can run in parallel (each auto-allocates a unique port). The framework rests on three pillars: `Locator` for semantic node queries, `expect()` for auto-retry assertions, and engine log capture so every error carries the Godot logs that preceded it.
Quick Start — conftest.py + Test File
# conftest.py (per test directory — explicit project path control;
# alternatively set GODOT_E2E_PROJECT_PATH env or pytest.ini
# `godot_e2e_project_path` and use the auto-registered `game` fixture).
# Replace "/root/Main" below with your project's entry-scene root —
# read it from `project.godot`'s `run/main_scene`.
import os
import pytest
from godot_e2e import GodotE2E
GODOT_PROJECT = os.path.join(os.path.dirname(__file__), "..")
GODOT_CONFIG = os.path.join(GODOT_PROJECT, ".claude", "godotmaker.yaml")
def _read_godot_path():
try:
with open(GODOT_CONFIG, "r", encoding="utf-8") as f:
for line in f:
line = line.split("#", 1)[0].strip()
if line.startswith("godot_path:"):
value = line.split(":", 1)[1].strip().strip("\"'")
return value or None
except OSError:
return None
return None
GODOT_PATH = _read_godot_path()
@pytest.fixture(scope="module")
def _game_process():
with GodotE2E.launch(
GODOT_PROJECT,
godot_path=GODOT_PATH,
timeout=15.0,
) as game:
game.wait_for_node("/root/Main", timeout=10.0)
yield game
@pytest.fixture(scope="function")
def game(_game_process):
_game_process.reload_scene()
_game_process.wait_for_node("/root/Main", timeout=5.0)
yield _game_process# test_player.py
from godot_e2e import expect
def test_player_moves_right(game):
player = game.locator(group="player") # Locator query
initial_x = player.get_property("position:x")
game.input_action("ui_right", True)
game.wait_physics_frames(10)
game.input_action("ui_right", False)
expect(player).to_satisfy(
lambda l: l.get_property("position:x") > initial_x,
description="player moved right",
)
def test_button_starts_game(game):
game.get_by_button("Start").click() # auto-waits actionability
expect(game.locator(name="GameStatus")).to_have_text("Playing")
errors = [e for e in game.collected_logs if e.level == "error"]
assert not errors, f"errors during click: {errors}"godot-e2e e2e/ -v
API Quick Reference
Launch / Lifecycle
| Method | Description | |---|---| | `GodotE2E.launch(project_path, godot_path=None, port=0, timeout=10.0, extra_args=None, log_verbosity=None)` | Context manager. Launch Godot + connect. `port=0` auto-allocates. `log_verbosity` ∈ `"error"`/`"warning"`/`"info"`. | | `GodotE2E.connect(host="127.0.0.1", port=6008, token="")` | Connect to already-running Godot. | | `game.close()` | Kill Godot process and close connection. |
Locator — Semantic Queries
`Locator` is lazy: queries re-resolve on every action, so a Locator created before `reload_scene()` still works after.
| Constructor | Description | |---|---| | `game.locator(path=, name=, group=, text=, type=, script=)` | At least one kwarg required; AND-composed. `name` / `text` accept glob (`*`, `?`). `type` matches via `is X` (descendants included, e.g. `type="BaseButton"` covers `Button`/`CheckBox`). | | `game.get_by_text(text)` | Sugar for `locator(text=text)`. | | `game.get_by_button(text)` | Sugar for `locator(type="BaseButton", text=text)`. |
| Refinement | Returns | Description | |---|---|---| | `loc.filter(**kwargs)` | `Locator` | Add AND-composed predicates. | | `loc.first()` / `loc.nth(i)` | `Locator` | Pick first / i-th match. | | `loc.all()` | `list[Locator]` | Snapshot of all matches; `[]` if none (no raise). | | `loc.locator(**kwargs)` | `Locator` | Sub-query under this Locator's resolved node (parent resolved at action time). |
| Inspection (no raise on miss) | Returns | |---|---| | `loc.exists()` / `loc.count()` | `bool` / `int` | | `loc.is_visible()` / `loc.is_actionable()` | `bool` (raises on multi-match / missing) |
| Action (re-resolves; requires exactly one match) | Notes | |---|---| | `loc.click(*, force=False, timeout=5.0)` | Auto-waits actionability for `Control` (visible + mouse_filter + in viewport); `Node2D` only checks visibility. `force=True` skips check. Raises `NotActionableError` on timeout. | | `loc.hover()` | Inject `InputEventMouseMotion` at node's screen position. | | `loc.get_property(prop)
Autonomous text-to-game pipeline for Godot, powered by Claude Code,Codex,Opencode
Repo: RandallLiuXin/GodotMaker
Other skills on godotmaker.
- /background-map
Generate and validate a fixed-viewport background, map base, or parallax plate as a ready-to-load Texture2D.
Open skill - /card-kit
Produce reusable card art sources and native Godot card UI resources.
Open skill - /character-bundle
Produce one illustrated character SpriteFrames resource from high-level body-action intent, optional character and style references, and a resolved animation plan.
Open skill - /compact-prop-pack
Produce a reusable compact-prop atlas from one provider source sheet, with independently loadable AtlasTexture resources for every declared prop.
Open skill - /fx-bundle
Produce a standalone static Texture2D effect or one explicitly timed animated SpriteFrames effect.
Open skill - /platform-strip
Generate non-pixel-art, horizontally repeatable platform strips from real image sources as fixed Texture2D cells or AtlasTexture regions.
Open skill

