new-site/scripts/tests/debug_submit.py
justin f8cd37ac8c Initial commit — Performance West telecom compliance platform
Includes: API (Express/TypeScript), Astro site, Python workers,
document generators, FCC compliance tools, Canada CRTC formation,
Ansible infrastructure, and deployment scripts.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-27 06:54:22 -05:00

204 lines
8.9 KiB
Python

"""
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())