Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update reporter to be compatible with wdio v5 #14

Merged
merged 13 commits into from
Dec 14, 2019
9 changes: 8 additions & 1 deletion .babelrc
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
{
"presets": [
"es2015"
[
"env",
{
"targets": {
"node": "8"
}
}
]
]
}
4 changes: 1 addition & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,4 @@ notifications:

language: node_js
node_js:
- "6"
- "4"
- "0.12"
- "8"
65 changes: 30 additions & 35 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
'use strict';

const { EventEmitter } = require('events');
const WdioReporter = require('@wdio/reporter').default;
const { buildFormatter, events } = require('./lib/message');
const { flow } = require('lodash');
const { inherits } = require('util');

module.exports = WdioTeamcityReporter;
module.exports.reporterName = 'teamcity';

/**
* @param {object} baseReporter
Expand All @@ -17,36 +13,35 @@ module.exports.reporterName = 'teamcity';
* @param {string} reporterOptions.message
* @return {wdioTeamcityReporter}
*/
function WdioTeamcityReporter(baseReporter, wdioConf, reporterOptions = {}) {
this.baseReporter = baseReporter;

const opts = {
captureStandardOutput: typeof reporterOptions.captureStandardOutput === 'boolean'
? reporterOptions.captureStandardOutput
: false,
flowId: typeof reporterOptions.flowId === 'boolean'
? reporterOptions.flowId
: true,
name: typeof reporterOptions.message === 'string'
? reporterOptions.message
: '[title]',
};

this.enableRealTimeOutput(opts);
}

inherits(WdioTeamcityReporter, EventEmitter);

