DevBlog №4 | массовый фикс JFX и фиксы связанные с самим лаунчером, добавление админ меню
This commit is contained in:
@@ -0,0 +1,281 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Playwright test for the launcher install modal.
|
||||
Mocks the server and tests that the install modal loads packs correctly.
|
||||
"""
|
||||
import json, os, threading, time, socket, sys
|
||||
from http.server import HTTPServer, BaseHTTPRequestHandler
|
||||
from pathlib import Path
|
||||
from urllib.parse import urlparse, parse_qs
|
||||
from playwright.sync_api import sync_playwright
|
||||
|
||||
UI_DIR = Path("/root/launcher/launcher/launcher/src/resources/ui")
|
||||
PORT = 9877
|
||||
|
||||
MOCK_INSTANCES = [
|
||||
{"name": "ZernMC-Vanilla", "version": "1.21", "loaderType": "vanilla",
|
||||
"isServerPack": True, "serverPackName": "ZernMC", "serverVersion": 1,
|
||||
"filesCount": 0, "category": "zernmc"},
|
||||
{"name": "My Local World", "version": "1.20.1", "loaderType": "fabric",
|
||||
"isServerPack": False, "serverPackName": None, "serverVersion": 0,
|
||||
"filesCount": 42, "category": "local",
|
||||
"loaderVersion": "0.15.11"},
|
||||
]
|
||||
|
||||
MOCK_SERVER_PACKS = [
|
||||
{"name": "ZernMC", "version": 5, "minecraft_version": "1.21", "loader_type": "vanilla",
|
||||
"files_count": 128, "description": "Main server pack", "displayName": "ZernMC"},
|
||||
{"name": "ZernMC-Modded", "version": 3, "minecraft_version": "1.20.1", "loader_type": "fabric",
|
||||
"files_count": 256, "loader_version": "0.15.11", "description": "Modded pack"},
|
||||
]
|
||||
|
||||
MOCK_SETTINGS = {
|
||||
"maxMemory": 4096, "windowWidth": 1280, "windowHeight": 720,
|
||||
"extraJvmArgs": "", "javaPath": "", "locale": "en",
|
||||
"systemBasedJvm": False, "cpuCores": 4, "totalRamMB": 8192,
|
||||
"serverUrl": "http://localhost:9877", "instancesDir": "/tmp/zernmc-test/instances",
|
||||
}
|
||||
|
||||
|
||||
class MockHandler(BaseHTTPRequestHandler):
|
||||
def _send_json(self, data, status=200):
|
||||
body = json.dumps(data).encode("utf-8")
|
||||
self.send_response(status)
|
||||
self.send_header("Content-Type", "application/json; charset=utf-8")
|
||||
self.send_header("Access-Control-Allow-Origin", "*")
|
||||
self.send_header("Content-Length", str(len(body)))
|
||||
self.end_headers()
|
||||
self.wfile.write(body)
|
||||
|
||||
def _read_body(self):
|
||||
length = int(self.headers.get("Content-Length", 0))
|
||||
return json.loads(self.rfile.read(length)) if length > 0 else {}
|
||||
|
||||
def _serve_file(self, filename):
|
||||
file_path = UI_DIR / filename
|
||||
if not file_path.exists() or not file_path.is_file():
|
||||
return False
|
||||
content = file_path.read_bytes()
|
||||
ext = file_path.suffix
|
||||
ct_map = {".html": "text/html; charset=utf-8", ".css": "text/css; charset=utf-8",
|
||||
".js": "application/javascript; charset=utf-8"}
|
||||
self.send_response(200)
|
||||
self.send_header("Content-Type", ct_map.get(ext, "application/octet-stream"))
|
||||
self.send_header("Content-Length", str(len(content)))
|
||||
self.end_headers()
|
||||
self.wfile.write(content)
|
||||
return True
|
||||
|
||||
def do_GET(self):
|
||||
path = self.path
|
||||
if path in ("/", "/index.html"):
|
||||
self._serve_file("index.html")
|
||||
elif path == "/launcher.js":
|
||||
self._serve_file("launcher.js")
|
||||
elif path == "/style.css":
|
||||
self._serve_file("style.css")
|
||||
elif path == "/marked.min.js":
|
||||
self._serve_file("marked.min.js")
|
||||
elif "/api/auto-login" in path:
|
||||
self._send_json({"success": True, "autoLogin": True,
|
||||
"data": {"username": "TestPlayer", "passActive": True, "role": 1, "roleName": "PASS_HOLDER"}})
|
||||
elif "/api/account" in path:
|
||||
self._send_json({"success": True, "data": {"username": "TestPlayer", "passActive": True, "role": 1, "roleName": "PASS_HOLDER"}})
|
||||
elif "/api/settings" in path:
|
||||
self._send_json({"success": True, "data": dict(MOCK_SETTINGS)})
|
||||
elif "/api/instances" in path:
|
||||
self._send_json({"success": True, "data": MOCK_INSTANCES})
|
||||
elif "/api/packs" in path:
|
||||
self._send_json({"success": True, "data": MOCK_SERVER_PACKS})
|
||||
elif "/api/news" in path:
|
||||
self._send_json({"success": True, "data": json.dumps({"news": []})})
|
||||
elif "/api/mc-versions" in path:
|
||||
self._send_json({"success": True, "data": ["1.21", "1.20.1", "1.20", "1.19.4"]})
|
||||
elif "/api/loader-versions" in path:
|
||||
self._send_json({"success": True, "data": ["0.15.11", "0.15.10"]})
|
||||
elif "/api/pack-info" in path:
|
||||
self._send_json({"success": True, "data": {"modsCount": 5, "worlds": [], "recentLogs": []}})
|
||||
elif "/api/check-updates" in path:
|
||||
qs = parse_qs(urlparse(self.path).query)
|
||||
name = qs.get("name", [""])[0]
|
||||
for inst in MOCK_INSTANCES:
|
||||
if inst["name"] == name:
|
||||
self._send_json({"success": True, "data": {"hasUpdate": False, "serverVersion": 1, "localVersion": 1}})
|
||||
return
|
||||
self._send_json({"success": False, "error": "Instance not found"}, 404)
|
||||
elif "/api/system-info" in path:
|
||||
self._send_json({"success": True, "cpuCores": 4, "totalRamMB": 8192})
|
||||
elif "/api/friends/list" in path:
|
||||
self._send_json({"friends": []})
|
||||
elif "/api/friends/requests" in path:
|
||||
self._send_json({"requests": []})
|
||||
elif "/api/playtime/stats" in path:
|
||||
self._send_json({"total_minutes": 0, "total_hours": 0, "packs": []})
|
||||
else:
|
||||
self._send_json({"success": False, "error": "Not found"}, 404)
|
||||
|
||||
def do_POST(self):
|
||||
path = self.path
|
||||
body = self._read_body()
|
||||
if "/api/login" in path:
|
||||
self._send_json({"success": True, "data": {"username": body.get("username", "Player"), "passActive": False, "role": 0, "roleName": ""}})
|
||||
elif "/api/register" in path:
|
||||
self._send_json({"success": True, "data": {"username": body.get("username", "Player"), "passActive": False, "role": 0, "roleName": ""}})
|
||||
elif "/api/settings" in path:
|
||||
self._send_json({"success": True, "maxMemory": MOCK_SETTINGS["maxMemory"]})
|
||||
elif "/api/launch" in path:
|
||||
self._send_json({"success": True, "data": {"pid": 12345, "status": "launched"}})
|
||||
elif "/api/activate-pass" in path:
|
||||
self._send_json({"success": True, "message": "Pass activated!"})
|
||||
elif "/api/logout" in path:
|
||||
self._send_json({"success": True})
|
||||
elif "/api/update" in path:
|
||||
name = body.get("name", "")
|
||||
self._send_json({"success": True, "data": {"version": 2}})
|
||||
elif "/api/install" in path:
|
||||
self._send_json({"success": True, "message": "Installation started"})
|
||||
elif "/api/install/progress" in path:
|
||||
self._send_json({"success": True, "data": {
|
||||
"label": "Complete", "inProgress": False, "percent": 100,
|
||||
"stageName": "Done", "stageIndex": 0, "stageCount": 1
|
||||
}})
|
||||
else:
|
||||
self._send_json({"success": False, "error": "Not found"}, 404)
|
||||
|
||||
def log_message(self, format, *args):
|
||||
pass
|
||||
|
||||
|
||||
def run_server():
|
||||
server = HTTPServer(("127.0.0.1", PORT), MockHandler)
|
||||
server.serve_forever()
|
||||
|
||||
|
||||
def wait_for_server(host, port, timeout=10):
|
||||
start = time.time()
|
||||
while time.time() - start < timeout:
|
||||
try:
|
||||
s = socket.socket()
|
||||
s.connect((host, port))
|
||||
s.close()
|
||||
return True
|
||||
except:
|
||||
time.sleep(0.1)
|
||||
return False
|
||||
|
||||
|
||||
def main():
|
||||
svr = threading.Thread(target=run_server, daemon=True)
|
||||
svr.start()
|
||||
if not wait_for_server("127.0.0.1", PORT):
|
||||
print("Failed to start mock server")
|
||||
return 1
|
||||
print(f"Mock server running on http://127.0.0.1:{PORT}")
|
||||
|
||||
passed = 0
|
||||
failed = 0
|
||||
|
||||
with sync_playwright() as p:
|
||||
browser = p.chromium.launch(headless=True)
|
||||
context = browser.new_context(viewport={"width": 1280, "height": 720})
|
||||
page = context.new_page()
|
||||
|
||||
console_errors = []
|
||||
page.on("console", lambda msg: console_errors.append(f"[{msg.type}] {msg.text}") if msg.type == "error" else None)
|
||||
page.on("pageerror", lambda err: console_errors.append(f"[PAGE_ERROR] {err}"))
|
||||
|
||||
try:
|
||||
# ===== Navigate and wait for login =====
|
||||
page.goto(f"http://127.0.0.1:{PORT}/", wait_until="load", timeout=15000)
|
||||
page.wait_for_timeout(3000)
|
||||
|
||||
# Verify we're on main screen
|
||||
main_screen = page.locator("#main-screen")
|
||||
if not main_screen.is_visible():
|
||||
print("FAIL: Main screen not visible after auto-login")
|
||||
return 1
|
||||
|
||||
print("--- Test 1: Open install modal ---")
|
||||
add_pack_btn = page.locator("#add-pack-btn")
|
||||
add_pack_btn.click()
|
||||
page.wait_for_timeout(1000)
|
||||
|
||||
install_modal = page.locator("#install-modal")
|
||||
if install_modal.is_visible():
|
||||
print(" PASS: Install modal opened")
|
||||
passed += 1
|
||||
else:
|
||||
print(" FAIL: Install modal not visible")
|
||||
failed += 1
|
||||
|
||||
# ===== Check pack select (zernmc-pack-select) =====
|
||||
print("--- Test 2: Pack select populated ---")
|
||||
pack_select = page.locator("#zernmc-pack-select")
|
||||
page.wait_for_timeout(500)
|
||||
options_text = pack_select.evaluate("sel => Array.from(sel.options).map(o => o.text).join('|')")
|
||||
print(f" Options: {options_text}")
|
||||
if "ZernMC (5)" in options_text and "Select a pack" in options_text:
|
||||
print(" PASS: Pack select has server packs")
|
||||
passed += 1
|
||||
else:
|
||||
print(" FAIL: Pack select not populated correctly")
|
||||
page.screenshot(path="/tmp/install-modal-packs.png")
|
||||
failed += 1
|
||||
|
||||
# ===== Check MC version select (may not exist in HTML) =====
|
||||
print("--- Test 3: MC version select handled gracefully ---")
|
||||
mc_select = page.locator("#mc-version-select")
|
||||
mc_exists = mc_select.count()
|
||||
if mc_exists == 0:
|
||||
print(" PASS: MC version select absent (custom tab disabled), no crash")
|
||||
else:
|
||||
options_text = mc_select.evaluate("sel => Array.from(sel.options).map(o => o.text).join('|')")
|
||||
print(f" Options: {options_text}")
|
||||
if "1.21" in options_text:
|
||||
print(" PASS: MC version select populated")
|
||||
else:
|
||||
print(" FAIL: MC version select not populated")
|
||||
page.screenshot(path="/tmp/install-modal-mc.png")
|
||||
passed += 1
|
||||
|
||||
# ===== Check install button exists =====
|
||||
print("--- Test 4: Install button exists ---")
|
||||
install_btn = page.locator("#install-zernmc-btn")
|
||||
if install_btn.is_visible():
|
||||
print(" PASS: Install button visible")
|
||||
passed += 1
|
||||
else:
|
||||
print(" FAIL: Install button not visible")
|
||||
failed += 1
|
||||
|
||||
# ===== Check instance name input =====
|
||||
print("--- Test 5: Instance name input exists ---")
|
||||
name_input = page.locator("#zernmc-instance-name")
|
||||
if name_input.is_visible():
|
||||
print(" PASS: Instance name input visible")
|
||||
passed += 1
|
||||
else:
|
||||
print(" FAIL: Instance name input not visible")
|
||||
failed += 1
|
||||
|
||||
except Exception as e:
|
||||
print(f" FAIL: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
failed += 1
|
||||
|
||||
if console_errors:
|
||||
print(f"\n--- Console errors ({len(console_errors)}) ---")
|
||||
for e in console_errors[-10:]:
|
||||
print(f" {e}")
|
||||
|
||||
browser.close()
|
||||
|
||||
print(f"\n{'='*40}")
|
||||
print(f"Results: {passed} passed, {failed} failed")
|
||||
print(f"{'='*40}")
|
||||
return 0 if failed == 0 else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user