From 03f09691c8dd329fdb6a1a57e894334e240f4b95 Mon Sep 17 00:00:00 2001 From: AlexanderMoskovkin Date: Thu, 21 Sep 2017 19:28:55 +0300 Subject: [PATCH] Add the capability to get browser console messages (closes #1738) --- src/api/test-controller/index.js | 7 +++ src/client/driver/driver.js | 45 +++++++++++++++++++ src/test-run/commands/actions.js | 6 +++ src/test-run/commands/type.js | 1 + .../api/es-next/console/pages/empty.html | 8 ++++ .../api/es-next/console/pages/index.html | 27 +++++++++++ .../fixtures/api/es-next/console/test.js | 9 ++++ .../console/testcafe-fixtures/console-test.js | 39 ++++++++++++++++ .../typescript-defs/test-controller.ts | 38 ++++++++++++++-- ts-defs/index.d.ts | 23 ++++++++++ 10 files changed, 200 insertions(+), 3 deletions(-) create mode 100644 test/functional/fixtures/api/es-next/console/pages/empty.html create mode 100644 test/functional/fixtures/api/es-next/console/pages/index.html create mode 100644 test/functional/fixtures/api/es-next/console/test.js create mode 100644 test/functional/fixtures/api/es-next/console/testcafe-fixtures/console-test.js diff --git a/src/api/test-controller/index.js b/src/api/test-controller/index.js index 472e6710362..a3cd9f22ebe 100644 --- a/src/api/test-controller/index.js +++ b/src/api/test-controller/index.js @@ -24,6 +24,7 @@ import { SwitchToMainWindowCommand, SetNativeDialogHandlerCommand, GetNativeDialogHistoryCommand, + GetConsoleMessagesCommand, SetTestSpeedCommand, SetPageLoadTimeoutCommand, UseRoleCommand @@ -239,6 +240,12 @@ export default class TestController { return this.testRun.executeCommand(new GetNativeDialogHistoryCommand(), callsite); } + _getConsoleMessages$ () { + var callsite = getCallsiteForMethod('getConsoleMessages'); + + return this.testRun.executeCommand(new GetConsoleMessagesCommand(), callsite); + } + _expect$ (actual) { return new Assertion(actual, this); } diff --git a/src/client/driver/driver.js b/src/client/driver/driver.js index 026adc337c1..b8d23beb9ba 100644 --- a/src/client/driver/driver.js +++ b/src/client/driver/driver.js @@ -59,6 +59,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 +115,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 +126,19 @@ export default class Driver { return this.contextStorage.getItem(TEST_SPEED); } + get consoleMessages () { + return this.contextStorage.getItem(CONSOLE_MESSAGES) || { + log: [], + info: [], + error: [], + warn: [] + }; + } + + set consoleMessages (messages) { + return this.contextStorage.setItem(CONSOLE_MESSAGES, messages); + } + // Error handling _onJsError (err) { // NOTE: we should not send any message to the server if we've @@ -156,6 +171,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[meth].push(Array.prototype.slice.call(args).join(' ')); + + this.consoleMessages = messages; + } // Status _addPendingErrorToStatus (status) { @@ -327,6 +362,13 @@ export default class Driver { })); } + _onGetConsoleMessagesCommand () { + this._onReady(new DriverStatus({ + isCommandResult: true, + result: this.consoleMessages + })); + } + _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.getConsoleMessages) + this._onGetConsoleMessagesCommand(command); + else if (command.type === COMMAND_TYPE.setTestSpeed) this._onSetTestSpeedCommand(command); diff --git a/src/test-run/commands/actions.js b/src/test-run/commands/actions.js index 26f19a70ae2..1056c57b898 100644 --- a/src/test-run/commands/actions.js +++ b/src/test-run/commands/actions.js @@ -418,6 +418,12 @@ export class GetNativeDialogHistoryCommand { } } +export class GetConsoleMessagesCommand { + constructor () { + this.type = TYPE.getConsoleMessages; + } +} + 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..fe9bc9131ad 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', + getConsoleMessages: 'get-console-messages', setTestSpeed: 'set-test-speed', setPageLoadTimeout: 'set-page-load-timeout', debug: 'debug', 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 @@ + + + + Console messages (empty page) + + + + diff --git a/test/functional/fixtures/api/es-next/console/pages/index.html b/test/functional/fixtures/api/es-next/console/pages/index.html new file mode 100644 index 00000000000..b56df5d8467 --- /dev/null +++ b/test/functional/fixtures/api/es-next/console/pages/index.html @@ -0,0 +1,27 @@ + + + + Console messages + + + + +Reload + + + + diff --git a/test/functional/fixtures/api/es-next/console/test.js b/test/functional/fixtures/api/es-next/console/test.js new file mode 100644 index 00000000000..0f7f93b28f9 --- /dev/null +++ b/test/functional/fixtures/api/es-next/console/test.js @@ -0,0 +1,9 @@ +describe('[API] t.getConsoleMessages()', function () { + it('Should return messages from the console', function () { + return runTests('./testcafe-fixtures/console-test.js', 't.getConsoleMessages'); + }); + + it('Should format messages if several args were passed', function () { + return runTests('./testcafe-fixtures/console-test.js', 'messages formatting'); + }); +}); diff --git a/test/functional/fixtures/api/es-next/console/testcafe-fixtures/console-test.js b/test/functional/fixtures/api/es-next/console/testcafe-fixtures/console-test.js new file mode 100644 index 00000000000..ea1603d8259 --- /dev/null +++ b/test/functional/fixtures/api/es-next/console/testcafe-fixtures/console-test.js @@ -0,0 +1,39 @@ +fixture `Double Click`; + + +test + .page `http://localhost:3000/fixtures/api/es-next/console/pages/index.html` +('t.getConsoleMessages', async t => { + let messages = await t.getConsoleMessages(); + + await t + .expect(messages.error).eql(['error1']) + .expect(messages.warn).eql(['warn1']) + .expect(messages.log).eql(['log1']) + .expect(messages.info).eql(['info1']) + + .click('#trigger-messages') + + // Check the driver keeps the messages between page reloads + .click('#reload'); + + messages = await t.getConsoleMessages(); + + await t + .expect(messages.error).eql(['error1', 'error2']) + .expect(messages.warn).eql(['warn1', 'warn2']) + .expect(messages.log).eql(['log1', 'log2']) + .expect(messages.info).eql(['info1', 'info2']); +}); + +test + .page `http://localhost:3000/fixtures/api/es-next/console/pages/empty.html` +('messages formatting', async t => { + /* eslint-disable no-console */ + await t.eval(() => console.log('a', 1, null, void 0, ['b', 2], { c: 3 })); + /* eslint-enable no-console */ + + const { log } = await t.getConsoleMessages(); + + await t.expect(log[0]).eql('a 1 null undefined b,2 [object Object]'); +}); diff --git a/test/server/data/test-suites/typescript-defs/test-controller.ts b/test/server/data/test-suites/typescript-defs/test-controller.ts index a9f1ed6a681..f5aa3d63bee 100644 --- a/test/server/data/test-suites/typescript-defs/test-controller.ts +++ b/test/server/data/test-suites/typescript-defs/test-controller.ts @@ -1,6 +1,6 @@ /// -import { Selector, ClientFunction } from 'testcafe'; -import { expect } from 'chai'; +import {Selector, ClientFunction} from 'testcafe'; +import {expect} from 'chai'; fixture(`TestController`) .page(`http://localhost:3000/fixtures/api/es-next/assertions/pages/index.html`); @@ -704,7 +704,7 @@ test('Take a screenshot in quarantine mode', async t => { test('Type text in input', async t => { - await t.typeText('#input', 'a', { replace: true }); + await t.typeText('#input', 'a', {replace: true}); }); @@ -746,3 +746,35 @@ test('Chaining callsites', async t => { .click('#error') .click('#btn3'); }); + +test('t.getConsoleMessages', async t => { + let messages = await t.getConsoleMessages(); + + await t + .expect(messages.error).eql(['error1']) + .expect(messages.warn).eql(['warn1']) + .expect(messages.log).eql(['log1']) + .expect(messages.info).eql(['info1']) + + .click('#trigger-messages') + + // Check the driver keeps the messages between page reloads + .click('#reload'); + + messages = await t.getConsoleMessages(); + + await t + .expect(messages.error).eql(['error1', 'error2']) + .expect(messages.warn).eql(['warn1', 'warn2']) + .expect(messages.log).eql(['log1', 'log2']) + .expect(messages.info).eql(['info1', 'info2']); +}); + +test('messages formatting', async t => { + // Several arguments + await t.eval(() => console.log('a', 1, null, void 0, ['b', 2], {c: 3})) + + let {log} = await t.getConsoleMessages(); + + await t.expect(log[0]).eql('a 1 null undefined b,2 [object Object]'); +}); \ No newline at end of file diff --git a/ts-defs/index.d.ts b/ts-defs/index.d.ts index 2020675e839..e1dcf527a4b 100644 --- a/ts-defs/index.d.ts +++ b/ts-defs/index.d.ts @@ -799,6 +799,25 @@ interface NativeDialogHistoryItem { url: string; } +interface ConsoleMessagesCollection { + /** + * TODO: + */ + log: string[], + /** + * TODO: + */ + warn: string[], + /** + * TODO: + */ + error: string[], + /** + * TODO: + */ + info: string[] +} + interface TestController { /** * Dictionary that is shared between test hook functions and test code. @@ -1004,6 +1023,10 @@ interface TestController { * corresponds to a certain native dialog that appears in the main window or in an `