diff --git a/src/api/test-controller/index.js b/src/api/test-controller/index.js index 472e6710362..0b94dcf0832 100644 --- a/src/api/test-controller/index.js +++ b/src/api/test-controller/index.js @@ -24,6 +24,7 @@ import { SwitchToMainWindowCommand, SetNativeDialogHandlerCommand, GetNativeDialogHistoryCommand, + GetBrowserConsoleMessagesCommand, SetTestSpeedCommand, SetPageLoadTimeoutCommand, UseRoleCommand @@ -239,6 +240,12 @@ export default class TestController { return this.testRun.executeCommand(new GetNativeDialogHistoryCommand(), callsite); } + _getBrowserConsoleMessages$ () { + var callsite = getCallsiteForMethod('getBrowserConsoleMessages'); + + return this.testRun.executeCommand(new GetBrowserConsoleMessagesCommand(), callsite); + } + _expect$ (actual) { return new Assertion(actual, this); } diff --git a/src/client/driver/driver.js b/src/client/driver/driver.js index 026adc337c1..a088b175038 100644 --- a/src/client/driver/driver.js +++ b/src/client/driver/driver.js @@ -27,6 +27,8 @@ import { CurrentIframeNotFoundError, CurrentIframeIsInvisibleError } from '../../errors/test-run'; + +import BrowserConsoleMessages from '../../test-run/browser-console-messages'; import NativeDialogTracker from './native-dialog-tracker'; import { SetNativeDialogHandlerMessage, TYPE as MESSAGE_TYPE } from './driver-link/messages'; @@ -59,6 +61,7 @@ const ACTIVE_IFRAME_SELECTOR = 'testcafe|driver|active-iframe-sele const TEST_SPEED = 'testcafe|driver|test-speed'; const ASSERTION_RETRIES_TIMEOUT = 'testcafe|driver|assertion-retries-timeout'; const ASSERTION_RETRIES_START_TIME = 'testcafe|driver|assertion-retries-start-time'; +const CONSOLE_MESSAGES = 'testcafe|driver|console-messages'; const CHECK_IFRAME_DRIVER_LINK_DELAY = 500; const ACTION_IFRAME_ERROR_CTORS = { @@ -114,6 +117,7 @@ export default class Driver { hammerhead.on(hammerhead.EVENTS.uncaughtJsError, err => this._onJsError(err)); hammerhead.on(hammerhead.EVENTS.unhandledRejection, err => this._onJsError(err)); + hammerhead.on(hammerhead.EVENTS.consoleMethCalled, e => this._onConsoleMessage(e)); } set speed (val) { @@ -124,6 +128,14 @@ export default class Driver { return this.contextStorage.getItem(TEST_SPEED); } + get consoleMessages () { + return new BrowserConsoleMessages(this.contextStorage.getItem(CONSOLE_MESSAGES)); + } + + set consoleMessages (messages) { + return this.contextStorage.setItem(CONSOLE_MESSAGES, messages ? messages.getCopy() : null); + } + // Error handling _onJsError (err) { // NOTE: we should not send any message to the server if we've @@ -156,6 +168,26 @@ export default class Driver { return false; } + // Console messages + _onConsoleMessage (e) { + const meth = e.meth; + + const args = e.args.map(arg => { + if (arg === null) + return 'null'; + + if (arg === void 0) + return 'undefined'; + + return arg.toString(); + }); + + const messages = this.consoleMessages; + + messages.addMessage(meth, Array.prototype.slice.call(args).join(' ')); + + this.consoleMessages = messages; + } // Status _addPendingErrorToStatus (status) { @@ -173,12 +205,18 @@ export default class Driver { status.pageError = status.pageError || dialogError; } + _addConsoleMessagesToStatus (status) { + status.consoleMessages = this.consoleMessages; + this.consoleMessages = null; + } + _sendStatus (status) { // NOTE: We should not modify the status if it is resent after // the page load because the server has cached the response if (!status.resent) { this._addPendingErrorToStatus(status); this._addUnexpectedDialogErrorToStatus(status); + this._addConsoleMessagesToStatus(status); } this.contextStorage.setItem(PENDING_STATUS, status); @@ -327,6 +365,10 @@ export default class Driver { })); } + _onGetBrowserConsoleMessagesCommand () { + this._onReady(new DriverStatus({ isCommandResult: true })); + } + _onNavigateToCommand (command) { this.contextStorage.setItem(this.COMMAND_EXECUTING_FLAG, true); @@ -489,6 +531,9 @@ export default class Driver { else if (command.type === COMMAND_TYPE.getNativeDialogHistory) this._onGetNativeDialogHistoryCommand(command); + else if (command.type === COMMAND_TYPE.getBrowserConsoleMessages) + this._onGetBrowserConsoleMessagesCommand(command); + else if (command.type === COMMAND_TYPE.setTestSpeed) this._onSetTestSpeedCommand(command); diff --git a/src/client/driver/status.js b/src/client/driver/status.js index 613e6de516b..b805a2180af 100644 --- a/src/client/driver/status.js +++ b/src/client/driver/status.js @@ -12,6 +12,7 @@ export default class DriverStatus extends Assignable { this.pageError = null; this.resent = false; this.result = null; + this.consoleMessages = null; this._assignFrom(obj, true); } @@ -21,7 +22,8 @@ export default class DriverStatus extends Assignable { { name: 'isCommandResult' }, { name: 'executionError' }, { name: 'pageError' }, - { name: 'result' } + { name: 'result' }, + { name: 'consoleMessages' } ]; } } diff --git a/src/test-run/bookmark.js b/src/test-run/bookmark.js index ac990f49b51..9e3db23508c 100644 --- a/src/test-run/bookmark.js +++ b/src/test-run/bookmark.js @@ -28,6 +28,7 @@ export default class TestRunBookmark { this.pageLoadTimeout = testRun.pageLoadTimeout; this.ctx = testRun.ctx; this.fixtureCtx = testRun.fixtureCtx; + this.consoleMessages = testRun.consoleMessages; } async init () { @@ -94,8 +95,9 @@ export default class TestRunBookmark { this.testRun.phase = TEST_RUN_PHASE.inBookmarkRestore; - this.testRun.ctx = this.ctx; - this.testRun.fixtureCtx = this.fixtureCtx; + this.testRun.ctx = this.ctx; + this.testRun.fixtureCtx = this.fixtureCtx; + this.testRun.consoleMessages = this.consoleMessages; try { await this._restoreSpeed(); diff --git a/src/test-run/browser-console-messages.js b/src/test-run/browser-console-messages.js new file mode 100644 index 00000000000..5c7db976990 --- /dev/null +++ b/src/test-run/browser-console-messages.js @@ -0,0 +1,46 @@ +// ------------------------------------------------------------- +// WARNING: this file is used by both the client and the server. +// Do not use any browser or node-specific API! +// ------------------------------------------------------------- +import { assignIn } from 'lodash'; +import Assignable from '../utils/assignable'; + + +export default class BrowserConsoleMessages extends Assignable { + constructor (obj) { + super(); + + this.log = []; + this.info = []; + this.warn = []; + this.error = []; + + this._assignFrom(obj); + } + + _getAssignableProperties () { + return [ + { name: 'log' }, + { name: 'info' }, + { name: 'warn' }, + { name: 'error' } + ]; + } + + concat (consoleMessages) { + this.log = this.log.concat(consoleMessages.log); + this.info = this.info.concat(consoleMessages.info); + this.warn = this.warn.concat(consoleMessages.warn); + this.error = this.error.concat(consoleMessages.error); + } + + addMessage (type, msg) { + this[type].push(msg); + } + + getCopy () { + const { log, info, warn, error } = this; + + return assignIn({}, { log, info, warn, error }); + } +} diff --git a/src/test-run/commands/actions.js b/src/test-run/commands/actions.js index 26f19a70ae2..7b00a5bd80f 100644 --- a/src/test-run/commands/actions.js +++ b/src/test-run/commands/actions.js @@ -418,6 +418,12 @@ export class GetNativeDialogHistoryCommand { } } +export class GetBrowserConsoleMessagesCommand { + constructor () { + this.type = TYPE.getBrowserConsoleMessages; + } +} + export class SetTestSpeedCommand extends Assignable { constructor (obj) { super(obj); diff --git a/src/test-run/commands/type.js b/src/test-run/commands/type.js index 5e1be877b48..bebd3807c04 100644 --- a/src/test-run/commands/type.js +++ b/src/test-run/commands/type.js @@ -34,6 +34,7 @@ export default { switchToMainWindow: 'switch-to-main-window', setNativeDialogHandler: 'set-native-dialog-handler', getNativeDialogHistory: 'get-native-dialog-history', + getBrowserConsoleMessages: 'get-browser-console-messages', setTestSpeed: 'set-test-speed', setPageLoadTimeout: 'set-page-load-timeout', debug: 'debug', diff --git a/src/test-run/index.js b/src/test-run/index.js index 02029124939..ce8a560b4e8 100644 --- a/src/test-run/index.js +++ b/src/test-run/index.js @@ -22,7 +22,7 @@ import ROLE_PHASE from '../role/phase'; import TestRunBookmark from './bookmark'; import ClientFunctionBuilder from '../client-functions/client-function-builder'; import ReporterPluginHost from '../reporter/plugin-host'; - +import BrowserConsoleMessages from './browser-console-messages'; import { TakeScreenshotOnFailCommand } from './commands/browser-manipulation'; import { SetNativeDialogHandlerCommand, SetTestSpeedCommand, SetPageLoadTimeoutCommand } from './commands/actions'; @@ -73,6 +73,8 @@ export default class TestRun extends Session { this.speed = this.opts.speed; this.pageLoadTimeout = this.opts.pageLoadTimeout; + this.consoleMessages = new BrowserConsoleMessages(); + this.pendingRequest = null; this.pendingPageError = null; @@ -269,6 +271,12 @@ export default class TestRun extends Session { return this.executeCommand(new PrepareBrowserManipulationCommand(command.type), callsite); } + async _enqueueBrowserConsoleMessagesCommand (command, callsite) { + await this._enqueueCommand(command, callsite); + + return this.consoleMessages.getCopy(); + } + async _enqueueSetBreakpointCommand (callsite, error) { debugLogger.showBreakpoint(this.id, this.browserConnection.userAgent, callsite, error); @@ -344,6 +352,8 @@ export default class TestRun extends Session { var currentTaskRejectedByError = pageError && this._handlePageErrorStatus(pageError); + this.consoleMessages.concat(driverStatus.consoleMessages); + if (!currentTaskRejectedByError && driverStatus.isCommandResult) { if (this.currentDriverTask.command.type === COMMAND_TYPE.testDone) { this._resolveCurrentDriverTask(); @@ -426,6 +436,9 @@ export default class TestRun extends Session { if (command.type === COMMAND_TYPE.assertion) return this._executeAssertion(command, callsite); + if (command.type === COMMAND_TYPE.getBrowserConsoleMessages) + return await this._enqueueBrowserConsoleMessagesCommand(command, callsite); + return this._enqueueCommand(command, callsite); } @@ -448,8 +461,9 @@ export default class TestRun extends Session { } async switchToCleanRun () { - this.ctx = Object.create(null); - this.fixtureCtx = Object.create(null); + this.ctx = Object.create(null); + this.fixtureCtx = Object.create(null); + this.consoleMessages = new BrowserConsoleMessages(); this.useStateSnapshot(null); diff --git a/test/functional/fixtures/api/es-next/console/pages/empty.html b/test/functional/fixtures/api/es-next/console/pages/empty.html new file mode 100644 index 00000000000..bf22ae584aa --- /dev/null +++ b/test/functional/fixtures/api/es-next/console/pages/empty.html @@ -0,0 +1,8 @@ + + +
+