Godot Skill Develop, test, build, and deploy Godot 4.x games. Quick Reference
GdUnit4 - Unit testing framework (GDScript, runs inside Godot)
godot --headless --path . -s res://addons/gdUnit4/bin/GdUnitCmdTool.gd --run-tests
PlayGodot - Game automation framework (Python, like Playwright for games)
export GODOT_PATH = /path/to/godot-automation-fork pytest tests/ -v
Export web build
godot --headless --export-release "Web" ./build/index.html
Deploy to Vercel
vercel deploy ./build --prod Testing Overview GdUnit4 PlayGodot Type Unit testing Game automation Language GDScript Python Runs Inside Godot External (like Playwright) Requires Addon Custom Godot fork Best for Unit/component tests E2E/integration tests GdUnit4 (GDScript Tests) GdUnit4 runs tests written in GDScript directly inside Godot. Project Structure project/ ├── addons/gdUnit4/ # GdUnit4 addon ├── test/ # Test directory │ ├── game_test.gd │ └── player_test.gd └── scripts/ └── game.gd Setup
Install GdUnit4
git clone --depth 1 https://github.com/MikeSchulze/gdUnit4.git addons/gdUnit4
Enable plugin in Project Settings → Plugins
Basic Unit Test
test/game_test.gd
extends GdUnitTestSuite var game : Node func before_test ( ) -> void : game = auto_free ( load ( "res://scripts/game.gd" ) . new ( ) ) func test_initial_state ( ) -> void : assert_that ( game . is_game_active ( ) ) . is_true ( ) assert_that ( game . get_current_player ( ) ) . is_equal ( "X" ) func test_make_move ( ) -> void : var success := game . make_move ( 4 ) assert_that ( success ) . is_true ( ) assert_that ( game . get_board_state ( ) [ 4 ] ) . is_equal ( "X" ) Scene Test with Input Simulation
test/game_scene_test.gd
extends GdUnitTestSuite var runner : GdUnitSceneRunner func before_test ( ) -> void : runner = scene_runner ( "res://scenes/main.tscn" ) func after_test ( ) -> void : runner . free ( ) func test_click_cell ( ) -> void : await runner . await_idle_frame ( ) var cell = runner . find_child ( "Cell4" ) runner . set_mouse_position ( cell . global_position + cell . size / 2 ) runner . simulate_mouse_button_pressed ( MOUSE_BUTTON_LEFT ) await runner . await_input_processed ( ) var game = runner . scene ( ) assert_that ( game . get_board_state ( ) [ 4 ] ) . is_equal ( "X" ) func test_keyboard_restart ( ) -> void : runner . simulate_key_pressed ( KEY_R ) await runner . await_input_processed ( ) assert_that ( runner . scene ( ) . is_game_active ( ) ) . is_true ( ) Running GdUnit4 Tests
All tests
godot --headless --path . -s res://addons/gdUnit4/bin/GdUnitCmdTool.gd --run-tests
Specific test file
godot --headless --path . -s res://addons/gdUnit4/bin/GdUnitCmdTool.gd \ --run-tests --add res://test/my_test.gd
Generate reports for CI
godot --headless --path . -s res://addons/gdUnit4/bin/GdUnitCmdTool.gd \ --run-tests --report-directory ./reports GdUnit4 Assertions
Values
assert_that ( value ) . is_equal ( expected ) assert_that ( value ) . is_not_null ( ) assert_that ( condition ) . is_true ( )
Numbers
assert_that ( number ) . is_greater ( 5 ) assert_that ( number ) . is_between ( 1 , 100 )
Strings
assert_that ( text ) . contains ( "expected" ) assert_that ( text ) . starts_with ( "prefix" )
Arrays
assert_that ( array ) . contains ( element ) assert_that ( array ) . has_size ( 5 )
Signals
await assert_signal ( node ) . is_emitted ( "signal_name" ) Scene Runner Input API
Mouse
runner . set_mouse_position ( Vector2 ( 100 , 100 ) ) runner . simulate_mouse_button_pressed ( MOUSE_BUTTON_LEFT ) runner . simulate_mouse_button_released ( MOUSE_BUTTON_LEFT )
Keyboard
runner . simulate_key_pressed ( KEY_SPACE ) runner . simulate_key_pressed ( KEY_S , false , true )
Ctrl+S
Input actions
runner . simulate_action_pressed ( "jump" ) runner . simulate_action_released ( "jump" )
Waiting
await runner . await_input_processed ( ) await runner . await_idle_frame ( ) await runner . await_signal ( "game_over" , [ ] , 5000 ) PlayGodot (Game Automation) PlayGodot is a game automation framework for Godot - like Playwright, but for games. It enables E2E testing, automated gameplay, and external control of Godot games via the native RemoteDebugger protocol. Requirements: Custom Godot fork: Randroids-Dojo/godot (automation branch) PlayGodot Python library Setup
Install PlayGodot
python -m venv .venv source .venv/bin/activate
Windows: .venv\Scripts\activate
pip install playgodot
Option 1: Download pre-built binary (recommended)
See releases: https://github.com/Randroids-Dojo/godot/releases/tag/automation-latest
- godot-automation-linux-x86_64.zip
- godot-automation-macos-universal.zip (Intel + Apple Silicon)
Option 2: Build custom Godot fork from source
git clone https://github.com/Randroids-Dojo/godot.git cd godot && git checkout automation scons platform = macos arch = arm64 target = editor -j8
macOS Apple Silicon
scons platform=macos arch=x86_64 target=editor -j8 # macOS Intel
scons platform=linuxbsd target=editor -j8 # Linux
scons platform=windows target=editor -j8 # Windows
Test Configuration (conftest.py) import os import pytest_asyncio from pathlib import Path from playgodot import Godot GODOT_PROJECT = Path ( file ) . parent . parent GODOT_PATH = os . environ . get ( "GODOT_PATH" , "/path/to/godot-fork" ) @pytest_asyncio . fixture async def game ( ) : async with Godot . launch ( str ( GODOT_PROJECT ) , headless = True , timeout = 15.0 , godot_path = GODOT_PATH , ) as g : await g . wait_for_node ( "/root/Game" ) yield g Writing PlayGodot Tests import pytest GAME = "/root/Game" @pytest . mark . asyncio async def test_game_starts_empty ( game ) : board = await game . call ( GAME , "get_board_state" ) assert board == [ "" , "" , "" , "" , "" , "" , "" , "" , "" ] @pytest . mark . asyncio async def test_clicking_cell ( game ) : await game . click ( "/root/Game/VBoxContainer/GameBoard/GridContainer/Cell4" ) board = await game . call ( GAME , "get_board_state" ) assert board [ 4 ] == "X" @pytest . mark . asyncio async def test_game_win ( game ) : for pos in [ 0 , 3 , 1 , 4 , 2 ] :
X wins top row
await game . call ( GAME , "make_move" , [ pos ] ) is_active = await game . call ( GAME , "is_game_active" ) assert is_active is False Running PlayGodot Tests export GODOT_PATH = /path/to/godot-automation-fork pytest tests/ -v pytest tests/test_game.py::test_clicking_cell -v PlayGodot API
Node interaction
node
await game . get_node ( "/root/Game" ) await game . wait_for_node ( "/root/Game" , timeout = 10.0 ) exists = await game . node_exists ( "/root/Game" ) result = await game . call ( "/root/Node" , "method" , [ arg1 , arg2 ] ) value = await game . get_property ( "/root/Node" , "property" ) await game . set_property ( "/root/Node" , "property" , value )
Node queries
paths
await game . query_nodes ( "Button" ) count = await game . count_nodes ( "Label" )
Mouse input
await game . click ( "/root/Button" ) await game . click ( 300 , 200 ) await game . double_click ( "/root/Button" ) await game . right_click ( 100 , 100 ) await game . drag ( "/root/Item" , "/root/Slot" )
Keyboard input
await game . press_key ( "space" ) await game . press_key ( "ctrl+s" ) await game . type_text ( "hello" )
Input actions
await game . press_action ( "jump" ) await game . hold_action ( "sprint" , 2.0 )
Touch input
await game . tap ( 300 , 200 ) await game . swipe ( 100 , 100 , 400 , 100 ) await game . pinch ( ( 200 , 200 ) , 0.5 )
Screenshots
png_bytes
await game . screenshot ( ) await game . screenshot ( "/tmp/screenshot.png" ) similarity = await game . compare_screenshot ( "expected.png" ) await game . assert_screenshot ( "reference.png" , threshold = 0.99 )
Scene management
scene
await game . get_current_scene ( ) await game . change_scene ( "res://scenes/level2.tscn" ) await game . reload_scene ( )
Game state
await game . pause ( ) await game . unpause ( ) is_paused = await game . is_paused ( ) await game . set_time_scale ( 0.5 ) scale = await game . get_time_scale ( )
Waiting
await game . wait_for_node ( "/root/Game/SpawnedEnemy" , timeout = 5.0 ) await game . wait_for_visible ( "/root/Game/UI/GameOverPanel" , timeout = 10.0 ) await game . wait_for_signal ( "game_over" ) await game . wait_for_signal ( "health_changed" , source = "/root/Game/Player" ) Building & Deployment Web Export
Requires export_presets.cfg with Web preset
godot --headless --export-release "Web" ./build/index.html Export Preset (export_presets.cfg) [ preset.0 ] name = " Web " platform = " Web " runnable = true export_path = " build/index.html " Deploy to Vercel npm i -g vercel vercel deploy ./build --prod CI/CD GitHub Actions Example - name : Setup Godot uses : chickensoft - games/setup - godot@v2 with : version : 4.3.0 include-templates : true - name : Run GdUnit4 Tests run : | godot --headless --path . \ -s res://addons/gdUnit4/bin/GdUnitCmdTool.gd \ --run-tests --report-directory ./reports - name : Upload Results uses : actions/upload - artifact@v4 if : always() with : name : test - results path : reports/ References references/gdunit4-quickstart.md - GdUnit4 setup references/scene-runner.md - Input simulation API references/assertions.md - Assertion methods references/playgodot.md - PlayGodot guide references/deployment.md - Deployment guide references/ci-integration.md - CI/CD setup