Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
87695df
Add telemetry hook and normalize handlers
ikostan Aug 15, 2026
11e2936
Create splash_transition_flow_test.py
ikostan Aug 15, 2026
cafa396
Add canvas geometry assertions to splash test
ikostan Aug 15, 2026
815c46e
Enhance splash transition telemetry checks
ikostan Aug 15, 2026
cbcc5ff
style: format code with Black and isort
deepsource-autofix[bot] Aug 15, 2026
4b737d9
Cleanup & minor fixes in splash_transition_flow_test
ikostan Aug 15, 2026
08b0354
CodeRabbit Generated Unit Tests: Add Generated Unit Tests for PR Changes
coderabbitai[bot] Aug 15, 2026
c3015ff
Merge branch 'web-loading-lifecycle-progress-transition-refactor' of …
ikostan Aug 15, 2026
2207cf9
style: format code with Black and isort
deepsource-autofix[bot] Aug 15, 2026
ea8467f
style: format code with Black and isort
deepsource-autofix[bot] Aug 15, 2026
7d70c5b
Merge branch 'web-loading-lifecycle-progress-transition-refactor' int…
ikostan Aug 15, 2026
2e21602
style: format code with Black and isort
deepsource-autofix[bot] Aug 15, 2026
e3302a2
Merge pull request #897 from ikostan/coderabbitai/utg/96e3a12
ikostan Aug 15, 2026
d419aa4
Use test_utils for coverage and artifacts in splash test
ikostan Aug 15, 2026
d370602
style: format code with Black and isort
deepsource-autofix[bot] Aug 15, 2026
32c1ba2
Refactor splash test: extract validation helpers
ikostan Aug 15, 2026
9ccc9b1
style: format code with Black and isort
deepsource-autofix[bot] Aug 15, 2026
5c3d18a
Update splash_transition_flow_test.py
ikostan Aug 15, 2026
d3335fb
style: format code with Black and isort
deepsource-autofix[bot] Aug 15, 2026
bca00be
Update test_utils.py
ikostan Aug 16, 2026
b49bd2a
Improve loading accessibility and focus handling
ikostan Aug 16, 2026
2faa85e
Update tests/splash_transition_flow_test.py
ikostan Aug 16, 2026
93ff096
Use regex to extract onProgress function
ikostan Aug 16, 2026
2cb1779
Capture screenshot in test failure artifacts
ikostan Aug 16, 2026
94aacdd
style: format code with Black and isort
deepsource-autofix[bot] Aug 16, 2026
193ee20
Update custom_shell.html
ikostan Aug 17, 2026
4f9cdc6
style: format code with Black and isort
deepsource-autofix[bot] Aug 17, 2026
9396dbe
Update custom_shell.html
ikostan Aug 17, 2026
fc04b07
Merge branch 'web-loading-lifecycle-progress-transition-refactor' of …
ikostan Aug 17, 2026
c43bb43
Update splash_transition_flow_test.py
ikostan Aug 17, 2026
0e0a5cc
style: format code with Black and isort
deepsource-autofix[bot] Aug 17, 2026
feb1e4b
Update splash_transition_flow_test.py
ikostan Aug 17, 2026
ae294e7
Merge branch 'web-loading-lifecycle-progress-transition-refactor' of …
ikostan Aug 17, 2026
70f4a4a
style: format code with Black and isort
deepsource-autofix[bot] Aug 17, 2026
e1eaa3e
Refactor formatting in splash_transition tests
ikostan Aug 17, 2026
a5002db
Merge branch 'web-loading-lifecycle-progress-transition-refactor' of …
ikostan Aug 17, 2026
644cf4b
style: format code with Black and isort
deepsource-autofix[bot] Aug 17, 2026
0c0c101
Clamp transfer progress telemetry
ikostan Aug 17, 2026
f47ffd1
Create Part_1_Web_loading_lifecycle_progress_transition_refactor.md
ikostan Aug 17, 2026
2bd1add
Update Part_1_Web_loading_lifecycle_progress_transition_refactor.md
ikostan Aug 17, 2026
0221bf7
Update test_utils.py
ikostan Aug 17, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 21 additions & 10 deletions custom_shell.html
Original file line number Diff line number Diff line change
Expand Up @@ -188,13 +188,24 @@

<script src="$GODOT_URL"></script> <!-- Godot replaces with actual JS file, e.g., tmp_js_export.js -->
<script>
var engine = new Engine($GODOT_CONFIG); // Official placeholder for config
// Consolidate engine configuration with custom telemetry hooks
var customConfig = Object.assign({}, $GODOT_CONFIG, {
onProgress: function(current, total) {
if (total > 0) {
var percent = Math.floor((current / total) * 100);
console.log("Telemetry - Assembly Transfer: " + percent + "%");
}
}
});

// Initialize engine instance with customConfig
var engine = new Engine(customConfig);

