SkillAgentSearch skills...

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.

Install / Use

npx skills add RandallLiuXin/GodotMaker --skill godot-e2e

Installs into whichever agent you are using.

About this skill
📄

SKILL.md

Installable skill definition

Quality Score

92/100

Category

Other

Supported Platforms

Universal

Our assessment of godot-e2e

godot-e2e scores 92/100 on our quality scale, 32nd of 241 Other skills we index (top 14%).

Its SKILL.md is 15 KB long, well organised into 26 sections with 6 code examples: a thorough specification that gives an agent plenty to work with.

It has 549 GitHub stars, a meaningful sign that others use it.

Substance
30/30
Structure
20/20
Description
15/15
Adoption
12/20
Freshness
15/15

Maintenance, license and trust

  • The repository was last updated 16 days ago, so godot-e2e is actively maintained.
  • No license is declared. By default that means all rights are reserved: you can read it, but reusing or redistributing it is not clearly permitted. Ask the author before building on it commercially.
  • Its trust signals score 88/100, with 1 caution from licensing, adoption, age or documentation. These come from repository metadata, not a code audit — read the skill file before letting an agent act on it.

godot-e2e compared with similar skills

All 4 of these similar skills score higher than godot-e2e; compare them before choosing.

SkillScoreStarsUpdatedFormat
godot-e2e (this skill)by RandallLiuXin9254916d agoSKILL.md
Agent-Reachby Panniantong10089.8k18d agoCLAUDE.md
headroomby headroomlabs-ai10074.4ktodayCLAUDE.md
Scraplingby D4Vinci10085.5ktodayMCP Server
crawl4aiby unclecode10084.7k8d agoMCP Server

Frequently asked questions

How do I install godot-e2e?
Run npx skills add RandallLiuXin/GodotMaker --skill godot-e2e. The install tabs above show the steps for each supported agent.
Which AI agents does godot-e2e work with?
It is written for Universal, as a SKILL.md file. Other agents that read the same format can often use it too.
Is godot-e2e safe to use?
It declares no license and scores 88/100 on trust signals. Skills are instructions an agent will follow, so read the file before installing it and do not approve commands you do not understand.
Is godot-e2e still maintained?
The repository was last updated 16 days ago, so godot-e2e is actively maintained.

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) / loc.set_property(prop, value) / loc.call(method, args=None) | Same as game.* versions, but path-pinned. | | loc.wait_visible(*, timeout=5.0) | Block until target passes actionability. Raises NotActionableError with reasons + checks. | | loc.wait_for_signal(signal_name, timeout=5.0) | Block until resolved node emits signal. |

expect() — Auto-Retry Assertions

expect(locator, *, timeout=5.0, poll_interval=0.05) → LocatorAssertions re-resolves the Locator on each poll. Lookup errors during polling (NodeNotFoundError, MultipleMatchesError, CommandError) are caught; node may appear / disambiguate later.

| Matcher | Passes When | |---|---| | to_have_property(name, value) | locator.get_property(name) == value | | to_have_text(text) | Target's text property equals (sugar for property "text"). | | to_be_visible() | Visible in scene tree (Control / Node2D). | | to_exist() | Query resolves to ≥1 node. | | to_satisfy(predicate, *, description=None) | predicate(locator) truthy. Use description= for readable failure messages. |

ExpectationFailedError dual-inherits AssertionError → pytest renders it as a regular assertion failure, with attributes actual, observation_captured, matcher, scene_tree, last_error.

Engine Log Capture

| Member | Description | |---|---| | game.last_logs / game.collected_logs | List of LogEntry. last_logs cleared each command; collected_logs cleared per test by built-in fixtures. | | game.reset_collected_logs() | Manual narrowing — reset window before a sub-assertion. | | game.set_log_verbosity(level) / game.set_log_buffer_size(size) | Runtime tuning. Levels: "error" / "warning" (default) / "info". | | LogEntry fields | level / message / function / file / line (last three populated for engine errors only). | | Every GodotE2EError carries .logs | List of LogEntry from the failing command's response. Empty when capture inactive. |

