Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
AlexKamaev committed Feb 8, 2022
1 parent f9a6786 commit 78d426a
Show file tree
Hide file tree
Showing 12 changed files with 108 additions and 7 deletions.
11 changes: 11 additions & 0 deletions src/browser/connection/gateway.ts
Expand Up @@ -64,6 +64,8 @@ export default class BrowserConnectionGateway {
this._dispatch('/browser/init-script/{id}', proxy, BrowserConnectionGateway._onInitScriptResponse, 'POST');
this._dispatch('/browser/active-window-id/{id}', proxy, BrowserConnectionGateway._onGetActiveWindowIdRequest);
this._dispatch('/browser/active-window-id/{id}', proxy, BrowserConnectionGateway._onSetActiveWindowIdRequest, 'POST');
this._dispatch('/browser/close-window/{id}', proxy, BrowserConnectionGateway._onCloseWindowRequest, 'POST');


proxy.GET(SERVICE_ROUTES.connect, (req: IncomingMessage, res: ServerResponse) => this._connectNextRemoteBrowser(req, res));
proxy.GET(SERVICE_ROUTES.connectWithTrailingSlash, (req: IncomingMessage, res: ServerResponse) => this._connectNextRemoteBrowser(req, res));
Expand Down Expand Up @@ -184,6 +186,15 @@ export default class BrowserConnectionGateway {
}
}

private static _onCloseWindowRequest (req: IncomingMessage, res: ServerResponse, connection: BrowserConnection): void {
if (BrowserConnectionGateway._ensureConnectionReady(res, connection)) {
connection.provider.closeBrowserChildWindow(connection.id)
.then(() => {
respondWithJSON(res);
});
}
}