WdioTeamcityReporter.prototype.enableRealTimeOutput = function (opts) {
events.forEach(event => this.on(event, flow(buildFormatter(event, opts), output)));
};
class WdioTeamcityReporter extends WdioReporter {
constructor(reporterOptions) {
Object.assign(reporterOptions, {
captureStandardOutput: typeof reporterOptions.captureStandardOutput === 'boolean'
? reporterOptions.captureStandardOutput
: false,
flowId: typeof reporterOptions.flowId === 'boolean'
? reporterOptions.flowId
: true,
name: typeof reporterOptions.message === 'string'
? reporterOptions.message
: '[title]',
stdout: true,
});
super(reporterOptions);
this.enableRealTimeOutput(reporterOptions);
}

/**
* @param {string} msg
* @return {string}
*/
function output(msg) {
if (msg) {
console.log(msg);
enableRealTimeOutput(opts) {
events.forEach(event => {
this[event] = (...args) =>
flow(buildFormatter(event, opts), msg => {
if (msg) {
this.write(msg);
Copy link

@dylanlive dylanlive Jul 25, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's no new line appended to this.write, so everyone gets dumped as a giant blob with no new lines.

Can you add one via:
this.write(`${msg}\n`)

See example of usage.

}
})(...args);
});
}
}

module.exports.default = WdioTeamcityReporter;
module.exports.reporterName = 'teamcity';

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like wdio tries to use this as a ES6 module.
Adding module.exports.default = WdioTeamcityReporter; made it not crash for me, when trying to consume this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just added that. It should be working now. I was using importing the reporter instead of specifying the name so I didn't catch that. Thanks!

20 changes: 10 additions & 10 deletions lib/message.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,20 @@ const { camelCase, get, invoke } = require('lodash');

const paths = {
browser: ({ suite }) => ['suite', 'runner', suite.cid, 'browserName'],

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this might need an update as well. I did a console.log(ctx) to see what object properties are available - you can see the result here
https://gist.github.com/dylanlive/f508a8201f36353954d7e3b03800b297#file-ctx_object-js-L39

Based on that, this works (at least for chrome):
browser: () => 'reporter.browser.capabilities.browserName',

details: () => 'suite.err.stack',
details: () => 'suite.error.stack',
flowId: ({ suite }) => ['reporter', 'baseReporter', 'stats', 'runners', suite.cid, 'sessionID'],

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this needs to be updated. I fixed using:

flowId: () => 'reporter.browser.sessionId',

This may also work, based on the console.log output of the ctx object, but I haven't tested

flowId: () => 'reporter.driver.sessionId',

hash: () => 'suite.specHash',
message: () => 'suite.err.message',
message: () => 'suite.error.message',
title: () => 'suite.title',
};

const events = {
'suite:start': '##teamcity[testSuiteStarted flowId=\'[flowId]\' name=\'[name]\']',
'test:start': '##teamcity[testStarted flowId=\'[flowId]\' name=\'[name]\' captureStandardOutput=\'[captureStandardOutput]\']',
'test:end': '##teamcity[testFinished flowId=\'[flowId]\' name=\'[name]\']',
'test:fail': '##teamcity[testFailed flowId=\'[flowId]\' name=\'[name]\' message=\'[message]\' details=\'[details]\']',
'test:pending': '##teamcity[testIgnored flowId=\'[flowId]\' name=\'[name]\' message=\'pending\']',
'suite:end': '##teamcity[testSuiteFinished flowId=\'[flowId]\' name=\'[name]\']',
var events = {
'onSuiteStart': '##teamcity[testSuiteStarted flowId=\'[flowId]\' name=\'[name]\']',
'onTestStart': '##teamcity[testStarted flowId=\'[flowId]\' name=\'[name]\' captureStandardOutput=\'[captureStandardOutput]\']',
'onTestEnd': '##teamcity[testFinished flowId=\'[flowId]\' name=\'[name]\']',
'onTestFail': '##teamcity[testFailed flowId=\'[flowId]\' name=\'[name]\' message=\'[message]\' details=\'[details]\']',
'onTestSkip': '##teamcity[testIgnored flowId=\'[flowId]\' name=\'[name]\' message=\'pending\']',
'onSuiteEnd': '##teamcity[testSuiteFinished flowId=\'[flowId]\' name=\'[name]\']'
};

exports.buildFormatter = buildFormatter;
Expand All @@ -52,7 +52,7 @@ function buildFormatter(eventName, opts) {
return formatter;

function formatter(suite) {
if (suite.pending && eventName !== 'test:pending') {
if (suite.pending && eventName !== 'onTestSkip') {
return '';
}

Expand Down
16 changes: 12 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"description": "WebdriverIO Teamcity reporter",
"main": "index.js",
"engines": {
"node": ">=0.12"
"node": ">=8.11.0"
},
"scripts": {
"cleanup": "git reset --hard",
Expand Down Expand Up @@ -33,13 +33,21 @@
"lodash": "^4.15.0"
},
"devDependencies": {
"@wdio/cli": "^5.0.0",
"@wdio/local-runner": "^5.7.0",
"@wdio/mocha-framework": "^5.7.0",
"@wdio/reporter": "^5.7.0",
"@wdio/selenium-standalone-service": "^5.7.0",
"@wdio/sync": "^5.7.0",
"babel-cli": "^6.11.4",
"babel-preset-env": "^1.7.0",
"babel-preset-es2015": "^6.13.2",
"babel-register": "^6.11.6",
"eslint": "^2.13.1",
"in-publish": "^2.0.0",
"tape": "^4.6.0",
"wdio-mocha-framework": "^0.2.13",
"webdriverio": "^4.2.5"
"tape": "^4.6.0"
},
"peerDependencies": {
"@wdio/cli": "^5.0.0"
}
}
12 changes: 5 additions & 7 deletions test/integration/wdio.conf.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
'use strict';

const teamcity = require('../../');
const teamcity = require('../../').default;

exports.config = {
specs: ['./test/integration/*.js'],

maxInstances: 2,

capabilities: [{
maxInstances: 2,
browserName: 'firefox',
Expand All @@ -26,15 +24,15 @@ exports.config = {

framework: 'mocha',

reporters: [teamcity],

reporterOptions: {
reporters: [[teamcity, {
flowId: false,
message: '[browser]/[title]', // [browser] [title] [hash]
},
}]],

mochaOpts: {
timeout: 60000,
ui: 'tdd',
},

services: ['selenium-standalone'],
};
16 changes: 8 additions & 8 deletions test/unit/events.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,29 +14,29 @@ const opts = {
test('formatter for events', t => {
const reporter = mockReporterContext();

t.test('suite:start', a => {
const msg = buildFormatter('suite:start', opts).call(reporter, mockSuite('suite:start'));
t.test('onSuiteStart', a => {
const msg = buildFormatter('onSuiteStart', opts).call(reporter, mockSuite('onSuiteStart'));

a.plan(1);
assertMsg(a, '##teamcity[testSuiteStarted ', msg);
});

t.test('test:start', a => {
const msg = buildFormatter('test:start', opts).call(reporter, mockSuite('test:start'));
t.test('onTestStart', a => {
const msg = buildFormatter('onTestStart', opts).call(reporter, mockSuite('onTestStart'));

a.plan(1);
assertMsg(a, '##teamcity[testStarted ', msg);
});

t.test('test:end', a => {
const msg = buildFormatter('test:end', opts).call(reporter, mockSuite('test:end'));
t.test('onTestEnd', a => {
const msg = buildFormatter('onTestEnd', opts).call(reporter, mockSuite('onTestEnd'));

a.plan(1);
assertMsg(a, '##teamcity[testFinished ', msg);
});

t.test('suite:end', a => {
const msg = buildFormatter('suite:end', opts).call(reporter, mockSuite('suite:end'));
t.test('onSuiteEnd', a => {
const msg = buildFormatter('onSuiteEnd', opts).call(reporter, mockSuite('onSuiteEnd'));

a.plan(1);
assertMsg(a, '##teamcity[testSuiteFinished ', msg);
Expand Down
40 changes: 23 additions & 17 deletions test/unit/index.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,31 @@
'use strict';

const Reporter = require('../../');
const mockReporterContext = require('./fixture/reporter-context');
const Reporter = require('../../').default;
const test = require('tape');

const events = [
'start',
'suite:start',
'hook:start',
'hook:end',
'test:start',
'test:end',
'test:pass',
'test:fail',
'test:pending',
'suite:end',
'end',
'onRunnerStart',
'onBeforeCommand',
'onAfterCommand',
'onScreenshot',
'onSuiteStart',
'onHookStart',
'onHookEnd',
'onTestStart',
'onTestPass',
'onTestFail',
'onTestSkip',
'onTestEnd',
'onSuiteEnd',
'onRunnerEnd',
];

const mockedContext = mockReporterContext();
const reporter = new Reporter(mockedContext.baseReporter);
const reporter = new Reporter({});
reporter.outputStream = {
write(msg) {
console.log(msg);
},
};

events.forEach(event => test(event, t => {
const data = {
Expand All @@ -36,9 +42,9 @@ events.forEach(event => test(event, t => {
};

if (event.indexOf('fail') > -1) {
data.err = new Error('artificial error');
data.error = new Error('artificial error');
}

reporter.emit(event, data);
reporter[event](data);
t.end();
}));