@tvlabs/wdio-service 0.1.5 → 0.1.6
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/README.md +16 -2
- package/cjs/channels/base.d.ts +20 -0
- package/cjs/channels/base.d.ts.map +1 -0
- package/cjs/channels/build.d.ts +16 -0
- package/cjs/channels/build.d.ts.map +1 -0
- package/cjs/{channel.d.ts → channels/session.d.ts} +4 -14
- package/cjs/channels/session.d.ts.map +1 -0
- package/cjs/index.js +202 -70
- package/cjs/service.d.ts +4 -1
- package/cjs/service.d.ts.map +1 -1
- package/cjs/types.d.ts +17 -1
- package/cjs/types.d.ts.map +1 -1
- package/esm/channels/base.d.ts +20 -0
- package/esm/channels/base.d.ts.map +1 -0
- package/esm/channels/build.d.ts +16 -0
- package/esm/channels/build.d.ts.map +1 -0
- package/esm/{channel.d.ts → channels/session.d.ts} +4 -14
- package/esm/channels/session.d.ts.map +1 -0
- package/esm/index.js +199 -70
- package/esm/service.d.ts +4 -1
- package/esm/service.d.ts.map +1 -1
- package/esm/types.d.ts +17 -1
- package/esm/types.d.ts.map +1 -1
- package/package.json +5 -4
- package/src/channels/base.ts +110 -0
- package/src/channels/build.ts +174 -0
- package/src/{channel.ts → channels/session.ts} +18 -100
- package/src/service.ts +41 -8
- package/src/types.ts +20 -1
- package/cjs/channel.d.ts.map +0 -1
- package/esm/channel.d.ts.map +0 -1
package/esm/index.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { SevereServiceError } from 'webdriverio';
|
|
2
|
-
import * as crypto from 'crypto';
|
|
2
|
+
import * as crypto$1 from 'crypto';
|
|
3
3
|
import { WebSocket } from 'ws';
|
|
4
4
|
import { Socket } from 'phoenix';
|
|
5
|
+
import * as fs from 'node:fs';
|
|
6
|
+
import * as path from 'node:path';
|
|
7
|
+
import * as crypto from 'node:crypto';
|
|
5
8
|
|
|
6
9
|
const LOG_LEVELS = {
|
|
7
10
|
error: 0,
|
|
@@ -107,7 +110,7 @@ class Logger {
|
|
|
107
110
|
}
|
|
108
111
|
|
|
109
112
|
var name = "@tvlabs/wdio-service";
|
|
110
|
-
var version = "0.1.
|
|
113
|
+
var version = "0.1.6";
|
|
111
114
|
var packageJson = {
|
|
112
115
|
name: name,
|
|
113
116
|
version: version};
|
|
@@ -125,15 +128,81 @@ function getServiceName() {
|
|
|
125
128
|
return packageJson.name;
|
|
126
129
|
}
|
|
127
130
|
|
|
128
|
-
class
|
|
131
|
+
class BaseChannel {
|
|
129
132
|
endpoint;
|
|
130
133
|
maxReconnectRetries;
|
|
131
134
|
key;
|
|
132
135
|
logLevel;
|
|
133
136
|
socket;
|
|
137
|
+
log;
|
|
138
|
+
constructor(endpoint, maxReconnectRetries, key, logLevel = 'info', loggerName) {
|
|
139
|
+
this.endpoint = endpoint;
|
|
140
|
+
this.maxReconnectRetries = maxReconnectRetries;
|
|
141
|
+
this.key = key;
|
|
142
|
+
this.logLevel = logLevel;
|
|
143
|
+
this.log = new Logger(loggerName, this.logLevel);
|
|
144
|
+
this.socket = new Socket(this.endpoint, {
|
|
145
|
+
transport: WebSocket,
|
|
146
|
+
params: this.params(),
|
|
147
|
+
reconnectAfterMs: this.reconnectAfterMs.bind(this),
|
|
148
|
+
});
|
|
149
|
+
this.socket.onError((...args) => BaseChannel.logSocketError(this.log, ...args));
|
|
150
|
+
}
|
|
151
|
+
async join(topic) {
|
|
152
|
+
return new Promise((res, rej) => {
|
|
153
|
+
topic
|
|
154
|
+
.join()
|
|
155
|
+
.receive('ok', (_resp) => {
|
|
156
|
+
res();
|
|
157
|
+
})
|
|
158
|
+
.receive('error', ({ response }) => {
|
|
159
|
+
rej('Failed to join topic: ' + response);
|
|
160
|
+
})
|
|
161
|
+
.receive('timeout', () => {
|
|
162
|
+
rej('timeout');
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
async push(topic, event, payload) {
|
|
167
|
+
return new Promise((res, rej) => {
|
|
168
|
+
topic
|
|
169
|
+
.push(event, payload)
|
|
170
|
+
.receive('ok', (msg) => {
|
|
171
|
+
res(msg);
|
|
172
|
+
})
|
|
173
|
+
.receive('error', (reason) => {
|
|
174
|
+
rej(reason);
|
|
175
|
+
})
|
|
176
|
+
.receive('timeout', () => {
|
|
177
|
+
rej('timeout');
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
params() {
|
|
182
|
+
const serviceInfo = getServiceInfo();
|
|
183
|
+
this.log.debug('Info:', serviceInfo);
|
|
184
|
+
return {
|
|
185
|
+
...serviceInfo,
|
|
186
|
+
api_key: this.key,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
reconnectAfterMs(tries) {
|
|
190
|
+
if (tries > this.maxReconnectRetries) {
|
|
191
|
+
throw new SevereServiceError('Could not connect to TV Labs, please check your connection.');
|
|
192
|
+
}
|
|
193
|
+
const wait = [0, 1000, 3000, 5000][tries] || 10000;
|
|
194
|
+
this.log.info(`[${tries}/${this.maxReconnectRetries}] Waiting ${wait}ms before re-attempting to connect...`);
|
|
195
|
+
return wait;
|
|
196
|
+
}
|
|
197
|
+
static logSocketError(log, event, _transport, _establishedConnections) {
|
|
198
|
+
const error = event.error;
|
|
199
|
+
log.error('Socket error:', error || event);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
class SessionChannel extends BaseChannel {
|
|
134
204
|
lobbyTopic;
|
|
135
205
|
requestTopic;
|
|
136
|
-
log;
|
|
137
206
|
events = {
|
|
138
207
|
SESSION_READY: 'session:ready',
|
|
139
208
|
SESSION_FAILED: 'session:failed',
|
|
@@ -143,17 +212,7 @@ class TVLabsChannel {
|
|
|
143
212
|
REQUEST_MATCHING: 'request:matching',
|
|
144
213
|
};
|
|
145
214
|
constructor(endpoint, maxReconnectRetries, key, logLevel = 'info') {
|
|
146
|
-
|
|
147
|
-
this.maxReconnectRetries = maxReconnectRetries;
|
|
148
|
-
this.key = key;
|
|
149
|
-
this.logLevel = logLevel;
|
|
150
|
-
this.log = new Logger('@tvlabs/wdio-channel', this.logLevel);
|
|
151
|
-
this.socket = new Socket(this.endpoint, {
|
|
152
|
-
transport: WebSocket,
|
|
153
|
-
params: this.params(),
|
|
154
|
-
reconnectAfterMs: this.reconnectAfterMs.bind(this),
|
|
155
|
-
});
|
|
156
|
-
this.socket.onError((...args) => TVLabsChannel.logSocketError(this.log, ...args));
|
|
215
|
+
super(endpoint, maxReconnectRetries, key, logLevel, '@tvlabs/session-channel');
|
|
157
216
|
this.lobbyTopic = this.socket.channel('requests:lobby');
|
|
158
217
|
}
|
|
159
218
|
async disconnect() {
|
|
@@ -165,14 +224,14 @@ class TVLabsChannel {
|
|
|
165
224
|
}
|
|
166
225
|
async connect() {
|
|
167
226
|
try {
|
|
168
|
-
this.log.debug('Connecting to
|
|
227
|
+
this.log.debug('Connecting to session channel...');
|
|
169
228
|
this.socket.connect();
|
|
170
229
|
await this.join(this.lobbyTopic);
|
|
171
|
-
this.log.debug('Connected to
|
|
230
|
+
this.log.debug('Connected to session channel!');
|
|
172
231
|
}
|
|
173
232
|
catch (error) {
|
|
174
|
-
this.log.error('Error connecting to
|
|
175
|
-
throw new SevereServiceError('Could not connect to
|
|
233
|
+
this.log.error('Error connecting to session channel:', error);
|
|
234
|
+
throw new SevereServiceError('Could not connect to session channel, please check your connection.');
|
|
176
235
|
}
|
|
177
236
|
}
|
|
178
237
|
async newSession(capabilities, maxRetries, retry = 0) {
|
|
@@ -250,57 +309,111 @@ class TVLabsChannel {
|
|
|
250
309
|
throw error;
|
|
251
310
|
}
|
|
252
311
|
}
|
|
253
|
-
|
|
254
|
-
return
|
|
255
|
-
topic
|
|
256
|
-
.join()
|
|
257
|
-
.receive('ok', (_resp) => {
|
|
258
|
-
res();
|
|
259
|
-
})
|
|
260
|
-
.receive('error', ({ response }) => {
|
|
261
|
-
rej('Failed to join topic: ' + response);
|
|
262
|
-
})
|
|
263
|
-
.receive('timeout', () => {
|
|
264
|
-
rej('timeout');
|
|
265
|
-
});
|
|
266
|
-
});
|
|
312
|
+
tvlabsSessionLink(sessionId) {
|
|
313
|
+
return `https://tvlabs.ai/app/sessions/${sessionId}`;
|
|
267
314
|
}
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
});
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
class BuildChannel extends BaseChannel {
|
|
318
|
+
lobbyTopic;
|
|
319
|
+
constructor(endpoint, maxReconnectRetries, key, logLevel = 'info') {
|
|
320
|
+
super(endpoint, maxReconnectRetries, key, logLevel, '@tvlabs/build-channel');
|
|
321
|
+
this.lobbyTopic = this.socket.channel('upload:lobby');
|
|
322
|
+
}
|
|
323
|
+
async disconnect() {
|
|
324
|
+
return new Promise((res, _rej) => {
|
|
325
|
+
this.lobbyTopic.leave();
|
|
326
|
+
this.socket.disconnect(() => res());
|
|
281
327
|
});
|
|
282
328
|
}
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
}
|
|
329
|
+
async connect() {
|
|
330
|
+
try {
|
|
331
|
+
this.log.debug('Connecting to build channel...');
|
|
332
|
+
this.socket.connect();
|
|
333
|
+
await this.join(this.lobbyTopic);
|
|
334
|
+
this.log.debug('Connected to build channel!');
|
|
335
|
+
}
|
|
336
|
+
catch (error) {
|
|
337
|
+
this.log.error('Error connecting to build channel:', error);
|
|
338
|
+
throw new SevereServiceError('Could not connect to build channel, please check your connection.');
|
|
339
|
+
}
|
|
290
340
|
}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
341
|
+
async uploadBuild(buildPath, appSlug) {
|
|
342
|
+
const metadata = await this.getFileMetadata(buildPath);
|
|
343
|
+
this.log.info(`Requesting upload for build ${metadata.filename} (${metadata.type}, ${metadata.size} bytes)`);
|
|
344
|
+
const { url, build_id } = await this.requestUploadUrl(metadata, appSlug);
|
|
345
|
+
this.log.info('Uploading build...');
|
|
346
|
+
await this.uploadToUrl(url, buildPath, metadata);
|
|
347
|
+
const { application_id } = await this.extractBuildInfo();
|
|
348
|
+
this.log.info(`Build "${application_id}" processed successfully`);
|
|
349
|
+
return build_id;
|
|
350
|
+
}
|
|
351
|
+
async requestUploadUrl(metadata, appSlug) {
|
|
352
|
+
try {
|
|
353
|
+
return await this.push(this.lobbyTopic, 'request_upload_url', { metadata, application_slug: appSlug });
|
|
354
|
+
}
|
|
355
|
+
catch (error) {
|
|
356
|
+
this.log.error('Error requesting upload URL:', error);
|
|
357
|
+
throw error;
|
|
294
358
|
}
|
|
295
|
-
const wait = [0, 1000, 3000, 5000][tries] || 10000;
|
|
296
|
-
this.log.info(`[${tries}/${this.maxReconnectRetries}] Waiting ${wait}ms before re-attempting to connect...`);
|
|
297
|
-
return wait;
|
|
298
359
|
}
|
|
299
|
-
|
|
300
|
-
|
|
360
|
+
async uploadToUrl(url, filePath, metadata) {
|
|
361
|
+
try {
|
|
362
|
+
const response = await fetch(url, {
|
|
363
|
+
method: 'PUT',
|
|
364
|
+
headers: {
|
|
365
|
+
'Content-Type': metadata.type,
|
|
366
|
+
'Content-Length': String(metadata.size),
|
|
367
|
+
},
|
|
368
|
+
body: fs.createReadStream(filePath),
|
|
369
|
+
duplex: 'half',
|
|
370
|
+
});
|
|
371
|
+
if (!response.ok) {
|
|
372
|
+
throw new SevereServiceError(`Failed to upload build to storage, got ${response.status}`);
|
|
373
|
+
}
|
|
374
|
+
this.log.info('Upload complete');
|
|
375
|
+
}
|
|
376
|
+
catch (error) {
|
|
377
|
+
this.log.error('Error uploading build:', error);
|
|
378
|
+
throw error;
|
|
379
|
+
}
|
|
301
380
|
}
|
|
302
|
-
|
|
303
|
-
log.
|
|
381
|
+
async extractBuildInfo() {
|
|
382
|
+
this.log.info('Processing uploaded build...');
|
|
383
|
+
try {
|
|
384
|
+
return await this.push(this.lobbyTopic, 'extract_build_info', {});
|
|
385
|
+
}
|
|
386
|
+
catch (error) {
|
|
387
|
+
this.log.error('Error processing build:', error);
|
|
388
|
+
throw error;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
async getFileMetadata(buildPath) {
|
|
392
|
+
const filename = path.basename(buildPath);
|
|
393
|
+
const size = fs.statSync(buildPath).size;
|
|
394
|
+
const type = this.detectMimeType(filename);
|
|
395
|
+
const sha256 = await this.computeSha256(buildPath);
|
|
396
|
+
return { filename, type, size, sha256 };
|
|
397
|
+
}
|
|
398
|
+
async computeSha256(buildPath) {
|
|
399
|
+
return new Promise((resolve, reject) => {
|
|
400
|
+
const hash = crypto.createHash('sha256');
|
|
401
|
+
const stream = fs.createReadStream(buildPath);
|
|
402
|
+
stream.on('data', (chunk) => hash.update(chunk));
|
|
403
|
+
stream.on('end', () => resolve(hash.digest('hex')));
|
|
404
|
+
stream.on('error', (err) => reject(err));
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
detectMimeType(filename) {
|
|
408
|
+
const fileExtension = path.extname(filename).toLowerCase();
|
|
409
|
+
switch (fileExtension) {
|
|
410
|
+
case '.apk':
|
|
411
|
+
return 'application/vnd.android.package-archive';
|
|
412
|
+
case '.zip':
|
|
413
|
+
return 'application/zip';
|
|
414
|
+
default:
|
|
415
|
+
return 'application/octet-stream';
|
|
416
|
+
}
|
|
304
417
|
}
|
|
305
418
|
}
|
|
306
419
|
|
|
@@ -324,15 +437,22 @@ class TVLabsService {
|
|
|
324
437
|
}
|
|
325
438
|
}
|
|
326
439
|
async beforeSession(_config, capabilities, _specs, _cid) {
|
|
327
|
-
const
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
440
|
+
const buildPath = this.buildPath();
|
|
441
|
+
if (buildPath) {
|
|
442
|
+
const buildChannel = new BuildChannel(this.buildEndpoint(), this.reconnectRetries(), this.apiKey(), this.logLevel());
|
|
443
|
+
await buildChannel.connect();
|
|
444
|
+
capabilities['tvlabs:build'] = await buildChannel.uploadBuild(buildPath, this.appSlug());
|
|
445
|
+
await buildChannel.disconnect();
|
|
446
|
+
}
|
|
447
|
+
const sessionChannel = new SessionChannel(this.sessionEndpoint(), this.reconnectRetries(), this.apiKey(), this.logLevel());
|
|
448
|
+
await sessionChannel.connect();
|
|
449
|
+
capabilities['tvlabs:session_id'] = await sessionChannel.newSession(capabilities, this.retries());
|
|
450
|
+
await sessionChannel.disconnect();
|
|
331
451
|
}
|
|
332
452
|
setupRequestId() {
|
|
333
453
|
const originalTransformRequest = this._config.transformRequest;
|
|
334
454
|
this._config.transformRequest = (requestOptions) => {
|
|
335
|
-
const requestId = crypto.randomUUID();
|
|
455
|
+
const requestId = crypto$1.randomUUID();
|
|
336
456
|
const originalRequestOptions = typeof originalTransformRequest === 'function'
|
|
337
457
|
? originalTransformRequest(requestOptions)
|
|
338
458
|
: requestOptions;
|
|
@@ -357,8 +477,17 @@ class TVLabsService {
|
|
|
357
477
|
}
|
|
358
478
|
}
|
|
359
479
|
}
|
|
360
|
-
|
|
361
|
-
return this._options.
|
|
480
|
+
buildPath() {
|
|
481
|
+
return this._options.buildPath;
|
|
482
|
+
}
|
|
483
|
+
appSlug() {
|
|
484
|
+
return this._options.app;
|
|
485
|
+
}
|
|
486
|
+
sessionEndpoint() {
|
|
487
|
+
return this._options.sessionEndpoint ?? 'wss://tvlabs.ai/appium';
|
|
488
|
+
}
|
|
489
|
+
buildEndpoint() {
|
|
490
|
+
return this._options.buildEndpoint ?? 'wss://tvlabs.ai/cli';
|
|
362
491
|
}
|
|
363
492
|
retries() {
|
|
364
493
|
return this._options.retries ?? 3;
|
package/esm/service.d.ts
CHANGED
|
@@ -10,7 +10,10 @@ export default class TVLabsService implements Services.ServiceInstance {
|
|
|
10
10
|
beforeSession(_config: Omit<Options.Testrunner, 'capabilities'>, capabilities: TVLabsCapabilities, _specs: string[], _cid: string): Promise<void>;
|
|
11
11
|
private setupRequestId;
|
|
12
12
|
private setRequestHeader;
|
|
13
|
-
private
|
|
13
|
+
private buildPath;
|
|
14
|
+
private appSlug;
|
|
15
|
+
private sessionEndpoint;
|
|
16
|
+
private buildEndpoint;
|
|
14
17
|
private retries;
|
|
15
18
|
private apiKey;
|
|
16
19
|
private logLevel;
|
package/esm/service.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,QAAQ,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AACnE,OAAO,KAAK,EACV,kBAAkB,EAClB,oBAAoB,EAErB,MAAM,YAAY,CAAC;AAEpB,MAAM,CAAC,OAAO,OAAO,aAAc,YAAW,QAAQ,CAAC,eAAe;IAIlE,OAAO,CAAC,QAAQ;IAChB,OAAO,CAAC,aAAa;IACrB,OAAO,CAAC,OAAO;IALjB,OAAO,CAAC,GAAG,CAAS;gBAGV,QAAQ,EAAE,oBAAoB,EAC9B,aAAa,EAAE,YAAY,CAAC,8BAA8B,EAC1D,OAAO,EAAE,OAAO,CAAC,WAAW;IAQtC,SAAS,CACP,OAAO,EAAE,OAAO,CAAC,UAAU,EAC3B,KAAK,EAAE,YAAY,CAAC,sBAAsB;IAStC,aAAa,CACjB,OAAO,EAAE,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,cAAc,CAAC,EACjD,YAAY,EAAE,kBAAkB,EAChC,MAAM,EAAE,MAAM,EAAE,EAChB,IAAI,EAAE,MAAM;IAuCd,OAAO,CAAC,cAAc;IA0BtB,OAAO,CAAC,gBAAgB;IAgBxB,OAAO,CAAC,SAAS;IAIjB,OAAO,CAAC,OAAO;IAIf,OAAO,CAAC,eAAe;IAIvB,OAAO,CAAC,aAAa;IAIrB,OAAO,CAAC,OAAO;IAIf,OAAO,CAAC,MAAM;IAId,OAAO,CAAC,QAAQ;IAIhB,OAAO,CAAC,eAAe;IAIvB,OAAO,CAAC,gBAAgB;CAGzB"}
|
package/esm/types.d.ts
CHANGED
|
@@ -2,8 +2,11 @@ import type { Capabilities } from '@wdio/types';
|
|
|
2
2
|
export type LogLevel = 'error' | 'warn' | 'info' | 'debug' | 'trace' | 'silent';
|
|
3
3
|
export type TVLabsServiceOptions = {
|
|
4
4
|
apiKey: string;
|
|
5
|
-
|
|
5
|
+
sessionEndpoint?: string;
|
|
6
|
+
buildEndpoint?: string;
|
|
6
7
|
retries?: number;
|
|
8
|
+
buildPath?: string;
|
|
9
|
+
app?: string;
|
|
7
10
|
reconnectRetries?: number;
|
|
8
11
|
attachRequestId?: boolean;
|
|
9
12
|
};
|
|
@@ -38,4 +41,17 @@ export type TVLabsServiceInfo = {
|
|
|
38
41
|
service_version: string;
|
|
39
42
|
service_name: string;
|
|
40
43
|
};
|
|
44
|
+
export type TVLabsRequestUploadUrlResponse = {
|
|
45
|
+
url: string;
|
|
46
|
+
build_id: string;
|
|
47
|
+
};
|
|
48
|
+
export type TVLabsExtractBuildInfoResponse = {
|
|
49
|
+
application_id: string;
|
|
50
|
+
};
|
|
51
|
+
export type TVLabsBuildMetadata = {
|
|
52
|
+
filename: string;
|
|
53
|
+
type: string;
|
|
54
|
+
size: number;
|
|
55
|
+
sha256: string;
|
|
56
|
+
};
|
|
41
57
|
//# sourceMappingURL=types.d.ts.map
|
package/esm/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,GAAG,QAAQ,CAAC;AAEhF,MAAM,MAAM,oBAAoB,GAAG;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,GAAG,QAAQ,CAAC;AAEhF,MAAM,MAAM,oBAAoB,GAAG;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAC5B,YAAY,CAAC,+BAA+B,GAAG;IAC7C,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,oBAAoB,CAAC,EAAE;QACrB,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,kCAAkC,CAAC,EAAE,MAAM,CAAC;QAC5C,qBAAqB,CAAC,EAAE,OAAO,CAAC;KACjC,CAAC;IACF,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,uBAAuB,CAAC,EAAE,MAAM,CAAC;CAClC,CAAC;AAEJ,MAAM,MAAM,gCAAgC,GAAG,CAC7C,QAAQ,EAAE,0BAA0B,KACjC,IAAI,CAAC;AAEV,MAAM,MAAM,0BAA0B,GAAG;IACvC,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,4BAA4B,GAAG;IACzC,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG,iBAAiB,GAAG;IACnD,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,eAAe,EAAE,MAAM,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,8BAA8B,GAAG;IAC3C,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,8BAA8B,GAAG;IAC3C,cAAc,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tvlabs/wdio-service",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "WebdriverIO service that provides a better integration into TV Labs",
|
|
5
5
|
"author": "Regan Karlewicz <regan@tvlabs.ai>",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
},
|
|
10
10
|
"repository": {
|
|
11
11
|
"type": "git",
|
|
12
|
-
"url": "git+https://github.com/tv-labs/wdio-
|
|
12
|
+
"url": "git+https://github.com/tv-labs/wdio-service.git"
|
|
13
13
|
},
|
|
14
14
|
"keywords": [
|
|
15
15
|
"wdio-plugin",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"appium"
|
|
20
20
|
],
|
|
21
21
|
"bugs": {
|
|
22
|
-
"url": "https://github.com/tv-labs/wdio-
|
|
22
|
+
"url": "https://github.com/tv-labs/wdio-service/issues"
|
|
23
23
|
},
|
|
24
24
|
"scripts": {
|
|
25
25
|
"build": "npm run clean && rollup -c",
|
|
@@ -29,7 +29,8 @@
|
|
|
29
29
|
"format": "prettier --write .",
|
|
30
30
|
"format:check": "prettier --check .",
|
|
31
31
|
"lint": "eslint",
|
|
32
|
-
"test": "vitest --config vitest.config.ts --coverage",
|
|
32
|
+
"test": "vitest --config vitest.config.ts --coverage --run",
|
|
33
|
+
"test:watch": "vitest --config vitest.config.ts --coverage",
|
|
33
34
|
"publish:dry": "npm publish --access public --provenance --dry-run"
|
|
34
35
|
},
|
|
35
36
|
"type": "module",
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { WebSocket } from 'ws';
|
|
2
|
+
import { Socket, type Channel } from 'phoenix';
|
|
3
|
+
import { SevereServiceError } from 'webdriverio';
|
|
4
|
+
import { Logger } from '../logger.js';
|
|
5
|
+
import { getServiceInfo } from '../utils.js';
|
|
6
|
+
|
|
7
|
+
import type { TVLabsSocketParams, LogLevel } from '../types.js';
|
|
8
|
+
import type { PhoenixChannelJoinResponse } from '../phoenix.js';
|
|
9
|
+
|
|
10
|
+
export abstract class BaseChannel {
|
|
11
|
+
protected socket: Socket;
|
|
12
|
+
protected log: Logger;
|
|
13
|
+
|
|
14
|
+
constructor(
|
|
15
|
+
protected endpoint: string,
|
|
16
|
+
protected maxReconnectRetries: number,
|
|
17
|
+
protected key: string,
|
|
18
|
+
protected logLevel: LogLevel = 'info',
|
|
19
|
+
loggerName: string,
|
|
20
|
+
) {
|
|
21
|
+
this.log = new Logger(loggerName, this.logLevel);
|
|
22
|
+
|
|
23
|
+
this.socket = new Socket(this.endpoint, {
|
|
24
|
+
transport: WebSocket,
|
|
25
|
+
params: this.params(),
|
|
26
|
+
reconnectAfterMs: this.reconnectAfterMs.bind(this),
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
this.socket.onError((...args) =>
|
|
30
|
+
BaseChannel.logSocketError(this.log, ...args),
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
abstract connect(): Promise<void>;
|
|
35
|
+
abstract disconnect(): Promise<void>;
|
|
36
|
+
|
|
37
|
+
protected async join(topic: Channel): Promise<void> {
|
|
38
|
+
return new Promise((res, rej) => {
|
|
39
|
+
topic
|
|
40
|
+
.join()
|
|
41
|
+
.receive('ok', (_resp: PhoenixChannelJoinResponse) => {
|
|
42
|
+
res();
|
|
43
|
+
})
|
|
44
|
+
.receive('error', ({ response }: PhoenixChannelJoinResponse) => {
|
|
45
|
+
rej('Failed to join topic: ' + response);
|
|
46
|
+
})
|
|
47
|
+
.receive('timeout', () => {
|
|
48
|
+
rej('timeout');
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
protected async push<T>(
|
|
54
|
+
topic: Channel,
|
|
55
|
+
event: string,
|
|
56
|
+
payload: object,
|
|
57
|
+
): Promise<T> {
|
|
58
|
+
return new Promise((res, rej) => {
|
|
59
|
+
topic
|
|
60
|
+
.push(event, payload)
|
|
61
|
+
.receive('ok', (msg: T) => {
|
|
62
|
+
res(msg);
|
|
63
|
+
})
|
|
64
|
+
.receive('error', (reason: string) => {
|
|
65
|
+
rej(reason);
|
|
66
|
+
})
|
|
67
|
+
.receive('timeout', () => {
|
|
68
|
+
rej('timeout');
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
private params(): TVLabsSocketParams {
|
|
74
|
+
const serviceInfo = getServiceInfo();
|
|
75
|
+
|
|
76
|
+
this.log.debug('Info:', serviceInfo);
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
...serviceInfo,
|
|
80
|
+
api_key: this.key,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
private reconnectAfterMs(tries: number) {
|
|
85
|
+
if (tries > this.maxReconnectRetries) {
|
|
86
|
+
throw new SevereServiceError(
|
|
87
|
+
'Could not connect to TV Labs, please check your connection.',
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const wait = [0, 1000, 3000, 5000][tries] || 10000;
|
|
92
|
+
|
|
93
|
+
this.log.info(
|
|
94
|
+
`[${tries}/${this.maxReconnectRetries}] Waiting ${wait}ms before re-attempting to connect...`,
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
return wait;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
private static logSocketError(
|
|
101
|
+
log: Logger,
|
|
102
|
+
event: ErrorEvent,
|
|
103
|
+
_transport: new (endpoint: string) => object,
|
|
104
|
+
_establishedConnections: number,
|
|
105
|
+
) {
|
|
106
|
+
const error = event.error;
|
|
107
|
+
|
|
108
|
+
log.error('Socket error:', error || event);
|
|
109
|
+
}
|
|
110
|
+
}
|