@tvlabs/wdio-service 0.1.5 → 0.1.7

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 CHANGED
@@ -10,9 +10,11 @@
10
10
 
11
11
  ## Introduction
12
12
 
13
- The `@tvlabs/wdio-service` package uses a websocket to connect to the TV Labs platform before an Appium session begins, logging events relating to TV Labs session creation as they occur. This offloads the responsibility of creating the TV Labs session from the `POST /session` Webdriver endpoint, leading to more reliable session requests and creation.
13
+ The `@tvlabs/wdio-service` package uses a websocket to connect to the TV Labs platform before an Appium session begins, logging events relating to build upload and session creation as they occur. This offloads the responsibility of creating the TV Labs session from the `POST /session` Webdriver endpoint, leading to more reliable session requests and creation.
14
14
 
15
- The service first makes a session request, and then subscribes to events for that request. Once the session has been filled and is ready for the Webdriver script to begin, the service receives a ready event with the TV Labs session ID. This session ID is injected into the capabilities as `tvlabs:session_id` on the Webdriver session create request.
15
+ If a build path is provided, the service first makes a build upload request to the TV Labs platform, and then sets the `tvlabs:build` capability to the newly created build ID.
16
+
17
+ The service then makes a session request and then subscribes to events for that request. Once the session has been filled and is ready for the Webdriver script to begin, the service receives a ready event with the TV Labs session ID. This session ID is injected into the capabilities as `tvlabs:session_id` on the Webdriver session create request.
16
18
 
17
19
  Additionally, the service adds a unique request ID for each request made. The service will generate and attach an `x-request-id` header before each request to the TV Labs platform. This can be used to correlate requests in the client side logs to the Appium server logs.
18
20
 
@@ -100,6 +102,18 @@ run();
100
102
  - **Required:** Yes
101
103
  - **Description:** TV Labs API key used for authentication to the platform
102
104
 
105
+ ### `buildPath`
106
+
107
+ - **Type:** `string`
108
+ - **Required:** No
109
+ - **Description:** Path to the packaged build to use for the session. When provided, this will perform a build upload before the session is created, and sets the `tvlabs:build` capability to the newly created build ID. The build is uploaded under the organizations default App unless the `app` option is provided
110
+
111
+ ### `app`
112
+
113
+ - **Type:** `string`
114
+ - **Required:** No
115
+ - **Description:** Slug of the App for build uploads. When provided in combination with `buildPath`, the build is uploaded under this specified App.
116
+
103
117
  ### `retries`
104
118
 
105
119
  - **Type:** `number`
