@wdio/testingbot-service 9.0.0-alpha.78 → 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/build/index.d.ts.map +1 -1
- package/build/index.js +263 -6
- package/build/launcher.d.ts +1 -1
- package/build/launcher.d.ts.map +1 -1
- package/build/service.d.ts +1 -1
- package/build/service.d.ts.map +1 -1
- package/package.json +10 -8
- package/build/launcher.js +0 -60
- package/build/service.js +0 -218
- package/build/types.js +0 -1
- /package/{LICENSE-MIT → LICENSE} +0 -0
package/build/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,kBAAkB,MAAM,eAAe,CAAA;AAC9C,OAAO,iBAAiB,MAAM,cAAc,CAAA;AAC5C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAEnD,eAAe,iBAAiB,CAAA;AAChC,eAAO,MAAM,QAAQ,2BAAqB,CAAA;AAC1C,cAAc,YAAY,CAAA;AAE1B,OAAO,CAAC,MAAM,CAAC;IACX,UAAU,WAAW,CAAC;QAClB,UAAU,aAAc,SAAQ,iBAAiB;SAAG;KACvD;CACJ"}
|
package/build/index.js
CHANGED
|
@@ -1,6 +1,263 @@
|
|
|
1
|
-
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
// src/launcher.ts
|
|
2
|
+
import { performance, PerformanceObserver } from "node:perf_hooks";
|
|
3
|
+
import { promisify } from "node:util";
|
|
4
|
+
import testingbotTunnel from "testingbot-tunnel-launcher";
|
|
5
|
+
import logger from "@wdio/logger";
|
|
6
|
+
var log = logger("@wdio/testingbot-service");
|
|
7
|
+
var TestingBotLauncher = class {
|
|
8
|
+
options;
|
|
9
|
+
tbTunnelOpts;
|
|
10
|
+
tunnel;
|
|
11
|
+
constructor(options) {
|
|
12
|
+
this.options = options;
|
|
13
|
+
}
|
|
14
|
+
async onPrepare(config, capabilities) {
|
|
15
|
+
if (!this.options.tbTunnel || !config.user || !config.key) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
const tbTunnelIdentifier = this.options.tbTunnelOpts?.tunnelIdentifier || `TB-tunnel-${Math.random().toString().slice(2)}`;
|
|
19
|
+
this.tbTunnelOpts = Object.assign({
|
|
20
|
+
apiKey: config.user,
|
|
21
|
+
apiSecret: config.key,
|
|
22
|
+
"tunnel-identifier": tbTunnelIdentifier
|
|
23
|
+
}, this.options.tbTunnelOpts);
|
|
24
|
+
const capabilitiesEntries = Array.isArray(capabilities) ? capabilities.every((cap) => Object.values(cap).length > 0 && Object.values(cap).every((c) => typeof c === "object" && c.capabilities)) ? capabilities.flatMap((cap) => Object.values(cap)) : capabilities : Object.values(capabilities);
|
|
25
|
+
for (const capability of capabilitiesEntries) {
|
|
26
|
+
const caps = capability.capabilities || capability;
|
|
27
|
+
const c = caps.alwaysMatch || caps;
|
|
28
|
+
if (!c["tb:options"]) {
|
|
29
|
+
c["tb:options"] = {};
|
|
30
|
+
}
|
|
31
|
+
c["tb:options"]["tunnel-identifier"] = tbTunnelIdentifier;
|
|
32
|
+
}
|
|
33
|
+
const obs = new PerformanceObserver((list) => {
|
|
34
|
+
const entry = list.getEntries()[0];
|
|
35
|
+
log.info(`TestingBot tunnel successfully started after ${entry.duration}ms`);
|
|
36
|
+
});
|
|
37
|
+
obs.observe({ entryTypes: ["measure"] });
|
|
38
|
+
performance.mark("tbTunnelStart");
|
|
39
|
+
this.tunnel = await promisify(testingbotTunnel)(this.tbTunnelOpts);
|
|
40
|
+
performance.mark("tbTunnelEnd");
|
|
41
|
+
performance.measure("bootTime", "tbTunnelStart", "tbTunnelEnd");
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Shut down the tunnel
|
|
45
|
+
* @returns {Promise} Resolved promise when tunnel is closed
|
|
46
|
+
*/
|
|
47
|
+
onComplete() {
|
|
48
|
+
if (!this.tunnel) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
return new Promise((resolve) => this.tunnel.close(resolve));
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
// src/service.ts
|
|
56
|
+
import logger2 from "@wdio/logger";
|
|
57
|
+
var log2 = logger2("@wdio/testingbot-service");
|
|
58
|
+
var jobDataProperties = ["name", "tags", "public", "build", "extra"];
|
|
59
|
+
var TestingBotService = class {
|
|
60
|
+
constructor(_options, _capabilities, _config) {
|
|
61
|
+
this._options = _options;
|
|
62
|
+
this._capabilities = _capabilities;
|
|
63
|
+
this._config = _config;
|
|
64
|
+
this._tbUser = this._config.user;
|
|
65
|
+
this._tbSecret = this._config.key;
|
|
66
|
+
this._isServiceEnabled = Boolean(this._tbUser && this._tbSecret);
|
|
67
|
+
}
|
|
68
|
+
_browser;
|
|
69
|
+
_isServiceEnabled;
|
|
70
|
+
_suiteTitle;
|
|
71
|
+
_tbSecret;
|
|
72
|
+
_tbUser;
|
|
73
|
+
_failures = 0;
|
|
74
|
+
_testCnt = 0;
|
|
75
|
+
before(caps, specs, browser) {
|
|
76
|
+
this._browser = browser;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Before suite
|
|
80
|
+
* @param {object} suite Suite
|
|
81
|
+
*/
|
|
82
|
+
beforeSuite(suite) {
|
|
83
|
+
this._suiteTitle = suite.title;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Before test
|
|
87
|
+
* @param {object} test Test
|
|
88
|
+
*/
|
|
89
|
+
beforeTest(test) {
|
|
90
|
+
if (!this._isServiceEnabled || !this._browser) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
if (this._suiteTitle === "Jasmine__TopLevel__Suite") {
|
|
94
|
+
this._suiteTitle = test.fullName.slice(0, test.fullName.indexOf(test.title) - 1);
|
|
95
|
+
}
|
|
96
|
+
const context = (
|
|
97
|
+
/**
|
|
98
|
+
* Jasmine
|
|
99
|
+
*/
|
|
100
|
+
test.fullName || /**
|
|
101
|
+
* Mocha
|
|
102
|
+
*/
|
|
103
|
+
`${test.parent} - ${test.title}`
|
|
104
|
+
);
|
|
105
|
+
this._browser.execute("tb:test-context=" + context);
|
|
106
|
+
}
|
|
107
|
+
afterSuite(suite) {
|
|
108
|
+
if (Object.prototype.hasOwnProperty.call(suite, "error")) {
|
|
109
|
+
++this._failures;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* After test
|
|
114
|
+
* @param {object} test Test
|
|
115
|
+
*/
|
|
116
|
+
afterTest(test, context, results) {
|
|
117
|
+
if (!results.passed) {
|
|
118
|
+
++this._failures;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* For CucumberJS
|
|
123
|
+
*/
|
|
124
|
+
/**
|
|
125
|
+
* Before feature
|
|
126
|
+
* @param {string} uri
|
|
127
|
+
* @param {object} feature
|
|
128
|
+
*/
|
|
129
|
+
beforeFeature(uri, feature) {
|
|
130
|
+
if (!this._isServiceEnabled || !this._browser) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
this._suiteTitle = feature.name;
|
|
134
|
+
this._browser.execute("tb:test-context=Feature: " + this._suiteTitle);
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Before scenario
|
|
138
|
+
* @param {string} uri
|
|
139
|
+
* @param {object} feature
|
|
140
|
+
* @param {object} scenario
|
|
141
|
+
*/
|
|
142
|
+
beforeScenario(world) {
|
|
143
|
+
if (!this._isServiceEnabled || !this._browser) {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
const scenarioName = world.pickle.name;
|
|
147
|
+
this._browser.execute("tb:test-context=Scenario: " + scenarioName);
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
*
|
|
151
|
+
* Runs before a Cucumber Scenario.
|
|
152
|
+
* @param world world object containing information on pickle and test step
|
|
153
|
+
* @param result result object containing
|
|
154
|
+
* @param result.passed true if scenario has passed
|
|
155
|
+
* @param result.error error stack if scenario failed
|
|
156
|
+
* @param result.duration duration of scenario in milliseconds
|
|
157
|
+
*/
|
|
158
|
+
afterScenario(world, result) {
|
|
159
|
+
if (!result.passed) {
|
|
160
|
+
++this._failures;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Update TestingBot info
|
|
165
|
+
* @return {Promise} Promise with result of updateJob method call
|
|
166
|
+
*/
|
|
167
|
+
after(result) {
|
|
168
|
+
if (!this._isServiceEnabled || !this._browser) {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
let failures = this._failures;
|
|
172
|
+
if (this._config.mochaOpts?.bail && Boolean(result)) {
|
|
173
|
+
failures = 1;
|
|
174
|
+
}
|
|
175
|
+
const status = "status: " + (failures > 0 ? "failing" : "passing");
|
|
176
|
+
if (!this._browser.isMultiremote) {
|
|
177
|
+
log2.info(`Update job with sessionId ${this._browser.sessionId}, ${status}`);
|
|
178
|
+
return this.updateJob(this._browser.sessionId, failures);
|
|
179
|
+
}
|
|
180
|
+
const browser = this._browser;
|
|
181
|
+
return Promise.all(Object.keys(this._capabilities).map((browserName) => {
|
|
182
|
+
log2.info(`Update multiremote job for browser "${browserName}" and sessionId ${browser.getInstance(browserName).sessionId}, ${status}`);
|
|
183
|
+
return this.updateJob(browser.getInstance(browserName).sessionId, failures, false, browserName);
|
|
184
|
+
}));
|
|
185
|
+
}
|
|
186
|
+
onReload(oldSessionId, newSessionId) {
|
|
187
|
+
if (!this._isServiceEnabled || !this._browser) {
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
const status = "status: " + (this._failures > 0 ? "failing" : "passing");
|
|
191
|
+
if (!this._browser.isMultiremote) {
|
|
192
|
+
log2.info(`Update (reloaded) job with sessionId ${oldSessionId}, ${status}`);
|
|
193
|
+
return this.updateJob(oldSessionId, this._failures, true);
|
|
194
|
+
}
|
|
195
|
+
const browser = this._browser;
|
|
196
|
+
const browserName = browser.instances.filter(
|
|
197
|
+
(browserName2) => browser.getInstance(browserName2).sessionId === newSessionId
|
|
198
|
+
)[0];
|
|
199
|
+
log2.info(`Update (reloaded) multiremote job for browser "${browserName}" and sessionId ${oldSessionId}, ${status}`);
|
|
200
|
+
return this.updateJob(oldSessionId, this._failures, true, browserName);
|
|
201
|
+
}
|
|
202
|
+
async updateJob(sessionId, failures, calledOnReload = false, browserName) {
|
|
203
|
+
if (!this._browser) {
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
let headers = {
|
|
207
|
+
"Content-Type": "application/json; charset=utf-8"
|
|
208
|
+
};
|
|
209
|
+
if (this._tbUser && this._tbSecret) {
|
|
210
|
+
const encodedAuth = Buffer.from(`${this._tbUser}:${this._tbSecret}`, "utf8").toString("base64");
|
|
211
|
+
headers = {
|
|
212
|
+
...headers,
|
|
213
|
+
Authorization: `Basic ${encodedAuth}`
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
const json = this.getBody(failures, calledOnReload, browserName);
|
|
217
|
+
this._failures = 0;
|
|
218
|
+
const response = await fetch(this.getRestUrl(sessionId), {
|
|
219
|
+
method: "PUT",
|
|
220
|
+
body: JSON.stringify(json),
|
|
221
|
+
headers
|
|
222
|
+
});
|
|
223
|
+
return await response.json();
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
*
|
|
227
|
+
* @param {String} sessionId Session id
|
|
228
|
+
* @returns {String} TestingBot API URL
|
|
229
|
+
*/
|
|
230
|
+
getRestUrl(sessionId) {
|
|
231
|
+
return `https://api.testingbot.com/v1/tests/${sessionId}`;
|
|
232
|
+
}
|
|
233
|
+
getBody(failures, calledOnReload = false, browserName) {
|
|
234
|
+
const body = { test: {} };
|
|
235
|
+
body.test.name = this._suiteTitle;
|
|
236
|
+
if ((calledOnReload || this._testCnt) && this._browser) {
|
|
237
|
+
let testCnt = ++this._testCnt;
|
|
238
|
+
if (this._browser.isMultiremote) {
|
|
239
|
+
testCnt = Math.ceil(testCnt / this._browser.instances.length);
|
|
240
|
+
}
|
|
241
|
+
body.test.name += ` (${testCnt})`;
|
|
242
|
+
}
|
|
243
|
+
for (const prop of jobDataProperties) {
|
|
244
|
+
if (!this._capabilities[prop]) {
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
body.test[prop] = this._capabilities[prop];
|
|
248
|
+
}
|
|
249
|
+
if (browserName) {
|
|
250
|
+
body.test.name = `${browserName}: ${body.test.name}`;
|
|
251
|
+
}
|
|
252
|
+
body.test.success = failures === 0 ? "1" : "0";
|
|
253
|
+
return body;
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
// src/index.ts
|
|
258
|
+
var src_default = TestingBotService;
|
|
259
|
+
var launcher = TestingBotLauncher;
|
|
260
|
+
export {
|
|
261
|
+
src_default as default,
|
|
262
|
+
launcher
|
|
263
|
+
};
|
package/build/launcher.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ export default class TestingBotLauncher implements Services.ServiceInstance {
|
|
|
5
5
|
tbTunnelOpts: TunnelLauncherOptions;
|
|
6
6
|
tunnel?: TestingbotTunnel;
|
|
7
7
|
constructor(options: TestingbotOptions);
|
|
8
|
-
onPrepare(config: Options.Testrunner, capabilities: Capabilities.
|
|
8
|
+
onPrepare(config: Options.Testrunner, capabilities: Capabilities.TestrunnerCapabilities): Promise<void>;
|
|
9
9
|
/**
|
|
10
10
|
* Shut down the tunnel
|
|
11
11
|
* @returns {Promise} Resolved promise when tunnel is closed
|
package/build/launcher.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"launcher.d.ts","sourceRoot":"","sources":["../src/launcher.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AAElE,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;AAI5F,MAAM,CAAC,OAAO,OAAO,kBAAmB,YAAW,QAAQ,CAAC,eAAe;IACvE,OAAO,EAAE,iBAAiB,CAAA;IAC1B,YAAY,EAAG,qBAAqB,CAAA;IACpC,MAAM,CAAC,EAAE,gBAAgB,CAAA;gBACZ,OAAO,EAAE,iBAAiB;IAIjC,SAAS,CAAE,MAAM,EAAE,OAAO,CAAC,UAAU,EAAE,YAAY,EAAE,YAAY,CAAC,
|
|
1
|
+
{"version":3,"file":"launcher.d.ts","sourceRoot":"","sources":["../src/launcher.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AAElE,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;AAI5F,MAAM,CAAC,OAAO,OAAO,kBAAmB,YAAW,QAAQ,CAAC,eAAe;IACvE,OAAO,EAAE,iBAAiB,CAAA;IAC1B,YAAY,EAAG,qBAAqB,CAAA;IACpC,MAAM,CAAC,EAAE,gBAAgB,CAAA;gBACZ,OAAO,EAAE,iBAAiB;IAIjC,SAAS,CAAE,MAAM,EAAE,OAAO,CAAC,UAAU,EAAE,YAAY,EAAE,YAAY,CAAC,sBAAsB;IA+C9F;;;OAGG;IACH,UAAU;CAOb"}
|
package/build/service.d.ts
CHANGED
|
@@ -11,7 +11,7 @@ export default class TestingBotService implements Services.ServiceInstance {
|
|
|
11
11
|
private _tbUser?;
|
|
12
12
|
private _failures;
|
|
13
13
|
private _testCnt;
|
|
14
|
-
constructor(_options: TestingbotOptions, _capabilities: Capabilities.
|
|
14
|
+
constructor(_options: TestingbotOptions, _capabilities: Capabilities.ResolvedTestrunnerCapabilities, _config: Options.Testrunner);
|
|
15
15
|
before(caps: unknown, specs: unknown, browser: WebdriverIO.Browser | WebdriverIO.MultiRemoteBrowser): void;
|
|
16
16
|
/**
|
|
17
17
|
* Before suite
|
package/build/service.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAE9E,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAKnD,MAAM,CAAC,OAAO,OAAO,iBAAkB,YAAW,QAAQ,CAAC,eAAe;IAUlE,OAAO,CAAC,QAAQ;IAChB,OAAO,CAAC,aAAa;IACrB,OAAO,CAAC,OAAO;IAXnB,OAAO,CAAC,QAAQ,CAAC,CAAsD;IACvE,OAAO,CAAC,iBAAiB,CAAC,CAAS;IACnC,OAAO,CAAC,WAAW,CAAC,CAAQ;IAC5B,OAAO,CAAC,SAAS,CAAC,CAAQ;IAC1B,OAAO,CAAC,OAAO,CAAC,CAAQ;IACxB,OAAO,CAAC,SAAS,CAAI;IACrB,OAAO,CAAC,QAAQ,CAAI;gBAGR,QAAQ,EAAE,iBAAiB,EAC3B,aAAa,EAAE,YAAY,CAAC,
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAE9E,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAKnD,MAAM,CAAC,OAAO,OAAO,iBAAkB,YAAW,QAAQ,CAAC,eAAe;IAUlE,OAAO,CAAC,QAAQ;IAChB,OAAO,CAAC,aAAa;IACrB,OAAO,CAAC,OAAO;IAXnB,OAAO,CAAC,QAAQ,CAAC,CAAsD;IACvE,OAAO,CAAC,iBAAiB,CAAC,CAAS;IACnC,OAAO,CAAC,WAAW,CAAC,CAAQ;IAC5B,OAAO,CAAC,SAAS,CAAC,CAAQ;IAC1B,OAAO,CAAC,OAAO,CAAC,CAAQ;IACxB,OAAO,CAAC,SAAS,CAAI;IACrB,OAAO,CAAC,QAAQ,CAAI;gBAGR,QAAQ,EAAE,iBAAiB,EAC3B,aAAa,EAAE,YAAY,CAAC,8BAA8B,EAC1D,OAAO,EAAE,OAAO,CAAC,UAAU;IAQvC,MAAM,CACF,IAAI,EAAE,OAAO,EACb,KAAK,EAAE,OAAO,EACd,OAAO,EAAE,WAAW,CAAC,OAAO,GAAG,WAAW,CAAC,kBAAkB;IAKjE;;;MAGE;IACF,WAAW,CAAE,KAAK,EAAE,UAAU,CAAC,KAAK;IAIpC;;;MAGE;IACF,UAAU,CAAE,IAAI,EAAE,UAAU,CAAC,IAAI;IA6BjC,UAAU,CAAE,KAAK,EAAE,UAAU,CAAC,KAAK;IAMnC;;;OAGG;IACH,SAAS,CAAE,IAAI,EAAE,UAAU,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,UAAU,CAAC,UAAU;IAM9E;;OAEG;IAEH;;;;OAIG;IACH,aAAa,CAAE,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE;IAStD;;;;;OAKG;IACH,cAAc,CAAE,KAAK,EAAE,UAAU,CAAC,KAAK;IAQvC;;;;;;;;OAQG;IACH,aAAa,CAAC,KAAK,EAAE,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,CAAC,YAAY;IAOtE;;;OAGG;IACH,KAAK,CAAE,MAAM,CAAC,EAAE,MAAM;IA6BtB,QAAQ,CAAE,YAAY,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM;IAkB9C,SAAS,CAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,cAAc,UAAQ,EAAE,WAAW,CAAC,EAAE,MAAM;IAyBlG;;;;OAIG;IACH,UAAU,CAAE,SAAS,EAAE,MAAM;IAI7B,OAAO,CAAE,QAAQ,EAAE,MAAM,EAAE,cAAc,UAAQ,EAAE,WAAW,CAAC,EAAE,MAAM;cACxC,GAAG;;CAkCrC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wdio/testingbot-service",
|
|
3
|
-
"version": "9.0.0
|
|
3
|
+
"version": "9.0.0",
|
|
4
4
|
"description": "A WebdriverIO service that provides a better integration into TestingBot",
|
|
5
5
|
"author": "Jochen Delabie <info@testingbot.com>",
|
|
6
6
|
"homepage": "https://github.com/webdriverio/webdriverio/tree/main/packages/wdio-testingbot-service",
|
|
@@ -25,22 +25,24 @@
|
|
|
25
25
|
"type": "module",
|
|
26
26
|
"types": "./build/index.d.ts",
|
|
27
27
|
"exports": {
|
|
28
|
-
".":
|
|
29
|
-
|
|
28
|
+
".": {
|
|
29
|
+
"types": "./build/index.d.ts",
|
|
30
|
+
"import": "./build/index.js"
|
|
31
|
+
}
|
|
30
32
|
},
|
|
31
33
|
"typeScriptVersion": "3.8.3",
|
|
32
34
|
"dependencies": {
|
|
33
|
-
"@wdio/logger": "9.0.0
|
|
34
|
-
"@wdio/types": "9.0.0
|
|
35
|
+
"@wdio/logger": "9.0.0",
|
|
36
|
+
"@wdio/types": "9.0.0",
|
|
35
37
|
"testingbot-tunnel-launcher": "^1.1.7",
|
|
36
|
-
"webdriverio": "9.0.0
|
|
38
|
+
"webdriverio": "9.0.0"
|
|
37
39
|
},
|
|
38
40
|
"devDependencies": {
|
|
39
41
|
"@types/node": "^20.1.0",
|
|
40
|
-
"@wdio/globals": "9.0.0
|
|
42
|
+
"@wdio/globals": "9.0.0"
|
|
41
43
|
},
|
|
42
44
|
"publishConfig": {
|
|
43
45
|
"access": "public"
|
|
44
46
|
},
|
|
45
|
-
"gitHead": "
|
|
47
|
+
"gitHead": "957693463371a4cb329395dcdbce8fb0c930ab93"
|
|
46
48
|
}
|
package/build/launcher.js
DELETED
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import { performance, PerformanceObserver } from 'node:perf_hooks';
|
|
2
|
-
import { promisify } from 'node:util';
|
|
3
|
-
import testingbotTunnel from 'testingbot-tunnel-launcher';
|
|
4
|
-
import logger from '@wdio/logger';
|
|
5
|
-
const log = logger('@wdio/testingbot-service');
|
|
6
|
-
export default class TestingBotLauncher {
|
|
7
|
-
options;
|
|
8
|
-
tbTunnelOpts;
|
|
9
|
-
tunnel;
|
|
10
|
-
constructor(options) {
|
|
11
|
-
this.options = options;
|
|
12
|
-
}
|
|
13
|
-
async onPrepare(config, capabilities) {
|
|
14
|
-
if (!this.options.tbTunnel || !config.user || !config.key) {
|
|
15
|
-
return;
|
|
16
|
-
}
|
|
17
|
-
const tbTunnelIdentifier = (this.options.tbTunnelOpts?.tunnelIdentifier ||
|
|
18
|
-
`TB-tunnel-${Math.random().toString().slice(2)}`);
|
|
19
|
-
this.tbTunnelOpts = Object.assign({
|
|
20
|
-
apiKey: config.user,
|
|
21
|
-
apiSecret: config.key,
|
|
22
|
-
'tunnel-identifier': tbTunnelIdentifier,
|
|
23
|
-
}, this.options.tbTunnelOpts);
|
|
24
|
-
const capabilitiesEntries = Array.isArray(capabilities) ?
|
|
25
|
-
capabilities.every(cap => Object.values(cap).length > 0 && Object.values(cap).every(c => typeof c === 'object' && c.capabilities)) ?
|
|
26
|
-
capabilities.flatMap((cap) => Object.values(cap))
|
|
27
|
-
: capabilities
|
|
28
|
-
: Object.values(capabilities);
|
|
29
|
-
for (const capability of capabilitiesEntries) {
|
|
30
|
-
const caps = capability.capabilities || capability;
|
|
31
|
-
const c = caps.alwaysMatch || caps;
|
|
32
|
-
if (!c['tb:options']) {
|
|
33
|
-
c['tb:options'] = {};
|
|
34
|
-
}
|
|
35
|
-
c['tb:options']['tunnel-identifier'] = tbTunnelIdentifier;
|
|
36
|
-
}
|
|
37
|
-
/**
|
|
38
|
-
* measure TestingBot tunnel boot time
|
|
39
|
-
*/
|
|
40
|
-
const obs = new PerformanceObserver((list) => {
|
|
41
|
-
const entry = list.getEntries()[0];
|
|
42
|
-
log.info(`TestingBot tunnel successfully started after ${entry.duration}ms`);
|
|
43
|
-
});
|
|
44
|
-
obs.observe({ entryTypes: ['measure'] });
|
|
45
|
-
performance.mark('tbTunnelStart');
|
|
46
|
-
this.tunnel = await promisify(testingbotTunnel)(this.tbTunnelOpts);
|
|
47
|
-
performance.mark('tbTunnelEnd');
|
|
48
|
-
performance.measure('bootTime', 'tbTunnelStart', 'tbTunnelEnd');
|
|
49
|
-
}
|
|
50
|
-
/**
|
|
51
|
-
* Shut down the tunnel
|
|
52
|
-
* @returns {Promise} Resolved promise when tunnel is closed
|
|
53
|
-
*/
|
|
54
|
-
onComplete() {
|
|
55
|
-
if (!this.tunnel) {
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
|
-
return new Promise(resolve => this.tunnel.close(resolve));
|
|
59
|
-
}
|
|
60
|
-
}
|
package/build/service.js
DELETED
|
@@ -1,218 +0,0 @@
|
|
|
1
|
-
import logger from '@wdio/logger';
|
|
2
|
-
const log = logger('@wdio/testingbot-service');
|
|
3
|
-
const jobDataProperties = ['name', 'tags', 'public', 'build', 'extra'];
|
|
4
|
-
export default class TestingBotService {
|
|
5
|
-
_options;
|
|
6
|
-
_capabilities;
|
|
7
|
-
_config;
|
|
8
|
-
_browser;
|
|
9
|
-
_isServiceEnabled;
|
|
10
|
-
_suiteTitle;
|
|
11
|
-
_tbSecret;
|
|
12
|
-
_tbUser;
|
|
13
|
-
_failures = 0;
|
|
14
|
-
_testCnt = 0;
|
|
15
|
-
constructor(_options, _capabilities, _config) {
|
|
16
|
-
this._options = _options;
|
|
17
|
-
this._capabilities = _capabilities;
|
|
18
|
-
this._config = _config;
|
|
19
|
-
this._tbUser = this._config.user;
|
|
20
|
-
this._tbSecret = this._config.key;
|
|
21
|
-
this._isServiceEnabled = Boolean(this._tbUser && this._tbSecret);
|
|
22
|
-
}
|
|
23
|
-
before(caps, specs, browser) {
|
|
24
|
-
this._browser = browser;
|
|
25
|
-
}
|
|
26
|
-
/**
|
|
27
|
-
* Before suite
|
|
28
|
-
* @param {object} suite Suite
|
|
29
|
-
*/
|
|
30
|
-
beforeSuite(suite) {
|
|
31
|
-
this._suiteTitle = suite.title;
|
|
32
|
-
}
|
|
33
|
-
/**
|
|
34
|
-
* Before test
|
|
35
|
-
* @param {object} test Test
|
|
36
|
-
*/
|
|
37
|
-
beforeTest(test) {
|
|
38
|
-
if (!this._isServiceEnabled || !this._browser) {
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
|
-
/**
|
|
42
|
-
* in jasmine we get Jasmine__TopLevel__Suite as title since service using test
|
|
43
|
-
* framework hooks in order to execute async functions.
|
|
44
|
-
* This tweak allows us to set the real suite name for jasmine jobs.
|
|
45
|
-
*/
|
|
46
|
-
/* istanbul ignore if */
|
|
47
|
-
if (this._suiteTitle === 'Jasmine__TopLevel__Suite') {
|
|
48
|
-
this._suiteTitle = test.fullName.slice(0, test.fullName.indexOf(test.title) - 1);
|
|
49
|
-
}
|
|
50
|
-
const context = (
|
|
51
|
-
/**
|
|
52
|
-
* Jasmine
|
|
53
|
-
*/
|
|
54
|
-
test.fullName ||
|
|
55
|
-
/**
|
|
56
|
-
* Mocha
|
|
57
|
-
*/
|
|
58
|
-
`${test.parent} - ${test.title}`);
|
|
59
|
-
this._browser.execute('tb:test-context=' + context);
|
|
60
|
-
}
|
|
61
|
-
afterSuite(suite) {
|
|
62
|
-
if (Object.prototype.hasOwnProperty.call(suite, 'error')) {
|
|
63
|
-
++this._failures;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
/**
|
|
67
|
-
* After test
|
|
68
|
-
* @param {object} test Test
|
|
69
|
-
*/
|
|
70
|
-
afterTest(test, context, results) {
|
|
71
|
-
if (!results.passed) {
|
|
72
|
-
++this._failures;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
/**
|
|
76
|
-
* For CucumberJS
|
|
77
|
-
*/
|
|
78
|
-
/**
|
|
79
|
-
* Before feature
|
|
80
|
-
* @param {string} uri
|
|
81
|
-
* @param {object} feature
|
|
82
|
-
*/
|
|
83
|
-
beforeFeature(uri, feature) {
|
|
84
|
-
if (!this._isServiceEnabled || !this._browser) {
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
87
|
-
this._suiteTitle = feature.name;
|
|
88
|
-
this._browser.execute('tb:test-context=Feature: ' + this._suiteTitle);
|
|
89
|
-
}
|
|
90
|
-
/**
|
|
91
|
-
* Before scenario
|
|
92
|
-
* @param {string} uri
|
|
93
|
-
* @param {object} feature
|
|
94
|
-
* @param {object} scenario
|
|
95
|
-
*/
|
|
96
|
-
beforeScenario(world) {
|
|
97
|
-
if (!this._isServiceEnabled || !this._browser) {
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
100
|
-
const scenarioName = world.pickle.name;
|
|
101
|
-
this._browser.execute('tb:test-context=Scenario: ' + scenarioName);
|
|
102
|
-
}
|
|
103
|
-
/**
|
|
104
|
-
*
|
|
105
|
-
* Runs before a Cucumber Scenario.
|
|
106
|
-
* @param world world object containing information on pickle and test step
|
|
107
|
-
* @param result result object containing
|
|
108
|
-
* @param result.passed true if scenario has passed
|
|
109
|
-
* @param result.error error stack if scenario failed
|
|
110
|
-
* @param result.duration duration of scenario in milliseconds
|
|
111
|
-
*/
|
|
112
|
-
afterScenario(world, result) {
|
|
113
|
-
// check if scenario has failed
|
|
114
|
-
if (!result.passed) {
|
|
115
|
-
++this._failures;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
/**
|
|
119
|
-
* Update TestingBot info
|
|
120
|
-
* @return {Promise} Promise with result of updateJob method call
|
|
121
|
-
*/
|
|
122
|
-
after(result) {
|
|
123
|
-
if (!this._isServiceEnabled || !this._browser) {
|
|
124
|
-
return;
|
|
125
|
-
}
|
|
126
|
-
let failures = this._failures;
|
|
127
|
-
/**
|
|
128
|
-
* set failures if user has bail option set in which case afterTest and
|
|
129
|
-
* afterSuite aren't executed before after hook
|
|
130
|
-
*/
|
|
131
|
-
if (this._config.mochaOpts?.bail && Boolean(result)) {
|
|
132
|
-
failures = 1;
|
|
133
|
-
}
|
|
134
|
-
const status = 'status: ' + (failures > 0 ? 'failing' : 'passing');
|
|
135
|
-
if (!this._browser.isMultiremote) {
|
|
136
|
-
log.info(`Update job with sessionId ${this._browser.sessionId}, ${status}`);
|
|
137
|
-
return this.updateJob(this._browser.sessionId, failures);
|
|
138
|
-
}
|
|
139
|
-
const browser = this._browser;
|
|
140
|
-
return Promise.all(Object.keys(this._capabilities).map((browserName) => {
|
|
141
|
-
log.info(`Update multiremote job for browser "${browserName}" and sessionId ${browser.getInstance(browserName).sessionId}, ${status}`);
|
|
142
|
-
return this.updateJob(browser.getInstance(browserName).sessionId, failures, false, browserName);
|
|
143
|
-
}));
|
|
144
|
-
}
|
|
145
|
-
onReload(oldSessionId, newSessionId) {
|
|
146
|
-
if (!this._isServiceEnabled || !this._browser) {
|
|
147
|
-
return;
|
|
148
|
-
}
|
|
149
|
-
const status = 'status: ' + (this._failures > 0 ? 'failing' : 'passing');
|
|
150
|
-
if (!this._browser.isMultiremote) {
|
|
151
|
-
log.info(`Update (reloaded) job with sessionId ${oldSessionId}, ${status}`);
|
|
152
|
-
return this.updateJob(oldSessionId, this._failures, true);
|
|
153
|
-
}
|
|
154
|
-
const browser = this._browser;
|
|
155
|
-
const browserName = browser.instances.filter((browserName) => browser.getInstance(browserName).sessionId === newSessionId)[0];
|
|
156
|
-
log.info(`Update (reloaded) multiremote job for browser "${browserName}" and sessionId ${oldSessionId}, ${status}`);
|
|
157
|
-
return this.updateJob(oldSessionId, this._failures, true, browserName);
|
|
158
|
-
}
|
|
159
|
-
async updateJob(sessionId, failures, calledOnReload = false, browserName) {
|
|
160
|
-
if (!this._browser) {
|
|
161
|
-
return;
|
|
162
|
-
}
|
|
163
|
-
let headers = {
|
|
164
|
-
'Content-Type': 'application/json; charset=utf-8',
|
|
165
|
-
};
|
|
166
|
-
if (this._tbUser && this._tbSecret) {
|
|
167
|
-
const encodedAuth = Buffer.from(`${this._tbUser}:${this._tbSecret}`, 'utf8').toString('base64');
|
|
168
|
-
headers = {
|
|
169
|
-
...headers,
|
|
170
|
-
Authorization: `Basic ${encodedAuth}`,
|
|
171
|
-
};
|
|
172
|
-
}
|
|
173
|
-
const json = this.getBody(failures, calledOnReload, browserName);
|
|
174
|
-
this._failures = 0;
|
|
175
|
-
const response = await fetch(this.getRestUrl(sessionId), {
|
|
176
|
-
method: 'PUT',
|
|
177
|
-
body: JSON.stringify(json),
|
|
178
|
-
headers
|
|
179
|
-
});
|
|
180
|
-
return await response.json();
|
|
181
|
-
}
|
|
182
|
-
/**
|
|
183
|
-
*
|
|
184
|
-
* @param {String} sessionId Session id
|
|
185
|
-
* @returns {String} TestingBot API URL
|
|
186
|
-
*/
|
|
187
|
-
getRestUrl(sessionId) {
|
|
188
|
-
return `https://api.testingbot.com/v1/tests/${sessionId}`;
|
|
189
|
-
}
|
|
190
|
-
getBody(failures, calledOnReload = false, browserName) {
|
|
191
|
-
const body = { test: {} };
|
|
192
|
-
/**
|
|
193
|
-
* set default values
|
|
194
|
-
*/
|
|
195
|
-
body.test.name = this._suiteTitle;
|
|
196
|
-
/**
|
|
197
|
-
* add reload count to title if reload is used
|
|
198
|
-
*/
|
|
199
|
-
if ((calledOnReload || this._testCnt) && this._browser) {
|
|
200
|
-
let testCnt = ++this._testCnt;
|
|
201
|
-
if (this._browser.isMultiremote) {
|
|
202
|
-
testCnt = Math.ceil(testCnt / this._browser.instances.length);
|
|
203
|
-
}
|
|
204
|
-
body.test.name += ` (${testCnt})`;
|
|
205
|
-
}
|
|
206
|
-
for (const prop of jobDataProperties) {
|
|
207
|
-
if (!this._capabilities[prop]) {
|
|
208
|
-
continue;
|
|
209
|
-
}
|
|
210
|
-
body.test[prop] = this._capabilities[prop];
|
|
211
|
-
}
|
|
212
|
-
if (browserName) {
|
|
213
|
-
body.test.name = `${browserName}: ${body.test.name}`;
|
|
214
|
-
}
|
|
215
|
-
body.test.success = failures === 0 ? '1' : '0';
|
|
216
|
-
return body;
|
|
217
|
-
}
|
|
218
|
-
}
|
package/build/types.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
/package/{LICENSE-MIT → LICENSE}
RENAMED
|
File without changes
|