Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions packages/web-integration/src/playwright/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import type { WebPageAgentOpt } from '@/web-element';
import { getDebug } from '@midscene/shared/logger';
import semver from 'semver';
import {
autoSwitchToNewTab,
forceChromeSelectRendering as applyChromeSelectRendering,
forceClosePopup,
} from '../puppeteer/base-page';
Expand Down Expand Up @@ -46,6 +47,8 @@ export class PlaywrightAgent extends PageAgent<PlaywrightWebPage> {

if (forceSameTabNavigation) {
forceClosePopup(page, debug);
} else {
autoSwitchToNewTab(page, webPage, debug);
}

if (forceChromeSelectRendering) {
Expand Down
49 changes: 49 additions & 0 deletions packages/web-integration/src/puppeteer/base-page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -780,6 +780,55 @@ export function forceClosePopup(
});
}

/**
* When forceSameTabNavigation is false, automatically switch the agent's
* underlying page reference to newly opened tabs so that subsequent actions
* operate on the new tab instead of the original one.
*/
export function autoSwitchToNewTab(
page: PuppeteerPage | PlaywrightPage,
webPage: Page<any, any>,
debugProfile: DebugFunction,
) {
page.on('popup', async (popup) => {
if (!popup) {
console.warn(
'[midscene] got a popup event, but the popup is not ready yet, skip',
);
return;
}

try {
const popupPage = popup as PuppeteerPage;
if (popupPage.isClosed()) {
debugProfile('popup is already closed, skip switching');
return;
}

const url = popupPage.url();
debugProfile(`New tab detected: ${url}, switching to it`);

// Update the underlying page reference to the new tab
webPage.underlyingPage = popup as any;

// Bring the new tab to the front
await popupPage.bringToFront();

// Recursively listen for popups on the new tab as well
autoSwitchToNewTab(
popup as PuppeteerPage | PlaywrightPage,
webPage,
debugProfile,
);
} catch (error) {
debugProfile(`failed to switch to new tab: ${error}`);
console.warn(
`[midscene:warning] Failed to switch to newly opened tab: ${error}`,
);
}
});
}

/**
* Force Chrome to render select elements using base-select appearance instead of OS-native rendering.
* This makes select elements visible in screenshots captured by Playwright/Puppeteer.
Expand Down
3 changes: 3 additions & 0 deletions packages/web-integration/src/puppeteer/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type { Page as PuppeteerPage } from 'puppeteer';
import semver from 'semver';
import { getWebpackRequire } from '../utils';
import {
autoSwitchToNewTab,
forceChromeSelectRendering as applyChromeSelectRendering,
forceClosePopup,
} from './base-page';
Expand Down Expand Up @@ -43,6 +44,8 @@ export class PuppeteerAgent extends PageAgent<PuppeteerWebPage> {

if (forceSameTabNavigation) {
forceClosePopup(page, debug);
} else {
autoSwitchToNewTab(page, webPage, debug);
}

if (forceChromeSelectRendering) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ describe(
() => {
const ctx = createTestContext();

it('not tracking active tab', async () => {
it('auto switch to new tab', async () => {
const htmlPath = getFixturePath('tab-navigation.html');
const { originPage, reset } = await launchPage(`file://${htmlPath}`);
ctx.resetFn = reset;
Expand All @@ -25,11 +25,9 @@ describe(
});
await sleep(3000);

// When forceSameTabNavigation is false, the agent should NOT follow the new tab
// So the weather forecast (which appears in the new tab) should NOT be visible
await expect(async () => {
await ctx.agent!.aiAssert('There is a weather forecast in the page');
}).rejects.toThrowError();
// When forceSameTabNavigation is false, the agent should automatically
// switch to the newly opened tab so subsequent actions operate on it
await ctx.agent.aiWaitFor('There is a weather forecast in the page');
});

it('tracking active tab', async () => {
Expand Down
Loading