@qanalyzer/forge-wdio 1.1.1
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/LICENSE +202 -0
- package/README.md +136 -0
- package/dist/__fixtures__/saucedemo-13.json +183 -0
- package/dist/cucumber-tags.d.ts +15 -0
- package/dist/cucumber-tags.js +60 -0
- package/dist/enrich-screenshots.d.ts +2 -0
- package/dist/enrich-screenshots.js +51 -0
- package/dist/failure-screenshot-buffer.d.ts +20 -0
- package/dist/failure-screenshot-buffer.js +35 -0
- package/dist/helpers.d.ts +36 -0
- package/dist/helpers.js +101 -0
- package/dist/hooks.d.ts +23 -0
- package/dist/hooks.js +61 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +26 -0
- package/dist/metadata-manager.d.ts +13 -0
- package/dist/metadata-manager.js +19 -0
- package/dist/publish.d.ts +6 -0
- package/dist/publish.js +54 -0
- package/dist/report-builder.d.ts +25 -0
- package/dist/report-builder.js +83 -0
- package/dist/reporter.d.ts +44 -0
- package/dist/reporter.js +290 -0
- package/dist/results-buffer.d.ts +17 -0
- package/dist/results-buffer.js +44 -0
- package/dist/service.d.ts +48 -0
- package/dist/service.js +106 -0
- package/package.json +61 -0
package/dist/reporter.js
ADDED
|
@@ -0,0 +1,290 @@
|
|
|
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.QaWdioReporter = void 0;
|
|
7
|
+
const reporter_1 = __importDefault(require("@wdio/reporter"));
|
|
8
|
+
const forge_commons_1 = require("@qanalyzer/forge-commons");
|
|
9
|
+
const cucumber_tags_1 = require("./cucumber-tags");
|
|
10
|
+
const metadata_manager_1 = require("./metadata-manager");
|
|
11
|
+
const publish_1 = require("./publish");
|
|
12
|
+
const report_builder_1 = require("./report-builder");
|
|
13
|
+
const results_buffer_1 = require("./results-buffer");
|
|
14
|
+
function ancestorTitles(test, suiteStack) {
|
|
15
|
+
if (suiteStack.length > 0) {
|
|
16
|
+
return suiteStack.map((s) => s.title).filter((t) => t && t !== '(root)');
|
|
17
|
+
}
|
|
18
|
+
if (test.parent && test.parent !== '(root)') {
|
|
19
|
+
return [test.parent];
|
|
20
|
+
}
|
|
21
|
+
return [];
|
|
22
|
+
}
|
|
23
|
+
function failureMessages(test) {
|
|
24
|
+
const errors = test.errors ?? [];
|
|
25
|
+
return errors
|
|
26
|
+
.map((e) => {
|
|
27
|
+
if (!e)
|
|
28
|
+
return '';
|
|
29
|
+
if (typeof e === 'string')
|
|
30
|
+
return e;
|
|
31
|
+
const err = e;
|
|
32
|
+
return err.stack || err.message || String(e);
|
|
33
|
+
})
|
|
34
|
+
.filter(Boolean);
|
|
35
|
+
}
|
|
36
|
+
function suiteTags(suite) {
|
|
37
|
+
const tags = suite.tags;
|
|
38
|
+
return Array.isArray(tags) ? tags : [];
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* WebdriverIO reporter for QAnalyzer.
|
|
42
|
+
*
|
|
43
|
+
* Configure:
|
|
44
|
+
* reporters: [[QaWdioReporter, { disableWebdriverStepsReporting: true }]]
|
|
45
|
+
* // Cucumber:
|
|
46
|
+
* reporters: [[QaWdioReporter, { useCucumber: true }]]
|
|
47
|
+
*
|
|
48
|
+
* Mocha: each `it()` → FR41 assertion; `qa.step` → `meta.qa.steps`.
|
|
49
|
+
* Cucumber: each scenario suite → FR41 assertion; Gherkin steps → `meta.qa.steps` (FR135).
|
|
50
|
+
*/
|
|
51
|
+
class QaWdioReporter extends reporter_1.default {
|
|
52
|
+
qaOptions;
|
|
53
|
+
disableWebdriverStepsReporting;
|
|
54
|
+
disableWebdriverScreenshotsReporting;
|
|
55
|
+
useCucumber;
|
|
56
|
+
suiteStack = [];
|
|
57
|
+
currentFile = 'unknown';
|
|
58
|
+
scenario = null;
|
|
59
|
+
constructor(options = {}) {
|
|
60
|
+
const { disableWebdriverStepsReporting = true, disableWebdriverScreenshotsReporting = true, useCucumber = false, outputDir, mode, projectKey, debug, launchName, frameworkPackage, frameworkName, reporterName, fallback, file, ...rest } = options;
|
|
61
|
+
super({
|
|
62
|
+
stdout: true,
|
|
63
|
+
writeStream: process.stdout,
|
|
64
|
+
outputDir,
|
|
65
|
+
...rest,
|
|
66
|
+
});
|
|
67
|
+
this.disableWebdriverStepsReporting = disableWebdriverStepsReporting;
|
|
68
|
+
this.disableWebdriverScreenshotsReporting =
|
|
69
|
+
disableWebdriverScreenshotsReporting;
|
|
70
|
+
this.useCucumber = useCucumber;
|
|
71
|
+
this.qaOptions = {
|
|
72
|
+
mode,
|
|
73
|
+
projectKey,
|
|
74
|
+
debug,
|
|
75
|
+
launchName,
|
|
76
|
+
file,
|
|
77
|
+
frameworkPackage: frameworkPackage ?? '@wdio/cli',
|
|
78
|
+
frameworkName: frameworkName ?? 'wdio',
|
|
79
|
+
reporterName: reporterName ?? '@qanalyzer/forge-wdio',
|
|
80
|
+
fallback,
|
|
81
|
+
};
|
|
82
|
+
results_buffer_1.ResultsBuffer.reset(this.qaOptions);
|
|
83
|
+
forge_commons_1.QAnalyzerReporter.getInstance(this.qaOptions);
|
|
84
|
+
}
|
|
85
|
+
onSuiteStart(suite) {
|
|
86
|
+
if (suite.file) {
|
|
87
|
+
this.currentFile = suite.file;
|
|
88
|
+
}
|
|
89
|
+
if (this.useCucumber && suite.type === 'scenario') {
|
|
90
|
+
metadata_manager_1.MetadataManager.clear();
|
|
91
|
+
const tagResult = (0, cucumber_tags_1.applyCucumberTags)(suiteTags(suite));
|
|
92
|
+
this.scenario = {
|
|
93
|
+
title: suite.title,
|
|
94
|
+
file: suite.file || this.currentFile || 'unknown',
|
|
95
|
+
ancestors: this.suiteStack
|
|
96
|
+
.map((s) => s.title)
|
|
97
|
+
.filter((t) => t && t !== '(root)'),
|
|
98
|
+
issueKeys: tagResult.issueKeys,
|
|
99
|
+
steps: [],
|
|
100
|
+
};
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
this.suiteStack.push({ uid: suite.uid, title: suite.title });
|
|
104
|
+
}
|
|
105
|
+
onSuiteEnd(suite) {
|
|
106
|
+
if (this.useCucumber && suite.type === 'scenario') {
|
|
107
|
+
this.finalizeCucumberScenario(suite);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
const idx = [...this.suiteStack]
|
|
111
|
+
.reverse()
|
|
112
|
+
.findIndex((s) => s.uid === suite.uid);
|
|
113
|
+
if (idx >= 0) {
|
|
114
|
+
this.suiteStack.splice(this.suiteStack.length - 1 - idx, 1);
|
|
115
|
+
}
|
|
116
|
+
else if (this.suiteStack.length > 0) {
|
|
117
|
+
this.suiteStack.pop();
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
onTestStart(test) {
|
|
121
|
+
if (this.useCucumber) {
|
|
122
|
+
if (this.scenario) {
|
|
123
|
+
this.scenario.currentStep = test.title;
|
|
124
|
+
metadata_manager_1.MetadataManager.push('qa-step-start', test.title);
|
|
125
|
+
}
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
metadata_manager_1.MetadataManager.clear();
|
|
129
|
+
}
|
|
130
|
+
onTestPass(test) {
|
|
131
|
+
if (this.useCucumber) {
|
|
132
|
+
this.endCucumberStep(test, 'passed');
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
this.record(test, 'passed');
|
|
136
|
+
}
|
|
137
|
+
onTestFail(test) {
|
|
138
|
+
if (this.useCucumber) {
|
|
139
|
+
this.endCucumberStep(test, 'failed');
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
this.record(test, 'failed');
|
|
143
|
+
}
|
|
144
|
+
onTestSkip(test) {
|
|
145
|
+
if (this.useCucumber) {
|
|
146
|
+
this.endCucumberStep(test, 'skipped');
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
this.record(test, 'pending');
|
|
150
|
+
}
|
|
151
|
+
onTestRetry(test) {
|
|
152
|
+
if (this.useCucumber) {
|
|
153
|
+
this.endCucumberStep(test, test.state === 'failed' ? 'failed' : 'passed');
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
this.record(test, (0, report_builder_1.mapWdioStatus)(test.state));
|
|
157
|
+
}
|
|
158
|
+
async onRunnerEnd() {
|
|
159
|
+
try {
|
|
160
|
+
await (0, publish_1.publishBufferedResults)();
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
// Never fail the WDIO run because of reporter publish errors
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
endCucumberStep(test, status) {
|
|
167
|
+
if (!this.scenario)
|
|
168
|
+
return;
|
|
169
|
+
const name = test.title || this.scenario.currentStep || 'step';
|
|
170
|
+
const messages = status === 'failed' ? failureMessages(test) : [];
|
|
171
|
+
metadata_manager_1.MetadataManager.push('qa-step-end', {
|
|
172
|
+
name,
|
|
173
|
+
status: status === 'skipped' ? 'skipped' : status,
|
|
174
|
+
});
|
|
175
|
+
this.scenario.steps.push({
|
|
176
|
+
name,
|
|
177
|
+
status,
|
|
178
|
+
failureMessages: messages,
|
|
179
|
+
});
|
|
180
|
+
this.scenario.currentStep = undefined;
|
|
181
|
+
}
|
|
182
|
+
finalizeCucumberScenario(suite) {
|
|
183
|
+
const scenario = this.scenario;
|
|
184
|
+
this.scenario = null;
|
|
185
|
+
if (!scenario)
|
|
186
|
+
return;
|
|
187
|
+
try {
|
|
188
|
+
const children = [
|
|
189
|
+
...(suite.tests ?? []),
|
|
190
|
+
...(suite.hooks ?? []),
|
|
191
|
+
];
|
|
192
|
+
const stepStatuses = scenario.steps.map((s) => s.status);
|
|
193
|
+
const allSkipped = stepStatuses.length > 0 &&
|
|
194
|
+
stepStatuses.every((s) => s === 'skipped') &&
|
|
195
|
+
(children.length === 0 ||
|
|
196
|
+
children.every((c) => (c.state ?? 'passed') === 'skipped' || c.state === 'passed'));
|
|
197
|
+
let status = 'passed';
|
|
198
|
+
if (scenario.steps.some((s) => s.status === 'failed') ||
|
|
199
|
+
children.some((c) => c.state === 'failed')) {
|
|
200
|
+
status = 'failed';
|
|
201
|
+
}
|
|
202
|
+
else if (allSkipped ||
|
|
203
|
+
(scenario.steps.length === 0 &&
|
|
204
|
+
children.every((c) => (c.state ?? 'skipped') === 'skipped'))) {
|
|
205
|
+
status = 'pending';
|
|
206
|
+
}
|
|
207
|
+
const failureMessages = scenario.steps
|
|
208
|
+
.filter((s) => s.status === 'failed')
|
|
209
|
+
.flatMap((s) => s.failureMessages);
|
|
210
|
+
const entries = metadata_manager_1.MetadataManager.getEntries();
|
|
211
|
+
let wire = (0, forge_commons_1.qaMetaFromEntries)(entries, {
|
|
212
|
+
framework: 'wdio',
|
|
213
|
+
reporter: '@qanalyzer/forge-wdio',
|
|
214
|
+
});
|
|
215
|
+
metadata_manager_1.MetadataManager.clear();
|
|
216
|
+
if (scenario.issueKeys.length > 0) {
|
|
217
|
+
const key = scenario.issueKeys[0];
|
|
218
|
+
if (!scenario.title.includes(key)) {
|
|
219
|
+
scenario.title = `${scenario.issueKeys.join(' ')} ${scenario.title}`;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
// Prefer buffered Gherkin step outcomes (includes skipped)
|
|
223
|
+
if (scenario.steps.length > 0) {
|
|
224
|
+
const stepsWire = scenario.steps.map((s, index) => ({
|
|
225
|
+
id: `step-${index}`,
|
|
226
|
+
stepType: 'gherkin',
|
|
227
|
+
name: s.name,
|
|
228
|
+
status: (s.status === 'skipped'
|
|
229
|
+
? 'skipped'
|
|
230
|
+
: s.status),
|
|
231
|
+
}));
|
|
232
|
+
wire = {
|
|
233
|
+
...(wire ?? {}),
|
|
234
|
+
framework: 'wdio',
|
|
235
|
+
reporter: '@qanalyzer/forge-wdio',
|
|
236
|
+
steps: stepsWire,
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
const title = scenario.title;
|
|
240
|
+
const ancestors = scenario.ancestors;
|
|
241
|
+
const assertion = {
|
|
242
|
+
ancestorTitles: ancestors,
|
|
243
|
+
title,
|
|
244
|
+
fullName: ancestors.length > 0 ? `${ancestors.join(' ')} ${title}` : title,
|
|
245
|
+
status,
|
|
246
|
+
duration: undefined,
|
|
247
|
+
failureMessages: status === 'failed' ? failureMessages : [],
|
|
248
|
+
};
|
|
249
|
+
if (wire) {
|
|
250
|
+
assertion.meta = { qa: wire };
|
|
251
|
+
}
|
|
252
|
+
results_buffer_1.ResultsBuffer.appendAssertion(scenario.file, assertion);
|
|
253
|
+
}
|
|
254
|
+
catch {
|
|
255
|
+
// Never fail the WDIO run
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
record(test, state) {
|
|
259
|
+
try {
|
|
260
|
+
const entries = metadata_manager_1.MetadataManager.getEntries();
|
|
261
|
+
const wire = (0, forge_commons_1.qaMetaFromEntries)(entries, {
|
|
262
|
+
framework: 'wdio',
|
|
263
|
+
reporter: '@qanalyzer/forge-wdio',
|
|
264
|
+
});
|
|
265
|
+
metadata_manager_1.MetadataManager.clear();
|
|
266
|
+
const ancestors = ancestorTitles(test, this.suiteStack);
|
|
267
|
+
const title = test.title;
|
|
268
|
+
const assertion = {
|
|
269
|
+
ancestorTitles: ancestors,
|
|
270
|
+
title,
|
|
271
|
+
fullName: ancestors.length > 0 ? `${ancestors.join(' ')} ${title}` : title,
|
|
272
|
+
status: (0, report_builder_1.mapWdioStatus)(state),
|
|
273
|
+
duration: test.duration,
|
|
274
|
+
failureMessages: state === 'failed' ? failureMessages(test) : [],
|
|
275
|
+
};
|
|
276
|
+
if (wire) {
|
|
277
|
+
assertion.meta = { qa: wire };
|
|
278
|
+
}
|
|
279
|
+
const file = test.file ??
|
|
280
|
+
this.currentFile ??
|
|
281
|
+
'unknown';
|
|
282
|
+
results_buffer_1.ResultsBuffer.appendAssertion(file, assertion);
|
|
283
|
+
}
|
|
284
|
+
catch {
|
|
285
|
+
// Never fail the WDIO run
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
exports.QaWdioReporter = QaWdioReporter;
|
|
290
|
+
exports.default = QaWdioReporter;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { OptionsType } from '@qanalyzer/forge-commons';
|
|
2
|
+
import type { WdioSpecInput } from './report-builder';
|
|
3
|
+
/**
|
|
4
|
+
* In-process collection of specs for publish from reporter and/or afterRunHook.
|
|
5
|
+
*/
|
|
6
|
+
export declare const ResultsBuffer: {
|
|
7
|
+
options: OptionsType;
|
|
8
|
+
runStart: number;
|
|
9
|
+
specs: WdioSpecInput[];
|
|
10
|
+
published: boolean;
|
|
11
|
+
reset(options?: OptionsType): void;
|
|
12
|
+
setOptions(options: OptionsType): void;
|
|
13
|
+
appendSpec(spec: WdioSpecInput): void;
|
|
14
|
+
/** Merge assertions into an existing file bucket or create one. */
|
|
15
|
+
appendAssertion(file: string, assertion: WdioSpecInput["assertions"][number]): void;
|
|
16
|
+
takeSpecs(): WdioSpecInput[];
|
|
17
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ResultsBuffer = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* In-process collection of specs for publish from reporter and/or afterRunHook.
|
|
6
|
+
*/
|
|
7
|
+
exports.ResultsBuffer = {
|
|
8
|
+
options: {},
|
|
9
|
+
runStart: Date.now(),
|
|
10
|
+
specs: [],
|
|
11
|
+
published: false,
|
|
12
|
+
reset(options = {}) {
|
|
13
|
+
this.options = options;
|
|
14
|
+
this.runStart = Date.now();
|
|
15
|
+
this.specs = [];
|
|
16
|
+
this.published = false;
|
|
17
|
+
},
|
|
18
|
+
setOptions(options) {
|
|
19
|
+
this.options = { ...this.options, ...options };
|
|
20
|
+
},
|
|
21
|
+
appendSpec(spec) {
|
|
22
|
+
this.specs.push(spec);
|
|
23
|
+
},
|
|
24
|
+
/** Merge assertions into an existing file bucket or create one. */
|
|
25
|
+
appendAssertion(file, assertion) {
|
|
26
|
+
let spec = this.specs.find((s) => s.name === file);
|
|
27
|
+
if (!spec) {
|
|
28
|
+
spec = {
|
|
29
|
+
name: file,
|
|
30
|
+
startTime: this.runStart,
|
|
31
|
+
endTime: Date.now(),
|
|
32
|
+
assertions: [],
|
|
33
|
+
};
|
|
34
|
+
this.specs.push(spec);
|
|
35
|
+
}
|
|
36
|
+
spec.assertions.push(assertion);
|
|
37
|
+
spec.endTime = Date.now();
|
|
38
|
+
},
|
|
39
|
+
takeSpecs() {
|
|
40
|
+
const out = this.specs;
|
|
41
|
+
this.specs = [];
|
|
42
|
+
return out;
|
|
43
|
+
},
|
|
44
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WDIO service for QAnalyzer (FR123).
|
|
3
|
+
*
|
|
4
|
+
* Register: `services: [[QaWdioService, { disableWebdriverScreenshotsReporting: false }]]`
|
|
5
|
+
*
|
|
6
|
+
* On test failure, captures a still-image screenshot (no video) and uploads via
|
|
7
|
+
* Forge when configured (FR133). Cucumber: `afterScenario` (FR135). Never fails the WDIO run.
|
|
8
|
+
*/
|
|
9
|
+
export type QaWdioServiceOptions = {
|
|
10
|
+
/** When true (default), skip automatic failure screenshots. */
|
|
11
|
+
disableWebdriverScreenshotsReporting?: boolean;
|
|
12
|
+
};
|
|
13
|
+
type AfterTestResult = {
|
|
14
|
+
passed?: boolean;
|
|
15
|
+
error?: Error;
|
|
16
|
+
};
|
|
17
|
+
type TestLike = {
|
|
18
|
+
title?: string;
|
|
19
|
+
fullTitle?: string | (() => string);
|
|
20
|
+
};
|
|
21
|
+
type ScenarioLike = {
|
|
22
|
+
title?: string;
|
|
23
|
+
name?: string;
|
|
24
|
+
tags?: Array<{
|
|
25
|
+
name?: string;
|
|
26
|
+
} | string>;
|
|
27
|
+
};
|
|
28
|
+
type WorldLike = {
|
|
29
|
+
passed?: boolean;
|
|
30
|
+
error?: Error;
|
|
31
|
+
};
|
|
32
|
+
export declare class QaWdioService {
|
|
33
|
+
private readonly disableScreenshots;
|
|
34
|
+
constructor(options?: QaWdioServiceOptions);
|
|
35
|
+
before(): void;
|
|
36
|
+
beforeTest(): void;
|
|
37
|
+
afterTest(test: TestLike, _context: unknown, result: AfterTestResult): Promise<void>;
|
|
38
|
+
/**
|
|
39
|
+
* Cucumber / Gherkin failure still-image (FR135 + FR133).
|
|
40
|
+
* WDIO cucumber framework invokes this after each scenario.
|
|
41
|
+
*/
|
|
42
|
+
afterScenario(world: WorldLike, result: {
|
|
43
|
+
passed?: boolean;
|
|
44
|
+
error?: Error;
|
|
45
|
+
} | undefined, scenario: ScenarioLike): Promise<void>;
|
|
46
|
+
after(): void;
|
|
47
|
+
}
|
|
48
|
+
export {};
|
package/dist/service.js
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* WDIO service for QAnalyzer (FR123).
|
|
4
|
+
*
|
|
5
|
+
* Register: `services: [[QaWdioService, { disableWebdriverScreenshotsReporting: false }]]`
|
|
6
|
+
*
|
|
7
|
+
* On test failure, captures a still-image screenshot (no video) and uploads via
|
|
8
|
+
* Forge when configured (FR133). Cucumber: `afterScenario` (FR135). Never fails the WDIO run.
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.QaWdioService = void 0;
|
|
12
|
+
const forge_commons_1 = require("@qanalyzer/forge-commons");
|
|
13
|
+
const failure_screenshot_buffer_1 = require("./failure-screenshot-buffer");
|
|
14
|
+
function resolveTitle(test) {
|
|
15
|
+
if (typeof test.fullTitle === 'function') {
|
|
16
|
+
try {
|
|
17
|
+
return test.fullTitle() || test.title || 'unknown';
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
// fall through
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
if (typeof test.fullTitle === 'string' && test.fullTitle) {
|
|
24
|
+
return test.fullTitle;
|
|
25
|
+
}
|
|
26
|
+
return test.title || 'unknown';
|
|
27
|
+
}
|
|
28
|
+
function scenarioTitle(scenario) {
|
|
29
|
+
return scenario.title || scenario.name || 'unknown';
|
|
30
|
+
}
|
|
31
|
+
function scenarioTagNames(scenario) {
|
|
32
|
+
if (!Array.isArray(scenario.tags))
|
|
33
|
+
return [];
|
|
34
|
+
return scenario.tags
|
|
35
|
+
.map((t) => (typeof t === 'string' ? t : t?.name || ''))
|
|
36
|
+
.filter(Boolean);
|
|
37
|
+
}
|
|
38
|
+
function browserRef() {
|
|
39
|
+
const g = globalThis;
|
|
40
|
+
return g.browser;
|
|
41
|
+
}
|
|
42
|
+
async function captureFailureScreenshot(label, issueKeySources) {
|
|
43
|
+
const browser = browserRef();
|
|
44
|
+
if (!browser?.takeScreenshot)
|
|
45
|
+
return;
|
|
46
|
+
const b64 = await browser.takeScreenshot();
|
|
47
|
+
if (!b64)
|
|
48
|
+
return;
|
|
49
|
+
const content = Buffer.from(b64, 'base64');
|
|
50
|
+
const outcome = await (0, forge_commons_1.uploadAttachmentForQa)({
|
|
51
|
+
fileName: 'screenshot.png',
|
|
52
|
+
mimeType: 'image/png',
|
|
53
|
+
content,
|
|
54
|
+
issueKeySources,
|
|
55
|
+
});
|
|
56
|
+
failure_screenshot_buffer_1.FailureScreenshotBuffer.add(label, outcome.attachment);
|
|
57
|
+
}
|
|
58
|
+
class QaWdioService {
|
|
59
|
+
disableScreenshots;
|
|
60
|
+
constructor(options = {}) {
|
|
61
|
+
this.disableScreenshots =
|
|
62
|
+
options.disableWebdriverScreenshotsReporting ?? true;
|
|
63
|
+
}
|
|
64
|
+
before() {
|
|
65
|
+
failure_screenshot_buffer_1.FailureScreenshotBuffer.clear();
|
|
66
|
+
}
|
|
67
|
+
beforeTest() {
|
|
68
|
+
// no-op
|
|
69
|
+
}
|
|
70
|
+
async afterTest(test, _context, result) {
|
|
71
|
+
if (this.disableScreenshots)
|
|
72
|
+
return;
|
|
73
|
+
if (result?.passed)
|
|
74
|
+
return;
|
|
75
|
+
const title = resolveTitle(test);
|
|
76
|
+
try {
|
|
77
|
+
await captureFailureScreenshot(test.title || title, [title, test.title]);
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
// Never fail the WDIO run
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Cucumber / Gherkin failure still-image (FR135 + FR133).
|
|
85
|
+
* WDIO cucumber framework invokes this after each scenario.
|
|
86
|
+
*/
|
|
87
|
+
async afterScenario(world, result, scenario) {
|
|
88
|
+
if (this.disableScreenshots)
|
|
89
|
+
return;
|
|
90
|
+
const passed = result?.passed ?? world?.passed;
|
|
91
|
+
if (passed)
|
|
92
|
+
return;
|
|
93
|
+
const title = scenarioTitle(scenario);
|
|
94
|
+
const tags = scenarioTagNames(scenario);
|
|
95
|
+
try {
|
|
96
|
+
await captureFailureScreenshot(title, [title, ...tags]);
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
// Never fail the WDIO run
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
after() {
|
|
103
|
+
// no-op
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
exports.QaWdioService = QaWdioService;
|
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@qanalyzer/forge-wdio",
|
|
3
|
+
"version": "1.1.1",
|
|
4
|
+
"description": "QAnalyzer WebdriverIO reporter + service \u2014 ingest or file modes via @qanalyzer/forge-commons",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"qanalyzer",
|
|
7
|
+
"webdriverio",
|
|
8
|
+
"wdio",
|
|
9
|
+
"reporter",
|
|
10
|
+
"testing",
|
|
11
|
+
"jira"
|
|
12
|
+
],
|
|
13
|
+
"main": "./dist/index.js",
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"default": "./dist/index.js"
|
|
19
|
+
},
|
|
20
|
+
"./helpers": {
|
|
21
|
+
"types": "./dist/helpers.d.ts",
|
|
22
|
+
"default": "./dist/helpers.js"
|
|
23
|
+
},
|
|
24
|
+
"./package.json": "./package.json"
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"dist",
|
|
28
|
+
"!dist/__tests__",
|
|
29
|
+
"README.md"
|
|
30
|
+
],
|
|
31
|
+
"engines": {
|
|
32
|
+
"node": ">=18"
|
|
33
|
+
},
|
|
34
|
+
"scripts": {
|
|
35
|
+
"build": "npm run clean && npx tsc --project tsconfig.build.json && cp -R src/__fixtures__ dist/__fixtures__",
|
|
36
|
+
"clean": "rm -rf dist",
|
|
37
|
+
"test": "npm run build && node --test dist/__tests__/**/*.test.js",
|
|
38
|
+
"prepublishOnly": "npm test"
|
|
39
|
+
},
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"@cucumber/messages": "34.1.0",
|
|
42
|
+
"@wdio/reporter": "^9.29.1",
|
|
43
|
+
"@qanalyzer/forge-commons": "^1.1.1"
|
|
44
|
+
},
|
|
45
|
+
"peerDependencies": {
|
|
46
|
+
"@wdio/cucumber-framework": ">=8.0.0",
|
|
47
|
+
"@wdio/mocha-framework": ">=8.0.0"
|
|
48
|
+
},
|
|
49
|
+
"peerDependenciesMeta": {
|
|
50
|
+
"@wdio/mocha-framework": {
|
|
51
|
+
"optional": true
|
|
52
|
+
},
|
|
53
|
+
"@wdio/cucumber-framework": {
|
|
54
|
+
"optional": true
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
"devDependencies": {
|
|
58
|
+
"@types/node": "^22.13.10"
|
|
59
|
+
},
|
|
60
|
+
"license": "Apache-2.0"
|
|
61
|
+
}
|