A browser test that passes until a login popup, payment handoff, or SSO redirect opens a second tab is rarely “just flaky.” It usually means one of four things broke: the test kept talking to the wrong window, the app lost state during navigation, storage was isolated differently than the author expected, or the redirect happened before the page finished wiring itself back together.

If your browser tests fail on popup redirect, start by treating the failure as a state-transfer problem, not a locator problem. The UI on the original page is often fine. The bug is the handoff between pages.

The short version

When a flow crosses windows or tabs, inspect these first:

  1. Window handles or page references, did your test switch to the new page at the right moment?
  2. Storage and cookies, did login state survive the redirect, and is it shared the way you assumed?
  3. Opener behavior, did the popup lose access to the parent because of rel=noopener, browser policy, or framework behavior?
  4. Redirect timing, did your assertion run before the callback page finished setting state?
  5. Popup blocker conditions, did the click really open a window, or did the browser suppress it because the action was not trusted?

In multi-window flows, the first failure is often not the app result, it is the test losing the conversation with the right page.

Know the difference: popup, new window, and cross-tab redirect

These terms are easy to mix together, but they fail differently.

  • Popup, a browser window opened by script or user action, often used for OAuth, SSO, payment, or consent flows.
  • New window / new tab, a separate top-level browsing context. The automation API may expose it as another page, tab, or window handle.
  • Cross-tab redirect, the browser navigates to a new origin or returns from an external site, but the test still needs to recover state afterward.

The debugging path changes depending on which one you have. For example, a popup may be blocked if the click is not considered user-initiated, while a cross-tab redirect can succeed but still drop local storage or rely on a cookie that is not visible after the return.

First isolate the failure class

Before changing test code, answer three questions from logs or screenshots:

  • Did the new page open?
  • Did the browser stay on the intended page after the redirect?
  • Did the app state disappear, or did the test simply stop reading from the right context?

That distinction saves hours.

Decision table

Symptom Likely cause First thing to inspect What it points to
Click does nothing, no popup appears Popup blocked or click not trusted Browser console, user gesture path Test bug or app event wiring bug
Popup opens, but assertions run on old page Window handle / page reference not switched Active tab or page object after click Test bug
Popup returns to app, session lost Storage or cookie scope mismatch Cookies, local storage, session storage App auth flow or domain isolation issue
Redirect succeeds only with slow runs Timing race after navigation Wait conditions, event hooks Test bug or brittle app lifecycle
Works in one browser, fails in another Browser policy or opener behavior difference noopener, third-party cookie policy, window naming Compatibility issue or app design issue

Step 1, verify you are in the right window

A surprising number of failures happen because the test keeps using the original page object after the popup opens. That is especially easy to miss when the old page remains visible behind the modal or when the popup launches and closes quickly.

Playwright: wait for the popup and capture the new page

Use the framework’s page event rather than guessing timing.

const popupPromise = page.waitForEvent('popup');
await page.getByRole('button', { name: 'Continue with SSO' }).click();
const popup = await popupPromise;
await popup.waitForLoadState('domcontentloaded');

Then confirm you are asserting against popup, not page.

If the popup opens a redirect chain, wait for a stable milestone on the final page, not just the first navigation event. The Playwright docs cover page events, contexts, and navigation waiting patterns that are useful here.

Selenium: capture the new handle explicitly

With Selenium, the browser may expose multiple handles and you need to switch deliberately.

handles_before = driver.window_handles
button.click()
WebDriverWait(driver, 10).until(lambda d: len(d.window_handles) > len(handles_before))
new_handle = [h for h in driver.window_handles if h not in handles_before][0]
driver.switch_to.window(new_handle)

The Selenium documentation is the right reference for browser windows, waits, and switching context, see the Selenium docs.

If your test fails with “element not found” right after a popup opens, first ask whether the test is still attached to the original page.

Step 2, inspect storage and cookies after the handoff

When login or payment state disappears after a redirect, the application may have successfully authenticated but failed to transfer the result back to the original origin. In browser automation, that often shows up as missing cookies, unexpected storage resets, or session data that exists in one page but not another.

Check these items in both the parent page and the popup or returning tab:

  • Cookies, especially domain, path, SameSite, and secure flags
  • Local storage, if the app uses client-side tokens or transient flags
  • Session storage, which is scoped to the top-level browsing context
  • Origin, because storage is origin-bound and not automatically shared across sites

A useful debugging move is to print the active storage state before and after the redirect. If the auth token appears in the popup but not in the parent page, the app may be relying on storage that does not survive the boundary the way the team assumed.

A quick diagnostic checklist

  • Is the callback page on the same origin as the app shell?
  • Does the app expect window.opener to exist so it can pass state back?
  • Are cookies marked SameSite=Lax or SameSite=None; Secure as appropriate for the flow?
  • Does the redirect use a different subdomain, which can change cookie reachability?

