--- title: "Discord Bot Browser Testing via Playwright + CDP" description: "Step-by-step workflow for automated Discord bot testing using Playwright connected to Brave browser via Chrome DevTools Protocol. Covers setup, slash command execution, and screenshot capture." type: runbook domain: paper-dynasty tags: [paper-dynasty, discord, testing, playwright, automation] --- # Discord Bot Browser Testing via Playwright + CDP Automated testing of Paper Dynasty Discord bot commands by connecting Playwright to a running Brave browser instance with Discord open. ## Prerequisites - Brave browser installed (`brave-browser-stable`) - Playwright installed (`pip install playwright && playwright install chromium`) - Discord logged in via browser (not desktop app) - Discord bot running (locally via docker-compose or on remote host) - Bot's `API_TOKEN` must match the target API environment ## Setup ### 1. Launch Brave with CDP enabled Brave must be started with `--remote-debugging-port`. If Brave is already running, **kill it first** — otherwise the flag is ignored and the new process merges into the existing one. ```bash killall brave && sleep 2 && brave-browser-stable --remote-debugging-port=9222 & ``` ### 2. Verify CDP is responding ```bash curl -s http://localhost:9222/json/version | python3 -m json.tool ``` Should return JSON with `Browser`, `webSocketDebuggerUrl`, etc. ### 3. Open Discord in browser Navigate to `https://discord.com/channels//` in Brave. **Paper Dynasty test server:** - Server: Cals Test Server (`669356687294988350`) - Channel: #pd-game-test (`982850262903451658`) - URL: `https://discord.com/channels/669356687294988350/982850262903451658` ### 4. Verify bot is running with correct API token ```bash # Check docker-compose.yml has the right API_TOKEN for the target environment grep API_TOKEN /mnt/NV2/Development/paper-dynasty/discord-app/docker-compose.yml # Dev API token lives on the dev host: ssh pd-database "docker exec sba_postgres psql -U sba_admin -d paperdynasty_dev -c \"SELECT 1;\"" # Restart bot if token was changed: cd /mnt/NV2/Development/paper-dynasty/discord-app && docker compose up -d ``` ## Running Commands ### Find the Discord tab ```python from playwright.sync_api import sync_playwright import time with sync_playwright() as p: browser = p.chromium.connect_over_cdp('http://localhost:9222') for ctx in browser.contexts: for page in ctx.pages: if 'discord' in page.url.lower(): print(f'Found: {page.url}') break browser.close() ``` ### Execute a slash command and capture result ```python from playwright.sync_api import sync_playwright import time def run_slash_command(command: str, wait_seconds: int = 5, screenshot_path: str = '/tmp/discord_result.png'): """ Type a slash command in Discord, select the top autocomplete option, submit it, wait for the bot response, and take a screenshot. """ with sync_playwright() as p: browser = p.chromium.connect_over_cdp('http://localhost:9222') for ctx in browser.contexts: for page in ctx.pages: if 'discord' in page.url.lower(): msg_box = page.locator('[role="textbox"][data-slate-editor="true"]') msg_box.click() time.sleep(0.3) # Type the command (delay simulates human typing for autocomplete) msg_box.type(command, delay=80) time.sleep(2) # Tab selects the top autocomplete option page.keyboard.press('Tab') time.sleep(1) # Enter submits the command page.keyboard.press('Enter') time.sleep(wait_seconds) page.screenshot(path=screenshot_path) print(f'Screenshot saved to {screenshot_path}') break browser.close() # Example usage: run_slash_command('/refractor status') ``` ### Commands with parameters After pressing Tab to select the command, Discord shows an options panel. To fill parameters: 1. The first parameter input is auto-focused after Tab 2. Type the value, then Tab to move to the next parameter 3. Press Enter when ready to submit ```python # Example: /refractor status with tier filter msg_box.type('/refractor status', delay=80) time.sleep(2) page.keyboard.press('Tab') # Select command from autocomplete time.sleep(1) # Now fill parameters if needed, or just submit page.keyboard.press('Enter') ``` ## Key Selectors | Element | Selector | |---------|----------| | Message input box | `[role="textbox"][data-slate-editor="true"]` | | Autocomplete popup | `[class*="autocomplete"]` | ## Gotchas - **Brave must be killed before relaunch** — if an instance is already running, `--remote-debugging-port` is silently ignored - **Bot token mismatch** — the bot's `API_TOKEN` in `docker-compose.yml` must match the target API (dev or prod). Symptoms: `{"detail":"Unauthorized"}` in bot logs - **Viewport is None** — when connecting via CDP, `page.viewport_size` returns None. Use `page.evaluate('() => ({w: window.innerWidth, h: window.innerHeight})')` instead - **Autocomplete timing** — typing too fast may not trigger Discord's autocomplete. The `delay=80` on `msg_box.type()` simulates human speed - **Multiple bots** — if multiple bots register the same slash command (e.g. MantiTestBot and PucklTestBot), Tab selects the top option. Verify the correct bot name in the autocomplete popup before proceeding ## Test Plan Reference The Refractor integration test plan is at: `discord-app/tests/refractor-integration-test-plan.md` Key test case groups: - REF-01 to REF-06: Tier badges and display - REF-10 to REF-15: Progress bars and filtering - REF-40 to REF-42: Cross-command badges (card, roster) - REF-70 to REF-72: Cross-command badge propagation (the current priority) ## Verified On - **Date:** 2026-04-06 - **Browser:** Brave 146.0.7680.178 (Chromium-based) - **Playwright:** Node.js driver via Python sync API - **Bot:** MantiTestBot on Cals Test Server, #pd-game-test channel - **API:** pddev.manticorum.com (dev environment)