Skip to content

Commit

Permalink
refactor: partial support of multiple windows tests in compiler servi…
Browse files Browse the repository at this point in the history
…ce (#6394)

* compiler service: partial support of multiple windows tests

* fix tests
  • Loading branch information
miherlosev authored Jul 19, 2021
1 parent 62309a9 commit f101901
Show file tree
Hide file tree
Showing 22 changed files with 201 additions and 71 deletions.
1 change: 0 additions & 1 deletion gulp/constants/functional-test-globs.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ const MIGRATE_ALL_TESTS_TO_COMPILER_SERVICE_GLOB = [
BASIC_TESTS_GLOB,
COMPILER_SERVICE_TESTS_GLOB,
'!test/functional/fixtures/multiple-windows/test.js',
'!test/functional/fixtures/api/es-next/assertions/test.js',
'!test/functional/fixtures/regression/gh-2846/test.js',
];

Expand Down
6 changes: 3 additions & 3 deletions src/api/test-controller/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -153,12 +153,12 @@ export default class TestController {
}

_validateMultipleWindowCommand (apiMethodName) {
const { disableMultipleWindows, browserConnection } = this.testRun;
const { disableMultipleWindows, activeWindowId } = this.testRun;

if (disableMultipleWindows)
throw new MultipleWindowsModeIsDisabledError(apiMethodName);

if (!browserConnection.activeWindowId)
if (!activeWindowId)
throw new MultipleWindowsModeIsNotAvailableInRemoteBrowserError(apiMethodName);
}

Expand Down Expand Up @@ -397,7 +397,7 @@ export default class TestController {
if (typeof windowSelector === 'function') {
command = SwitchToWindowByPredicateCommand;

args = { findWindow: windowSelector };
args = { checkWindow: windowSelector };
}
else {
command = SwitchToWindowCommand;
Expand Down
4 changes: 4 additions & 0 deletions src/api/test-run-tracker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,10 @@ class TestRunTracker extends EventEmitter {

testRun.onAny((eventName: string, eventData: unknown) => this.emit(eventName, { testRun, data: eventData }));
}

public removeActiveTestRun (id: string): void {
delete this.activeTestRuns[id];
}
}

// Tracker
Expand Down
4 changes: 2 additions & 2 deletions src/client/driver/driver.js
Original file line number Diff line number Diff line change
Expand Up @@ -1247,7 +1247,7 @@ export default class Driver extends serviceUtils.EventEmitter {

async _onSwitchToWindow (command, err) {
const wnd = this._getTopOpenedWindow();
const response = await this._validateChildWindowSwitchToWindowCommandExists({ windowId: command.windowId, fn: command.findWindow }, wnd);
const response = await this._validateChildWindowSwitchToWindowCommandExists({ windowId: command.windowId, fn: command.checkWindow }, wnd);
const result = response.result;

if (!result.success) {
Expand All @@ -1259,7 +1259,7 @@ export default class Driver extends serviceUtils.EventEmitter {
else {
this._stopInternal();

sendMessageToDriver(new SwitchToWindowCommandMessage({ windowId: command.windowId, fn: command.findWindow }), wnd, WAIT_FOR_WINDOW_DRIVER_RESPONSE_TIMEOUT, CannotSwitchToWindowError);
sendMessageToDriver(new SwitchToWindowCommandMessage({ windowId: command.windowId, fn: command.checkWindow }), wnd, WAIT_FOR_WINDOW_DRIVER_RESPONSE_TIMEOUT, CannotSwitchToWindowError);
}
}

Expand Down
1 change: 0 additions & 1 deletion src/runner/bootstrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import parseFileList from '../utils/parse-file-list';
import resolvePathRelativelyCwd from '../utils/resolve-path-relatively-cwd';
import loadClientScripts from '../custom-client-scripts/load';
import { getConcatenatedValuesString } from '../utils/string';

import { Writable as WritableStream } from 'stream';
import { ReporterSource, ReporterPluginSource } from '../reporter/interfaces';
import ClientScript from '../custom-client-scripts/client-script';
Expand Down
16 changes: 12 additions & 4 deletions src/services/compiler/host.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import path from 'path';
import url from 'url';
import { pathToFileURL } from 'url';
import cdp from 'chrome-remote-interface';
import EventEmitter from 'events';
import { spawn, ChildProcess } from 'child_process';
Expand Down Expand Up @@ -63,13 +63,14 @@ import {
ExecuteAsyncJsExpressionArguments,
CommandLocator,
AddUnexpectedErrorArguments,
CheckWindowArgument,
} from './interfaces';

import { UncaughtExceptionError, UnhandledPromiseRejectionError } from '../../errors/test-run';
import { handleUnexpectedError } from '../../utils/handle-errors';

const SERVICE_PATH = require.resolve('./service');
const INTERNAL_FILES_URL = url.pathToFileURL(path.join(__dirname, '../../'));
const INTERNAL_FILES_URL = pathToFileURL(path.join(__dirname, '../../'));

interface RuntimeResources {
service: ChildProcess;
Expand Down Expand Up @@ -136,6 +137,7 @@ export default class CompilerHost extends AsyncEventEmitter implements CompilerP
this.executeAsyncJsExpression,
this.executeAssertionFn,
this.addUnexpectedError,
this.checkWindow,
], this);
}

Expand Down Expand Up @@ -422,10 +424,10 @@ export default class CompilerHost extends AsyncEventEmitter implements CompilerP
await this.emit('removeRequestEventListeners', { rules });
}

public async initializeTestRunData ({ testRunId, testId, browser }: InitializeTestRunDataArguments): Promise<void> {
public async initializeTestRunData ({ testRunId, testId, browser, activeWindowId }: InitializeTestRunDataArguments): Promise<void> {
const { proxy } = await this._getRuntime();

return proxy.call(this.initializeTestRunData, { testRunId, testId, browser });
return proxy.call(this.initializeTestRunData, { testRunId, testId, browser, activeWindowId });
}

public async getAssertionActualValue ({ testRunId, commandId }: CommandLocator): Promise<unknown> {
Expand Down Expand Up @@ -497,4 +499,10 @@ export default class CompilerHost extends AsyncEventEmitter implements CompilerP

handleUnexpectedError(ErrorTypeConstructor, message);
}

public async checkWindow ({ testRunId, commandId, url, title }: CheckWindowArgument): Promise<boolean> {
const { proxy } = await this._getRuntime();

return proxy.call(this.checkWindow, { testRunId, commandId, url, title });
}
}
7 changes: 7 additions & 0 deletions src/services/compiler/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ export interface ExecuteMockPredicate extends RequestFilterRuleLocator {
export interface InitializeTestRunDataArguments extends TestRunLocator {
testId: string;
browser: Browser;
activeWindowId: string | null;
}

export interface RoleLocator {
Expand Down Expand Up @@ -137,3 +138,9 @@ export interface AddUnexpectedErrorArguments {
message: string;
}

export interface CheckWindowArgument extends TestRunLocator {
commandId: string;
url: URL;
title: string;
}

2 changes: 2 additions & 0 deletions src/services/compiler/protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
ExecuteAsyncJsExpressionArguments,
CommandLocator,
AddUnexpectedErrorArguments,
CheckWindowArgument,
} from './interfaces';

export const BEFORE_AFTER_PROPERTIES = ['beforeFn', 'afterFn'] as const;
Expand Down Expand Up @@ -85,4 +86,5 @@ export interface CompilerProtocol extends TestRunDispatcherProtocol {
executeAsyncJsExpression ({ expression, testRunId, callsite }: ExecuteAsyncJsExpressionArguments): Promise<unknown>;
executeAssertionFn ({ testRunId, commandId }: CommandLocator): Promise<unknown>;
addUnexpectedError ({ type, message }: AddUnexpectedErrorArguments): Promise<void>;
checkWindow ({ url, title }: CheckWindowArgument): Promise<boolean>;
}
28 changes: 25 additions & 3 deletions src/services/compiler/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ import {
ExecuteAsyncJsExpressionArguments,
CommandLocator,
AddUnexpectedErrorArguments,
CheckWindowArgument,
} from './interfaces';

import { CompilerArguments } from '../../compiler/interfaces';
Expand Down Expand Up @@ -85,6 +86,7 @@ import {
import { renderHtmlWithoutStack, shouldRenderHtmlWithoutStack } from '../../errors/test-run/render-error-template/utils';
import setupSourceMapSupport from '../../utils/setup-sourcemap-support';
import { formatError } from '../../utils/handle-errors';
import { SwitchToWindowPredicateError } from '../../shared/errors';

setupSourceMapSupport();

Expand All @@ -100,6 +102,13 @@ interface WrapSetMockArguments extends RequestHookLocator {
event: RequestEvent;
}

interface InitTestRunProxyData {
testRunId: string;
test: Test;
browser: Browser;
activeWindowId: string | null;
}

class CompilerService implements CompilerProtocol {
private readonly proxy: IPCProxy;
private readonly state: ServiceState;
Expand Down Expand Up @@ -190,6 +199,7 @@ class CompilerService implements CompilerProtocol {
this.executeJsExpression,
this.executeAsyncJsExpression,
this.addUnexpectedError,
this.checkWindow,
], this);
}

Expand Down Expand Up @@ -239,13 +249,14 @@ class CompilerService implements CompilerProtocol {
};
}

private _initializeTestRunProxy (testRunId: string, test: Test, browser: Browser): void {
private _initializeTestRunProxy ({ testRunId, test, browser, activeWindowId }: InitTestRunProxyData): void {
const testRunProxy = new TestRunProxy({
dispatcher: this,
id: testRunId,
options: this.state.options,
test,
browser,
activeWindowId,
});

this.state.testRuns[testRunId] = testRunProxy;
Expand Down Expand Up @@ -383,10 +394,10 @@ class CompilerService implements CompilerProtocol {
return await this.proxy.call(this.removeRequestEventListeners, { rules });
}

public async initializeTestRunData ({ testRunId, testId, browser }: InitializeTestRunDataArguments): Promise<void> {
public async initializeTestRunData ({ testRunId, testId, browser, activeWindowId }: InitializeTestRunDataArguments): Promise<void> {
const test = this.state.units[testId] as Test;

this._initializeTestRunProxy(testRunId, test, browser);
this._initializeTestRunProxy({ testRunId, test, browser, activeWindowId });
this._initializeFixtureCtx(test);
}

Expand Down Expand Up @@ -473,6 +484,17 @@ class CompilerService implements CompilerProtocol {
public async addUnexpectedError ({ type, message }: AddUnexpectedErrorArguments): Promise<void> {
return this.proxy.call(this.addUnexpectedError, { type, message });
}

public async checkWindow ({ testRunId, commandId, url, title }: CheckWindowArgument): Promise<boolean> {
try {
return this
._getTargetTestRun(testRunId)
.checkWindow(commandId, { title, url });
}
catch (err) {
throw new SwitchToWindowPredicateError(err.message);
}
}
}

export default new CompilerService();
60 changes: 41 additions & 19 deletions src/services/compiler/test-run-proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,13 @@ import Test from '../../api/structure/test';
import RequestHook from '../../api/request-hooks/hook';
import { generateUniqueId } from 'testcafe-hammerhead';
import { CallsiteRecord } from 'callsite-record';
import { UseRoleCommand } from '../../test-run/commands/actions';

import {
CheckWindowPredicateData,
SwitchToWindowByPredicateCommand,
UseRoleCommand,
} from '../../test-run/commands/actions';

import ReExecutablePromise from '../../utils/re-executable-promise';
import AsyncEventEmitter from '../../utils/async-event-emitter';
import testRunMarker from '../../test-run/marker-symbol';
Expand All @@ -37,29 +43,31 @@ class TestRunProxy extends AsyncEventEmitter {
public ctx: object;
private readonly _options: Dictionary<OptionValue>;
private readonly assertionCommands: Map<string, AssertionCommand>;
private readonly switchToWindowByPredicateCommands: Map<string, SwitchToWindowByPredicateCommand>;
private readonly asyncJsExpressionCallsites: Map<string, CallsiteRecord>;
public readonly browser: Browser;
public readonly disableMultipleWindows: boolean;
public activeWindowId: null | string;

public constructor ({ dispatcher, id, test, options, browser }: TestRunProxyInit) {
public constructor ({ dispatcher, id, test, options, browser, activeWindowId }: TestRunProxyInit) {
super();

this[testRunMarker] = true;
this.dispatcher = dispatcher;
this.id = id;
this.test = test;
this.ctx = Object.create(null);
this.fixtureCtx = Object.create(null);
this._options = options;
this.browser = browser;

this.assertionCommands = new Map<string, AssertionCommand>();
this.asyncJsExpressionCallsites = new Map<string, CallsiteRecord>();

// TODO: Synchronize these properties with their real counterparts in the main process.
// Postponed until (GH-3244). See details in (GH-5250).
this.controller = new TestController(this);
this.observedCallsites = new ObservedCallsitesStorage();
this.warningLog = new WarningLog();
this[testRunMarker] = true;
this.dispatcher = dispatcher;
this.id = id;
this.test = test;
this.ctx = Object.create(null);
this.fixtureCtx = Object.create(null);
this._options = options;
this.browser = browser;
this.assertionCommands = new Map<string, AssertionCommand>();
this.switchToWindowByPredicateCommands = new Map<string, SwitchToWindowByPredicateCommand>();
this.asyncJsExpressionCallsites = new Map<string, CallsiteRecord>();
this.controller = new TestController(this);
this.observedCallsites = new ObservedCallsitesStorage();
this.warningLog = new WarningLog();
this.disableMultipleWindows = options.disableMultipleWindows as boolean;
this.activeWindowId = activeWindowId;

testRunTracker.addActiveTestRun(this);

Expand All @@ -84,6 +92,12 @@ class TestRunProxy extends AsyncEventEmitter {
this.assertionCommands.set(command.id, command);
}

private _storeSwitchToWindowByPredicateCommand (command: SwitchToWindowByPredicateCommand): void {
command.id = generateUniqueId();

this.switchToWindowByPredicateCommands.set(command.id, command);
}

private _handleAssertionCommand (command: AssertionCommand): void {
if (isFunction(command.actual)) {
command.originActual = command.actual;
Expand Down Expand Up @@ -122,6 +136,8 @@ class TestRunProxy extends AsyncEventEmitter {
this._handleAssertionCommand(command as AssertionCommand);
else if (command.type === COMMAND_TYPE.useRole)
this.dispatcher.onRoleAppeared((command as UseRoleCommand).role);
else if (command.type === COMMAND_TYPE.switchToWindowByPredicate)
this._storeSwitchToWindowByPredicateCommand(command as SwitchToWindowByPredicateCommand);

return this.dispatcher.executeAction({
apiMethodName,
Expand Down Expand Up @@ -201,6 +217,12 @@ class TestRunProxy extends AsyncEventEmitter {

this.asyncJsExpressionCallsites.clear();
}

public checkWindow (commandId: string, { title, url }: CheckWindowPredicateData): boolean {
const command = this.switchToWindowByPredicateCommands.get(commandId) as SwitchToWindowByPredicateCommand;

return command.checkWindow({ title, url });
}
}

export default TestRunProxy;
Expand Down
1 change: 1 addition & 0 deletions src/services/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ export interface TestRunProxyInit {
test: Test;
options: Dictionary<OptionValue>;
browser: Browser;
activeWindowId: null | string;
}

1 change: 1 addition & 0 deletions src/services/serialization/prepare-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const NECESSARY_OPTIONS = [
OptionNames.assertionTimeout,
OptionNames.speed,
OptionNames.pageLoadTimeout,
OptionNames.disableMultipleWindows,
];

export default function (value: Dictionary<OptionValue>): Dictionary<OptionValue> {
Expand Down
2 changes: 2 additions & 0 deletions src/services/serialization/replicator/create-replicator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import TestCafeErrorListTransform from './transforms/testcafe-error-list-transfo
import FunctionMarkerTransform from './transforms/function-marker-transform';
import PromiseMarkerTransform from './transforms/promise-marker-transform';
import ConfigureResponseEventOptionTransform from './transforms/configure-response-event-option-transform';
import URLTransform from './transforms/url-transform';

const DEFAULT_ERROR_TRANSFORM_TYPE = '[[Error]]';

Expand All @@ -34,6 +35,7 @@ export default function (): Replicator {
.addTransforms([
customErrorTransform,
defaultErrorTransform,
new URLTransform(),
new TestCafeErrorListTransform(),
new BrowserConsoleMessagesTransform(),
new ReExecutablePromiseTransform(),
Expand Down
Loading

0 comments on commit f101901

Please sign in to comment.