Skip to content

feat: Add --chrome-port option for auto-discovery of existing Chrome instances #47

@szymdzum

Description

@szymdzum

Summary

Add a --chrome-port option to automatically discover and connect to an existing Chrome instance without requiring manual WebSocket URL retrieval.

Current Behavior

Users must manually retrieve the WebSocket URL from Chrome's debugging endpoint:

# Step 1: Get WebSocket URL
WS_URL=$(curl -s http://localhost:9222/json | jq -r '.[0].webSocketDebuggerUrl')

# Step 2: Connect bdg
bdg example.com --chrome-ws-url "$WS_URL"

Proposed Enhancement

Add --chrome-port option for single-step connection:

bdg example.com --chrome-port 9222

This would:

  1. Query http://localhost:9222/json/list for available targets
  2. Select appropriate page target (see selection logic below)
  3. Extract webSocketDebuggerUrl
  4. Connect to the target (same flow as --chrome-ws-url)

Implementation Details

1. CLI Option

File: src/commands/start.ts

.addOption(
  new Option('--chrome-port <port>', 'Auto-discover and connect to existing Chrome on port')
    .argParser(parseInt)
    .conflicts(['chromeWsUrl'])  // Mutually exclusive with --chrome-ws-url
)

2. Configuration

File: src/daemon/worker/types.ts

export interface WorkerConfig {
  // ... existing fields
  chromeWsUrl?: string;
  chromePort?: number;  // NEW
}

3. Chrome Connection Logic

File: src/daemon/lifecycle/chromeConnection.ts

export async function setupChromeConnection(
  config: WorkerConfig,
  telemetryStore: TelemetryStore,
  log: Logger
): Promise<LaunchedChrome | null> {
  // NEW: Auto-discovery flow
  if (config.chromePort) {
    return setupChromeByPort(config, telemetryStore, log);
  }
  
  // Existing: Direct WebSocket URL
  if (config.chromeWsUrl) {
    return setupExternalChrome(config, telemetryStore);
  }
  
  // Existing: Launch Chrome
  return setupLaunchedChrome(config, telemetryStore, log);
}

4. New Function: setupChromeByPort

async function setupChromeByPort(
  config: WorkerConfig,
  telemetryStore: TelemetryStore,
  log: Logger
): Promise<LaunchedChrome | null> {
  const port = config.chromePort!;
  
  // Fetch available targets
  const targets = await fetchCDPTargets(port);
  
  // Select target (see selection logic below)
  const target = selectTarget(targets, config.targetUrl);
  
  if (!target) {
    throw new CommandError(
      `No suitable Chrome target found on port ${port}`,
      { 
        suggestion: 'Ensure Chrome is running with --remote-debugging-port and has a page open',
        details: `Found ${targets.length} target(s) but none matched criteria`
      },
      EXIT_CODES.RESOURCE_NOT_FOUND
    );
  }
  
  log.info(`Connecting to existing Chrome target: ${target.title}`);
  
  // Use existing external Chrome setup with discovered WebSocket URL
  return setupExternalChrome(
    { ...config, chromeWsUrl: target.webSocketDebuggerUrl },
    telemetryStore
  );
}

5. Target Selection Logic

Strategy: Prefer exact URL match, fallback to first page

function selectTarget(
  targets: CDPTarget[],
  targetUrl?: string
): CDPTarget | undefined {
  // Filter to page targets only (exclude service workers, extensions, etc.)
  const pages = targets.filter(t => t.type === 'page');
  
  if (pages.length === 0) {
    return undefined;
  }
  
  // If targetUrl specified, try to match
  if (targetUrl) {
    const normalizedTarget = normalizeUrl(targetUrl);
    const match = pages.find(page => 
      page.url.includes(normalizedTarget) || 
      normalizedTarget.includes(page.url)
    );
    if (match) return match;
  }
  
  // Fallback: Return first page target
  return pages[0];
}

User Workflows

Workflow 1: Connect to First Available Tab

# Chrome already running with debugging on port 9222
bdg example.com --chrome-port 9222

Workflow 2: Connect to Specific Tab by URL

# If multiple tabs open, bdg will prefer tab matching the target URL
bdg github.com --chrome-port 9222  # Connects to GitHub tab if open

Workflow 3: Chrome in Docker

# Launch Chrome container
docker run -d -p 9222:9222 browserless/chrome \
  --remote-debugging-port=9222 \
  --remote-debugging-address=0.0.0.0

# Connect bdg with auto-discovery
bdg example.com --chrome-port 9222

Error Handling

No Targets Available

Error: No suitable Chrome target found on port 9222
Suggestion: Ensure Chrome is running with --remote-debugging-port and has a page open
Details: Found 0 target(s) but none matched criteria
Exit Code: 83 (RESOURCE_NOT_FOUND)

Connection Failed

Error: Failed to connect to Chrome on port 9222
Suggestion: Check if Chrome is running and port is accessible
Caused by: ECONNREFUSED
Exit Code: 101 (CDP_CONNECTION_FAILURE)

Multiple Matches (Future Enhancement)

If multiple tabs match the target URL, could add --interactive mode to let user select.

Testing Strategy

Unit Tests

  • Target selection logic with various scenarios
  • URL matching behavior
  • Error cases (no targets, connection failure)

Integration Tests

  • Connect to Chrome launched separately
  • Navigate and collect telemetry
  • Verify session cleanup doesn't kill external Chrome

Manual Testing

# Terminal 1: Launch Chrome with debugging
google-chrome --remote-debugging-port=9222 --user-data-dir=/tmp/chrome

# Terminal 2: Test bdg connection
bdg localhost:3000 --chrome-port 9222
bdg peek
bdg stop

Benefits

  • Single-step connection - No manual URL retrieval
  • Simpler UX - Port number more intuitive than WebSocket URL
  • Backward compatible - --chrome-ws-url still works for advanced use cases
  • Scriptable - Easier to automate in CI/CD pipelines

Drawbacks

  • Less explicit - Users don't see which tab was selected
  • Ambiguity - Multiple tabs require selection heuristics
  • Added complexity - More code paths in connection logic

Alternatives Considered

1. Keep Only --chrome-ws-url

Pros: Simple, explicit, already works
Cons: Requires manual URL discovery (two-step process)

2. Add bdg discover Command First

Pros: Helps users find WebSocket URLs without changing connection flow
Cons: Still requires two steps

3. Support Both --chrome-port and bdg discover

Pros: Covers both convenience and explicit control
Cons: More surface area to maintain

Recommendation

Implement --chrome-port as described above. It provides the best balance of convenience and control while maintaining backward compatibility.

Related

  • Existing implementation: --chrome-ws-url (src/daemon/lifecycle/chromeConnection.ts:27-70)
  • Target discovery: fetchCDPTargets() already exists in src/utils/http.ts
  • Documentation: Update docs/CLI_REFERENCE.md and docs/DOCKER.md with new option

Acceptance Criteria

  • --chrome-port option added to start command
  • Auto-discovery connects to appropriate target
  • Error messages guide users when connection fails
  • --chrome-port and --chrome-ws-url are mutually exclusive
  • Session cleanup doesn't kill external Chrome
  • Documentation updated (CLI_REFERENCE.md, README.md, CLAUDE.md)
  • Tests added for target selection logic
  • Integration test with externally launched Chrome

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions