creevey 0.10.0-beta.40 → 0.10.0-beta.41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/web/assets/{index-B0Xv0lOY.js → index-C47njyZV.js} +2 -2
- package/dist/client/web/index.html +1 -1
- package/dist/server/config.js +3 -2
- package/dist/server/config.js.map +1 -1
- package/dist/server/master/runner.js +6 -4
- package/dist/server/master/runner.js.map +1 -1
- package/dist/server/{reporter.d.ts → reporters/creevey.d.ts} +0 -4
- package/dist/server/reporters/creevey.js +63 -0
- package/dist/server/reporters/creevey.js.map +1 -0
- package/dist/server/reporters/index.d.ts +2 -0
- package/dist/server/reporters/index.js +16 -0
- package/dist/server/reporters/index.js.map +1 -0
- package/dist/server/reporters/junit.d.ts +16 -0
- package/dist/server/reporters/junit.js +165 -0
- package/dist/server/reporters/junit.js.map +1 -0
- package/dist/server/reporters/teamcity.d.ts +7 -0
- package/dist/server/reporters/teamcity.js +60 -0
- package/dist/server/reporters/teamcity.js.map +1 -0
- package/dist/server/worker/start.js +2 -0
- package/dist/server/worker/start.js.map +1 -1
- package/dist/types.d.ts +7 -3
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
- package/src/server/config.ts +2 -1
- package/src/server/master/runner.ts +6 -4
- package/src/server/reporters/creevey.ts +71 -0
- package/src/server/reporters/index.ts +11 -0
- package/src/server/reporters/junit.ts +205 -0
- package/src/server/reporters/teamcity.ts +74 -0
- package/src/server/worker/start.ts +2 -0
- package/src/types.ts +7 -4
- package/dist/server/reporter.js +0 -117
- package/dist/server/reporter.js.map +0 -1
- package/src/server/reporter.ts +0 -141
@@ -0,0 +1,71 @@
|
|
1
|
+
import chalk from 'chalk';
|
2
|
+
import Logger from 'loglevel';
|
3
|
+
import prefix from 'loglevel-plugin-prefix';
|
4
|
+
import { FakeTest, isImageError, TEST_EVENTS } from '../../types.js';
|
5
|
+
import EventEmitter from 'events';
|
6
|
+
|
7
|
+
const testLevels: Record<string, string> = {
|
8
|
+
INFO: chalk.green('PASS'),
|
9
|
+
WARN: chalk.yellow('START'),
|
10
|
+
ERROR: chalk.red('FAIL'),
|
11
|
+
};
|
12
|
+
|
13
|
+
export class CreeveyReporter {
|
14
|
+
private logger: Logger.Logger | null = null;
|
15
|
+
// TODO Output in better way, like vitest, maybe
|
16
|
+
constructor(runner: EventEmitter) {
|
17
|
+
runner.on(TEST_EVENTS.TEST_BEGIN, (test: FakeTest) => {
|
18
|
+
this.getLogger(test.creevey).warn(chalk.cyan(test.fullTitle()));
|
19
|
+
});
|
20
|
+
runner.on(TEST_EVENTS.TEST_PASS, (test: FakeTest) => {
|
21
|
+
this.getLogger(test.creevey).info(chalk.cyan(test.fullTitle()), chalk.gray(`(${test.duration} ms)`));
|
22
|
+
});
|
23
|
+
runner.on(TEST_EVENTS.TEST_FAIL, (test: FakeTest, error) => {
|
24
|
+
this.getLogger(test.creevey).error(
|
25
|
+
chalk.cyan(test.fullTitle()),
|
26
|
+
chalk.gray(`(${test.duration} ms)`),
|
27
|
+
'\n ',
|
28
|
+
this.getErrors(
|
29
|
+
error,
|
30
|
+
(error, imageName) => `${chalk.bold(imageName ?? test.creevey.browserName)}:${error}`,
|
31
|
+
(error) => error.stack ?? error.message,
|
32
|
+
).join('\n '),
|
33
|
+
);
|
34
|
+
});
|
35
|
+
}
|
36
|
+
|
37
|
+
private getLogger(options: { sessionId: string; browserName: string }) {
|
38
|
+
if (this.logger) return this.logger;
|
39
|
+
const { sessionId, browserName } = options;
|
40
|
+
const testLogger = Logger.getLogger(sessionId);
|
41
|
+
|
42
|
+
this.logger = prefix.apply(testLogger, {
|
43
|
+
format(level) {
|
44
|
+
return `[${browserName}:${chalk.gray(process.pid)}] ${testLevels[level]} => ${chalk.gray(sessionId)}`;
|
45
|
+
},
|
46
|
+
});
|
47
|
+
|
48
|
+
return this.logger;
|
49
|
+
}
|
50
|
+
|
51
|
+
private getErrors(
|
52
|
+
error: unknown,
|
53
|
+
imageErrorToString: (error: string, imageName?: string) => string,
|
54
|
+
errorToString: (error: Error) => string,
|
55
|
+
): string[] {
|
56
|
+
const errors = [];
|
57
|
+
if (!(error instanceof Error)) {
|
58
|
+
errors.push(error as string);
|
59
|
+
} else if (!isImageError(error)) {
|
60
|
+
errors.push(errorToString(error));
|
61
|
+
} else if (typeof error.images == 'string') {
|
62
|
+
errors.push(imageErrorToString(error.images));
|
63
|
+
} else {
|
64
|
+
const imageErrors = error.images ?? {};
|
65
|
+
Object.keys(imageErrors).forEach((imageName) => {
|
66
|
+
errors.push(imageErrorToString(imageErrors[imageName] ?? '', imageName));
|
67
|
+
});
|
68
|
+
}
|
69
|
+
return errors;
|
70
|
+
}
|
71
|
+
}
|
@@ -0,0 +1,11 @@
|
|
1
|
+
import { BaseReporter } from '../../types.js';
|
2
|
+
import { CreeveyReporter } from './creevey.js';
|
3
|
+
import { JUnitReporter } from './junit.js';
|
4
|
+
import { TeamcityReporter } from './teamcity.js';
|
5
|
+
|
6
|
+
export function getReporter(reporter: BaseReporter | 'creevey' | 'teamcity' | 'junit'): BaseReporter {
|
7
|
+
if (reporter === 'creevey') return CreeveyReporter;
|
8
|
+
if (reporter === 'teamcity') return TeamcityReporter;
|
9
|
+
if (reporter === 'junit') return JUnitReporter;
|
10
|
+
return reporter;
|
11
|
+
}
|
@@ -0,0 +1,205 @@
|
|
1
|
+
import EventEmitter from 'events';
|
2
|
+
import { dirname, resolve } from 'path';
|
3
|
+
import { closeSync, existsSync, mkdirSync, openSync, writeFileSync } from 'fs';
|
4
|
+
import { TEST_EVENTS, FakeTest } from '../../types.js';
|
5
|
+
import { logger } from '../logger.js';
|
6
|
+
|
7
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
8
|
+
class IndentedLogger<T = any> {
|
9
|
+
private currentIndent = '';
|
10
|
+
|
11
|
+
constructor(private baseLog: (text: string) => T) {}
|
12
|
+
|
13
|
+
indent(): void {
|
14
|
+
this.currentIndent += ' ';
|
15
|
+
}
|
16
|
+
|
17
|
+
unindent(): void {
|
18
|
+
this.currentIndent = this.currentIndent.substring(0, this.currentIndent.length - 4);
|
19
|
+
}
|
20
|
+
|
21
|
+
log(text: string): T {
|
22
|
+
return this.baseLog(this.currentIndent + text);
|
23
|
+
}
|
24
|
+
}
|
25
|
+
|
26
|
+
// NOTE: This is a reworked copy of the JUnitReporter class from Vitest.
|
27
|
+
export class JUnitReporter {
|
28
|
+
private reportFile: string;
|
29
|
+
private fileFd?: number;
|
30
|
+
private logger: IndentedLogger<void>;
|
31
|
+
private suites: Record<string, FakeTest[]> = {};
|
32
|
+
// TODO classnameTemplate
|
33
|
+
constructor(runner: EventEmitter, options: { reportDir: string; reporterOptions: { outputFile?: string } }) {
|
34
|
+
const { reportDir, reporterOptions } = options;
|
35
|
+
|
36
|
+
this.reportFile = reporterOptions.outputFile ?? resolve(reportDir, 'junit.xml');
|
37
|
+
|
38
|
+
this.logger = new IndentedLogger((text) => {
|
39
|
+
this.fileFd ??= openSync(this.reportFile, 'w+');
|
40
|
+
|
41
|
+
writeFileSync(this.fileFd, `${text}\n`);
|
42
|
+
});
|
43
|
+
|
44
|
+
runner.on(TEST_EVENTS.RUN_BEGIN, () => {
|
45
|
+
this.suites = {};
|
46
|
+
|
47
|
+
const outputDirectory = dirname(this.reportFile);
|
48
|
+
if (!existsSync(outputDirectory)) {
|
49
|
+
mkdirSync(outputDirectory, { recursive: true });
|
50
|
+
}
|
51
|
+
|
52
|
+
this.fileFd = openSync(this.reportFile, 'w+');
|
53
|
+
});
|
54
|
+
runner.on(TEST_EVENTS.TEST_PASS, (test: FakeTest) => {
|
55
|
+
const suite = this.suites[test.parent.title] ?? [];
|
56
|
+
suite.push(test);
|
57
|
+
});
|
58
|
+
runner.on(TEST_EVENTS.TEST_FAIL, (test: FakeTest) => {
|
59
|
+
const suite = this.suites[test.parent.title] ?? [];
|
60
|
+
suite.push(test);
|
61
|
+
});
|
62
|
+
runner.on(TEST_EVENTS.RUN_END, () => {
|
63
|
+
this.onFinished();
|
64
|
+
});
|
65
|
+
}
|
66
|
+
|
67
|
+
private writeElement(name: string, attrs: Record<string, string | number | undefined>, children?: () => void): void {
|
68
|
+
const pairs: string[] = [];
|
69
|
+
for (const key in attrs) {
|
70
|
+
const attr = attrs[key];
|
71
|
+
if (attr === undefined) {
|
72
|
+
continue;
|
73
|
+
}
|
74
|
+
|
75
|
+
pairs.push(`${key}="${escapeXML(attr)}"`);
|
76
|
+
}
|
77
|
+
|
78
|
+
this.logger.log(`<${name}${pairs.length ? ` ${pairs.join(' ')}` : ''}>`);
|
79
|
+
this.logger.indent();
|
80
|
+
children?.call(this);
|
81
|
+
this.logger.unindent();
|
82
|
+
|
83
|
+
this.logger.log(`</${name}>`);
|
84
|
+
}
|
85
|
+
|
86
|
+
private writeTasks(tests: FakeTest[]): void {
|
87
|
+
for (const test of tests) {
|
88
|
+
const classname = test.parent.title;
|
89
|
+
|
90
|
+
this.writeElement(
|
91
|
+
'testcase',
|
92
|
+
{
|
93
|
+
classname,
|
94
|
+
name: test.title,
|
95
|
+
time: getDuration(test),
|
96
|
+
},
|
97
|
+
() => {
|
98
|
+
if (test.state === 'failed') {
|
99
|
+
const error = test.err;
|
100
|
+
this.writeElement('failure', { message: error });
|
101
|
+
}
|
102
|
+
},
|
103
|
+
);
|
104
|
+
}
|
105
|
+
}
|
106
|
+
|
107
|
+
private onFinished(): void {
|
108
|
+
this.logger.log('<?xml version="1.0" encoding="UTF-8" ?>');
|
109
|
+
|
110
|
+
const suites = Object.entries(this.suites).map(([name, tests]) => {
|
111
|
+
return {
|
112
|
+
name,
|
113
|
+
tests,
|
114
|
+
failures: tests.filter((test) => test.state === 'failed').length,
|
115
|
+
time: tests.reduce((acc, test) => acc + (test.duration ?? 0), 0),
|
116
|
+
};
|
117
|
+
});
|
118
|
+
const stats = suites.reduce(
|
119
|
+
(s, { tests, failures, time }) => {
|
120
|
+
s.tests += tests.length;
|
121
|
+
s.failures += failures;
|
122
|
+
s.time += time;
|
123
|
+
return s;
|
124
|
+
},
|
125
|
+
{ name: 'creevey tests', tests: 0, failures: 0, time: 0 },
|
126
|
+
);
|
127
|
+
|
128
|
+
this.writeElement('testsuites', { ...stats, time: executionTime(stats.time) }, () => {
|
129
|
+
suites.forEach(({ name, tests, failures, time }) => {
|
130
|
+
this.writeElement(
|
131
|
+
'testsuite',
|
132
|
+
{
|
133
|
+
name,
|
134
|
+
tests: tests.length,
|
135
|
+
failures,
|
136
|
+
time: executionTime(time),
|
137
|
+
},
|
138
|
+
() => {
|
139
|
+
this.writeTasks(tests);
|
140
|
+
},
|
141
|
+
);
|
142
|
+
});
|
143
|
+
});
|
144
|
+
|
145
|
+
if (this.reportFile) {
|
146
|
+
logger().info(`JUNIT report written to ${this.reportFile}`);
|
147
|
+
}
|
148
|
+
|
149
|
+
if (this.fileFd) {
|
150
|
+
closeSync(this.fileFd);
|
151
|
+
this.fileFd = undefined;
|
152
|
+
}
|
153
|
+
}
|
154
|
+
}
|
155
|
+
|
156
|
+
// https://gist.github.com/john-doherty/b9195065884cdbfd2017a4756e6409cc
|
157
|
+
function removeInvalidXMLCharacters(value: string, removeDiscouragedChars: boolean): string {
|
158
|
+
let regex =
|
159
|
+
// eslint-disable-next-line no-control-regex
|
160
|
+
/([\0-\x08\v\f\x0E-\x1F\uFFFD\uFFFE\uFFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF])/g;
|
161
|
+
value = String(value).replace(regex, '');
|
162
|
+
|
163
|
+
if (removeDiscouragedChars) {
|
164
|
+
// remove everything discouraged by XML 1.0 specifications
|
165
|
+
regex = new RegExp(
|
166
|
+
'([\\x7F-\\x84]|[\\x86-\\x9F]|[\\uFDD0-\\uFDEF]|\\uD83F[\\uDFFE\\uDFFF]|(?:\\uD87F[\\uDF' +
|
167
|
+
'FE\\uDFFF])|\\uD8BF[\\uDFFE\\uDFFF]|\\uD8FF[\\uDFFE\\uDFFF]|(?:\\uD93F[\\uDFFE\\uD' +
|
168
|
+
'FFF])|\\uD97F[\\uDFFE\\uDFFF]|\\uD9BF[\\uDFFE\\uDFFF]|\\uD9FF[\\uDFFE\\uDFFF]' +
|
169
|
+
'|\\uDA3F[\\uDFFE\\uDFFF]|\\uDA7F[\\uDFFE\\uDFFF]|\\uDABF[\\uDFFE\\uDFFF]|(?:\\' +
|
170
|
+
'uDAFF[\\uDFFE\\uDFFF])|\\uDB3F[\\uDFFE\\uDFFF]|\\uDB7F[\\uDFFE\\uDFFF]|(?:\\uDBBF' +
|
171
|
+
'[\\uDFFE\\uDFFF])|\\uDBFF[\\uDFFE\\uDFFF](?:[\\0-\\t\\v\\f\\x0E-\\u2027\\u202A-\\uD7FF\\' +
|
172
|
+
'uE000-\\uFFFF]|[\\uD800-\\uDBFF][\\uDC00-\\uDFFF]|[\\uD800-\\uDBFF](?![\\uDC00-\\uDFFF])|' +
|
173
|
+
'(?:[^\\uD800-\\uDBFF]|^)[\\uDC00-\\uDFFF]))',
|
174
|
+
'g',
|
175
|
+
);
|
176
|
+
|
177
|
+
value = value.replace(regex, '');
|
178
|
+
}
|
179
|
+
|
180
|
+
return value;
|
181
|
+
}
|
182
|
+
|
183
|
+
function escapeXML(value: string | number): string {
|
184
|
+
return removeInvalidXMLCharacters(
|
185
|
+
String(value)
|
186
|
+
.replace(/&/g, '&')
|
187
|
+
.replace(/"/g, '"')
|
188
|
+
.replace(/'/g, ''')
|
189
|
+
.replace(/</g, '<')
|
190
|
+
.replace(/>/g, '>'),
|
191
|
+
true,
|
192
|
+
);
|
193
|
+
}
|
194
|
+
|
195
|
+
function executionTime(durationMS: number) {
|
196
|
+
return (durationMS / 1000).toLocaleString('en-US', {
|
197
|
+
useGrouping: false,
|
198
|
+
maximumFractionDigits: 10,
|
199
|
+
});
|
200
|
+
}
|
201
|
+
|
202
|
+
function getDuration(task: FakeTest): string | undefined {
|
203
|
+
const duration = task.duration ?? 0;
|
204
|
+
return executionTime(duration);
|
205
|
+
}
|
@@ -0,0 +1,74 @@
|
|
1
|
+
import { FakeTest, Images, isDefined, TEST_EVENTS } from '../../types.js';
|
2
|
+
import EventEmitter from 'events';
|
3
|
+
|
4
|
+
export class TeamcityReporter {
|
5
|
+
constructor(runner: EventEmitter, options: { reportDir: string }) {
|
6
|
+
const { reportDir } = options;
|
7
|
+
|
8
|
+
runner.on(TEST_EVENTS.TEST_BEGIN, (test: FakeTest) => {
|
9
|
+
console.log(`##teamcity[testStarted name='${this.escape(test.fullTitle())}' flowId='${test.creevey.workerId}']`);
|
10
|
+
});
|
11
|
+
|
12
|
+
runner.on(TEST_EVENTS.TEST_PASS, (test: FakeTest) => {
|
13
|
+
console.log(`##teamcity[testFinished name='${this.escape(test.fullTitle())}' flowId='${test.creevey.workerId}']`);
|
14
|
+
});
|
15
|
+
|
16
|
+
runner.on(TEST_EVENTS.TEST_FAIL, (test: FakeTest, error: Error) => {
|
17
|
+
const browserName = this.escape(test.creevey.browserName);
|
18
|
+
Object.entries(test.creevey.images).forEach(([name, image]) => {
|
19
|
+
if (!image) return;
|
20
|
+
const filePath = test
|
21
|
+
.titlePath()
|
22
|
+
.slice(0, -1)
|
23
|
+
.concat(name == browserName ? [] : [browserName])
|
24
|
+
.map(this.escape)
|
25
|
+
.join('/');
|
26
|
+
|
27
|
+
const { error: _, ...rest } = image;
|
28
|
+
Object.values(rest as Partial<Images>)
|
29
|
+
.filter(isDefined)
|
30
|
+
.forEach((fileName) => {
|
31
|
+
console.log(`##teamcity[publishArtifacts '${reportDir}/${filePath}/${fileName} => report/${filePath}']`);
|
32
|
+
console.log(
|
33
|
+
`##teamcity[testMetadata testName='${this.escape(
|
34
|
+
test.fullTitle(),
|
35
|
+
)}' type='image' value='report/${filePath}/${fileName}' flowId='${test.creevey.workerId}']`,
|
36
|
+
);
|
37
|
+
});
|
38
|
+
});
|
39
|
+
|
40
|
+
// Output failed test as passed due TC don't support retry mechanic
|
41
|
+
// https://teamcity-support.jetbrains.com/hc/en-us/community/posts/207216829-Count-test-as-successful-if-at-least-one-try-is-successful?page=1#community_comment_207394125
|
42
|
+
|
43
|
+
if (test.creevey.willRetry)
|
44
|
+
console.log(
|
45
|
+
`##teamcity[testFinished name='${this.escape(test.fullTitle())}' flowId='${test.creevey.workerId}']`,
|
46
|
+
);
|
47
|
+
else
|
48
|
+
console.log(
|
49
|
+
`##teamcity[testFailed name='${this.escape(test.fullTitle())}' message='${this.escape(
|
50
|
+
error.message,
|
51
|
+
)}' details='${this.escape(error.stack ?? '')}' flowId='${test.creevey.workerId}']`,
|
52
|
+
);
|
53
|
+
});
|
54
|
+
}
|
55
|
+
|
56
|
+
private escape = (str: string): string => {
|
57
|
+
if (!str) return '';
|
58
|
+
return (
|
59
|
+
str
|
60
|
+
.toString()
|
61
|
+
// eslint-disable-next-line no-control-regex
|
62
|
+
.replace(/\x1B.*?m/g, '')
|
63
|
+
.replace(/\|/g, '||')
|
64
|
+
.replace(/\n/g, '|n')
|
65
|
+
.replace(/\r/g, '|r')
|
66
|
+
.replace(/\[/g, '|[')
|
67
|
+
.replace(/\]/g, '|]')
|
68
|
+
.replace(/\u0085/g, '|x')
|
69
|
+
.replace(/\u2028/g, '|l')
|
70
|
+
.replace(/\u2029/g, '|p')
|
71
|
+
.replace(/'/g, "|'")
|
72
|
+
);
|
73
|
+
};
|
74
|
+
}
|
@@ -215,6 +215,8 @@ export async function start(browser: string, gridUrl: string, config: Config, op
|
|
215
215
|
} else {
|
216
216
|
const result = {
|
217
217
|
sessionId,
|
218
|
+
browserName: baseContext.browserName,
|
219
|
+
workerId: process.pid,
|
218
220
|
images: imagesContext.images,
|
219
221
|
error: serializeError(error),
|
220
222
|
duration,
|
package/src/types.ts
CHANGED
@@ -189,7 +189,7 @@ export interface DockerAuth {
|
|
189
189
|
}
|
190
190
|
|
191
191
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
192
|
-
export type BaseReporter = new (runner: EventEmitter, options: { reporterOptions: any }) => void;
|
192
|
+
export type BaseReporter = new (runner: EventEmitter, options: { reportDir: string; reporterOptions: any }) => void;
|
193
193
|
|
194
194
|
export interface Config {
|
195
195
|
/**
|
@@ -224,8 +224,9 @@ export interface Config {
|
|
224
224
|
/**
|
225
225
|
* Specify a custom reporter for test results. Creevey accepts only mocha-like reporters
|
226
226
|
* @optional
|
227
|
+
* @default 'creevey'
|
227
228
|
*/
|
228
|
-
reporter: BaseReporter;
|
229
|
+
reporter: BaseReporter | 'creevey' | 'teamcity' | 'junit';
|
229
230
|
/**
|
230
231
|
* Options which are used by reporter
|
231
232
|
*/
|
@@ -428,6 +429,8 @@ export interface TestResult {
|
|
428
429
|
duration?: number;
|
429
430
|
attachments?: string[];
|
430
431
|
sessionId?: string;
|
432
|
+
browserName?: string;
|
433
|
+
workerId?: number;
|
431
434
|
}
|
432
435
|
|
433
436
|
export class ImagesError extends Error {
|
@@ -508,15 +511,15 @@ export interface FakeTest {
|
|
508
511
|
state?: 'failed' | 'passed';
|
509
512
|
// NOTE > duration, > duration / 2, > 0
|
510
513
|
speed?: 'slow' | 'medium' | 'fast';
|
511
|
-
err?:
|
514
|
+
err?: string;
|
512
515
|
// NOTE: image files
|
513
516
|
attachments?: string[];
|
514
517
|
|
515
518
|
// NOTE: Creevey specific fields
|
516
519
|
creevey: {
|
517
|
-
reportDir: string;
|
518
520
|
sessionId: string;
|
519
521
|
browserName: string;
|
522
|
+
workerId: number;
|
520
523
|
willRetry: boolean;
|
521
524
|
images: Partial<Record<string, Partial<Images>>>;
|
522
525
|
};
|
package/dist/server/reporter.js
DELETED
@@ -1,117 +0,0 @@
|
|
1
|
-
"use strict";
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
4
|
-
};
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
6
|
-
exports.TeamcityReporter = exports.CreeveyReporter = void 0;
|
7
|
-
const chalk_1 = __importDefault(require("chalk"));
|
8
|
-
const loglevel_1 = __importDefault(require("loglevel"));
|
9
|
-
const loglevel_plugin_prefix_1 = __importDefault(require("loglevel-plugin-prefix"));
|
10
|
-
const types_js_1 = require("../types.js");
|
11
|
-
const testLevels = {
|
12
|
-
INFO: chalk_1.default.green('PASS'),
|
13
|
-
WARN: chalk_1.default.yellow('START'),
|
14
|
-
ERROR: chalk_1.default.red('FAIL'),
|
15
|
-
};
|
16
|
-
class CreeveyReporter {
|
17
|
-
logger = null;
|
18
|
-
// TODO Output in better way, like vitest, maybe
|
19
|
-
constructor(runner) {
|
20
|
-
runner.on(types_js_1.TEST_EVENTS.TEST_BEGIN, (test) => {
|
21
|
-
this.getLogger(test.creevey).warn(chalk_1.default.cyan(test.fullTitle()));
|
22
|
-
});
|
23
|
-
runner.on(types_js_1.TEST_EVENTS.TEST_PASS, (test) => {
|
24
|
-
this.getLogger(test.creevey).info(chalk_1.default.cyan(test.fullTitle()), chalk_1.default.gray(`(${test.duration} ms)`));
|
25
|
-
});
|
26
|
-
runner.on(types_js_1.TEST_EVENTS.TEST_FAIL, (test, error) => {
|
27
|
-
this.getLogger(test.creevey).error(chalk_1.default.cyan(test.fullTitle()), chalk_1.default.gray(`(${test.duration} ms)`), '\n ', this.getErrors(error, (error, imageName) => `${chalk_1.default.bold(imageName ?? test.creevey.browserName)}:${error}`, (error) => error.stack ?? error.message).join('\n '));
|
28
|
-
});
|
29
|
-
}
|
30
|
-
getLogger(options) {
|
31
|
-
if (this.logger)
|
32
|
-
return this.logger;
|
33
|
-
const { sessionId, browserName } = options;
|
34
|
-
const testLogger = loglevel_1.default.getLogger(sessionId);
|
35
|
-
this.logger = loglevel_plugin_prefix_1.default.apply(testLogger, {
|
36
|
-
format(level) {
|
37
|
-
return `[${browserName}:${chalk_1.default.gray(process.pid)}] ${testLevels[level]} => ${chalk_1.default.gray(sessionId)}`;
|
38
|
-
},
|
39
|
-
});
|
40
|
-
return this.logger;
|
41
|
-
}
|
42
|
-
getErrors(error, imageErrorToString, errorToString) {
|
43
|
-
const errors = [];
|
44
|
-
if (!(error instanceof Error)) {
|
45
|
-
errors.push(error);
|
46
|
-
}
|
47
|
-
else if (!(0, types_js_1.isImageError)(error)) {
|
48
|
-
errors.push(errorToString(error));
|
49
|
-
}
|
50
|
-
else if (typeof error.images == 'string') {
|
51
|
-
errors.push(imageErrorToString(error.images));
|
52
|
-
}
|
53
|
-
else {
|
54
|
-
const imageErrors = error.images ?? {};
|
55
|
-
Object.keys(imageErrors).forEach((imageName) => {
|
56
|
-
errors.push(imageErrorToString(imageErrors[imageName] ?? '', imageName));
|
57
|
-
});
|
58
|
-
}
|
59
|
-
return errors;
|
60
|
-
}
|
61
|
-
}
|
62
|
-
exports.CreeveyReporter = CreeveyReporter;
|
63
|
-
class TeamcityReporter {
|
64
|
-
constructor(runner) {
|
65
|
-
runner.on(types_js_1.TEST_EVENTS.TEST_BEGIN, (test) => {
|
66
|
-
console.log(`##teamcity[testStarted name='${this.escape(test.fullTitle())}' flowId='${process.pid}']`);
|
67
|
-
});
|
68
|
-
runner.on(types_js_1.TEST_EVENTS.TEST_PASS, (test) => {
|
69
|
-
console.log(`##teamcity[testFinished name='${this.escape(test.fullTitle())}' flowId='${process.pid}']`);
|
70
|
-
});
|
71
|
-
runner.on(types_js_1.TEST_EVENTS.TEST_FAIL, (test, error) => {
|
72
|
-
const browserName = this.escape(test.creevey.browserName);
|
73
|
-
Object.entries(test.creevey.images).forEach(([name, image]) => {
|
74
|
-
if (!image)
|
75
|
-
return;
|
76
|
-
const filePath = test
|
77
|
-
.titlePath()
|
78
|
-
.slice(0, -1)
|
79
|
-
.concat(name == browserName ? [] : [browserName])
|
80
|
-
.map(this.escape)
|
81
|
-
.join('/');
|
82
|
-
const { error: _, ...rest } = image;
|
83
|
-
Object.values(rest)
|
84
|
-
.filter(types_js_1.isDefined)
|
85
|
-
.forEach((fileName) => {
|
86
|
-
console.log(`##teamcity[publishArtifacts '${test.creevey.reportDir}/${filePath}/${fileName} => report/${filePath}']`);
|
87
|
-
console.log(`##teamcity[testMetadata testName='${this.escape(test.fullTitle())}' type='image' value='report/${filePath}/${fileName}' flowId='${process.pid}']`);
|
88
|
-
});
|
89
|
-
});
|
90
|
-
// Output failed test as passed due TC don't support retry mechanic
|
91
|
-
// https://teamcity-support.jetbrains.com/hc/en-us/community/posts/207216829-Count-test-as-successful-if-at-least-one-try-is-successful?page=1#community_comment_207394125
|
92
|
-
if (test.creevey.willRetry)
|
93
|
-
console.log(`##teamcity[testFinished name='${this.escape(test.fullTitle())}' flowId='${process.pid}']`);
|
94
|
-
else
|
95
|
-
console.log(`##teamcity[testFailed name='${this.escape(test.fullTitle())}' message='${this.escape(error.message)}' details='${this.escape(error.stack ?? '')}' flowId='${process.pid}']`);
|
96
|
-
});
|
97
|
-
}
|
98
|
-
escape = (str) => {
|
99
|
-
if (!str)
|
100
|
-
return '';
|
101
|
-
return (str
|
102
|
-
.toString()
|
103
|
-
// eslint-disable-next-line no-control-regex
|
104
|
-
.replace(/\x1B.*?m/g, '')
|
105
|
-
.replace(/\|/g, '||')
|
106
|
-
.replace(/\n/g, '|n')
|
107
|
-
.replace(/\r/g, '|r')
|
108
|
-
.replace(/\[/g, '|[')
|
109
|
-
.replace(/\]/g, '|]')
|
110
|
-
.replace(/\u0085/g, '|x')
|
111
|
-
.replace(/\u2028/g, '|l')
|
112
|
-
.replace(/\u2029/g, '|p')
|
113
|
-
.replace(/'/g, "|'"));
|
114
|
-
};
|
115
|
-
}
|
116
|
-
exports.TeamcityReporter = TeamcityReporter;
|
117
|
-
//# sourceMappingURL=reporter.js.map
|
@@ -1 +0,0 @@
|
|
1
|
-
{"version":3,"file":"reporter.js","sourceRoot":"","sources":["../../src/server/reporter.ts"],"names":[],"mappings":"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"}
|