Files
modbison/scripts/smoke_test_enter_world.gd
claude-checkpoint-tooling 92c10fa657 Add smoke_test_enter_world.gd + doc update that never made it into PR #50
Both were verified (9/9 passing) before PR #50 merged but got lost in the
handoff between my push and the concurrent live agent session reusing the
same workspace -- PR #50's actual merged diff only had the world_list.gd/
player.gd code changes, missing the new test file and the doc update.
Caught while auditing everything feeding PC2 for real completeness.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M6rUEynmv2QmweocAQYARN
2026-08-21 00:53:55 +00:00

147 lines
5.0 KiB
GDScript

extends SceneTree
## Regression/smoke test for the PC2 "Enter World" flow end to end.
##
## Real user path: register -> land on world_list.tscn -> click "Enter World"
## on a real card -> the playable level (scenes/game.tscn) actually loads
## with the player spawned in it -> pressing Escape returns to world_list.
##
## Run with:
## godot --headless --path . --script res://scripts/smoke_test_enter_world.gd
##
## Requires the backend to be running (default base URL
## `application/agp/api_base_url` -> http://127.0.0.1:8000, overridable via
## AGP_API_BASE_URL) and at least one real world to already exist.
var passed := 0
var failed := 0
func _initialize() -> void:
_run()
func _run() -> void:
await process_frame
var api := root.get_node("ApiClient")
if api == null:
printerr("FAIL: ApiClient autoload not found")
quit(1)
return
# 1. Register a real user and seed a world so the browse list has a
# real card to click, same pattern as smoke_test_world_list.gd.
randomize()
var unique_id: int = int(Time.get_unix_time_from_system() * 1000) + randi() % 100000
var username := "enterworld_%d" % unique_id
var email := "enterworld_%d@test.example.com" % unique_id
var reg_result: Dictionary = await api.register(username, email, "SmokeTest123!", true)
if not _check("setup: register succeeded", reg_result.ok):
print(" detail: ", reg_result.detail)
_finish()
return
var world_body := JSON.stringify({
"name": "EnterWorld Smoke Test World %d" % unique_id,
"description": "Seeded by smoke_test_enter_world.gd",
"visibility": "public",
})
var world_result: Dictionary = await api.request(
HTTPClient.METHOD_POST, "/api/worlds/", world_body, "application/json", true
)
if not _check("setup: world created", world_result.ok):
print(" detail: ", world_result.detail)
_finish()
return
# 2. Load world_list.tscn as a real scene and let it fetch worlds.
var packed: PackedScene = load("res://scenes/world_list.tscn")
var instance: Control = packed.instantiate()
root.add_child(instance)
# _ready() kicks off an async _refresh_worlds() -- wait real frames for
# the HTTP round trip rather than guessing a fixed count.
var world_list_node: VBoxContainer = instance.get_node("VBoxContainer/ScrollContainer/WorldList")
var settled := false
for i in 120:
await process_frame
if world_list_node.get_child_count() > 0:
settled = true
break
if not _check("world list populated with at least one card", settled):
_finish()
return
# 3. Click "Enter World" on the first real card -- find the button by
# walking the card's children rather than assuming an index, so this
# doesn't silently pass if _create_world_card's structure changes.
var card: Control = world_list_node.get_child(0)
var enter_button: Button = _find_button(card, "Enter World")
if not _check("Enter World button exists on the card", enter_button != null):
_finish()
return
enter_button.pressed.emit()
await _wait_for_scene_change(instance, 60)
var game_scene := current_scene
if not _check("game.tscn became current_scene after Enter World", game_scene != null and game_scene != instance):
_finish()
return
# 4. Verify the player actually exists and spawned at the level's
# PlayerSpawn marker -- not just that *some* scene loaded.
var player: Node = game_scene.get_node_or_null("Player")
_check("Player node exists in the loaded game scene", player is CharacterBody3D)
var spawn_marker: Node = game_scene.get_node_or_null("Level/PlayerSpawn")
_check("PlayerSpawn marker exists in the loaded level", spawn_marker is Marker3D)
if player is CharacterBody3D and spawn_marker is Marker3D:
var dist := (player as CharacterBody3D).global_position.distance_to((spawn_marker as Marker3D).global_position)
_check("player spawned at (or very near) the PlayerSpawn marker", dist < 0.5)
# 5. Escape should return to world_list.tscn (real key event, not a
# direct method call -- proves the actual input binding works).
var esc_event := InputEventKey.new()
esc_event.keycode = KEY_ESCAPE
esc_event.pressed = true
Input.parse_input_event(esc_event)
await _wait_for_scene_change(game_scene, 60)
var back_to_menu := current_scene
_check("Escape returned to world_list.tscn", back_to_menu != null and back_to_menu.scene_file_path == "res://scenes/world_list.tscn")
_finish()
func _find_button(node: Node, text: String) -> Button:
if node is Button and (node as Button).text == text:
return node
for child in node.get_children():
var found := _find_button(child, text)
if found:
return found
return null
func _wait_for_scene_change(previous_scene: Node, max_frames: int) -> void:
for i in max_frames:
await process_frame
if current_scene != previous_scene and current_scene != null:
return
func _check(description: String, condition: bool) -> bool:
if condition:
print(" PASS: ", description)
passed += 1
else:
printerr(" FAIL: ", description)
failed += 1
return condition
func _finish() -> void:
print("\n=== SMOKE RESULT: %d/%d passed ===" % [passed, passed + failed])
quit(0 if failed == 0 else 1)