---
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/<server_id>/<channel_id>` 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)
