diff --git a/files/docs/milestones/22/Part_10_Refactor_splash_loader.md b/files/docs/milestones/22/Part_10_Refactor_splash_loader.md new file mode 100644 index 000000000..96781f647 --- /dev/null +++ b/files/docs/milestones/22/Part_10_Refactor_splash_loader.md @@ -0,0 +1,119 @@ +# Refactor splash loader; add monotonic progress & tests - #893 + +--- + +## PR #893 Summary: Refactor splash loader; add monotonic progress & tests + +**Repository:** [ikostan/SkyLockAssault](https://github.com/ikostan/SkyLockAssault) +**Author:** @ikostan +**Branch:** `implement-three-state-presentation-pipeline-in-splash_screengd` → `main` +**Linked Issue:** #778 ([TASK] TASK-01: Implement Three-State Presentation Pipeline in splash_screen.gd) +**Milestone:** Milestone 22 – Optimize Test Suite Runtime & Fix Loading Screen +**Labels:** enhancement, web, GUI, frontend + +### Purpose + +Restructure `splash_screen.gd` into a clear three-state presentation pipeline that separates resource polling, UI progress presentation, and scene-transition routing. Deliver strictly monotonic, deterministic progress updates and harden loading/transition behavior with validation and fallbacks. Add Gut integration tests to lock in the new behavior. + +Restructure `splash_screen.gd` to separate concerns: polling backend, presentation smoothing, and transition routing. Add display_target and presentation_speed; use move_toward for deterministic, linear UI progress and ensure strictly monotonic display_target updates from threaded loader progress. Improve ResourceLoader handling (match on status), validate PackedScene on load, set load_failed on errors, and provide fallbacks (DEFAULT_STARTUP_SCENE or direct file load). Add defensive checks before change_scene_to_packed and ensure transitions respect min_load_time. Add Gut integration tests (test/gut/test_splash_screen.gd + .uid) covering monotonic scaling, presentation convergence, backend early-exit, invalid-resource handling, and transition timing. + +### Core Improvements + +#### 1. Three-State Pipeline (`scripts/ui/screens/splash_screen.gd`) + +Replaced the monolithic `_process` loop with three focused responsibilities: + +| Stage | Responsibility | +|--------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------| +| **Backend polling** (`_poll_resource_backend`) | Query `ResourceLoader.load_threaded_get_status`, update `display_target` monotonically via `max()`, handle `THREAD_LOAD_*` statuses | +| **Presentation** (`_update_presentation_handler`) | Drive `loader_progress` toward `display_target` with `move_toward(..., presentation_speed * delta)` for linear, frame-rate-independent smoothing | +| **Transition routing** (`_evaluate_transition_router`) | Gate scene change on loaded/failed state, `min_load_time` elapsed, progress ≈ 100%, and not already transitioning | + +#### 2. Deterministic & Monotonic Progress + +- New state: `display_target` and `presentation_speed` +- Backend progress only raises `display_target` (never decreases) +- UI progress uses `move_toward` instead of `lerp` for predictable linear convergence +- On load failure / invalid resource, force `display_target = 100.0` so the bar still completes and fallback routing can run + +#### 3. Hardened Loading & Transitions + +- Explicit `match` on ResourceLoader status values +- Validate loaded resource is a `PackedScene` before use; set `load_failed` otherwise +- Defensive checks (`is_instance_valid`, type check) before `change_scene_to_packed` +- Fallbacks: + - Empty `next_scene` → `DEFAULT_STARTUP_SCENE` + - Load failure / validation failure → direct `change_scene_to_file(target_path)` +- Transitions respect `min_load_time` so the splash is visible for a minimum duration + +#### 4. Gut Integration Tests (`test/gut/test_splash_screen.gd`) + +Coverage includes: +- Monotonic `display_target` behavior under decreasing backend progress +- Linear `move_toward` convergence tied to delta and `presentation_speed` +- Backend early-return when already loaded +- Invalid / non-PackedScene resource handling (`load_failed` + progress forced to 100) +- Transition router respects `min_load_time` even when progress is complete + +### Benefits + +- Clear separation of concerns makes the splash loader easier to reason about and extend +- Progress bar no longer jumps backward or stalls due to non-monotonic backend reports +- Safer scene transitions with explicit validation and fallback paths +- Automated tests protect against regressions in progress, failure handling, and timing + +### Status Notes + +Fully addresses the objectives of TASK-01 (#778): three-state pipeline, `move_toward` linear progress, monotonic scaling, and PackedScene validation with safe fallbacks. + +--- + +## Reviewer's Guide + +Refactors the splash screen loader into a three-stage pipeline (backend polling, presentation, and transition routing), introduces deterministic monotonic progress handling, hardens scene-loading fallbacks, slightly adjusts test timeouts, and adds Gut integration tests documenting and locking in the new behavior. + +### File-Level Changes + +| Change | Details | Files | +|---------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------| +| Refactor splash_screen.gd into a three-stage loader pipeline with monotonic progress and guarded scene transitions. | | `scripts/ui/screens/splash_screen.gd` | +| Relax shared test timeout configuration to accommodate longer-running or asynchronous tests. | | `tests/test_utils.py` | +| Add Gut integration tests and milestone documentation to lock in splash screen progress and transition behavior. | | `test/gut/test_splash_screen.gd`
`files/docs/milestones/22/Part_10_Refactor_splash_loader.md`
`test/gut/test_splash_screen.gd.uid` | + +### Assessment against linked issues + +| Issue | Objective | Addressed | Explanation | +|------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------|-------------| +| https://github.com/ikostan/SkyLockAssault/issues/778 | Refactor splash_screen.gd into a three-state presentation pipeline: update _process(delta) to capture frame delta, and split logic into _poll_resource_backend, a presentation handler using move_toward(), and a transition router, with strictly monotonic progress scaling via max() on backend progress. | ✅ | | +| https://github.com/ikostan/SkyLockAssault/issues/778 | Ensure progress animation is smooth and linear, independent of frame rate, and stop polling ResourceLoader.load_threaded_get_status() once the resource reaches its final cache state or failure. | ✅ | | +| https://github.com/ikostan/SkyLockAssault/issues/778 | Add defensive resource validation: perform an explicit PackedScene type check on ResourceLoader.load_threaded_get() results and protect change_scene_to_packed() with validation and safe fallbacks. | ✅ | | +| https://github.com/ikostan/SkyLockAssault/issues/780 | Implement a GUT unit test suite for splash_screen.gd in test_splash_screen.gd that follows repository test patterns (extends gut test.gd, proper setup/teardown, add_child_autofree, frame awaiting, and type-hinted variables). | ✅ | | +| https://github.com/ikostan/SkyLockAssault/issues/780 | Add unit tests validating monotonic progress behavior and move_toward-based convergent, frame-rate-independent progress resolution for the splash screen. | ✅ | | +| https://github.com/ikostan/SkyLockAssault/issues/780 | Add unit tests validating transition gating mechanics for the splash screen: locked when the progress bar is incomplete and firing when progress reaches full completion under appropriate conditions. | ✅ | | + +### Possibly linked issues + +- **#778**: PR fully implements TASK-01’s three-state pipeline, linear move_toward progress, monotonic scaling, and PackedScene safeguards. + +--- + +## PR #893 Summary: Bots / AI Contributions + +### AI / Bot Contributors + +- **@sourcery-ai** + Generated the PR summary and Reviewer’s Guide. Performed code review with suggestions (e.g., relaxing the exact `loader_progress == 100.0` check for `move_toward` convergence, and considering exposing `presentation_speed` as an exported variable). + +- **@coderabbitai** + Generated the PR summary, walkthrough, and poem. Conducted code review with actionable feedback (including test isolation for Globals settings and ensuring backend-path tests actually exercise the polled code). + +- **@deepsource-io** + Performed automated DeepSource Code Review and published a PR Report Card (Security / Reliability / Complexity / Hygiene). + +### Human Contributor + +- **@ikostan** + Primary author of the PR. Implemented the three-state splash loader refactor (backend polling, presentation smoothing with `move_toward` / monotonic `display_target`, and transition routing), hardened ResourceLoader / PackedScene validation and fallbacks, added Gut integration tests covering monotonic progress, presentation convergence, early-exit, invalid resources, and min-load-time gating, and iteratively refined the implementation across multiple commits. + +--- + diff --git a/scripts/ui/screens/splash_screen.gd b/scripts/ui/screens/splash_screen.gd index 9e96c055f..a96ce805f 100644 --- a/scripts/ui/screens/splash_screen.gd +++ b/scripts/ui/screens/splash_screen.gd @@ -13,9 +13,17 @@ extends Control const DEFAULT_STARTUP_SCENE := "res://scenes/main_menu.tscn" +const TRANSITION_PROGRESS_THRESHOLD: float = 99.9 + +@export_range(0.0, 500.0, 0.1, "or_greater") var presentation_speed: float: + get: + return _presentation_speed + set(value): + _presentation_speed = max(0.0, value) var resolved_next_scene: String = "" var loader_progress: float = 0.0 # Current smoothed progress value. +var display_target: float = 0.0 # Target display progress value. var min_load_time: float = 1.0 # Minimum splashing time in seconds for visibility. var load_start_time: float = 0.0 # Timestamp when splashing starts. var is_scene_loaded: bool = false # Flag to track if the scene is fully loaded. @@ -24,83 +32,111 @@ var load_failed: bool = false # Flag if splashing request failed. var transitioning: bool = false # Flag to prevent multiple scene changes. var label_text: String = "Loading: " +var _presentation_speed: float = 50.0 + @onready var progress_bar: ProgressBar = $ProgressBar # Progress bar UI element. @onready var label: Label = $Label # Label for displaying loading status. -# Polls loading status and updates UI. Changes scene when loaded. -# Eliminated fake_progress; relies on real ResourceLoader progress. -func _process(_delta: float) -> void: - var elapsed_time: float = (Time.get_ticks_msec() / 1000.0) - load_start_time +func _ready() -> void: + load_start_time = Time.get_ticks_msec() / 1000.0 + + +func _process(delta: float) -> void: + _poll_resource_backend() + _update_presentation_handler(delta) + _evaluate_transition_router() - var real_progress: float = 0.0 - if is_scene_loaded: - real_progress = 100.0 # Force 100% if already loaded (ignores post-load status). - elif load_failed: - real_progress = 0.0 # Keep at 0 if failed early. - else: - # Only poll if not done. - var progress_array: Array = [] - var status: int = ResourceLoader.load_threaded_get_status( - Globals.next_scene, progress_array - ) - - if status == ResourceLoader.THREAD_LOAD_IN_PROGRESS: + +## State 1: Resource polling backend. +## Polls status until cache state is reached or failure occurs. +func _poll_resource_backend() -> void: + # Immediately stop polling if already marked as loaded or failed + if is_scene_loaded or load_failed: + return + + var progress_array: Array = [] + var status: int = ResourceLoader.load_threaded_get_status(Globals.next_scene, progress_array) + + match status: + ResourceLoader.THREAD_LOAD_IN_PROGRESS: if progress_array.size() > 0: - real_progress = progress_array[0] * 100.0 # Convert to percentage. + var backend_progress: float = progress_array[0] * 100.0 + # Preserve strictly monotonic progress scaling + display_target = max(display_target, backend_progress) else: Globals.log_message( "Progress array empty during IN_PROGRESS.", Globals.LogLevel.WARNING ) - elif status == ResourceLoader.THREAD_LOAD_LOADED: - real_progress = 100.0 - if not is_scene_loaded: + ResourceLoader.THREAD_LOAD_LOADED: + display_target = 100.0 + var loaded_res := ResourceLoader.load_threaded_get(Globals.next_scene) + + # Type check validation on retrieved resource + if loaded_res is PackedScene: + scene = loaded_res is_scene_loaded = true - scene = ResourceLoader.load_threaded_get(Globals.next_scene) Globals.log_message("Scene loaded successfully.", Globals.LogLevel.DEBUG) + else: + Globals.log_message( + "Loaded resource is null or not a PackedScene.", Globals.LogLevel.ERROR + ) + load_failed = true - elif ( - status == ResourceLoader.THREAD_LOAD_FAILED - or status == ResourceLoader.THREAD_LOAD_INVALID_RESOURCE - ): + ResourceLoader.THREAD_LOAD_FAILED, ResourceLoader.THREAD_LOAD_INVALID_RESOURCE: Globals.log_message("Loading failed or invalid.", Globals.LogLevel.ERROR) load_failed = true + display_target = 100.0 # Force target to end on failure fallback - # Use real progress only (eliminates fake_progress process). - # Sub-threads + Web min_load_time fix 50% quirk and give breathing room. - var display_progress: float = real_progress - if load_failed: - display_progress = 100.0 # Force end on failure. - # Smooth progress with lerp. - loader_progress = lerp(loader_progress, display_progress, 0.01) - # Update UI. +## State 2: Presentation Handler. +## Smoothly steps UI display towards display_target linearly via move_toward. +func _update_presentation_handler(delta: float) -> void: + loader_progress = move_toward(loader_progress, display_target, presentation_speed * delta) + + # Update visual UI components progress_bar.value = loader_progress label.text = label_text + str(int(loader_progress)) + "%" - # Proceed only when both loaded (or failed fallback) and minimum time elapsed. - if (is_scene_loaded or load_failed) and elapsed_time >= min_load_time and not transitioning: - transitioning = true # Lock to prevent re-entry. - loader_progress = 100.0 - progress_bar.value = 100.0 - label.text = label_text + "100%" - # Optional delay at 100%. - await get_tree().create_timer(1.5).timeout +## State 3: Transition Router. +## Evaluates temporal and resource safety flags to switch scene context profiles. +func _evaluate_transition_router() -> void: + var elapsed_time: float = (Time.get_ticks_msec() / 1000.0) - load_start_time - var target_path: String = Globals.next_scene # Cache the path. - Globals.next_scene = "" # Reset to avoid stale values. + # Proceed only when loaded (or failed fallback), display progress >= threshold, + # and minimum time elapsed. + if ( + (is_scene_loaded or load_failed) + and elapsed_time >= min_load_time + and loader_progress >= TRANSITION_PROGRESS_THRESHOLD + and not transitioning + ): + transitioning = true # Lock to prevent re-entry + + var target_path: String = Globals.next_scene # Cache the path + Globals.next_scene = "" # Reset to avoid stale values if target_path == "": Globals.log_message( "Empty next_scene - returning to main menu.", Globals.LogLevel.ERROR ) - get_tree().change_scene_to_file("res://scenes/main_menu.tscn") + get_tree().change_scene_to_file(DEFAULT_STARTUP_SCENE) elif load_failed: # Fallback to direct load on failure Globals.log_message("Fallback: Loading scene directly.", Globals.LogLevel.WARNING) get_tree().change_scene_to_file(target_path) else: - # Change scene. - get_tree().change_scene_to_packed(scene) + # Defensive validation before changing scene + if is_instance_valid(scene) and scene is PackedScene: + get_tree().change_scene_to_packed(scene) + else: + Globals.log_message( + ( + "PackedScene validation failed during scene transition." + + " Direct load fallback." + ), + Globals.LogLevel.ERROR + ) + get_tree().change_scene_to_file(target_path) diff --git a/test/gut/test_splash_screen.gd b/test/gut/test_splash_screen.gd new file mode 100644 index 000000000..b9694b0aa --- /dev/null +++ b/test/gut/test_splash_screen.gd @@ -0,0 +1,256 @@ +## Copyright (C) 2026 Egor Kostan +## SPDX-License-Identifier: GPL-3.0-or-later +## test_splash_screen.gd +## +## Integration test suite validating TASK-01 and TASK-03 implementations in splash_screen.gd: +## 1. Monotonic progress updates driven through _poll_resource_backend. +## 2. Unsubscribing/ceasing polling upon cached completion or failure. +## 3. Linear progression convergence via move_toward. +## 4. Defensive PackedScene resource type verification via _poll_resource_backend. +## 5. Automatic initialization of load_start_time on _ready(). +## 6. Full TASK-03 (#780) transition gating and move_toward resolution assertions. + +extends "res://addons/gut/test.gd" + +const SPLASH_SCREEN_PATH: String = "res://scenes/splash_screen.tscn" + +var splash_instance: Control +var original_next_scene: String +var original_settings: GameSettingsResource + + +func before_each() -> void: + original_next_scene = Globals.next_scene + Globals.next_scene = "res://scenes/main_menu.tscn" + original_settings = Globals.settings + + # Silence logs during test runs + Globals.settings = GameSettingsResource.new() + Globals.settings.current_log_level = 4 + + +func after_each() -> void: + Globals.next_scene = original_next_scene + Globals.settings = original_settings + + AudioManager.stop_all_sfx() + + var focus_owner := get_viewport().gui_get_focus_owner() + if is_instance_valid(focus_owner): + focus_owner.release_focus() + + if is_instance_valid(splash_instance): + splash_instance.queue_free() + + # Safely release ONLY the specific dummy scene created by the transition router + await get_tree().process_frame + var current_scene := get_tree().current_scene + if is_instance_valid(current_scene) and current_scene.name == "SplashTestDummyScene": + current_scene.free() + + +## Factory helper to safely instantiate the full scene layout and resolve @onready nodes +func _create_splash_instance() -> Control: + assert_true( + FileAccess.file_exists(SPLASH_SCREEN_PATH), + "File integrity check: Scene path '%s' must exist." % SPLASH_SCREEN_PATH + ) + var scene := load(SPLASH_SCREEN_PATH) as PackedScene + assert_not_null(scene, "Failed to load splash screen scene from path: %s" % SPLASH_SCREEN_PATH) + + var instance := scene.instantiate() as Control + assert_not_null(instance, "Failed to instantiate splash screen scene layout.") + add_child_autofree(instance) + return instance + + +# ========================================================================== +# 1. LIFECYCLE & INITIALIZATION TESTS +# ========================================================================== + +## Asserts that load_start_time is initialized upon entering the scene tree (_ready). +func test_load_start_time_initialized_on_ready() -> void: + splash_instance = _create_splash_instance() + + assert_gt( + splash_instance.load_start_time, + 0.0, + "Regression: load_start_time must be initialized via _ready() upon entering tree." + ) + + +# ========================================================================== +# 2. MONOTONIC PROGRESS & PRESENTATION PIPELINE TESTS +# ========================================================================== + +## Verifies that progress display target is monotonic and does not regress +## when _poll_resource_backend processes lower progress values from the loader. +func test_monotonic_progress_scaling() -> void: + splash_instance = _create_splash_instance() + Globals.next_scene = "res://scenes/main_menu.tscn" + + # Simulate initial higher progress target + splash_instance.display_target = 60.0 + + # Request threaded loading to create an active IN_PROGRESS status + ResourceLoader.load_threaded_request(Globals.next_scene) + + # Execute polling; initial loader progress (<60%) must not decrease display_target + splash_instance._poll_resource_backend() + + assert_eq( + splash_instance.display_target, + 60.0, + "Display target must remain at peak value when _poll_resource_backend runs." + ) + + +## Explicit TASK-03 spec alias verifying presentation engine rejects downward layout tracking values +func test_monotonic_progress_safeguard() -> void: + test_monotonic_progress_scaling() + + +## Verifies that move_toward smoothly steps progress forward deterministically based on delta. +func test_presentation_handler_linear_convergence() -> void: + splash_instance = _create_splash_instance() + + splash_instance.loader_progress = 0.0 + splash_instance.display_target = 100.0 + splash_instance.presentation_speed = 50.0 # 50 units per second + + # Advance by 0.5 seconds -> expected move_toward step = 25.0 + splash_instance._update_presentation_handler(0.5) + + assert_eq( + splash_instance.loader_progress, + 25.0, + "Presentation handler must step progress linearly according to delta * presentation_speed." + ) + assert_eq( + splash_instance.progress_bar.value, + 25.0, + "ProgressBar UI value must reflect updated loader_progress." + ) + + +## Explicit TASK-03 spec alias evaluating frame-rate independent tracking and move_toward convergence +func test_convergent_move_toward_resolution() -> void: + test_presentation_handler_linear_convergence() + + +# ========================================================================== +# 3. BACKEND POLLING & EARLY EXIT SAFEGUARDS +# ========================================================================== + +## Ensures backend polling early-returns when the scene is already marked as loaded. +func test_poll_resource_backend_stops_when_loaded() -> void: + splash_instance = _create_splash_instance() + + splash_instance.is_scene_loaded = true + splash_instance.display_target = 100.0 + + # Act: Call backend polling + splash_instance._poll_resource_backend() + + # Assert display_target was unmodified by any ResourceLoader query + assert_eq( + splash_instance.display_target, + 100.0, + "Backend polling must exit immediately when is_scene_loaded flag is true." + ) + + +## Ensures backend polling sets load_failed and forces target to 100 on non-PackedScene resources. +func test_poll_resource_backend_handles_invalid_resource_type() -> void: + splash_instance = _create_splash_instance() + + # Resolve valid non-PackedScene resource path on disk + var non_packed_path := "res://scripts/ui/screens/splash_screen.gd" + if not FileAccess.file_exists(non_packed_path): + non_packed_path = "res://scripts/splash_screen.gd" + + Globals.next_scene = non_packed_path + + var err := ResourceLoader.load_threaded_request(Globals.next_scene) + assert_eq(err, OK, "Threaded load request for non-PackedScene resource must succeed.") + + # Wait until threaded loader finishes loading the GDScript resource + var status: int = ResourceLoader.load_threaded_get_status(Globals.next_scene) + while status == ResourceLoader.THREAD_LOAD_IN_PROGRESS: + await get_tree().process_frame + status = ResourceLoader.load_threaded_get_status(Globals.next_scene) + + # Execute production backend polling logic + splash_instance._poll_resource_backend() + + assert_true( + splash_instance.load_failed, + "Non-PackedScene resource package must trigger load_failed flag in _poll_resource_backend." + ) + assert_eq( + splash_instance.display_target, + 100.0, + "Failure condition in _poll_resource_backend must push display_target to 100." + ) + + +# ========================================================================== +# 4. TRANSITION ROUTER DEFENSIVE VALIDATION & GATING MECHANICS +# ========================================================================== + +## Validates that the transition router respects minimum load time before proceeding. +func test_transition_router_respects_min_load_time() -> void: + splash_instance = _create_splash_instance() + + splash_instance.is_scene_loaded = true + splash_instance.loader_progress = 100.0 + splash_instance.min_load_time = 10.0 # Set long minimum load time + + splash_instance._evaluate_transition_router() + + assert_false( + splash_instance.transitioning, + "Transition router must lock out scene change until min_load_time has elapsed." + ) + + +## TASK-03 Spec: Evaluates transition gating remains locked when progress bar is incomplete +func test_transition_gating_mechanics_locked_when_bar_incomplete() -> void: + splash_instance = _create_splash_instance() + + splash_instance.is_scene_loaded = true + splash_instance.loader_progress = 50.0 # Bar incomplete (<99.9%) + splash_instance.min_load_time = 0.0 + + splash_instance._evaluate_transition_router() + + assert_false( + splash_instance.transitioning, + "Transition router must remain locked when presentation progress is incomplete." + ) + + +## TASK-03 Spec: Evaluates transition gating unlocks and fires transition on full completion +func test_transition_gating_mechanics_fire_on_full_completion() -> void: + splash_instance = _create_splash_instance() + + # Pack a uniquely named dummy node to avoid root-level collision + var dummy_root := Node2D.new() + dummy_root.name = "SplashTestDummyScene" + + var dummy_packed := PackedScene.new() + dummy_packed.pack(dummy_root) + dummy_root.free() + + splash_instance.scene = dummy_packed + Globals.next_scene = "res://scenes/dummy_test_scene.tscn" + splash_instance.is_scene_loaded = true + splash_instance.loader_progress = 100.0 + splash_instance.min_load_time = 0.0 # Force time elapsed + + splash_instance._evaluate_transition_router() + + assert_true( + splash_instance.transitioning, + "Transition router must unlock and set transitioning flag upon full completion." + ) diff --git a/test/gut/test_splash_screen.gd.uid b/test/gut/test_splash_screen.gd.uid new file mode 100644 index 000000000..4cbc4190c --- /dev/null +++ b/test/gut/test_splash_screen.gd.uid @@ -0,0 +1 @@ +uid://cy3bqndjmni20 diff --git a/tests/test_utils.py b/tests/test_utils.py index 21c98aeb6..855ac47d4 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -15,7 +15,7 @@ # Shared timeout configurations across test suites DEFAULT_TIMEOUT = int(os.getenv("DEFAULT_TIMEOUT", "30000")) -TEST_TIMEOUT = int(os.getenv("TEST_TIMEOUT", "5000")) +TEST_TIMEOUT = int(os.getenv("TEST_TIMEOUT", "7000")) LOG_LEVEL_MAP: dict[str, int] = { "DEBUG": 0,