Skip to content

[Bug]: INTERCEPT strategy commands never capture data — generated JS not executed as IIFE #98

@Astro-Han

Description

@Astro-Han

Description

All commands using Strategy.INTERCEPT (search, notifications, followers, following) fail to capture API responses. The XHR/fetch interceptor is never actually installed because the generated JavaScript is never executed.

Root Cause

1. Arrow function expression not wrapped as IIFE

installInterceptor() and getInterceptedRequests() in src/browser/page.ts:261-279 call sendCommand('exec', ...) directly, bypassing wrapForEval().

The generated JS from src/interceptor.ts is a bare arrow function expression (() => { ... }), not an IIFE. When CDP Runtime.evaluate receives a bare function expression, it evaluates it as a function object but never executes the function body. The monkey-patch is never installed.

In contrast, page.evaluate() wraps code through wrapForEval() (page.ts:290-304) which detects arrow functions and wraps them as IIFEs: (() => { ... })().

The YAML pipeline's intercept step (src/pipeline/steps/intercept.ts:19) correctly uses page.evaluate() and is NOT affected.

2. Regression introduced by daemon migration

Git history confirms this is a regression from commit b2fa7da ("replace @playwright/mcp with lightweight daemon + Chrome Extension"). Before that commit, installInterceptor used this.evaluate() (with wrapping). The migration changed it to direct sendCommand(), losing the wrapForEval wrapping.

Commit Method
68840fc / cfad003 (Playwright era) this.evaluate(generateInterceptorJs(...)) — correct
b2fa7da (daemon migration) and after sendCommand('exec', { code: ... }) — broken

3. No wrapping anywhere in the chain

The daemon (daemon.ts:120) transparently forwards the command. The extension (background.ts:176) passes cmd.code directly to cdp.evaluateAsync(). CDP (cdp.ts:41) passes it directly to Runtime.evaluate. Zero wrapping at any layer.

4. Additional bug in followers.ts / following.ts

followers.ts:61 filters intercepted data with r?.url?.includes('Followers'), but getInterceptedRequests() returns response body JSON (from response.clone().json()), NOT request objects with a url field. Even after fixing the IIFE issue, this filter would discard all captured data. following.ts has the same problem.

Affected commands

Suggested Fix

page.ts — change both methods to use this.evaluate():

async installInterceptor(pattern: string): Promise<void> {
    const { generateInterceptorJs } = await import('../interceptor.js');
    await this.evaluate(generateInterceptorJs(JSON.stringify(pattern), {
      arrayName: '__opencli_xhr',
      patchGuard: '__opencli_interceptor_patched',
    }));
}

async getInterceptedRequests(): Promise<any[]> {
    const { generateReadInterceptedJs } = await import('../interceptor.js');
    const result = await this.evaluate(generateReadInterceptedJs('__opencli_xhr'));
    return (result as any[]) || [];
}

followers.ts / following.ts — remove the incorrect url.includes() filter (intercepted data is response JSON, not request objects).

Relationship to PR #91

PR #91 fixed a real timing issue (interceptor wiped by goto() navigation resetting JS context). That fix is necessary but insufficient — even with correct timing, the interceptor code was never being executed due to this IIFE bug. Both fixes are needed together.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions