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!", "1990-01-01") 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)