""" Debug CRTC order form submission end-to-end with Playwright. Captures console errors, network requests, and the exact failure point. Usage: python3 scripts/tests/debug_submit.py """ import asyncio import json import os import sys from playwright.async_api import async_playwright, Page, ConsoleMessage BASE = "https://dev.performancewest.net" API = "https://api.dev.performancewest.net" # Inject a verified identity session so we can skip real ID verification # Use the most recent verified session from the DB IDENTITY_SESSION = "vs_1TIEHJBXSidDHvshKTUm1iW8" async def fill_form(page: Page): await page.goto(f"{BASE}/order/canada-crtc?test_mode=1", wait_until="domcontentloaded") await page.wait_for_timeout(1000) # ── Step 1 — Company details ────────────────────────────────────────────── # numbered is already checked by default print("STEP 1: company details") await page.click("#btn-next") await page.wait_for_timeout(500) # ── Step 2 — Director ───────────────────────────────────────────────────── print("STEP 2: director") await page.fill("#director_first_name", "Justin") await page.fill("#director_last_name", "Hannah") # Select country first — reveals address fields await page.select_option("#director_country", "US") await page.wait_for_timeout(400) # Address fields should now be visible await page.fill("#director_street", "123 Main St") await page.fill("#director_city", "Dallas") # Province — select from dropdown and trigger change await page.select_option("#director_province_select", "TX") await page.evaluate("document.getElementById('director_province_select').dispatchEvent(new Event('change', {bubbles: true}))") await page.wait_for_timeout(200) await page.fill("#director_postal", "75201") # Citizenship try: await page.select_option("#director_citizenship", "United States", timeout=3000) except Exception as e: print(f" citizenship select skip: {e}") await page.click("#btn-next") await page.wait_for_timeout(500) # ── Step 3 — Telecom services ───────────────────────────────────────────── print("STEP 3: services") try: await page.fill("#service_description", "Voice and data reseller services", timeout=2000) except Exception: pass try: await page.fill("#geographic_coverage", "Canada-wide", timeout=2000) except Exception: pass await page.click("#btn-next") await page.wait_for_timeout(500) # ── Step 4 — Identity: inject verified result via JS then skip to step 5 ── print("STEP 4: inject identity result and skip to step 5") # Directly manipulate the page state to mark identity as verified await page.evaluate(f"""() => {{ // Set sessionStorage so the page won't try to reload identity sessionStorage.setItem('pw_identity_session', '{IDENTITY_SESSION}'); // Force identity UI to verified state and enable Next const states = ['identity-start','identity-loading','identity-failed','identity-waiting','identity-review']; states.forEach(id => {{ const el = document.getElementById(id); if (el) el.classList.add('hidden'); }}); const verified = document.getElementById('identity-verified'); if (verified) verified.classList.remove('hidden'); // Enable Next button const next = document.getElementById('btn-next'); if (next) {{ next.disabled = false; next.title = ''; }} // Hide pending notice const notice = document.getElementById('identity-pending-notice'); if (notice) notice.classList.add('hidden'); }}""") await page.wait_for_timeout(500) # Now click Next to go to step 5 await page.click("#btn-next", force=True) await page.wait_for_timeout(500) # Check we're on step 5 by looking for customer_name step5 = await page.query_selector("#customer_name") print(f" step5 visible: {step5 is not None}") # Debug current step value step_val = await page.evaluate("() => { try { return window.__currentStep || 'unknown'; } catch(e) { return 'err:' + e; } }") print(f" window.__currentStep: {step_val}") # Check which step panels are visible for i in range(1, 7): vis = await page.evaluate(f"() => !document.getElementById('step-{i}')?.classList.contains('hidden')") print(f" step-{i} visible: {vis}") # ── Step 5 — Contact ────────────────────────────────────────────────────── print("STEP 5: contact") await page.fill("#customer_name", "Justin Hannah") await page.fill("#customer_email", "justin+e2etest@performancewest.net") try: await page.fill("#customer_phone", "+12145550100", timeout=2000) except Exception: pass # Check consent checkbox await page.check("#consent") await page.wait_for_timeout(200) # Check currentStep before clicking step_before = await page.evaluate("() => { try { return window.__pw_currentStep || 'not exposed'; } catch(e) { return 'err'; } }") btn_text_before = await page.evaluate("() => document.getElementById('btn-next')?.textContent?.trim() || 'not found'") btn_disabled_before = await page.is_disabled("#btn-next") print(f" before click: step={step_before} btn_text='{btn_text_before}' disabled={btn_disabled_before}") print("Clicking Submit Order...") async def main(): async with async_playwright() as pw: browser = await pw.chromium.launch(headless=True) ctx = await browser.new_context() page = await ctx.new_page() # Capture console logs console_lines = [] def on_console(msg: ConsoleMessage): console_lines.append(f"[{msg.type}] {msg.text}") print(f"CONSOLE [{msg.type}]: {msg.text}") page.on("console", on_console) # Capture network requests network_log = [] def on_request(req): if "api" in req.url and req.method in ("POST", "GET"): print(f"REQUEST {req.method} {req.url}") def on_response(resp): if "api" in resp.url and resp.request.method in ("POST", "GET"): network_log.append((resp.url, resp.status)) print(f"RESPONSE {resp.status} {resp.url}") page.on("request", on_request) page.on("response", on_response) # Capture page errors def on_error(err): print(f"PAGE ERROR: {err}") page.on("pageerror", on_error) await fill_form(page) # Click submit await page.click("#btn-next") # Wait up to 15s for either a redirect or an error message for i in range(15): await page.wait_for_timeout(1000) url = page.url print(f" t+{i+1}s url={url[:80]}") # Check for error message err_el = await page.query_selector("#submit-status") if err_el: txt = await err_el.inner_text() cls = await err_el.get_attribute("class") if txt and txt.strip() not in ("", "Placing your order...", "Creating your order...", "Redirecting to payment..."): print(f"STATUS TEXT: {txt}") print(f"STATUS CLASS: {cls}") if "checkout.stripe.com" in url or "success" in url: print(f"SUCCESS: redirected to {url[:80]}") break if i == 14: print("TIMEOUT: still on order page after 15s") # Dump button state btn_text = await page.evaluate("() => document.getElementById('btn-next')?.innerHTML || 'not found'") btn_disabled = await page.is_disabled("#btn-next") status_text = await page.evaluate("() => document.getElementById('submit-status')?.textContent || 'not found'") print(f"BTN TEXT: {btn_text[:100]}") print(f"BTN DISABLED: {btn_disabled}") print(f"STATUS TEXT: {status_text}") # Try to read sessionStorage identity ss = await page.evaluate("() => ({identity: sessionStorage.getItem('pw_identity_session'), result: window._identityResult || 'unknown'})") print(f"SESSION STORAGE: {ss}") # Dump network log print(f"NETWORK LOG ({len(network_log)} requests):") for url2, code in network_log: print(f" {code} {url2}") # Take screenshot await page.screenshot(path="/tmp/debug_submit.png") print("Screenshot: /tmp/debug_submit.png") await browser.close() asyncio.run(main())