diff --git a/src/api/test-controller/index.js b/src/api/test-controller/index.js index 52419dd535c..d7f4878e129 100644 --- a/src/api/test-controller/index.js +++ b/src/api/test-controller/index.js @@ -41,6 +41,8 @@ import { import { WaitCommand, DebugCommand } from '../../test-run/commands/observation'; import assertRequestHookType from '../request-hooks/assert-type'; +const originalThen = Promise.resolve().then; + export default class TestController { constructor (testRun) { this.testRun = testRun; @@ -62,10 +64,8 @@ export default class TestController { // await t2.click('#btn3'); // <-- without check it will set callsiteWithoutAwait = null, so we will lost tracking _createExtendedPromise (promise, callsite) { const extendedPromise = promise.then(identity); - const originalThen = extendedPromise.then; const markCallsiteAwaited = () => this.callsitesWithoutAwait.delete(callsite); - extendedPromise.then = function () { markCallsiteAwaited(); @@ -84,11 +84,14 @@ export default class TestController { const callsite = getCallsiteForMethod(apiMethodName); const executor = createTaskExecutor(callsite); - this.executionChain = this.executionChain.then(executor); + this.executionChain.then = originalThen; + this.executionChain = this.executionChain.then(executor); this.callsitesWithoutAwait.add(callsite); - return this._createExtendedPromise(this.executionChain, callsite); + this.executionChain = this._createExtendedPromise(this.executionChain, callsite); + + return this.executionChain; } _enqueueCommand (apiMethodName, CmdCtor, cmdArgs) { diff --git a/src/utils/handle-errors.js b/src/utils/handle-errors.js index 8676e751d9b..6e97d4d93ca 100644 --- a/src/utils/handle-errors.js +++ b/src/utils/handle-errors.js @@ -1,4 +1,5 @@ import { UnhandledPromiseRejectionError, UncaughtExceptionError } from '../errors/test-run'; +import util from 'util'; const runningTests = {}; let handlingTestErrors = false; @@ -32,7 +33,7 @@ function formatUnhandledRejectionReason (reason) { if (reason instanceof Error) return reason.stack; - return Object.prototype.toString.call(reason); + return util.inspect(reason); } function onUnhandledRejection (reason) { diff --git a/test/server/error-handle-test.js b/test/server/error-handle-test.js index d1b7403d15b..d7aaaa5e29a 100644 --- a/test/server/error-handle-test.js +++ b/test/server/error-handle-test.js @@ -1,8 +1,10 @@ -const Promise = require('pinkie'); -const expect = require('chai').expect; -const createTestCafe = require('../../lib/'); -const types = require('../../lib/errors/test-run/type'); -const handleErrors = require('../../lib/utils/handle-errors'); +const Promise = require('pinkie'); +const expect = require('chai').expect; +const createTestCafe = require('../../lib/'); +const types = require('../../lib/errors/test-run/type'); +const handleErrors = require('../../lib/utils/handle-errors'); +const TestController = require('../../lib/api/test-controller'); +const AssertionExecutor = require('../../lib/assertions/executor'); class TestRunMock { @@ -15,16 +17,69 @@ class TestRunMock { addError (err) { this.errors.push(err); } + + executeCommand (command, callsite) { + return new AssertionExecutor(command, 0, callsite).run(); + } + } describe('Global error handlers', () => { + it('unhandled promise rejection on chain assertions', () => { + let unhandledRejection = false; + + const throwErrorOnUnhandledRejection = () => { + unhandledRejection = true; + }; + + process.once('unhandledRejection', throwErrorOnUnhandledRejection); + + return Promise.resolve() + .then(() => { + return new TestController(new TestRunMock('', '')).expect(10).eql(5).expect(10).eql(10); + }) + .catch(() => { + process.removeListener('unhandledRejection', throwErrorOnUnhandledRejection); + + expect(unhandledRejection).eql(false); + }); + }); + it('format UnhandledPromiseRejection reason', () => { handleErrors.registerErrorHandlers(); handleErrors.startHandlingTestErrors(); - const reasons = [new Error('test'), null, void 0, 1, 'string message', true, { a: 1 }]; - const testRunMocks = reasons.map((reason, index) => new TestRunMock(index, reason)); - const expectedErrors = ['Error: test', '[object Null]', 'undefined', '1', 'string message', 'true', '[object Object]']; + const obj = { a: 1, b: { c: 'd', e: { f: { g: 'too deep' } } } }; + + obj.circular = obj; + + const reasons = [ + new Error('test'), + null, + void 0, + 1, + 'string message', + true, + obj, + _ => _, + [1, 2], + /regex/ + ]; + + const testRunMocks = reasons.map((reason, index) => new TestRunMock(index, reason)); + + const expectedErrors = [ + 'Error: test', + 'null', + 'undefined', + '1', + 'string message', + 'true', + '{ a: 1, b: { c: \'d\', e: { f: [Object] } }, circular: [Circular] }', + '[Function]', + '[ 1, 2 ]', + '/regex/' + ]; testRunMocks.forEach(testRun => { handleErrors.addRunningTest(testRun);