// Start Godot engine (async for stability)
engine.startGame().then(() => {
console.log("Godot engine started successfully!");

// Hide the loading UI
// Hide the loading UI and set accessibility attributes
var loadingDiv = document.getElementById('loading');
if (loadingDiv) {
loadingDiv.style.display = 'none';
Comment thread
sourcery-ai[bot] marked this conversation as resolved.
Resolved
Expand Down Expand Up @@ -229,14 +240,14 @@
document.getElementById('mute-weapon').onchange = (e) => window.toggleMuteWeapon([e.target.checked]);
document.getElementById('mute-menu').onchange = (e) => window.toggleMuteMenu([e.target.checked]);
document.getElementById('mute-rotors').onchange = (e) => window.toggleMuteRotors([e.target.checked]);
document.getElementById('options-back-button').onclick = () => window.optionsBackPressed([]); // Add this for options back
document.getElementById('controls-back-button').onclick = () => window.controlsBackPressed([]); // Add this for controls back
document.getElementById('audio-back-button').onclick = () => window.audioBackPressed([]); // Add this for audio back
document.getElementById('audio-reset-button').onclick = () => window.audioResetPressed([]); // Add for reset
document.getElementById('advanced-back-button').onclick = () => window.advancedBackPressed([]); // Add this for advanced back
document.getElementById('advanced-reset-button').onclick = () => window.advancedResetPressed([]); // Add for reset
document.getElementById('gameplay-back-button').onclick = () => window.gameplayBackPressed([]); // Add this for advanced back
document.getElementById('gameplay-reset-button').onclick = () => window.gameplayResetPressed([]); // Add for reset
document.getElementById('options-back-button').onclick = () => window.optionsBackPressed([]);
document.getElementById('controls-back-button').onclick = () => window.controlsBackPressed([]);
document.getElementById('audio-back-button').onclick = () => window.audioBackPressed([]);
document.getElementById('audio-reset-button').onclick = () => window.audioResetPressed([]);
document.getElementById('advanced-back-button').onclick = () => window.advancedBackPressed([]);
document.getElementById('advanced-reset-button').onclick = () => window.advancedResetPressed([]);
document.getElementById('gameplay-back-button').onclick = () => window.gameplayBackPressed([]);
document.getElementById('gameplay-reset-button').onclick = () => window.gameplayResetPressed([]);
</script>
<div class="ime" contenteditable="true" style="background: none; opacity: 0; position: fixed; text-align: left; font-size: 1px; left: 0px; top: 0px; width: 100%; height: 40px; pointer-events: none; display: none;"></div>
</body>
Expand Down
189 changes: 189 additions & 0 deletions tests/splash_transition_flow_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
# Copyright (C) 2025-2026 Egor Kostan
# SPDX-License-Identifier: GPL-3.0-or-later
# tests/splash_transition_flow_test.py
"""
Splash Screen Transition & Telemetry Test Suite (Playwright + UI Automation)
=============================================================================

Overview
--------
Verifies the asynchronous web loading workflow, custom shell initialization pipeline,
progressive assembly telemetry, and in-game progress transition mechanics. Eliminates
race conditions by validating orderly handshakes between the DOM layout engine and the
WebAssembly runtime graphics context.

Prerequisites
-------------
- http://localhost:8080/index.html (HTML5 export with preloader & overlays)
- pytest, playwright

Running
-------
pytest -k splash_transition_flow -q

Artifacts
---------
v8_coverage_splash_transition_flow_test.json, artifacts/test_splash_failure_*.png/txt/html
Comment thread
ikostan marked this conversation as resolved.
Resolved
"""

import json
import os
import re
import time
from typing import Any

from playwright.sync_api import Page, expect

from tests.test_utils import DEFAULT_TIMEOUT, TEST_TIMEOUT


# DO NOT REFACTOR: Must consume function-scoped `page` to capture preloader & startup telemetry.
Comment thread
ikostan marked this conversation as resolved.
Resolved
def test_splash_transition_flow(page: Page) -> None:
Comment thread
ikostan marked this conversation as resolved.
Resolved
"""
Validates assembly stream metrics, progressive telemetry monotonicity,
WebGL frame canvas presentation, and orderly removal of preloader DOM layers.
"""
logs: list[dict[str, str]] = []
page_errors: list[str] = []
cdp_session = None

def on_console(msg: Any) -> None:
"""Capture all console logs to track runtime lifecycle telemetry."""
logs.append({"type": msg.type, "text": msg.text})

def on_page_error(exc: Any) -> None:
"""Capture uncaught runtime errors during engine boot."""
page_errors.append(f"Uncaught Exception: {exc.message}\n{exc.stack}")

# Register listeners BEFORE navigation to capture early boot telemetry & errors
page.on("console", on_console)
page.on("pageerror", on_page_error)

try:
# 1. INITIALIZE V8 PRECISE COVERAGE VIA CDP
cdp_session = page.context.new_cdp_session(page)
cdp_session.send("Profiler.enable")
cdp_session.send(
"Profiler.startPreciseCoverage", {"callCount": True, "detailed": True}
)

# 2. MONITOR INITIAL BROWSER LOADING LAYER
page.goto(
"http://localhost:8080/index.html",
wait_until="domcontentloaded",
timeout=DEFAULT_TIMEOUT,
)

# Verify custom HTML layout preloader maps instantly to the viewport
loading_overlay = page.locator("#loading")
expect(loading_overlay).to_be_visible(timeout=TEST_TIMEOUT)

# 3. VERIFY ENGINE INITIALIZATION & RUNTIME TELEMETRY
page.wait_for_function(
"() => window.godotInitialized === true", timeout=DEFAULT_TIMEOUT
)

# Parse progressive assembly transfer marks ("Telemetry - Assembly Transfer: X%")
Comment thread
ikostan marked this conversation as resolved.
Resolved
progress_values: list[int] = []
for log in logs:
match = re.search(r"Telemetry - Assembly Transfer:\s*(\d+)%", log["text"])
if match:
progress_values.append(int(match.group(1)))

# Assert telemetry presence, range bounds, and monotonic forward progression
assert (
len(progress_values) > 0
), "No 'Telemetry - Assembly Transfer:' marks captured during engine boot."
assert all(
0 <= val <= 100 for val in progress_values
), f"Telemetry percentage out of bounds [0, 100]: {progress_values}"
assert (
progress_values == sorted(progress_values)
), f"Assembly transfer telemetry did not progress monotonically: {progress_values}"
Comment thread
ikostan marked this conversation as resolved.
Resolved

# 4. ASSIGN RENDERING CANVAS HANDSHAKE & STRUCTURAL GEOMETRY
canvas_element = page.locator("#canvas")
expect(canvas_element).to_be_visible(timeout=TEST_TIMEOUT)

canvas_box = canvas_element.bounding_box()
assert canvas_box is not None, "Canvas element has no rendered bounding box"
assert canvas_box["width"] > 0, "Canvas rendered width is zero (viewport layout failure)"
assert canvas_box["height"] > 0, "Canvas rendered height is zero (viewport layout failure)"

assert (
"SkyLockAssault" in page.title()
), f"Target application title mismatch: '{page.title()}'"

# 5. VERIFY DOM TEARDOWN & LIFECYCLE INVARIANTS
expect(loading_overlay).to_be_hidden(timeout=TEST_TIMEOUT)
assert page.evaluate(
"() => document.getElementById('loading').getAttribute('aria-hidden') === 'true'"
Comment thread
ikostan marked this conversation as resolved.
Resolved
), "Loading container missing aria-hidden='true' post-initialization"

# Invariant: engine initialization state must remain true after overlay teardown
assert page.evaluate(
"() => window.godotInitialized === true"
), "window.godotInitialized lost its truthy state after splash transition completed"
Comment thread
ikostan marked this conversation as resolved.
Resolved

# 6. AUDIT FATAL PARSING & SCRIPT COMPILATION EXCEPTIONS
critical_faults = [
log["text"]
for log in logs
if log["type"] == "error"
and not any(
phrase in log["text"].lower()
for phrase in ["encryption aborted", "salt is empty"]
)
] + page_errors

assert (
len(critical_faults) == 0
), f"Critical exceptions found during web handshake:\n" + "\n".join(critical_faults)

except Exception as e:
print(f"Test: 'test_splash_transition_flow' failed: {e!s}")
os.makedirs("artifacts", exist_ok=True)
timestamp: int = int(time.time())

# Isolate diagnostic files on execution crashes
page.screenshot(path=f"artifacts/test_splash_failure_screenshot_{timestamp}.png")

with open(
f"artifacts/test_splash_failure_console_logs_{timestamp}.txt",
"w",
encoding="utf-8",
) as f:
f.write("--- CONSOLE LOGS ---\n")
for log in logs:
f.write(f"[{log['type']}] {log['text']}\n")
f.write("\n--- PAGE ERRORS ---\n")
for p_err in page_errors:
f.write(f"{p_err}\n")

with open(
f"artifacts/test_splash_failure_html_{timestamp}.html",
"w",
encoding="utf-8",
) as f:
f.write(page.content())
raise

finally:
try:
page.remove_listener("console", on_console)
page.remove_listener("pageerror", on_page_error)
except Exception:
pass

if cdp_session:
try:
coverage = cdp_session.send("Profiler.takePreciseCoverage")["result"]
cdp_session.send("Profiler.stopPreciseCoverage")
cdp_session.send("Profiler.disable")
cdp_session.detach()
with open(
"v8_coverage_splash_transition_flow_test.json", "w", encoding="utf-8"
) as f:
json.dump(coverage, f)
except Exception as cov_err:
print(f"Warning: Failed to harvest V8 coverage data: {cov_err}")
Loading