Pytest auto-includes captured godot logs section on failure (built-in plugin, no setup required). Buffer overflow synthesizes a "<N entries dropped>" warning.

Raw-Path Operations

Direct on game — used when you have a stable known path and a Locator query would just add ceremony (typically root-level singletons, autoloads, the Main entry node). Node ops (game.get_property / set_property / call / find_by_group / query_nodes / get_tree / batch), input (input_action / input_key / input_mouse_* / press_action / press_key / click / click_node), waits (wait_physics_frames / wait_process_frames / wait_seconds / wait_for_node / wait_for_signal / wait_for_property), scenes (get_scene / change_scene / reload_scene / screenshot).

Types & Exceptions

from godot_e2e import (
    Vector2, Vector2i, Vector3, Vector3i, Rect2, Rect2i,
    Color, Transform2D, NodePath,
    LogEntry, LogVerbosity, parse_log_entries,
    expect, Locator, LocatorAssertions,
)

| Exception | When | |---|---| | NodeNotFoundError | Node path doesn't exist. | | TimeoutError | wait_for_* exceeded. Has .scene_tree. | | ConnectionLostError | Godot crashed or TCP dropped. | | CommandError | Server returned an error. | | MultipleMatchesError | Locator action without .first()/.nth()/.filter() matched >1. Has .paths. | | NotActionableError | Locator.click() / wait_visible() timed out waiting for actionability. Has .path, .reasons, .checks. | | ExpectationFailedError | expect(...) matcher exceeded timeout. Dual-inherits AssertionError. |

All inherit from GodotE2EError (which carries .logs).

Critical Rules

| # | Rule | Detail | |---|------|--------| | 1 | Physics frames for movement | After input, use wait_physics_frames for position/collision assertions. wait_process_frames does NOT advance physics. | | 2 | Hold input for movement | press_action only taps (~4 frames). For sustained movement: input_action(act, True) → wait_physics_frames(N) → input_action(act, False). | | 3 | input_action needs 2 args | input_action("jump", True) not input_action("jump"). For tap, use press_action("jump"). | | 4 | Prefer expect() over manual wait + assert | expect(locator).to_have_property(...) retries with structured failure context (scene_tree + last_error) and renders as a normal pytest assertion. Use wait_for_property only when working with raw paths and no Locator is in scope. | | 5 | Assert direction, not exact values | assert new_x > initial_x not assert pos_x == 450.0. Physics varies per machine. | | 6 | wait_for_signal timing | Listener registers on arrival — signals emitted before are missed. Use expect().to_have_property for state assertions. | | 7 | Locators with semantic queries beat hardcoded paths | game.get_by_button("Start") / game.locator(group="player") survives tree restructuring; /root/Main/UI/Menu/StartButton doesn't. Reserve raw paths for unique top-level nodes. | | 8 | Read .logs on every E2E failure | Every GodotE2EError carries .logs — what Godot printed during the failing command. pytest -v auto-includes captured godot logs on failure. Ignoring it doubles diagnosis time. | | 9 | Default log verbosity is "warning" | push_error and push_warning are captured; print() is NOT. Bump to "info" (via log_verbosity="info" at launch or game.set_log_verbosity("info") at runtime) only when debugging — at info verbosity log buffer fills 4-10× faster. | | 10 | Use wait_seconds for Timer-gated waits | wait_process_frames(N) counts frames, not seconds. Under headless uncapped FPS, wait_process_frames(120) finishes in well under 2s. Use wait_seconds(t) or expect() for any wait gated by a Timer or wall-clock seconds. |

Fixture Strategies

| Strategy | Scope | Speed | Isolation | Use when | |---|---|---|---|---| | reload_scene | module process + funct

Truncated for display — read the full file on GitHub.

Related Skills

View on GitHub
GitHub Stars549
CategoryOther
Updated16d ago
Forks50

Languages

Python

Trust signals

88/100

From repository metadata: license, adoption, age and documentation. Not a code audit — see the Safety scan above for what the skill file itself contains.

1 medium