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>
204 lines
8.9 KiB
Python
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())
|