wdio-test-ledger-service 9.0.0
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/.github/workflows/release.yml +47 -0
- package/README.md +49 -0
- package/RELEASE.md +1 -0
- package/package.json +41 -0
- package/src/index.js +206 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
name: Publish to NPM
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
inputs:
|
|
6
|
+
release_type:
|
|
7
|
+
description: 'Release type'
|
|
8
|
+
required: true
|
|
9
|
+
type: 'choice'
|
|
10
|
+
default: 'patch'
|
|
11
|
+
options:
|
|
12
|
+
- 'patch'
|
|
13
|
+
- 'minor'
|
|
14
|
+
- 'major'
|
|
15
|
+
env:
|
|
16
|
+
NPM_ACCESS_TOKEN: ${{ secrets.NPM_ACCESS_TOKEN }}
|
|
17
|
+
|
|
18
|
+
jobs:
|
|
19
|
+
release:
|
|
20
|
+
runs-on: ubuntu-latest
|
|
21
|
+
steps:
|
|
22
|
+
- uses: actions/checkout@v3
|
|
23
|
+
with:
|
|
24
|
+
ref: 'master'
|
|
25
|
+
fetch-depth: 0
|
|
26
|
+
- uses: actions/setup-node@v3
|
|
27
|
+
with:
|
|
28
|
+
node-version: '20'
|
|
29
|
+
|
|
30
|
+
- name: NPM Setup
|
|
31
|
+
run: |
|
|
32
|
+
npm set registry "https://registry.npmjs.org/"
|
|
33
|
+
npm set //registry.npmjs.org/:_authToken $NPM_ACCESS_TOKEN
|
|
34
|
+
npm whoami
|
|
35
|
+
|
|
36
|
+
- name: Git Setup
|
|
37
|
+
run: |
|
|
38
|
+
git config --global user.email "wpbrock@gmail.com"
|
|
39
|
+
git config --global user.name "Test Reporter Service Release Bot"
|
|
40
|
+
|
|
41
|
+
- name: Install Dependencies
|
|
42
|
+
run: npm ci
|
|
43
|
+
|
|
44
|
+
- run: npm run release -- ${{ github.event.inputs.release_type }}
|
|
45
|
+
env:
|
|
46
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
47
|
+
NPM_ACCESS_TOKEN: ${{ secrets.NPM_ACCESS_TOKEN }}
|
package/README.md
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# WebdriverIO Test Reporter Service
|
|
2
|
+
|
|
3
|
+
WDIO service that takes results from [wdio-test-reporter](https://github.com/WillBrock/wdio-test-reporter) and uploads them to [testreporter.io](https://testreporter.io)
|
|
4
|
+
|
|
5
|
+
testreporter.io stores and tracks all test runs with extensive details about each test. This gives you a historical overivew of how your tests are running.
|
|
6
|
+
|
|
7
|
+
Setup is very simple. All you need to do is add the service and reporter to the `services` and `reporters` arrays in your wdio.conf.js file.
|
|
8
|
+
|
|
9
|
+
## Install the service
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
npm install wdio-test-reporter-service
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Add the service to the services array in wdio.conf.js
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
services: [['test-reporter', {
|
|
19
|
+
reporterOutputDir : `./testreporter`, // This must match the outputDir from the wdio-test-reporter
|
|
20
|
+
apiUrl : `app-api.testreporter.io`, // Defaults to app-api.testreporter.io if none is set
|
|
21
|
+
username : `jenkins@foobar.com`, // app.testreporter.io username
|
|
22
|
+
apiToken : `12345`, // Found in the app.testreporter.io under your proifle section
|
|
23
|
+
projectId : 123, // Only needed if using more than one project
|
|
24
|
+
appVersion : `2.8.10`, // The code version can also be set here
|
|
25
|
+
enableFlaky : 1, // Will mark tests as flaky if it detects them based on previous runs
|
|
26
|
+
}]],
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
You will create a custom `username` and `apiToken` in the UI under Settings -> Profile -> API Keys
|
|
30
|
+
|
|
31
|
+
## Add the wdio-test-reporter to the reporters array in wdio.conf.js
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
npm install wdio-test-reporter
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
reporters : [[`test`, {
|
|
39
|
+
outputDir : `./testreporter`
|
|
40
|
+
}]]
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Environment variables
|
|
44
|
+
|
|
45
|
+
Environment variables can be set when running tests that the server will use to add to the results
|
|
46
|
+
|
|
47
|
+
* `RUN_TITLE` - Title of the test run. This might be something like a Jira issue key. Defaults to a timestamp if not specified
|
|
48
|
+
* `RUN_UUID` - UUID which can be used to directly link to the test run results. e.g. https://app.testreporter.io/runs/c26b23d8-eb9f-4ff4-a884-5cb9f3d3aba5<uuid>
|
|
49
|
+
* `APP_VERSION` - Set the version of app this test run ran against
|
package/RELEASE.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
https://www.npmjs.com/package/np
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "wdio-test-ledger-service",
|
|
3
|
+
"version": "9.0.0",
|
|
4
|
+
"description": "WebdriverIO server to send reporter data to testledger.dev",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "src/index.js",
|
|
7
|
+
"engines": {
|
|
8
|
+
"node": "20.x"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"release": "release-it --github.release"
|
|
12
|
+
},
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "git+https://github.com/WillBrock/wdio-test-ledger-service.git"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"WebdriverIO",
|
|
19
|
+
"tests",
|
|
20
|
+
"test",
|
|
21
|
+
"e2e",
|
|
22
|
+
"wdio",
|
|
23
|
+
"testing",
|
|
24
|
+
"reporter",
|
|
25
|
+
"service"
|
|
26
|
+
],
|
|
27
|
+
"author": "Will Brock",
|
|
28
|
+
"license": "ISC",
|
|
29
|
+
"bugs": {
|
|
30
|
+
"url": "https://github.com/WillBrock/wdio-test-ledger-service/issues"
|
|
31
|
+
},
|
|
32
|
+
"homepage": "https://github.com/WillBrock/wdio-test-ledger-service#readme",
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"btoa": "^1.2.1",
|
|
35
|
+
"fs-extra": "^11.2.0",
|
|
36
|
+
"node-fetch": "^3.3.2"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"release-it": "^17.0.1"
|
|
40
|
+
}
|
|
41
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import btoa from 'btoa';
|
|
4
|
+
import { SevereServiceError } from 'webdriverio';
|
|
5
|
+
|
|
6
|
+
const api_url = `https://app-api.testreporter.io`;
|
|
7
|
+
|
|
8
|
+
class TestReporterLauncher {
|
|
9
|
+
constructor(options) {
|
|
10
|
+
this.options = options;
|
|
11
|
+
|
|
12
|
+
if(!this.options.reporterOutputDir) {
|
|
13
|
+
throw new SevereServiceError(`No reporterOutputDir specified`)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if(!this.options.username) {
|
|
17
|
+
throw new SevereServiceError(`No username specified`)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if(!this.options.apiToken) {
|
|
21
|
+
throw new SevereServiceError(`No apiToken specified`)
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
onPrepare() {
|
|
26
|
+
fs.emptyDirSync(this.options.reporterOutputDir);
|
|
27
|
+
|
|
28
|
+
fs.writeFileSync(`${this.options.reporterOutputDir}/trio-onPrepare.txt`, `onPrepare called`, { encoding : `utf-8` });
|
|
29
|
+
|
|
30
|
+
this.start = new Date();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async onComplete(exit_code, config) {
|
|
34
|
+
const data = this.buildData(config);
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
const tmp = await this.post(data);
|
|
38
|
+
fs.writeFileSync(`${this.options.reporterOutputDir}/trio-onComplete-post.txt`, `onComplete-post`, { encoding : `utf-8` });
|
|
39
|
+
}
|
|
40
|
+
catch(e) {
|
|
41
|
+
fs.writeFileSync(`${this.options.reporterOutputDir}/trio-post-error.txt`, e.message, { encoding : `utf-8` });
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
buildData(config) {
|
|
46
|
+
const directory = path.resolve(this.options.reporterOutputDir);
|
|
47
|
+
const files = fs.readdirSync(directory);
|
|
48
|
+
const suite_data = {};
|
|
49
|
+
const all_errors = {};
|
|
50
|
+
const all_hooks = {};
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
fs.writeFileSync(`${this.options.reporterOutputDir}/trio-skip-passed.txt`, `Value of SKIP_PASSED_UPLOADS: ${process.env.SKIP_PASSED_UPLOADS}`, { encoding : `utf-8` });
|
|
54
|
+
fs.writeFileSync(`${this.options.reporterOutputDir}/trio-buildData.txt`, `Starting buildData`, { encoding : `utf-8` });
|
|
55
|
+
|
|
56
|
+
const data = {
|
|
57
|
+
project_id : this.options.projectId,
|
|
58
|
+
uuid : process.env.RUN_UUID,
|
|
59
|
+
// This is a way to group runs together, for example if you're using sharding
|
|
60
|
+
group_uuid : process.env.GROUP_UUID,
|
|
61
|
+
main_run : Number(process.env.MAIN_RUN),
|
|
62
|
+
title : process.env.RUN_TITLE || this.start,
|
|
63
|
+
// Site the tests were ran on
|
|
64
|
+
site : process.env.SITE,
|
|
65
|
+
build_url : process.env.BUILD_URL,
|
|
66
|
+
run_date : this.start.toISOString(),
|
|
67
|
+
duration : new Date().getTime() - this.start.getTime(),
|
|
68
|
+
version : process.env.APP_VERSION || process.env.CODE_VERSION || this.options.appVersion || `0.0.1`,
|
|
69
|
+
suites_ran : config.suite ? config.suite.join(`, `) : (config.multiRun || config.repeat ? `RepeatRun` : ``),
|
|
70
|
+
issue_user : process.env.ISSUE_USER ?? null,
|
|
71
|
+
issue_summary : process.env.ISSUE_SUMMARY ?? null,
|
|
72
|
+
enable_flaky : Number(process.env.ENABLE_FLAKY) || this.options.enableFlaky || 0,
|
|
73
|
+
passed : 1,
|
|
74
|
+
failed : 0,
|
|
75
|
+
suites : [],
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
for(const file of files) {
|
|
79
|
+
if(!file.match(/test-reporter.log/)) {
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
let tmp = false;
|
|
84
|
+
try {
|
|
85
|
+
const filepath = `${directory}/${file}`;
|
|
86
|
+
tmp = fs.readFileSync(filepath, { encoding : `utf8` });
|
|
87
|
+
}
|
|
88
|
+
catch(e) {
|
|
89
|
+
fs.writeFileSync(`${this.options.reporterOutputDir}/trio-readfile-error.txt`, e.message, { encoding : `utf-8` });
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const identifier = file.match(/wdio-(\d+-\d+)-/)[1];
|
|
93
|
+
|
|
94
|
+
if(!tmp) {
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const content = JSON.parse(tmp);
|
|
99
|
+
const suite_key = btoa(`${identifier}:${content.spec_file}:${content.capabilities}:${content.title}`);
|
|
100
|
+
|
|
101
|
+
if(content.passed && Number(process.env.SKIP_PASSED_UPLOADS) === 1) {
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
suite_data[suite_key] = {
|
|
106
|
+
title : content.title,
|
|
107
|
+
spec_file : content.spec_file,
|
|
108
|
+
filepath : content.filepath,
|
|
109
|
+
capabilities : content.capabilities,
|
|
110
|
+
duration : content.duration,
|
|
111
|
+
retries : content.retries || 0,
|
|
112
|
+
passed : content.passed,
|
|
113
|
+
failed : content.failed,
|
|
114
|
+
skipped : content.skipped,
|
|
115
|
+
start : content.start,
|
|
116
|
+
tests : [],
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
for(const test of content.tests) {
|
|
120
|
+
const hook = test.type === `hook`;
|
|
121
|
+
const test_key = btoa(`${identifier}:${content.spec_file}:${content.capabilities}:${content.title}:${test.title}`);
|
|
122
|
+
|
|
123
|
+
if(!all_errors[test_key]) {
|
|
124
|
+
all_errors[test_key] = [];
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// This will make sure we have stored errors from the same test if it has retried
|
|
128
|
+
all_errors[test_key] = [...all_errors[test_key], ...test.errors];
|
|
129
|
+
|
|
130
|
+
const test_data = {
|
|
131
|
+
title : test.title,
|
|
132
|
+
duration : test.duration,
|
|
133
|
+
passed : test.passed,
|
|
134
|
+
retries : test.retries,
|
|
135
|
+
failed : test.failed,
|
|
136
|
+
skipped : test.skipped,
|
|
137
|
+
errors : all_errors[test_key],
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
suite_data[suite_key].tests.push(test_data);
|
|
141
|
+
|
|
142
|
+
if(hook && !all_hooks[suite_key]) {
|
|
143
|
+
all_hooks[suite_key] = [];
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if(hook) {
|
|
147
|
+
all_hooks[suite_key].push(test_data)
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if(all_hooks[suite_key]) {
|
|
152
|
+
suite_data[suite_key].tests = [...suite_data[suite_key].tests, ...all_hooks[suite_key]];
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const suites = Object.values(suite_data);
|
|
157
|
+
for(const suite of suites) {
|
|
158
|
+
if(!suite.failed) {
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
data.failed = 1;
|
|
163
|
+
data.passed = 0;
|
|
164
|
+
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
fs.writeFileSync(`${this.options.reporterOutputDir}/trio-end-buildData.txt`, `Ending buildData`, { encoding : `utf-8` });
|
|
169
|
+
|
|
170
|
+
data.suites = suites;
|
|
171
|
+
|
|
172
|
+
return data;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
post(data) {
|
|
176
|
+
return fetch(this.getApiRoute(), {
|
|
177
|
+
method : `POST`,
|
|
178
|
+
headers : {
|
|
179
|
+
'Content-Type' : `application/json`,
|
|
180
|
+
'Authorization' : `Basic ${this.getAuthToken()}`,
|
|
181
|
+
},
|
|
182
|
+
body : JSON.stringify(data),
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
getApiUrl() {
|
|
187
|
+
return `https://${this.options.apiUrl?.replace(`https://`, ``) || api_url}`;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
getApiRoute() {
|
|
191
|
+
return [
|
|
192
|
+
this.getApiUrl(),
|
|
193
|
+
`/runs`,
|
|
194
|
+
].join(``);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
getAuthToken() {
|
|
198
|
+
return btoa([
|
|
199
|
+
this.options.username,
|
|
200
|
+
this.options.apiToken,
|
|
201
|
+
].join(`:`));
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export default class TestReporterService {};
|
|
206
|
+
export const launcher = TestReporterLauncher;
|