Skip to content

Commit

Permalink
initial
Browse files Browse the repository at this point in the history
  • Loading branch information
miherlosev committed Jun 4, 2020
1 parent 075f6b7 commit 249ef47
Show file tree
Hide file tree
Showing 24 changed files with 215 additions and 132 deletions.
26 changes: 24 additions & 2 deletions src/browser/connection/gateway.ts
Expand Up @@ -5,6 +5,8 @@ import { Proxy } from 'testcafe-hammerhead';
import { Dictionary } from '../../configuration/interfaces';
import BrowserConnection from './index';
import { IncomingMessage, ServerResponse } from 'http';
import promisifyEvent from 'promisify-event';
import SetupWindowResult from './setup-window-result';

const IDLE_PAGE_SCRIPT = read('../../client/browser/idle-page/index.js');
const IDLE_PAGE_STYLE = read('../../client/browser/idle-page/styles.css');
Expand Down Expand Up @@ -52,6 +54,7 @@ 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/set-up-window/{id}', proxy, BrowserConnectionGateway._onSetupWindow, 'POST');

proxy.GET('/browser/connect', (req: IncomingMessage, res: ServerResponse) => this._connectNextRemoteBrowser(req, res));
proxy.GET('/browser/connect/', (req: IncomingMessage, res: ServerResponse) => this._connectNextRemoteBrowser(req, res));
Expand Down Expand Up @@ -84,15 +87,26 @@ export default class BrowserConnectionGateway {
}

// Route handlers
private static _onConnection (req: IncomingMessage, res: ServerResponse, connection: BrowserConnection): void {
private static async _onConnection (req: IncomingMessage, res: ServerResponse, connection: BrowserConnection): Promise<void> {
if (connection.isReady())
respond500(res, 'The connection is already established.');

else {
const userAgent = req.headers['user-agent'] as string;

connection.establish(userAgent);
redirect(res, connection.idleUrl);

if (connection.permanent)
redirect(res, connection.idleUrl);

else {
promisifyEvent(connection, 'jobAdded')
.then(async () => {
const testRunUrl = await connection.getTestRunUrl(true) || connection.idleUrl;

redirect(res, testRunUrl);
});
}
}
}

Expand Down Expand Up @@ -171,6 +185,14 @@ export default class BrowserConnectionGateway {
}
}

private static async _onSetupWindow (req: IncomingMessage, res: ServerResponse, connection: BrowserConnection): Promise<void> {
if (BrowserConnectionGateway._ensureConnectionReady(res, connection)) {
await connection.setUpBrowserWindow();

respondWithJSON(res, { result: SetupWindowResult.ok });
}
}

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

Expand Down
22 changes: 15 additions & 7 deletions src/browser/connection/index.ts
Expand Up @@ -58,6 +58,7 @@ export default class BrowserConnection extends EventEmitter {
public readonly idleUrl: string;
private forcedIdleUrl: string;
private readonly initScriptUrl: string;
private readonly setupWindowUrl: string;
private readonly heartbeatRelativeUrl: string;
private readonly statusRelativeUrl: string;
private readonly statusDoneRelativeUrl: string;
Expand Down Expand Up @@ -96,10 +97,11 @@ export default class BrowserConnection extends EventEmitter {
this.pendingTestRunUrl = null;
this.allowMultipleWindows = allowMultipleWindows;

this.url = `${gateway.domain}/browser/connect/${this.id}`;
this.idleUrl = `${gateway.domain}/browser/idle/${this.id}`;
this.forcedIdleUrl = `${gateway.domain}/browser/idle-forced/${this.id}`;
this.initScriptUrl = `${gateway.domain}/browser/init-script/${this.id}`;
this.url = `${gateway.domain}/browser/connect/${this.id}`;
this.idleUrl = `${gateway.domain}/browser/idle/${this.id}`;
this.forcedIdleUrl = `${gateway.domain}/browser/idle-forced/${this.id}`;
this.initScriptUrl = `${gateway.domain}/browser/init-script/${this.id}`;
this.setupWindowUrl = `${gateway.domain}/browser/set-up-window/${this.id}`;

this.heartbeatRelativeUrl = `/browser/heartbeat/${this.id}`;
this.statusRelativeUrl = `/browser/status/${this.id}`;
Expand Down Expand Up @@ -182,7 +184,7 @@ export default class BrowserConnection extends EventEmitter {
}, this.HEARTBEAT_TIMEOUT);
}

