From 7ee4db2e8d5bd4d378bf4468c187b56ab87c5f3f Mon Sep 17 00:00:00 2001 From: Christian Bromann Date: Thu, 31 Mar 2022 14:49:43 +0200 Subject: [PATCH 1/2] Fix maximum call stack size exceeded in Jasmine --- packages/wdio-runner/src/reporter.ts | 8 ++- packages/wdio-runner/tests/reporter.test.ts | 68 ++++++++++++++------- packages/wdio-utils/src/shim.ts | 9 +++ tests/jasmine/test-timeout.js | 5 ++ tests/smoke.runner.js | 29 ++++++++- 5 files changed, 93 insertions(+), 26 deletions(-) diff --git a/packages/wdio-runner/src/reporter.ts b/packages/wdio-runner/src/reporter.ts index aafb6ab0ac9..0070701a1c5 100644 --- a/packages/wdio-runner/src/reporter.ts +++ b/packages/wdio-runner/src/reporter.ts @@ -155,7 +155,9 @@ export default class BaseReporter { ReporterClass = reporter as Reporters.ReporterClass options.logFile = options.setLogFile ? options.setLogFile(this._cid, ReporterClass.name) - : this.getLogFile(ReporterClass.name) + : typeof options.logFile === 'string' + ? options.logFile + : this.getLogFile(ReporterClass.name) options.writeStream = this.getWriteStreamObject(ReporterClass.name) return new ReporterClass(options) } @@ -178,7 +180,9 @@ export default class BaseReporter { ReporterClass = initialisePlugin(reporter, 'reporter').default as Reporters.ReporterClass options.logFile = options.setLogFile ? options.setLogFile(this._cid, reporter) - : this.getLogFile(reporter) + : typeof options.logFile === 'string' + ? options.logFile + : this.getLogFile(reporter) options.writeStream = this.getWriteStreamObject(reporter) return new ReporterClass(options) } diff --git a/packages/wdio-runner/tests/reporter.test.ts b/packages/wdio-runner/tests/reporter.test.ts index 90f932a1194..0ddf4c43c15 100644 --- a/packages/wdio-runner/tests/reporter.test.ts +++ b/packages/wdio-runner/tests/reporter.test.ts @@ -1,4 +1,4 @@ -import type { Capability } from '@wdio/config' +import type { Options, Capabilities } from '@wdio/types' import BaseReporter from '../src/reporter' @@ -14,7 +14,7 @@ class CustomReporter { } } -const capability: Capability = { browserName: 'foo' } +const capability: Capabilities.Capabilities = { browserName: 'foo' } process.send = jest.fn() @@ -26,7 +26,7 @@ describe('BaseReporter', () => { 'dot', ['dot', { foo: 'bar' }] ] - }, '0-0', capability) + } as Options.Testrunner, '0-0', capability) expect(reporter['_reporters']).toHaveLength(2) }) @@ -38,12 +38,30 @@ describe('BaseReporter', () => { 'dot', ['dot', { foo: 'bar' }] ] - }, '0-0', capability) + } as Options.Testrunner, '0-0', capability) expect(reporter.getLogFile('foobar')) .toMatch(/(\\|\/)foo(\\|\/)bar(\\|\/)wdio-0-0-foobar-reporter.log/) }) + it('can set custom logFile property', () => { + const reporter = new BaseReporter({ + outputDir: '/foo/bar', + reporters: [ + [CustomReporter, { foo: 'bar', logFile: '/barfoo.log' }], + ['dot', { foo: 'bar', logFile: '/foobar.log' }] + ], + capabilities: [capability] + } as Options.Testrunner, '0-0', capability) + + // @ts-expect-error + expect(reporter['_reporters'][0].options.logFile) + .toBe('/barfoo.log') + // @ts-expect-error + expect(reporter['_reporters'][1].options.logFile) + .toBe('/foobar.log') + }) + it('should output log file to custom outputDir', () => { const reporter = new BaseReporter({ outputDir: '/foo/bar', @@ -52,8 +70,9 @@ describe('BaseReporter', () => { foo: 'bar', outputDir: '/foo/bar/baz' }] - ] - }, '0-0', capability) + ], + capabilities: [capability] + } as Options.Testrunner, '0-0', capability) expect(reporter.getLogFile('dot')) .toMatch(/(\\|\/)foo(\\|\/)bar(\\|\/)baz(\\|\/)wdio-0-0-dot-reporter.log/) }) @@ -70,7 +89,7 @@ describe('BaseReporter', () => { } }] ] - }, '0-0', capability) + } as Options.Testrunner, '0-0', capability) expect(reporter.getLogFile('dot')) .toMatch(/(\\|\/)foo(\\|\/)bar(\\|\/)wdio-results-0-0.xml/) @@ -87,7 +106,7 @@ describe('BaseReporter', () => { outputFileFormat: 'foo' }] ] - }, '0-0', capability) + } as Options.Testrunner, '0-0', capability) }).toThrow('outputFileFormat must be a function') }) @@ -97,7 +116,7 @@ describe('BaseReporter', () => { 'dot', ['dot', { foo: 'bar' }] ] - }, '0-0', capability) + } as Options.Testrunner, '0-0', capability) expect(reporter.getLogFile('foobar')).toBe(undefined) }) @@ -109,7 +128,7 @@ describe('BaseReporter', () => { 'dot', ['dot', { foo: 'bar' }] ] - }, '0-0', capability) + } as Options.Testrunner, '0-0', capability) const payload = { foo: [1, 2, 3] } reporter.emit('runner:start', payload) @@ -127,7 +146,7 @@ describe('BaseReporter', () => { 'dot', ['dot', { foo: 'bar' }] ] - }, '0-0', capability) + } as Options.Testrunner, '0-0', capability) const payload = { foo: [1, 2, 3] } reporter.emit('test:fail', payload) @@ -142,8 +161,9 @@ describe('BaseReporter', () => { it('should allow to load custom reporters', () => { const reporter = new BaseReporter({ outputDir: '/foo/bar', - reporters: [CustomReporter] - }, '0-0', capability) + reporters: [CustomReporter] as any, + capabilities: [capability] + } as Options.Testrunner, '0-0', capability) expect(reporter['_reporters']).toHaveLength(1) // @ts-ignore expect(reporter['_reporters'][0].isCustom).toBe(true) @@ -154,8 +174,9 @@ describe('BaseReporter', () => { outputDir: '/foo/bar', reporters: [[CustomReporter, { outputDir: '/foo/baz/bar' - }]] - }, '0-0', capability) + }]] as any, + capabilities: [capability] + } as Options.Testrunner, '0-0', capability) expect(reporter.getLogFile('CustomReporter')).toMatch(/(\\|\/)foo(\\|\/)baz(\\|\/)bar(\\|\/)wdio-0-0-CustomReporter-reporter.log/) }) @@ -165,8 +186,9 @@ describe('BaseReporter', () => { try { new BaseReporter({ outputDir: '/foo/bar', - reporters: [{ foo: 'bar' }] - }, '0-0', capability) + reporters: [{ foo: 'bar' } as any], + capabilities: [capability] + } as Options.Testrunner, '0-0', capability) } catch (err: any) { expect(err.message).toBe('Invalid reporters config') } @@ -178,8 +200,9 @@ describe('BaseReporter', () => { const start = Date.now() const reporter = new BaseReporter({ outputDir: '/foo/bar', - reporters: [CustomReporter, CustomReporter] - }, '0-0', capability) + reporters: [CustomReporter, CustomReporter] as any, + capabilities: [capability] + } as Options.Testrunner, '0-0', capability) // @ts-ignore test reporter param setTimeout(() => (reporter['_reporters'][0].inSync = true), 100) @@ -194,10 +217,11 @@ describe('BaseReporter', () => { const reporter = new BaseReporter({ outputDir: '/foo/bar', - reporters: [CustomReporter], + reporters: [CustomReporter] as any, reporterSyncInterval: 10, - reporterSyncTimeout: 100 - }, '0-0', capability) + reporterSyncTimeout: 100, + capabilities: [capability] + } as Options.Testrunner, '0-0', capability) // @ts-ignore test reporter param setTimeout(() => (reporter['_reporters'][0].inSync = true), 112) diff --git a/packages/wdio-utils/src/shim.ts b/packages/wdio-utils/src/shim.ts index 5e7eb15057c..bc19f99796a 100644 --- a/packages/wdio-utils/src/shim.ts +++ b/packages/wdio-utils/src/shim.ts @@ -411,6 +411,15 @@ async function executeAsync(this: any, fn: Function, retries: Retries, args: any asyncSpec = asyncSpecBefore } + /** + * if test fail in jasmine (e.g. timeout) the finally statement + * would not be executed + */ + if (isJasmine) { + // @ts-ignore + global.expect = expectSync + } + return await result } catch (err: any) { if (retries.limit > retries.attempts) { diff --git a/tests/jasmine/test-timeout.js b/tests/jasmine/test-timeout.js index 3144a143dfe..3bea9e8d0cb 100644 --- a/tests/jasmine/test-timeout.js +++ b/tests/jasmine/test-timeout.js @@ -2,4 +2,9 @@ describe('Jasmine timeout test', () => { it('should fail due to custom timeout', () => { return new Promise((resolve) => setTimeout(resolve, 2000)) }, 1000) + + // https://github.com/webdriverio/webdriverio/issues/8126 + it('do not run into max callstack errors', () => { + expect(true).toBe(false) + }) }) diff --git a/tests/smoke.runner.js b/tests/smoke.runner.js index 65f5069fc73..f7bd4ba2726 100644 --- a/tests/smoke.runner.js +++ b/tests/smoke.runner.js @@ -29,8 +29,16 @@ async function runTests (tests) { const testFilter = process.argv[2] if (process.env.CI || testFilter) { - // sequential const testsFiltered = testFilter ? tests.filter(test => test.name === testFilter) : tests + + if (testsFiltered.length === 0) { + throw new Error( + `No test was selected! Smoke test "${testFilter}" ` + + `picked but only ${tests.map(test => test.name).join(', ')} available` + ) + } + + // sequential for (let test of testsFiltered) { await test() } @@ -113,15 +121,32 @@ const jasmineReporter = async () => { * Jasmine timeout test */ const jasmineTimeout = async () => { + const logFile = path.join(__dirname, 'jasmineTimeout.spec.log') const err = await launch( path.resolve(__dirname, 'helpers', 'config.js'), { specs: [path.resolve(__dirname, 'jasmine', 'test-timeout.js')], - reporters: [['spec', { outputDir: __dirname }]], + reporters: [ + ['spec', { + outputDir: __dirname, + stdout: false, + logFile + }] + ], framework: 'jasmine' } ).catch(err => err) assert.strictEqual(err.message, 'Smoke test failed') + + const specLogs = (await fs.readFile(logFile)).toString() + assert.ok( + specLogs.includes('Error: Timeout - Async function did not complete within 1000ms (custom timeout)'), + 'spec was not failing due to timeout error' + ) + assert.ok( + !specLogs.includes('RangeError: Maximum call stack size exceeded'), + 'spec was failing due to unexpected "Maximum call stack size exceeded"' + ) } /** From fa04a012cc3e8a92b38d297a858eb963c460165d Mon Sep 17 00:00:00 2001 From: Christian Bromann Date: Thu, 31 Mar 2022 15:19:33 +0200 Subject: [PATCH 2/2] limit behavior for non fibers environments --- packages/wdio-utils/src/shim.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/wdio-utils/src/shim.ts b/packages/wdio-utils/src/shim.ts index bc19f99796a..9c9f5320483 100644 --- a/packages/wdio-utils/src/shim.ts +++ b/packages/wdio-utils/src/shim.ts @@ -413,9 +413,10 @@ async function executeAsync(this: any, fn: Function, retries: Retries, args: any /** * if test fail in jasmine (e.g. timeout) the finally statement - * would not be executed + * would not be executed in async mode where fibers is not supported */ - if (isJasmine) { + const nodeVersion = parseInt(process.version.slice(1).split('.').shift() || '0', 10) + if (isJasmine && nodeVersion >= 16) { // @ts-ignore global.expect = expectSync }