@@ -0,0 +1,20 @@
1
+ import { Socket, type Channel } from 'phoenix';
2
+ import { Logger } from '../logger.js';
3
+ import type { LogLevel } from '../types.js';
4
+ export declare abstract class BaseChannel {
5
+ protected endpoint: string;
6
+ protected maxReconnectRetries: number;
7
+ protected key: string;
8
+ protected logLevel: LogLevel;
9
+ protected socket: Socket;
10
+ protected log: Logger;
11
+ constructor(endpoint: string, maxReconnectRetries: number, key: string, logLevel: LogLevel | undefined, loggerName: string);
12
+ abstract connect(): Promise<void>;
13
+ abstract disconnect(): Promise<void>;
14
+ protected join(topic: Channel): Promise<void>;
15
+ protected push<T>(topic: Channel, event: string, payload: object): Promise<T>;
16
+ private params;
17
+ private reconnectAfterMs;
18
+ private static logSocketError;
19
+ }
20
+ //# sourceMappingURL=base.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"base.d.ts","sourceRoot":"","sources":["../../src/channels/base.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,KAAK,OAAO,EAAE,MAAM,SAAS,CAAC;AAE/C,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAGtC,OAAO,KAAK,EAAsB,QAAQ,EAAE,MAAM,aAAa,CAAC;AAGhE,8BAAsB,WAAW;IAK7B,SAAS,CAAC,QAAQ,EAAE,MAAM;IAC1B,SAAS,CAAC,mBAAmB,EAAE,MAAM;IACrC,SAAS,CAAC,GAAG,EAAE,MAAM;IACrB,SAAS,CAAC,QAAQ,EAAE,QAAQ;IAP9B,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC;IACzB,SAAS,CAAC,GAAG,EAAE,MAAM,CAAC;gBAGV,QAAQ,EAAE,MAAM,EAChB,mBAAmB,EAAE,MAAM,EAC3B,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,QAAQ,YAAS,EACrC,UAAU,EAAE,MAAM;IAepB,QAAQ,CAAC,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IACjC,QAAQ,CAAC,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;cAEpB,IAAI,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;cAgBnC,IAAI,CAAC,CAAC,EACpB,KAAK,EAAE,OAAO,EACd,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,CAAC,CAAC;IAgBb,OAAO,CAAC,MAAM;IAWd,OAAO,CAAC,gBAAgB;IAgBxB,OAAO,CAAC,MAAM,CAAC,cAAc;CAU9B"}
@@ -0,0 +1,16 @@
1
+ import { BaseChannel } from './base.js';
2
+ import type { LogLevel } from '../types.js';
3
+ export declare class BuildChannel extends BaseChannel {
4
+ private lobbyTopic;
5
+ constructor(endpoint: string, maxReconnectRetries: number, key: string, logLevel?: LogLevel);
6
+ disconnect(): Promise<void>;
7
+ connect(): Promise<void>;
8
+ uploadBuild(buildPath: string, appSlug?: string): Promise<string>;
9
+ private requestUploadUrl;
10
+ private uploadToUrl;
11
+ private extractBuildInfo;
12
+ private getFileMetadata;
13
+ private computeSha256;
14
+ private detectMimeType;
15
+ }
16
+ //# sourceMappingURL=build.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../src/channels/build.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAExC,OAAO,KAAK,EAIV,QAAQ,EACT,MAAM,aAAa,CAAC;AAErB,qBAAa,YAAa,SAAQ,WAAW;IAC3C,OAAO,CAAC,UAAU,CAAU;gBAG1B,QAAQ,EAAE,MAAM,EAChB,mBAAmB,EAAE,MAAM,EAC3B,GAAG,EAAE,MAAM,EACX,QAAQ,GAAE,QAAiB;IAYvB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAO3B,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAiBxB,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;YA2BzD,gBAAgB;YAgBhB,WAAW;YA8BX,gBAAgB;YAehB,eAAe;YAWf,aAAa;IAW3B,OAAO,CAAC,cAAc;CAYvB"}
@@ -1,13 +1,8 @@
1
- import type { TVLabsCapabilities, LogLevel } from './types.js';
2
- export declare class TVLabsChannel {
3
- private endpoint;
4
- private maxReconnectRetries;
5
- private key;
6
- private logLevel;
7
- private socket;
1
+ import { BaseChannel } from './base.js';
2
+ import type { TVLabsCapabilities, LogLevel } from '../types.js';
3
+ export declare class SessionChannel extends BaseChannel {
8
4
  private lobbyTopic;
9
5
  private requestTopic?;
10
- private log;
11
6
  private readonly events;
12
7
  constructor(endpoint: string, maxReconnectRetries: number, key: string, logLevel?: LogLevel);
13
8
  disconnect(): Promise<void>;
@@ -17,11 +12,6 @@ export declare class TVLabsChannel {
17
12
  private observeRequest;
18
13
  private unobserveRequest;
19
14
  private requestSession;
20
- private join;
21
- private push;
22
- private params;
23
- private reconnectAfterMs;
24
15
  private tvlabsSessionLink;
25
- private static logSocketError;
26
16
  }
27
- //# sourceMappingURL=channel.d.ts.map
17
+ //# sourceMappingURL=session.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/channels/session.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAExC,OAAO,KAAK,EACV,kBAAkB,EAGlB,QAAQ,EACT,MAAM,aAAa,CAAC;AAErB,qBAAa,cAAe,SAAQ,WAAW;IAC7C,OAAO,CAAC,UAAU,CAAU;IAC5B,OAAO,CAAC,YAAY,CAAC,CAAU;IAE/B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAOZ;gBAGT,QAAQ,EAAE,MAAM,EAChB,mBAAmB,EAAE,MAAM,EAC3B,GAAG,EAAE,MAAM,EACX,QAAQ,GAAE,QAAiB;IAYvB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAQ3B,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAiBxB,UAAU,CACd,YAAY,EAAE,kBAAkB,EAChC,UAAU,EAAE,MAAM,EAClB,KAAK,SAAI,GACR,OAAO,CAAC,MAAM,CAAC;YAWJ,WAAW;YAkBX,cAAc;IAsD5B,OAAO,CAAC,gBAAgB;YAUV,cAAc;IAuB5B,OAAO,CAAC,iBAAiB;CAG1B"}
package/cjs/index.js CHANGED
@@ -3,9 +3,12 @@
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
5
  var webdriverio = require('webdriverio');
6
- var crypto = require('crypto');
6
+ var crypto$1 = require('crypto');
7
7
  var ws = require('ws');
8
8
  var phoenix = require('phoenix');
9
+ var fs = require('node:fs');
10
+ var path = require('node:path');
11
+ var crypto = require('node:crypto');
9
12
 
10
13
  function _interopNamespaceDefault(e) {
11
14
  var n = Object.create(null);
@@ -24,6 +27,9 @@ function _interopNamespaceDefault(e) {
24
27
  return Object.freeze(n);
25
28
  }
26
29
 
30
+ var crypto__namespace$1 = /*#__PURE__*/_interopNamespaceDefault(crypto$1);
31
+ var fs__namespace = /*#__PURE__*/_interopNamespaceDefault(fs);
32
+ var path__namespace = /*#__PURE__*/_interopNamespaceDefault(path);
27
33
  var crypto__namespace = /*#__PURE__*/_interopNamespaceDefault(crypto);
28
34
 
29
35
  const LOG_LEVELS = {
@@ -130,7 +136,7 @@ class Logger {
130
136
  }
131
137
 
132
138
  var name = "@tvlabs/wdio-service";
133
- var version = "0.1.5";
139
+ var version = "0.1.7";
134
140
  var packageJson = {
135
141
  name: name,
136
142
  version: version};
@@ -148,15 +154,81 @@ function getServiceName() {
148
154
  return packageJson.name;
149
155
  }
150
156
 
151
- class TVLabsChannel {
157
+ class BaseChannel {
152
158
  endpoint;
153
159
  maxReconnectRetries;
154
160
  key;
155
161
  logLevel;
156
162
  socket;
163
+ log;
164
+ constructor(endpoint, maxReconnectRetries, key, logLevel = 'info', loggerName) {
165
+ this.endpoint = endpoint;
166
+ this.maxReconnectRetries = maxReconnectRetries;
167
+ this.key = key;
168
+ this.logLevel = logLevel;
169
+ this.log = new Logger(loggerName, this.logLevel);
170
+ this.socket = new phoenix.Socket(this.endpoint, {
171
+ transport: ws.WebSocket,
172
+ params: this.params(),
173
+ reconnectAfterMs: this.reconnectAfterMs.bind(this),
174
+ });
175
+ this.socket.onError((...args) => BaseChannel.logSocketError(this.log, ...args));
176
+ }
177
+ async join(topic) {
178
+ return new Promise((res, rej) => {
179
+ topic
180
+ .join()
181
+ .receive('ok', (_resp) => {
182
+ res();
183
+ })
184
+ .receive('error', ({ response }) => {
185
+ rej('Failed to join topic: ' + response);
186
+ })
187
+ .receive('timeout', () => {
188
+ rej('timeout');
189
+ });
190
+ });
191
+ }
192
+ async push(topic, event, payload) {
193
+ return new Promise((res, rej) => {
194
+ topic
195
+ .push(event, payload)
196
+ .receive('ok', (msg) => {
197
+ res(msg);
198
+ })
199
+ .receive('error', (reason) => {
200
+ rej(reason);
201
+ })
202
+ .receive('timeout', () => {
203
+ rej('timeout');
204
+ });
205
+ });
206
+ }
207
+ params() {
208
+ const serviceInfo = getServiceInfo();
209
+ this.log.debug('Info:', serviceInfo);
210
+ return {
211
+ ...serviceInfo,
212
+ api_key: this.key,
213
+ };
214
+ }
215
+ reconnectAfterMs(tries) {
216
+ if (tries > this.maxReconnectRetries) {
217
+ throw new webdriverio.SevereServiceError('Could not connect to TV Labs, please check your connection.');
218
+ }
219
+ const wait = [0, 1000, 3000, 5000][tries] || 10000;
220
+ this.log.info(`[${tries}/${this.maxReconnectRetries}] Waiting ${wait}ms before re-attempting to connect...`);
221
+ return wait;
222
+ }
223
+ static logSocketError(log, event, _transport, _establishedConnections) {
224
+ const error = event.error;
225
+ log.error('Socket error:', error || event);
226
+ }
227
+ }
228
+
229
+ class SessionChannel extends BaseChannel {
157
230
  lobbyTopic;
158
231
  requestTopic;
159
- log;
160
232
  events = {
161
233
  SESSION_READY: 'session:ready',
162
234
  SESSION_FAILED: 'session:failed',
@@ -166,17 +238,7 @@ class TVLabsChannel {
166
238
  REQUEST_MATCHING: 'request:matching',
167
239
  };
168
240
  constructor(endpoint, maxReconnectRetries, key, logLevel = 'info') {
169
- this.endpoint = endpoint;
170
- this.maxReconnectRetries = maxReconnectRetries;
171
- this.key = key;
172
- this.logLevel = logLevel;
173
- this.log = new Logger('@tvlabs/wdio-channel', this.logLevel);
174
- this.socket = new phoenix.Socket(this.endpoint, {
175
- transport: ws.WebSocket,
176
- params: this.params(),
177
- reconnectAfterMs: this.reconnectAfterMs.bind(this),
178
- });
179
- this.socket.onError((...args) => TVLabsChannel.logSocketError(this.log, ...args));
241
+ super(endpoint, maxReconnectRetries, key, logLevel, '@tvlabs/session-channel');
180
242
  this.lobbyTopic = this.socket.channel('requests:lobby');
181
243
  }
182
244
  async disconnect() {
@@ -188,14 +250,14 @@ class TVLabsChannel {
188
250
  }
189
251
  async connect() {
190
252
  try {
191
- this.log.debug('Connecting to TV Labs...');
253
+ this.log.debug('Connecting to session channel...');
192
254
  this.socket.connect();
193
255
  await this.join(this.lobbyTopic);
194
- this.log.debug('Connected to TV Labs!');
256
+ this.log.debug('Connected to session channel!');
195
257
  }
196
258
  catch (error) {
197
- this.log.error('Error connecting to TV Labs:', error);
198
- throw new webdriverio.SevereServiceError('Could not connect to TV Labs, please check your connection.');
259
+ this.log.error('Error connecting to session channel:', error);
260
+ throw new webdriverio.SevereServiceError('Could not connect to session channel, please check your connection.');
199
261
  }
200
262
  }
201
263
  async newSession(capabilities, maxRetries, retry = 0) {
@@ -273,57 +335,116 @@ class TVLabsChannel {
273
335
  throw error;
274
336
  }
275
337
  }
276
- async join(topic) {
277
- return new Promise((res, rej) => {
278
- topic
279
- .join()
280
- .receive('ok', (_resp) => {
281
- res();
282
- })
283
- .receive('error', ({ response }) => {
284
- rej('Failed to join topic: ' + response);
285
- })
286
- .receive('timeout', () => {
287
- rej('timeout');
288
- });
289
- });
338
+ tvlabsSessionLink(sessionId) {
339
+ return `https://tvlabs.ai/app/sessions/${sessionId}`;
290
340
  }
291
- async push(topic, event, payload) {
292
- return new Promise((res, rej) => {
293
- topic
294
- .push(event, payload)
295
- .receive('ok', (msg) => {
296
- res(msg);
297
- })
298
- .receive('error', (reason) => {
299
- rej(reason);
300
- })
301
- .receive('timeout', () => {
302
- rej('timeout');
303
- });
341
+ }
342
+
343
+ class BuildChannel extends BaseChannel {
344
+ lobbyTopic;
345
+ constructor(endpoint, maxReconnectRetries, key, logLevel = 'info') {
346
+ super(endpoint, maxReconnectRetries, key, logLevel, '@tvlabs/build-channel');
347
+ this.lobbyTopic = this.socket.channel('upload:lobby');
348
+ }
349
+ async disconnect() {
350
+ return new Promise((res, _rej) => {
351
+ this.lobbyTopic.leave();
352
+ this.socket.disconnect(() => res());
304
353
  });
305
354
  }
306
- params() {
307
- const serviceInfo = getServiceInfo();
308
- this.log.debug('Info:', serviceInfo);
309
- return {
310
- ...serviceInfo,
311
- api_key: this.key,
312
- };
355
+ async connect() {
356
+ try {
357
+ this.log.debug('Connecting to build channel...');
358
+ this.socket.connect();
359
+ await this.join(this.lobbyTopic);
360
+ this.log.debug('Connected to build channel!');
361
+ }
362
+ catch (error) {
363
+ this.log.error('Error connecting to build channel:', error);
364
+ throw new webdriverio.SevereServiceError('Could not connect to build channel, please check your connection.');
365
+ }
313
366
  }
314
- reconnectAfterMs(tries) {
315
- if (tries > this.maxReconnectRetries) {
316
- throw new webdriverio.SevereServiceError('Could not connect to TV Labs, please check your connection.');
367
+ async uploadBuild(buildPath, appSlug) {
368
+ const metadata = await this.getFileMetadata(buildPath);
369
+ this.log.info(`Requesting upload for build ${metadata.filename} (${metadata.type}, ${metadata.size} bytes)`);
370
+ const { existing, build_id, url } = await this.requestUploadUrl(metadata, appSlug);
371
+ if (existing) {
372
+ this.log.info('Build is pre-existing, skipping upload');
317
373
  }
318
- const wait = [0, 1000, 3000, 5000][tries] || 10000;
319
- this.log.info(`[${tries}/${this.maxReconnectRetries}] Waiting ${wait}ms before re-attempting to connect...`);
320
- return wait;
374
+ else {
375
+ this.log.info('Uploading build...');
376
+ await this.uploadToUrl(url, buildPath, metadata);
377
+ const { application_id } = await this.extractBuildInfo();
378
+ this.log.info(`Build "${application_id}" processed successfully`);
379
+ }
380
+ return build_id;
321
381
  }
322
- tvlabsSessionLink(sessionId) {
323
- return `https://tvlabs.ai/app/sessions/${sessionId}`;
382
+ async requestUploadUrl(metadata, appSlug) {
383
+ try {
384
+ return await this.push(this.lobbyTopic, 'request_upload_url', { metadata, application_slug: appSlug });
385
+ }
386
+ catch (error) {
387
+ this.log.error('Error requesting upload URL:', error);
388
+ throw error;
389
+ }
324
390
  }
325
- static logSocketError(log, event, _transport, _establishedConnections) {
326
- log.error('Socket error:', event.error);
391
+ async uploadToUrl(url, filePath, metadata) {
392
+ try {
393
+ const response = await fetch(url, {
394
+ method: 'PUT',
395
+ headers: {
396
+ 'Content-Type': metadata.type,
397
+ 'Content-Length': String(metadata.size),
398
+ },
399
+ body: fs__namespace.createReadStream(filePath),
400
+ duplex: 'half',
401
+ });
402
+ if (!response.ok) {
403
+ throw new webdriverio.SevereServiceError(`Failed to upload build to storage, got ${response.status}`);
404
+ }
405
+ this.log.info('Upload complete');
406
+ }
407
+ catch (error) {
408
+ this.log.error('Error uploading build:', error);
409
+ throw error;
410
+ }
411
+ }
412
+ async extractBuildInfo() {
413
+ this.log.info('Processing uploaded build...');
414
+ try {
415
+ return await this.push(this.lobbyTopic, 'extract_build_info', {});
416
+ }
417
+ catch (error) {
418
+ this.log.error('Error processing build:', error);
419
+ throw error;
420
+ }
421
+ }
422
+ async getFileMetadata(buildPath) {
423
+ const filename = path__namespace.basename(buildPath);
424
+ const size = fs__namespace.statSync(buildPath).size;
425
+ const type = this.detectMimeType(filename);
426
+ const sha256 = await this.computeSha256(buildPath);
427
+ return { filename, type, size, sha256 };
428
+ }
429
+ async computeSha256(buildPath) {
430
+ return new Promise((resolve, reject) => {
431
+ const hash = crypto__namespace.createHash('sha256');
432
+ const stream = fs__namespace.createReadStream(buildPath);
433
+ stream.on('data', (chunk) => hash.update(chunk));
434
+ stream.on('end', () => resolve(hash.digest('hex')));
435
+ stream.on('error', (err) => reject(err));
436
+ });
437
+ }
438
+ detectMimeType(filename) {
439
+ const fileExtension = path__namespace.extname(filename).toLowerCase();
440
+ switch (fileExtension) {
441
+ case '.apk':
442
+ return 'application/vnd.android.package-archive';
443
+ case '.zip':
444
+ return 'application/zip';
445
+ default:
446
+ return 'application/octet-stream';
447
+ }
327
448
  }
328
449
  }
329
450
 
@@ -347,15 +468,22 @@ class TVLabsService {
347
468
  }
348
469
  }
349
470
  async beforeSession(_config, capabilities, _specs, _cid) {
350
- const channel = new TVLabsChannel(this.endpoint(), this.reconnectRetries(), this.apiKey(), this.logLevel());
351
- await channel.connect();
352
- capabilities['tvlabs:session_id'] = await channel.newSession(capabilities, this.retries());
353
- await channel.disconnect();
471
+ const buildPath = this.buildPath();
472
+ if (buildPath) {
473
+ const buildChannel = new BuildChannel(this.buildEndpoint(), this.reconnectRetries(), this.apiKey(), this.logLevel());
474
+ await buildChannel.connect();
475
+ capabilities['tvlabs:build'] = await buildChannel.uploadBuild(buildPath, this.appSlug());
476
+ await buildChannel.disconnect();
477
+ }
478
+ const sessionChannel = new SessionChannel(this.sessionEndpoint(), this.reconnectRetries(), this.apiKey(), this.logLevel());
479
+ await sessionChannel.connect();
480
+ capabilities['tvlabs:session_id'] = await sessionChannel.newSession(capabilities, this.retries());
481
+ await sessionChannel.disconnect();
354
482
  }
355
483
  setupRequestId() {
356
484
  const originalTransformRequest = this._config.transformRequest;
357
485
  this._config.transformRequest = (requestOptions) => {
358
- const requestId = crypto__namespace.randomUUID();
486
+ const requestId = crypto__namespace$1.randomUUID();
359
487
  const originalRequestOptions = typeof originalTransformRequest === 'function'
360
488
  ? originalTransformRequest(requestOptions)
361
489
  : requestOptions;
@@ -380,8 +508,17 @@ class TVLabsService {
380
508
  }
381
509
  }
382
510
  }
383
- endpoint() {
384
- return this._options.endpoint ?? 'wss://tvlabs.ai/appium';
511
+ buildPath() {
512
+ return this._options.buildPath;
513
+ }
514
+ appSlug() {
515
+ return this._options.app;
516
+ }
517
+ sessionEndpoint() {
518
+ return this._options.sessionEndpoint ?? 'wss://tvlabs.ai/appium';
519
+ }
520
+ buildEndpoint() {
521
+ return this._options.buildEndpoint ?? 'wss://tvlabs.ai/cli';
385
522
  }
386
523
  retries() {
387
524
  return this._options.retries ?? 3;
package/cjs/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 endpoint;
13
+ private buildPath;
14
+ private appSlug;
15
+ private sessionEndpoint;
16
+ private buildEndpoint;
14
17
  private retries;
15
18
  private apiKey;
16
19
  private logLevel;
@@ -1 +1 @@
1
- {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAMA,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;IAmBd,OAAO,CAAC,cAAc;IA0BtB,OAAO,CAAC,gBAAgB;IAgBxB,OAAO,CAAC,QAAQ;IAIhB,OAAO,CAAC,OAAO;IAIf,OAAO,CAAC,MAAM;IAId,OAAO,CAAC,QAAQ;IAIhB,OAAO,CAAC,eAAe;IAIvB,OAAO,CAAC,gBAAgB;CAGzB"}
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/cjs/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
- endpoint?: string;
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,19 @@ 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
+ existing: boolean;
48
+ application_id?: string;
49
+ };
50
+ export type TVLabsExtractBuildInfoResponse = {
51
+ application_id: string;
52
+ };
53
+ export type TVLabsBuildMetadata = {
54
+ filename: string;
55
+ type: string;
56
+ size: number;
57
+ sha256: string;
58
+ };
41
59
  //# sourceMappingURL=types.d.ts.map
@@ -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,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,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"}
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;IACjB,QAAQ,EAAE,OAAO,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,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"}
@@ -0,0 +1,20 @@
1
+ import { Socket, type Channel } from 'phoenix';
2
+ import { Logger } from '../logger.js';
3
+ import type { LogLevel } from '../types.js';
4
+ export declare abstract class BaseChannel {
5
+ protected endpoint: string;
6
+ protected maxReconnectRetries: number;
7
+ protected key: string;
8
+ protected logLevel: LogLevel;
9
+ protected socket: Socket;
10
+ protected log: Logger;
11
+ constructor(endpoint: string, maxReconnectRetries: number, key: string, logLevel: LogLevel | undefined, loggerName: string);
12
+ abstract connect(): Promise<void>;
13
+ abstract disconnect(): Promise<void>;
14
+ protected join(topic: Channel): Promise<void>;
15
+ protected push<T>(topic: Channel, event: string, payload: object): Promise<T>;
16
+ private params;
17
+ private reconnectAfterMs;
18
+ private static logSocketError;
19
+ }
20
+ //# sourceMappingURL=base.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"base.d.ts","sourceRoot":"","sources":["../../src/channels/base.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,KAAK,OAAO,EAAE,MAAM,SAAS,CAAC;AAE/C,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAGtC,OAAO,KAAK,EAAsB,QAAQ,EAAE,MAAM,aAAa,CAAC;AAGhE,8BAAsB,WAAW;IAK7B,SAAS,CAAC,QAAQ,EAAE,MAAM;IAC1B,SAAS,CAAC,mBAAmB,EAAE,MAAM;IACrC,SAAS,CAAC,GAAG,EAAE,MAAM;IACrB,SAAS,CAAC,QAAQ,EAAE,QAAQ;IAP9B,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC;IACzB,SAAS,CAAC,GAAG,EAAE,MAAM,CAAC;gBAGV,QAAQ,EAAE,MAAM,EAChB,mBAAmB,EAAE,MAAM,EAC3B,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,QAAQ,YAAS,EACrC,UAAU,EAAE,MAAM;IAepB,QAAQ,CAAC,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IACjC,QAAQ,CAAC,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;cAEpB,IAAI,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;cAgBnC,IAAI,CAAC,CAAC,EACpB,KAAK,EAAE,OAAO,EACd,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,CAAC,CAAC;IAgBb,OAAO,CAAC,MAAM;IAWd,OAAO,CAAC,gBAAgB;IAgBxB,OAAO,CAAC,MAAM,CAAC,cAAc;CAU9B"}
@@ -0,0 +1,16 @@
1
+ import { BaseChannel } from './base.js';
2
+ import type { LogLevel } from '../types.js';
3
+ export declare class BuildChannel extends BaseChannel {
4
+ private lobbyTopic;
5
+ constructor(endpoint: string, maxReconnectRetries: number, key: string, logLevel?: LogLevel);
6
+ disconnect(): Promise<void>;
7
+ connect(): Promise<void>;
8
+ uploadBuild(buildPath: string, appSlug?: string): Promise<string>;
9
+ private requestUploadUrl;
10
+ private uploadToUrl;
11
+ private extractBuildInfo;
12
+ private getFileMetadata;
13
+ private computeSha256;
14
+ private detectMimeType;
15
+ }
16
+ //# sourceMappingURL=build.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../src/channels/build.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAExC,OAAO,KAAK,EAIV,QAAQ,EACT,MAAM,aAAa,CAAC;AAErB,qBAAa,YAAa,SAAQ,WAAW;IAC3C,OAAO,CAAC,UAAU,CAAU;gBAG1B,QAAQ,EAAE,MAAM,EAChB,mBAAmB,EAAE,MAAM,EAC3B,GAAG,EAAE,MAAM,EACX,QAAQ,GAAE,QAAiB;IAYvB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAO3B,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAiBxB,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;YA2BzD,gBAAgB;YAgBhB,WAAW;YA8BX,gBAAgB;YAehB,eAAe;YAWf,aAAa;IAW3B,OAAO,CAAC,cAAc;CAYvB"}
@@ -1,13 +1,8 @@
1
- import type { TVLabsCapabilities, LogLevel } from './types.js';
2
- export declare class TVLabsChannel {
3
- private endpoint;
4
- private maxReconnectRetries;
5
- private key;
6
- private logLevel;
7
- private socket;
1
+ import { BaseChannel } from './base.js';
2
+ import type { TVLabsCapabilities, LogLevel } from '../types.js';
3
+ export declare class SessionChannel extends BaseChannel {
8
4
  private lobbyTopic;
9
5
  private requestTopic?;
10
- private log;
11
6
  private readonly events;
12
7
  constructor(endpoint: string, maxReconnectRetries: number, key: string, logLevel?: LogLevel);
13
8
  disconnect(): Promise<void>;
@@ -17,11 +12,6 @@ export declare class TVLabsChannel {
17
12
  private observeRequest;
18
13
  private unobserveRequest;
19
14
  private requestSession;
20
- private join;
21
- private push;
22
- private params;
23
- private reconnectAfterMs;
24
15
  private tvlabsSessionLink;
25
- private static logSocketError;
26
16
  }
27
- //# sourceMappingURL=channel.d.ts.map
17
+ //# sourceMappingURL=session.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/channels/session.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAExC,OAAO,KAAK,EACV,kBAAkB,EAGlB,QAAQ,EACT,MAAM,aAAa,CAAC;AAErB,qBAAa,cAAe,SAAQ,WAAW;IAC7C,OAAO,CAAC,UAAU,CAAU;IAC5B,OAAO,CAAC,YAAY,CAAC,CAAU;IAE/B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAOZ;gBAGT,QAAQ,EAAE,MAAM,EAChB,mBAAmB,EAAE,MAAM,EAC3B,GAAG,EAAE,MAAM,EACX,QAAQ,GAAE,QAAiB;IAYvB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAQ3B,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAiBxB,UAAU,CACd,YAAY,EAAE,kBAAkB,EAChC,UAAU,EAAE,MAAM,EAClB,KAAK,SAAI,GACR,OAAO,CAAC,MAAM,CAAC;YAWJ,WAAW;YAkBX,cAAc;IAsD5B,OAAO,CAAC,gBAAgB;YAUV,cAAc;IAuB5B,OAAO,CAAC,iBAAiB;CAG1B"}