private async _getTestRunUrl (needPopNext: boolean): Promise<string> {
public async getTestRunUrl (needPopNext: boolean): Promise<string> {
if (needPopNext || !this.pendingTestRunUrl)
this.pendingTestRunUrl = await this._popNextTestRunUrl();

Expand Down Expand Up @@ -301,6 +303,8 @@ export default class BrowserConnection extends EventEmitter {

public addJob (job: any): void {
this.jobQueue.push(job);

this.emit('jobAdded');
}

public removeJob (job: any): void {
Expand Down Expand Up @@ -364,7 +368,7 @@ export default class BrowserConnection extends EventEmitter {
const initScriptPromise = this.initScriptsQueue.shift();

if (initScriptPromise)
initScriptPromise.resolve(JSON.parse(data));
initScriptPromise.resolve(data);
}

public isHeadlessBrowser (): boolean {
Expand All @@ -382,7 +386,7 @@ export default class BrowserConnection extends EventEmitter {
}

if (this.status === BrowserConnectionStatus.opened) {
const testRunUrl = await this._getTestRunUrl(isTestDone || this.testRunAborted);
const testRunUrl = await this.getTestRunUrl(isTestDone || this.testRunAborted);

this.testRunAborted = false;

Expand Down Expand Up @@ -413,4 +417,8 @@ export default class BrowserConnection extends EventEmitter {
this.status === BrowserConnectionStatus.opened ||
this.status === BrowserConnectionStatus.closing;
}

public async setUpBrowserWindow (): Promise<void> {
return this.provider.setUpBrowserWindow(this.id);
}
}
6 changes: 6 additions & 0 deletions src/browser/connection/setup-window-result.ts
@@ -0,0 +1,6 @@
enum SetupWindowResult {
ok = 'ok',
failed = 'failed'
}

export default SetupWindowResult;
8 changes: 8 additions & 0 deletions src/browser/interfaces.ts
@@ -0,0 +1,8 @@
export interface WindowDimentionsInfo {
width: number;
height: number;
outerWidth: number;
outerHeight: number;
availableWidth: number;
availableHeight: number;
}
9 changes: 6 additions & 3 deletions src/browser/provider/built-in/dedicated/chrome/index.js
Expand Up @@ -39,15 +39,18 @@ export default {

await this.waitForConnectionReady(browserId);

runtimeInfo.viewportSize = await this.runInitScript(browserId, GET_WINDOW_DIMENSIONS_INFO_SCRIPT);
runtimeInfo.activeWindowId = null;

if (allowMultipleWindows)
runtimeInfo.activeWindowId = this.calculateWindowId();

await cdp.createClient(runtimeInfo);

this.openedBrowsers[browserId] = runtimeInfo;
},

async resizeWindowAfterOpeningBrowser (browserId) {
const runtimeInfo = this.openedBrowsers[browserId];

runtimeInfo.viewportSize = await this.runInitScript(browserId, GET_WINDOW_DIMENSIONS_INFO_SCRIPT);

await this._ensureWindowIsExpanded(browserId, runtimeInfo.viewportSize);
},
Expand Down
26 changes: 12 additions & 14 deletions src/browser/provider/index.ts
Expand Up @@ -3,12 +3,10 @@ import OS from 'os-family';
import { dirname } from 'path';
import makeDir from 'make-dir';
import BrowserConnection from '../connection';
import delay from '../../utils/delay';
import { GET_TITLE_SCRIPT, GET_WINDOW_DIMENSIONS_INFO_SCRIPT } from './utils/client-functions';
import WARNING_MESSAGE from '../../notifications/warning-message';
import { Dictionary } from '../../configuration/interfaces';

const BROWSER_OPENING_DELAY = 2000;
import { WindowDimentionsInfo } from '../interfaces';

const RESIZE_DIFF_SIZE = {
width: 100,
Expand Down Expand Up @@ -94,17 +92,17 @@ export default class BrowserProvider {
if (!await browserTools.isMaximized(title))
return;

const currentSize = await this.plugin.runInitScript(browserId, GET_WINDOW_DIMENSIONS_INFO_SCRIPT);
const currentSize = await this.plugin.runInitScript(browserId, GET_WINDOW_DIMENSIONS_INFO_SCRIPT) as WindowDimentionsInfo;
const etalonSize = subtractSizes(currentSize, RESIZE_DIFF_SIZE);

await browserTools.resize(title, currentSize.width, currentSize.height, etalonSize.width, etalonSize.height);

let resizedSize = await this.plugin.runInitScript(browserId, GET_WINDOW_DIMENSIONS_INFO_SCRIPT);
let resizedSize = await this.plugin.runInitScript(browserId, GET_WINDOW_DIMENSIONS_INFO_SCRIPT) as WindowDimentionsInfo;
let correctionSize = subtractSizes(resizedSize, etalonSize);

await browserTools.resize(title, resizedSize.width, resizedSize.height, etalonSize.width, etalonSize.height);

resizedSize = await this.plugin.runInitScript(browserId, GET_WINDOW_DIMENSIONS_INFO_SCRIPT);
resizedSize = await this.plugin.runInitScript(browserId, GET_WINDOW_DIMENSIONS_INFO_SCRIPT) as WindowDimentionsInfo;

correctionSize = sumSizes(correctionSize, subtractSizes(resizedSize, etalonSize));

Expand All @@ -118,7 +116,7 @@ export default class BrowserProvider {
if (!this._isBrowserIdle(browserId))
return;

const sizeInfo = await this.plugin.runInitScript(browserId, GET_WINDOW_DIMENSIONS_INFO_SCRIPT);
const sizeInfo = await this.plugin.runInitScript(browserId, GET_WINDOW_DIMENSIONS_INFO_SCRIPT) as WindowDimentionsInfo;

if (this.localBrowsersInfo[browserId]) {
this.localBrowsersInfo[browserId].maxScreenSize = {
Expand All @@ -134,10 +132,6 @@ export default class BrowserProvider {

await this._ensureLocalBrowserInfo(browserId);

// NOTE: delay to ensure the window finished the opening
await this.plugin.waitForConnectionReady(browserId);
await delay(BROWSER_OPENING_DELAY);

if (this.localBrowsersInfo[browserId])
this.localBrowsersInfo[browserId].windowDescriptor = await browserTools.findWindow(browserId);
}
Expand Down Expand Up @@ -245,9 +239,6 @@ export default class BrowserProvider {

public async openBrowser (browserId: string, pageUrl: string, browserName: string, allowMultipleWindows: boolean): Promise<void> {
await this.plugin.openBrowser(browserId, pageUrl, browserName, allowMultipleWindows);

if (await this.canUseDefaultWindowActions(browserId))
await this._ensureBrowserWindowParameters(browserId);
}

public async closeBrowser (browserId: string): Promise<void> {
Expand Down Expand Up @@ -354,4 +345,11 @@ export default class BrowserProvider {
public setActiveWindowId (browserId: string, val: string): void {
this.plugin.setActiveWindowId(browserId, val);
}

public async setUpBrowserWindow (browserId: string): Promise<void> {
if (this.plugin.resizeWindowAfterOpeningBrowser)
await this.plugin.resizeWindowAfterOpeningBrowser(browserId);

await this._ensureBrowserWindowParameters(browserId);
}
}
@@ -1,9 +1,12 @@
/*eslint-disable no-undef, no-var*/
function getTitle () {
return document.title;
import { WindowDimentionsInfo } from '../../interfaces';

function getTitle (): string {
// @ts-ignore
return window['%testCafeCore%'].domUtils.getDocumentTitle(document);
}

function getWindowDimensionsInfo () {
function getWindowDimensionsInfo (): WindowDimentionsInfo {
return {
width: window.innerWidth,
height: window.innerHeight,
Expand Down
4 changes: 2 additions & 2 deletions src/client/browser/index.js
Expand Up @@ -88,7 +88,7 @@ function executeInitScript (initScriptUrl, createXHR) {
if (!res.code)
return null;

return sendXHR(initScriptUrl, createXHR, { method: 'POST', data: JSON.stringify(evaluate(res.code)) }); //eslint-disable-line no-restricted-globals
return sendXHR(initScriptUrl, createXHR, { method: 'POST', data: evaluate(res.code) });
})
.then(() => {
window.setTimeout(() => executeInitScript(initScriptUrl, createXHR), 1000);
Expand Down Expand Up @@ -163,6 +163,6 @@ export function getActiveWindowId (activeWindowIdUrl, createXHR) {
export function setActiveWindowId (activeWindowIdUrl, createXHR, windowId) {
return sendXHR(activeWindowIdUrl, createXHR, {
method: 'POST',
data: JSON.stringify({ windowId }) //eslint-disable-line no-restricted-globals
data: { windowId }
});
}
8 changes: 8 additions & 0 deletions src/client/core/utils/dom.js
Expand Up @@ -516,3 +516,11 @@ export function contains (element, target) {

return !!findParent(target, true, node => node === element);
}

export function setDocumentTitle (document, title) {
hammerhead.nativeMethods.documentTitleSetter.call(document, title);
}

export function getDocumentTitle (document) {
return hammerhead.nativeMethods.documentTitleGetter.call(document);
}
22 changes: 18 additions & 4 deletions src/client/driver/driver.js
Expand Up @@ -116,18 +116,21 @@ const STATUS_WITH_COMMAND_RESULT_EVENT = 'status-with-command-result-event';
const EMPTY_COMMAND_EVENT = 'empty-command-event';

export default class Driver extends serviceUtils.EventEmitter {
constructor (testRunId, communicationUrls, runInfo, options) {
constructor (testRunId, browserId, communicationUrls, runInfo, options) {
super();

this.COMMAND_EXECUTING_FLAG = 'testcafe|driver|command-executing-flag';
this.EXECUTING_IN_IFRAME_FLAG = 'testcafe|driver|executing-in-iframe-flag';
this.PENDING_WINDOW_SWITCHING_FLAG = 'testcafe|driver|pending-window-switching-flag';

this.testRunId = testRunId;
this.browserId = browserId;
this.heartbeatUrl = communicationUrls.heartbeat;
this.browserStatusUrl = communicationUrls.status;
this.browserStatusDoneUrl = communicationUrls.statusDone;
this.browserActiveWindowId = communicationUrls.activeWindowId;
this.initScriptUrl = communicationUrls.initScriptUrl;
this.setupWindowUrl = communicationUrls.setupWindowUrl;
this.userAgent = runInfo.userAgent;
this.fixtureName = runInfo.fixtureName;
this.testName = runInfo.testName;
Expand Down Expand Up @@ -161,6 +164,9 @@ export default class Driver extends serviceUtils.EventEmitter {
if (options.retryTestPages)
browser.enableRetryingTestPages();

browser.startHeartbeat(this.heartbeatUrl, hammerhead.createNativeXHR);
browser.startInitScriptExecution(this.initScriptUrl, hammerhead.createNativeXHR);

this.pageInitialRequestBarrier = new RequestBarrier();

this.readyPromise = eventUtils
Expand Down Expand Up @@ -1127,13 +1133,21 @@ export default class Driver extends serviceUtils.EventEmitter {
this.speed = this.initialSpeed;

this._initConsoleMessages();
this._setDocumentTitle();
}

_setDocumentTitle () {
const title = `[${this.browserId}]`;

domUtils.setDocumentTitle(document, title);
}

_doFirstPageLoadSetup () {
if (this.isFirstPageLoad && this.canUseDefaultWindowActions) {
// Stub: perform initial setup of the test first page

return Promise.resolve();
return browser.sendXHR(this.setupWindowUrl, hammerhead.createNativeXHR, { method: 'POST' })
.then(() => {
browser.stopInitScriptExecution();
});
}

return Promise.resolve();
Expand Down
4 changes: 2 additions & 2 deletions src/client/driver/iframe-driver.js
Expand Up @@ -9,8 +9,8 @@ import { TYPE as MESSAGE_TYPE } from './driver-link/messages';
import IframeNativeDialogTracker from './native-dialog-tracker/iframe';

export default class IframeDriver extends Driver {
constructor (testRunId, options) {
super(testRunId, {}, {}, options);
constructor (testRunId, browserId, options) {
super(testRunId, browserId, {}, {}, options);

this.lastParentDriverMessageId = null;
this.parentDriverLink = new ParentIframeDriverLink(window.parent);
Expand Down
2 changes: 1 addition & 1 deletion src/client/test-run/iframe.js.mustache
@@ -1,6 +1,6 @@
(function () {
var IframeDriver = window['%testCafeIframeDriver%'];
var driver = new IframeDriver({{{testRunId}}}, {
var driver = new IframeDriver({{{testRunId}}}, {{{browserId}}} {
selectorTimeout: {{{selectorTimeout}}},
pageLoadTimeout: {{{pageLoadTimeout}}},
dialogHandler: {{{dialogHandler}}},
Expand Down
10 changes: 8 additions & 2 deletions src/client/test-run/index.js.mustache
Expand Up @@ -19,6 +19,8 @@
var browserStatusUrl = origin + {{{browserStatusRelativeUrl}}};
var browserStatusDoneUrl = origin + {{{browserStatusDoneRelativeUrl}}};
var browserActiveWindowIdUrl = origin + {{{browserActiveWindowIdUrl}}};
var browserInitScriptUrl = {{{browserInitScriptUrl}}};
var browserSetupWindowUrl = {{{browserSetupWindowUrl}}};
var skipJsErrors = {{{skipJsErrors}}};
var dialogHandler = {{{dialogHandler}}};
var userAgent = {{{userAgent}}};
Expand All @@ -27,8 +29,12 @@
var canUseDefaultWindowActions = {{{canUseDefaultWindowActions}}};
var ClientDriver = window['%testCafeDriver%'];
var driver = new ClientDriver(testRunId,
{ heartbeat: browserHeartbeatUrl, status: browserStatusUrl, statusDone: browserStatusDoneUrl, activeWindowId: browserActiveWindowIdUrl },
var driver = new ClientDriver(testRunId, browserId,
{
heartbeat: browserHeartbeatUrl, status: browserStatusUrl, statusDone: browserStatusDoneUrl,
activeWindowId: browserActiveWindowIdUrl, initScriptUrl: browserInitScriptUrl,
setupWindowUrl: browserSetupWindowUrl
},
{ userAgent: userAgent, fixtureName: fixtureName, testName: testName },
{
selectorTimeout: selectorTimeout,
Expand Down
2 changes: 2 additions & 0 deletions src/test-run/index.js
Expand Up @@ -238,6 +238,8 @@ export default class TestRun extends AsyncEventEmitter {
browserStatusRelativeUrl: JSON.stringify(this.browserConnection.statusRelativeUrl),
browserStatusDoneRelativeUrl: JSON.stringify(this.browserConnection.statusDoneRelativeUrl),
browserActiveWindowIdUrl: JSON.stringify(this.browserConnection.activeWindowIdUrl),
browserInitScriptUrl: JSON.stringify(this.browserConnection.initScriptUrl),
browserSetupWindowUrl: JSON.stringify(this.browserConnection.setupWindowUrl),
userAgent: JSON.stringify(this.browserConnection.userAgent),
testName: JSON.stringify(this.test.name),
fixtureName: JSON.stringify(this.test.fixture.name),
Expand Down

0 comments on commit 249ef47

Please sign in to comment.