Skip to content

Playwright MCP Popup Window Workaround

Problem

The Playwright MCP tools cannot interact with popup windows created via window.open(). When a website opens OAuth login in a popup (like KlingAI's Google OAuth), the tools can only see the parent page and cannot switch to or interact with the popup.

Failed Approaches: 1. ❌ Regular clicks on OAuth buttons - popup opens but is inaccessible 2. ❌ playwright_click_and_switch_tab - times out waiting for new page event 3. ❌ Fullscreen mode - doesn't prevent popup behavior 4. ❌ Alternative browsers (Firefox/WebKit) - not installed, wouldn't solve the issue anyway 5. ❌ Direct JavaScript popup access - blocked by Cross-Origin policies

Solution: URL Interception & Direct Navigation

Instead of trying to access the popup, intercept the URL before the popup opens and navigate to it directly in the same window.

Step-by-Step Implementation

1. Navigate to the Target Site

await playwright_navigate({
  url: "https://app.klingai.com",
  headless: false
});

2. Install window.open Interceptor

CRITICAL: This must be done BEFORE triggering any OAuth flow.

await playwright_evaluate({
  script: `
    (() => {
      // Capture the OAuth URL without actually opening popup
      window.capturedPopupUrl = null;
      const originalWindowOpen = window.open;

      window.open = function(url, target, features) {
        console.log('window.open intercepted. URL:', url);
        window.capturedPopupUrl = url;

        // Don't actually open the popup, just capture the URL
        // Return a fake window object to prevent errors
        return {
          closed: false,
          close: () => {},
          focus: () => {}
        };
      };

      return {
        success: true,
        message: 'URL capture interceptor installed'
      };
    })()
  `
});

What this does: - Overrides window.open to capture the URL parameter - Stores the URL in window.capturedPopupUrl - Returns a fake window object to prevent JavaScript errors - Prevents the actual popup from opening

3. Trigger the OAuth Flow

Click the button that would normally open the popup:

// Close any promotional popups first
await playwright_press_key({ key: "Escape" });
await sleep(2);

// Open login modal
await playwright_click({ selector: "text=Sign In" });
await sleep(3);

// Click Google OAuth button (popup is intercepted)
await playwright_click({ selector: "text=Sign in with Google" });
await sleep(2);

4. Retrieve the Captured URL

const result = await playwright_evaluate({
  script: `
    (() => {
      return {
        capturedUrl: window.capturedPopupUrl,
        urlCaptured: !!window.capturedPopupUrl
      };
    })()
  `
});

// Result contains the OAuth URL:
// "https://accounts.google.com/o/oauth2/v2/auth?client_id=..."

5. Navigate Directly to the OAuth URL

Now the OAuth page loads in the same window context where Playwright tools work normally:

await playwright_navigate({
  url: result.capturedUrl,
  headless: false
});

6. Complete OAuth Flow Normally

All standard Playwright interactions now work:

// Fill email
await playwright_fill({
  selector: "input[type='email']",
  value: "your-email@gmail.com"
});

await playwright_click({ selector: "button:has-text('Next')" });
await sleep(4);

// Fill password
await playwright_fill({
  selector: "input[type='password']",
  value: "your-password"
});

await playwright_click({ selector: "button:has-text('Next')" });
await sleep(5);

// OAuth redirects back to the original site automatically

Why This Works

  1. Same Window Context: By preventing the popup and navigating directly, everything stays in the same page context that Playwright MCP can control

  2. OAuth Still Functions: OAuth doesn't care whether it's in a popup or main window - it still redirects back correctly with the auth token

  3. No Multi-Context Needed: Bypasses the fundamental limitation of Playwright MCP tools (single page context only)

KlingAI Specific Example

Complete Automated Login Flow

// 1. Navigate and install interceptor
await playwright_navigate({ url: "https://app.klingai.com", headless: false });
await sleep(5);

await playwright_evaluate({
  script: `
    (() => {
      window.capturedPopupUrl = null;
      const originalWindowOpen = window.open;
      window.open = function(url, target, features) {
        console.log('window.open intercepted. URL:', url);
        window.capturedPopupUrl = url;
        return { closed: false, close: () => {}, focus: () => {} };
      };
      return { success: true };
    })()
  `
});

// 2. Close promotional popup
await playwright_press_key({ key: "Escape" });
await sleep(2);

// 3. Open login modal
await playwright_click({ selector: "text=Sign In" });
await sleep(3);

// 4. Trigger Google OAuth (intercepted)
await playwright_click({ selector: "text=Sign in with Google" });
await sleep(2);

// 5. Get captured URL
const result = await playwright_evaluate({
  script: `(() => ({ capturedUrl: window.capturedPopupUrl }))()`
});

// 6. Navigate to OAuth page
await playwright_navigate({ url: result.capturedUrl, headless: false });
await sleep(5);

// 7. Complete Google login
await playwright_fill({
  selector: "input[type='email']",
  value: "codiedev42@gmail.com"
});
await playwright_click({ selector: "button:has-text('Next')" });
await sleep(4);

await playwright_fill({
  selector: "input[type='password']",
  value: "brKNsnjWa4wRSSwcomnpM19S94mjOlOM"
});
await playwright_click({ selector: "button:has-text('Next')" });
await sleep(5);

// 8. Handle consent screen if appears
// OAuth will redirect back to KlingAI with auth token

Key Considerations

Timing

  • Add sufficient sleep delays after navigation and clicks
  • OAuth pages can be slow to load
  • Wait longer for password verification (5+ seconds)

Session Timeouts

  • Google OAuth sessions can timeout if there's too much delay
  • If you see "Your session ended because there was no activity", restart the flow
  • Keep the entire OAuth flow under 2 minutes

Error Handling

  • Check for "Wrong password" errors
  • Handle consent screens that may appear
  • Detect when OAuth redirect completes

Security

  • Store credentials securely (e.g., ~/.nutrie-secrets with 600 permissions)
  • Never commit credentials to git
  • Use environment variables or secure credential stores

Limitations

  1. Only works with window.open popups - doesn't help with:
  2. New tabs opened by browser (Ctrl+Click, etc.)
  3. Windows opened by extensions
  4. Native dialogs

  5. Must intercept before popup opens - if popup already opened, this won't help

  6. Site-specific - some sites may detect and block this approach

  7. Special redirect protocols - CRITICAL LIMITATION: OAuth flows using special redirect protocols like storagerelay://, chrome-extension://, or custom URL schemes will fail. These protocols require popup window communication via postMessage and won't work with direct navigation.

  8. KlingAI specifically uses storagerelay:// for OAuth redirect, which breaks this workaround
  9. The OAuth completes on Google's side but cannot redirect back to KlingAI
  10. Authentication succeeds but the session isn't established in the browser

  11. Complex OAuth flows - Multiple redirects or multi-step OAuth may need additional handling

Why not use Puppeteer?

  • Would require different MCP server (not available by default)
  • Same multi-context limitation exists in most MCP implementations

Why not use Selenium?

  • Even heavier than Playwright
  • No MCP server readily available
  • Doesn't fundamentally solve the multi-context problem

Why not manually complete OAuth?

  • Defeats the purpose of automation
  • Not scalable
  • Requires human intervention

Conclusion

This workaround successfully automates OAuth flows that use popup windows by: 1. Intercepting the popup URL before it opens 2. Navigating to the URL directly in the same window 3. Completing OAuth in a context where Playwright tools work normally

This is the only reliable way to handle popup-based OAuth with Playwright MCP tools until they add proper multi-context support.