diff --git a/@types/junit-report-builder.d.ts b/@types/junit-report-builder.d.ts deleted file mode 100644 index f7c80a25c..000000000 --- a/@types/junit-report-builder.d.ts +++ /dev/null @@ -1,11 +0,0 @@ -declare module "junit-report-builder" { - interface Builder { - build: () => void - } - - const cssValue: { - newBuilder: () => Builder - } - - export = cssValue -} diff --git a/packages/wdio-junit-reporter/src/index.ts b/packages/wdio-junit-reporter/src/index.ts index 7e76b4883..abf62f3ed 100644 --- a/packages/wdio-junit-reporter/src/index.ts +++ b/packages/wdio-junit-reporter/src/index.ts @@ -4,7 +4,7 @@ import os from 'node:os' import type { RunnerStats, SuiteStats, TestStats } from '@wdio/reporter' import WDIOReporter from '@wdio/reporter' -import junit from 'junit-report-builder' +import junitReportBuilder, { type Builder as JUnitReportBuilder } from 'junit-report-builder' import type { JUnitReporterOptions } from './types.js' import { limit } from './utils.js' @@ -276,7 +276,7 @@ class JunitReporter extends WDIOReporter { } private _buildJunitXml(runner: RunnerStats) { - const builder = junit.newBuilder() + const builder = (junitReportBuilder as unknown as JUnitReportBuilder).newBuilder() if (runner.config.hostname !== undefined && runner.config.hostname.indexOf('browserstack') > -1) { // NOTE: deviceUUID is used to build sanitizedCapabilities resulting in a ever-changing package name in runner.sanitizedCapabilities when running Android tests under Browserstack. (i.e. ht79v1a03938.android.9) // NOTE: platformVersion is used to build sanitizedCapabilities which can be incorrect and includes a minor version for iOS which is not guaranteed to be the same under Browserstack. @@ -317,7 +317,14 @@ class JunitReporter extends WDIOReporter { return builder.build() as unknown as string } - private _buildOrderedReport(builder: any, runner: RunnerStats, specFileName: string, type: string, isCucumberFrameworkRunner: boolean) { + private _buildOrderedReport(builder: JUnitReportBuilder, runner: RunnerStats, specFileName: string, type: string, isCucumberFrameworkRunner: boolean) { + const suiteKeys = Object.keys(this.suites) + + if (suiteKeys.length === 0) { + const error = this.runnerStat?.error ?? 'No tests found' + return builder.testCase().failure(error) + } + for (const suiteKey of Object.keys(this.suites)) { /** * ignore root before all diff --git a/packages/wdio-reporter/src/index.ts b/packages/wdio-reporter/src/index.ts index 35d1c56ba..42512b3cb 100644 --- a/packages/wdio-reporter/src/index.ts +++ b/packages/wdio-reporter/src/index.ts @@ -209,6 +209,7 @@ export default class WDIOReporter extends EventEmitter { if (this.runnerStat) { this.runnerStat.failures = runner.failures this.runnerStat.retries = runner.retries + this.runnerStat.error = runner.error this.runnerStat.complete() this.onRunnerEnd(this.runnerStat) } diff --git a/packages/wdio-reporter/src/stats/runner.ts b/packages/wdio-reporter/src/stats/runner.ts index cfa709449..946a8c705 100644 --- a/packages/wdio-reporter/src/stats/runner.ts +++ b/packages/wdio-reporter/src/stats/runner.ts @@ -19,6 +19,7 @@ export default class RunnerStats extends RunnableStats { retry?: number failures?: number retries?: number + error?: string constructor (runner: Options.RunnerStart) { super('runner') diff --git a/packages/wdio-runner/src/index.ts b/packages/wdio-runner/src/index.ts index edfc95d0c..4ff77a56b 100644 --- a/packages/wdio-runner/src/index.ts +++ b/packages/wdio-runner/src/index.ts @@ -33,6 +33,7 @@ export default class Runner extends EventEmitter { private _cid?: string private _specs?: string[] private _caps?: Capabilities.RequestedStandaloneCapabilities | Capabilities.RequestedMultiremoteCapabilities + private _sessionInitError?: Error /** * run test suite @@ -272,7 +273,9 @@ export default class Runner extends EventEmitter { const browser = await this._startSession(config, caps) as WebdriverIO.Browser // return null if session couldn't get established - if (!browser) { return } + if (!browser) { + return + } /** * register global helper method to fetch elements @@ -366,8 +369,9 @@ export default class Runner extends EventEmitter { if (this._isMultiremote) { _setGlobal('multiremotebrowser', this._browser, config.injectGlobals) } - } catch (err: any) { - log.error(err) + } catch (error: any) { + log.error(error) + this._sessionInitError = error return } @@ -393,7 +397,7 @@ export default class Runner extends EventEmitter { config: this._config, isMultiremote: this._isMultiremote, instanceOptions: {}, - capabilities: { ...this._configParser!.getCapabilities() }, + capabilities: this._caps, retry: this._specFileRetryAttempts }) } @@ -401,7 +405,8 @@ export default class Runner extends EventEmitter { this._reporter!.emit('runner:end', { failures, cid: this._cid, - retries + retries, + ...(this._sessionInitError instanceof Error ? { error: this._sessionInitError.message } : {}) } as Options.RunnerEnd) try { await this._reporter!.waitForSync() diff --git a/packages/wdio-runner/src/reporter.ts b/packages/wdio-runner/src/reporter.ts index d837b37cb..af3e94113 100644 --- a/packages/wdio-runner/src/reporter.ts +++ b/packages/wdio-runner/src/reporter.ts @@ -49,13 +49,13 @@ export default class BaseReporter { uid?: string file?: string title?: string - error?: Error + error?: string sessionId?: string config?: unknown isMultiremote?: boolean instanceOptions?: Options.Testrunner capabilities?: unknown - retry?: number + retry?: number, }) { payload.cid = this._cid diff --git a/packages/wdio-runner/tests/index.test.ts b/packages/wdio-runner/tests/index.test.ts index 02662b139..23d4467c2 100644 --- a/packages/wdio-runner/tests/index.test.ts +++ b/packages/wdio-runner/tests/index.test.ts @@ -476,6 +476,7 @@ describe('wdio-runner', () => { const runner = new WDIORunner() runner['_configParser'] = { getCapabilities: vi.fn().mockReturnValue([{ browserName: 'safari' }]) } as any runner['_browser'] = {} as unknown as BrowserObject + runner['_caps'] = { 'browserName': 'safari' } runner['_reporter'] = { waitForSync: vi.fn().mockReturnValue(Promise.resolve()), emit: vi.fn() @@ -485,9 +486,7 @@ describe('wdio-runner', () => { const args = [ ['runner:start', { - 'capabilities': { - '0': { 'browserName': 'safari' } - }, + 'capabilities': { 'browserName': 'safari' }, 'cid': undefined, 'config': undefined, 'instanceOptions': {}, diff --git a/packages/wdio-spec-reporter/src/index.ts b/packages/wdio-spec-reporter/src/index.ts index 2ae7b9e68..2443aded7 100644 --- a/packages/wdio-spec-reporter/src/index.ts +++ b/packages/wdio-spec-reporter/src/index.ts @@ -213,6 +213,14 @@ export default class SpecReporter extends WDIOReporter { // Get the results const results = this.getResultDisplay(preface) + if (results.length === 0 && runner.error) { + results.push( + this.setMessageColor(`${this.getSymbol(State.FAILED)} Failed to create a session:`, State.FAILED), + runner.error, + '' + ) + } + // If there are no test results then return nothing if (results.length === 0) { return diff --git a/packages/wdio-spec-reporter/tests/__snapshots__/index.test.ts.snap b/packages/wdio-spec-reporter/tests/__snapshots__/index.test.ts.snap index c55fe4146..6467d849c 100644 --- a/packages/wdio-spec-reporter/tests/__snapshots__/index.test.ts.snap +++ b/packages/wdio-spec-reporter/tests/__snapshots__/index.test.ts.snap @@ -273,6 +273,21 @@ exports[`SpecReporter > onlyFailures > true > 1 failure 1`] = ` ] `; +exports[`SpecReporter > printReport > should print a report even if session could not be created 1`] = ` +[ + [ + "------------------------------------------------------------------ +[loremipsum 50 Windows 10 #0-0] Running: loremipsum (v50) on Windows 10 +[loremipsum 50 Windows 10 #0-0] Session ID: foobar +[loremipsum 50 Windows 10 #0-0] +[loremipsum 50 Windows 10 #0-0] red ✖ Failed to create a session: +[loremipsum 50 Windows 10 #0-0] No tests found +[loremipsum 50 Windows 10 #0-0] +", + ], +] +`; + exports[`SpecReporter > printReport > with disabled sharable Sauce report links > should print the default Sauce Labs job details page link 1`] = `[]`; exports[`SpecReporter > printReport > with normal setup > should print jobs of all instance when run with multiremote 1`] = ` diff --git a/packages/wdio-spec-reporter/tests/index.test.ts b/packages/wdio-spec-reporter/tests/index.test.ts index 1bc106e18..675c60a12 100644 --- a/packages/wdio-spec-reporter/tests/index.test.ts +++ b/packages/wdio-spec-reporter/tests/index.test.ts @@ -341,6 +341,16 @@ describe('SpecReporter', () => { expect(printReporter.write.mock.calls.length).toBe(0) }) + + it('should print a report even if session could not be created', () => { + printReporter['_suiteUids'] = [] + printReporter.suites = {} + const runner = getRunnerConfig() + runner.error = 'No tests found' + + printReporter.printReport(runner) + expect(printReporter.write.mock.calls).toMatchSnapshot() + }) }) describe('getHeaderDisplay', () => { diff --git a/packages/wdio-types/src/Options.ts b/packages/wdio-types/src/Options.ts index 43fcbe826..c5a2c68ac 100644 --- a/packages/wdio-types/src/Options.ts +++ b/packages/wdio-types/src/Options.ts @@ -358,4 +358,5 @@ export interface RunnerEnd { failures: number cid: string retries: number + error?: string } diff --git a/packages/webdriver/src/utils.ts b/packages/webdriver/src/utils.ts index eb828084e..3f05d9f47 100644 --- a/packages/webdriver/src/utils.ts +++ b/packages/webdriver/src/utils.ts @@ -91,7 +91,7 @@ export async function startWebDriverSession (params: RemoteConfig): Promise<{ se } catch (err) { log.error(err) const message = getSessionError(err as Error, params) - throw new Error('Failed to create session.\n' + message) + throw new Error(message) } const sessionId = response.value.sessionId || response.sessionId diff --git a/packages/webdriver/tests/utils.test.ts b/packages/webdriver/tests/utils.test.ts index ed9502d76..25b1d393d 100644 --- a/packages/webdriver/tests/utils.test.ts +++ b/packages/webdriver/tests/utils.test.ts @@ -331,7 +331,7 @@ describe('utils', () => { logLevel: 'warn', capabilities: {} }).catch((err) => err) - expect(error.message).toContain('Failed to create session') + expect(error.message).toContain('Invalid URL') }) it('should break if JSONWire and WebDriver caps are mixed together', async () => {