private async _connectNextRemoteBrowser (req: IncomingMessage, res: ServerResponse): Promise<void> {
preventCaching(res);

Expand Down
2 changes: 2 additions & 0 deletions src/browser/connection/index.ts
Expand Up @@ -100,6 +100,7 @@ export default class BrowserConnection extends EventEmitter {
private readonly heartbeatUrl: string;
private readonly statusUrl: string;
public readonly activeWindowIdUrl: string;
public readonly closeWindowUrl: string;
private statusDoneUrl: string;
private readonly debugLogger: debug.Debugger;

Expand Down Expand Up @@ -156,6 +157,7 @@ export default class BrowserConnection extends EventEmitter {
this.statusRelativeUrl = `/browser/status/${this.id}`;
this.statusDoneRelativeUrl = `/browser/status-done/${this.id}`;
this.activeWindowIdUrl = `/browser/active-window-id/${this.id}`;
this.closeWindowUrl = `/browser/close-window/${this.id}`;

this.heartbeatUrl = `${gateway.domain}${this.heartbeatRelativeUrl}`;
this.statusUrl = `${gateway.domain}${this.statusRelativeUrl}`;
Expand Down
7 changes: 7 additions & 0 deletions src/browser/provider/built-in/dedicated/base.js
Expand Up @@ -128,4 +128,11 @@ export default {

return browserClient.switchToMainWindow();
},

async closeBrowserChildWindow (browserId) {
const runtimeInfo = this.openedBrowsers[browserId];
const browserClient = this._getBrowserProtocolClient(runtimeInfo);

return browserClient.closeBrowserChildWindow();
},
};
47 changes: 42 additions & 5 deletions src/browser/provider/built-in/dedicated/chrome/cdp-client/index.ts
Expand Up @@ -27,14 +27,25 @@ import ClientFunctionExecutor from './client-function-executor';
import { SwitchToIframeCommand } from '../../../../../../test-run/commands/actions';
import ExecutionContext from './execution-context';
import * as clientsManager from './clients-manager';
import delay from '../../../../../../utils/delay';

const DEBUG_SCOPE = (id: string): string => `testcafe:browser:provider:built-in:chrome:browser-client:${id}`;
const DOWNLOADS_DIR = path.join(os.homedir(), 'Downloads');

const debugLog = debug('testcafe:browser:provider:built-in:dedicated:chrome');

class ProtocolApiInfo {
public inactive: boolean;
public client: remoteChrome.ProtocolApi;

public constructor (client: remoteChrome.ProtocolApi) {
this.client = client;
this.inactive = false;
}
}

export class BrowserClient {
private _clients: Dictionary<remoteChrome.ProtocolApi> = {};
private _clients: Dictionary<ProtocolApiInfo> = {};
private _runtimeInfo: RuntimeInfo;
private readonly _proxyless: boolean;
private _parentTarget?: remoteChrome.TargetInfo;
Expand Down Expand Up @@ -92,7 +103,7 @@ export class BrowserClient {
const client = await remoteChrome({ target, port: this._runtimeInfo.cdpPort });
const { Page, Network, Runtime } = client;

this._clients[this._clientKey] = client;
this._clients[this._clientKey] = new ProtocolApiInfo(client);

await guardTimeExecution(
async () => await Page.enable(),
Expand Down Expand Up @@ -224,18 +235,33 @@ export class BrowserClient {
return !!this._parentTarget && this._config.headless;
}

public async setClientInactive (): Promise<void> {
// NOTE: ensure client exists
await this.getActiveClient();

const client = this._clients[this._clientKey];

if (client)
client.inactive = true;
}

public async getActiveClient (): Promise<remoteChrome.ProtocolApi | void> {
try {
if (!this._clients[this._clientKey])
this._clients[this._clientKey] = await this._createClient();
await this._createClient();
}
catch (err) {
debugLog(err);

return void 0;
}

return this._clients[this._clientKey];
const info = this._clients[this._clientKey];

if (info.inactive)
return void 0;

return info.client;
}

public async init (): Promise<void> {
Expand Down Expand Up @@ -273,8 +299,12 @@ export class BrowserClient {

const client = await this.getActiveClient();

if (!client)
if (!client) {
// NOTE: required for https://github.com/DevExpress/testcafe/issues/6037
await delay(0);

return Buffer.alloc(0);
}

if (fullPage) {
const { contentSize, visualViewport } = await client.Page.getLayoutMetrics();
Expand Down Expand Up @@ -387,4 +417,11 @@ export class BrowserClient {
public switchToMainWindow (): void {
ExecutionContext.switchToMainWindow();
}

public async closeBrowserChildWindow (): Promise<void> {
await this.setClientInactive();

// NOTE: delay browser window closing
await delay(100);
}
}
4 changes: 4 additions & 0 deletions src/browser/provider/index.ts
Expand Up @@ -442,4 +442,8 @@ export default class BrowserProvider {
public setActiveWindowId (browserId: string, val: string): void {
this.plugin.setActiveWindowId(browserId, val);
}

public async closeBrowserChildWindow (browserId: string): Promise<void> {
await this.plugin.closeBrowserChildWindow(browserId);
}
}
4 changes: 4 additions & 0 deletions src/browser/provider/plugin-host.js
Expand Up @@ -153,4 +153,8 @@ export default class BrowserProviderPluginHost {
getConfig (value) {
return value;
}

closeBrowserChildWindow (/*browserId*/) {
return Promise.resolve();
}
}
7 changes: 7 additions & 0 deletions src/client/browser/index.js
Expand Up @@ -172,3 +172,10 @@ export function setActiveWindowId (activeWindowIdUrl, createXHR, windowId) {
data: JSON.stringify({ windowId }), //eslint-disable-line no-restricted-globals
});
}

export function closeWindow (closeWindowUrl, createXHR, windowId) {
return sendXHR(closeWindowUrl, createXHR, {
method: 'POST',
data: JSON.stringify({ windowId }), //eslint-disable-line no-restricted-globals
});
}
9 changes: 8 additions & 1 deletion src/client/driver/driver.js
Expand Up @@ -168,6 +168,7 @@ export default class Driver extends serviceUtils.EventEmitter {
this.browserStatusUrl = communicationUrls.status;
this.browserStatusDoneUrl = communicationUrls.statusDone;
this.browserActiveWindowId = communicationUrls.activeWindowId;
this.browserCloseWindowUrl = communicationUrls.closeWindow;
this.userAgent = runInfo.userAgent;
this.fixtureName = runInfo.fixtureName;
this.testName = runInfo.testName;
Expand Down Expand Up @@ -633,11 +634,17 @@ export default class Driver extends serviceUtils.EventEmitter {
});
}

_closeWindowAndWait (childWindowToClose, msg) {
async _closeWindowAndWait (childWindowToClose, msg) {
const waitWindowForClose = this._createWaitForEventPromise(CHILD_WINDOW_CLOSED_EVENT, CHILD_WINDOW_CLOSED_EVENT_TIMEOUT);

childWindowToClose.ignoreMasterSwitching = !msg.isCurrentWindow;

if (!this.closing) {
this.closing = true;

await browser.closeWindow(this.browserCloseWindowUrl, hammerhead.createNativeXHR, this.windowId);
}

childWindowToClose.driverWindow.close();

return waitWindowForClose;
Expand Down
3 changes: 2 additions & 1 deletion src/client/test-run/index.js.mustache
Expand Up @@ -19,6 +19,7 @@
var browserStatusUrl = origin + {{{browserStatusRelativeUrl}}};
var browserStatusDoneUrl = origin + {{{browserStatusDoneRelativeUrl}}};
var browserActiveWindowIdUrl = origin + {{{browserActiveWindowIdUrl}}};
var browserCloseWindowUrl = origin + {{{browserCloseWindowUrl}}};
var skipJsErrors = {{{skipJsErrors}}};
var dialogHandler = {{{dialogHandler}}};
var userAgent = {{{userAgent}}};
Expand All @@ -28,7 +29,7 @@
var ClientDriver = window['%testCafeDriver%'];
var driver = new ClientDriver(testRunId,
{ heartbeat: browserHeartbeatUrl, status: browserStatusUrl, statusDone: browserStatusDoneUrl, activeWindowId: browserActiveWindowIdUrl },
{ heartbeat: browserHeartbeatUrl, status: browserStatusUrl, statusDone: browserStatusDoneUrl, activeWindowId: browserActiveWindowIdUrl, closeWindow: browserCloseWindowUrl },
{ userAgent: userAgent, fixtureName: fixtureName, testName: testName },
{
selectorTimeout: selectorTimeout,
Expand Down
1 change: 1 addition & 0 deletions src/test-run/index.ts
Expand Up @@ -535,6 +535,7 @@ export default class TestRun extends AsyncEventEmitter {
browserStatusRelativeUrl: JSON.stringify(this.browserConnection.statusRelativeUrl),
browserStatusDoneRelativeUrl: JSON.stringify(this.browserConnection.statusDoneRelativeUrl),
browserActiveWindowIdUrl: JSON.stringify(this.browserConnection.activeWindowIdUrl),
browserCloseWindowUrl: JSON.stringify(this.browserConnection.closeWindowUrl),
userAgent: JSON.stringify(this.browserConnection.userAgent),
testName: JSON.stringify(this.test.name),
fixtureName: JSON.stringify((this.test.fixture as Fixture).name),
Expand Down
7 changes: 7 additions & 0 deletions test/functional/fixtures/multiple-windows/test.js
Expand Up @@ -156,6 +156,13 @@ describe('Multiple windows', () => {
return runTests('testcafe-fixtures/i6085.js');
});

it('Should not hang on close window whide video is recording', () => {
return runTests('testcafe-fixtures/i6037.js', '', {
only: 'chrome',
setVideoPath: true,
});
});

describe('API', () => {
it('Open child window', () => {
return runTests('testcafe-fixtures/api/api-test.js', 'Open child window', { only: 'chrome' });
Expand Down
@@ -0,0 +1,13 @@
const parent = 'http://localhost:3000/fixtures/multiple-windows/pages/api/parent.html';
const child = 'http://localhost:3000/fixtures/multiple-windows/pages/api/child-2.html';

fixture('Should not hang on close window whide video is recording')
.page(parent);

for (let i = 0; i < 10; i++) {
test(`attempt ${i}`, async t => {
await t
.openWindow(child)
.closeWindow();
});
}

0 comments on commit 78d426a

Please sign in to comment.