"""Does pythonnet fire NavigationCompleted for a real page load?
This validates the mechanism the player relies on to tell "page loaded" apart
from "page failed" (e.g. unreachable host on a closed network). If the event
does not fire, the player cannot detect a failed weblink and would show
Chromium's error page for the full slot.
Checks three things:
1. the delegate can be constructed and subscribed,
2. it fires for a GOOD page -> IsSuccess True,
3. it fires for a BAD page -> IsSuccess False.
Run: windows\\venv\\Scripts\\python.exe windows\\test_webview2_navigation.py
Exit code 0 = PASS.
"""
import ctypes
import http.server
import socketserver
import sys
import threading
import time
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent))
PORT = 18766
PAGE = '
KIWY-NAV-OK
'
class _Handler(http.server.BaseHTTPRequestHandler):
def do_GET(self):
body = PAGE.encode()
self.send_response(200)
self.send_header('Content-Type', 'text/html')
self.send_header('Content-Length', str(len(body)))
self.end_headers()
self.wfile.write(body)
def log_message(self, *args):
pass
def _start_server():
socketserver.TCPServer.allow_reuse_address = True
httpd = socketserver.TCPServer(('127.0.0.1', PORT), _Handler)
threading.Thread(target=httpd.serve_forever, daemon=True).start()
return httpd
user32 = ctypes.windll.user32
kernel32 = ctypes.windll.kernel32
WNDPROC = ctypes.WINFUNCTYPE(
ctypes.c_int64, ctypes.c_void_p, ctypes.c_uint, ctypes.c_void_p, ctypes.c_void_p
)
@WNDPROC
def _wnd_proc(hwnd, msg, wparam, lparam):
if msg == 0x0002:
user32.PostQuitMessage(0)
return 0
user32.DefWindowProcW.restype = ctypes.c_int64
user32.DefWindowProcW.argtypes = [
ctypes.c_void_p, ctypes.c_uint, ctypes.c_void_p, ctypes.c_void_p,
]
return user32.DefWindowProcW(hwnd, msg, wparam, lparam)
def _make_window(width=1024, height=768):
hinstance = kernel32.GetModuleHandleW(None)
name = 'KiwyNavTest'
class WNDCLASSEX(ctypes.Structure):
_fields_ = [
('cbSize', ctypes.c_uint), ('style', ctypes.c_uint),
('lpfnWndProc', WNDPROC), ('cbClsExtra', ctypes.c_int),
('cbWndExtra', ctypes.c_int), ('hInstance', ctypes.c_void_p),
('hIcon', ctypes.c_void_p), ('hCursor', ctypes.c_void_p),
('hbrBackground', ctypes.c_void_p), ('lpszMenuName', ctypes.c_wchar_p),
('lpszClassName', ctypes.c_wchar_p), ('hIconSm', ctypes.c_void_p),
]
wc = WNDCLASSEX()
wc.cbSize = ctypes.sizeof(WNDCLASSEX)
wc.style = 0x0002 | 0x0001
wc.lpfnWndProc = _wnd_proc
wc.hInstance = hinstance
wc.hbrBackground = ctypes.c_void_p(6)
wc.lpszClassName = name
user32.RegisterClassExW(ctypes.byref(wc))
hwnd = user32.CreateWindowExW(
0, name, 'Kiwy Nav Test', 0x00CF0000 | 0x10000000,
40, 40, width, height, 0, 0, hinstance, 0,
)
if not hwnd:
raise RuntimeError('CreateWindowExW failed')
user32.UpdateWindow(hwnd)
return hwnd
def _pump(seconds):
import ctypes.wintypes as wt
deadline = time.monotonic() + seconds
while time.monotonic() < deadline:
msg = wt.MSG()
while user32.PeekMessageW(ctypes.byref(msg), None, 0, 0, 1):
user32.TranslateMessage(ctypes.byref(msg))
user32.DispatchMessageW(ctypes.byref(msg))
time.sleep(0.02)
def _drive(browser, seconds):
deadline = time.monotonic() + seconds
while time.monotonic() < deadline:
browser._tick(0)
_pump(0.05)
def _wait_nav(browser, timeout):
deadline = time.monotonic() + timeout
while time.monotonic() < deadline:
browser._tick(0)
_pump(0.05)
if browser.navigation_succeeded() is not None:
return browser.navigation_succeeded()
return None
def main():
print('=' * 68)
print(' WebView2 NavigationCompleted test')
print('=' * 68)
from webview2_browser import WebView2Browser
if not WebView2Browser.is_available():
print('FAIL: WebView2 unavailable:', WebView2Browser._import_error)
return 1
httpd = _start_server()
good = f'http://127.0.0.1:{PORT}/index.html'
# A port nothing listens on: guarantees a real navigation failure.
bad = 'http://127.0.0.1:1/missing'
hwnd = _make_window()
browser = WebView2Browser(hwnd_provider=lambda: hwnd)
ok = True
print(f'\n[1] good page: {good}')
browser.show(good)
_drive(browser, 1.0)
result = _wait_nav(browser, 20)
print(f' navigation_succeeded = {result}')
print(f' status = {browser.navigation_status()!r}')
if result is not True:
print(' FAIL: good page did not report success')
ok = False
print(f'\n[2] bad page: {bad}')
browser.show(bad)
_drive(browser, 1.0)
result = _wait_nav(browser, 25)
print(f' navigation_succeeded = {result}')
print(f' status = {browser.navigation_status()!r}')
if result is not False:
print(' FAIL: bad page did not report failure')
ok = False
browser.shutdown()
httpd.shutdown()
httpd.server_close()
print('=' * 68)
print(' RESULT:', 'PASS' if ok else 'FAIL')
if ok:
print(' The player can tell a loaded page from a failed one,')
print(' so unreachable weblinks are skipped instead of shown blank.')
print('=' * 68)
return 0 if ok else 1
if __name__ == '__main__':
sys.exit(main())