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¶
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:
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¶
-
Same Window Context: By preventing the popup and navigating directly, everything stays in the same page context that Playwright MCP can control
-
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
-
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-secretswith 600 permissions) - Never commit credentials to git
- Use environment variables or secure credential stores
Limitations¶
- Only works with window.open popups - doesn't help with:
- New tabs opened by browser (Ctrl+Click, etc.)
- Windows opened by extensions
-
Native dialogs
-
Must intercept before popup opens - if popup already opened, this won't help
-
Site-specific - some sites may detect and block this approach
-
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. - KlingAI specifically uses
storagerelay://for OAuth redirect, which breaks this workaround - The OAuth completes on Google's side but cannot redirect back to KlingAI
-
Authentication succeeds but the session isn't established in the browser
-
Complex OAuth flows - Multiple redirects or multi-step OAuth may need additional handling
Alternative Approaches (Not Recommended)¶
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.