If the app depends on cookies for post-login state, validate the cookie attributes first. A redirect that looks correct in a local run can fail in CI if the browser or environment changes how third-party or cross-site cookies are handled.

Step 3, check window.opener and popup communication

Many popup flows use the parent window as a coordination point. The popup completes a login or consent step, then sends data back to the original tab through window.opener, postMessage, or a shared redirect URL.

A failure here can look like a test bug, but the root cause may be a browser security decision or a deliberate app setting such as rel="noopener".

What to verify

  • Does the popup need window.opener to call back into the parent?
  • Did the app set noopener, intentionally severing the opener relationship?
  • Does the page use postMessage and verify the target origin correctly?
  • Is the parent waiting for a message, but the popup redirected too quickly and never posted it?

If the app uses postMessage, make sure the test waits for the message delivery rather than asserting immediately after the popup closes. That is a classic timing trap.

Step 4, separate redirect timing bugs from true state bugs

A test can fail because the app lost state, or because the test asserted one event too early.

Watch for these timing patterns:

  • The popup navigates, but the callback script runs after your test already checked the parent page
  • The browser closes the popup before the test listens for the return signal
  • The app sets a cookie asynchronously, but the next page load reads the old session
  • The test waits for load, but the app renders usable UI before or after that event depending on the route

The fix is usually not a bigger sleep. Use a condition that matches the business event you care about, for example “callback cookie exists,” “auth banner disappears,” or “payment return page displays order number.”

Better than sleeping, wait for a business signal

await expect(page.getByText('Signed in')).toBeVisible();

That is better than waitForTimeout, because it ties the test to the state you actually need.

Step 5, reproduce with the simplest possible redirect chain

When the failure only happens in CI or only in one browser, reduce the flow to a minimal case:

  1. Open the app page.
  2. Click the control that opens the popup or new tab.
  3. Record the number and identity of window handles.
  4. Verify the popup URL and final callback URL.
  5. Read cookies or storage after the handoff.

If the minimal repro passes, the bug may live in the larger flow, not the popup mechanism itself. If it fails, you now have a smaller surface area for a browser bug report, app bug report, or test fix.

How to tell app bug from test bug

Use this decision tree.

It is probably a test bug if:

  • The test never switches to the new page
  • Assertions run against the wrong window handle
  • The test depends on fixed sleep intervals
  • The test assumes the popup always returns on the same origin
  • The test is brittle across browsers because it hard-codes focus or tab order

It is probably an app bug if:

  • The popup opens but never posts its result back
  • The callback URL is correct but session state is missing
  • The same flow fails manually in the browser without automation
  • The app assumes opener access that the browser no longer guarantees
  • The flow depends on storage behavior that differs by browser policy or site isolation

It may be a browser compatibility issue if:

  • One browser works and another consistently fails
  • The failure depends on third-party cookies, cross-site redirects, or opener restrictions
  • The failure appears only with specific browser versions or privacy settings

That last case deserves a real browser matrix, not a guess.

A practical debug harness to keep around

A tiny amount of reusable instrumentation can shorten the next investigation:

  • Log current URL and title before and after the popup opens
  • Log the full list of window handles or pages
  • Capture cookies and storage snapshots at each boundary
  • Record whether the popup closed itself or the parent closed it
  • Save the redirect URL chain in CI artifacts

Even a simple trace of those values will show whether the test lost context, the redirect never completed, or the app dropped state at the boundary.

Not the best fit if you need a single-page flow only

This guide is for flows that cross top-level browsing contexts. If your test never leaves one tab, the failure mode is usually different, focus on stale elements, network waits, animation timing, or bad locators instead.

Likewise, if your team already has a stable browser grid and consistent repro data, you may not need to debug the multi-window layer first. Start where the state transfer actually occurs.

Useful references

FAQ

Why do browser tests fail on popup redirect even when the popup opens?

Because opening the popup is only the first step. The test may still be attached to the wrong page, the callback may not have finished, or the app may have lost session state during the return path.

How do I debug window handles in Playwright and Selenium?

In Playwright, wait for the popup event and keep the returned page object. In Selenium, compare window_handles, switch to the new handle, and confirm you are asserting against the correct window.

What should I inspect first when cross-tab state testing fails?

Check cookies, local storage, session storage, the current origin, and whether the test has switched into the new browsing context. Those four items usually reveal the problem quickly.

Can popup blocker test failures be caused by the test itself?

Yes. If the click is not treated as a real user gesture, the browser may suppress the popup. A missing event chain or incorrect interaction can look like a browser bug when it is really a test setup problem.

When is a redirect problem more likely an app bug than an automation bug?

If the same flow fails manually in the browser, or the popup returns but the app never restores session state, the app probably has a state transfer problem rather than an automation issue.

Should I use fixed waits for popup redirects?

Usually no. Fixed waits hide the real timing issue and make the test slower and less reliable. Prefer waits tied to a visible business state, a message event, or the appearance of the expected cookie or URL.