@tvlabs/wdio-service 0.1.4 → 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 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
 
@@ -56,22 +58,22 @@ To use this with WebdriverIO remote but without the test runner, call the before
56
58
  import { remote } from 'webdriverio';
57
59
  import { TVLabsService } from '@tvlabs/wdio-service';
58
60
 
59
- const capabilities = { ... };
60
-
61
- const wdOpts = {
62
- capabilities,
63
- hostname: 'appium.tvlabs.ai',
64
- port: 4723,
65
- headers: {
66
- Authorization: `Bearer ${process.env.TVLABS_API_TOKEN}`,
67
- },
68
- };
69
-
70
- const serviceOpts = {
71
- apiKey: process.env.TVLABS_API_TOKEN,
72
- }
73
-
74
61
  async function run() {
62
+ const capabilities = { ... };
63
+
64
+ const wdOpts = {
65
+ capabilities,
66
+ hostname: 'appium.tvlabs.ai',
67
+ port: 4723,
68
+ headers: {
69
+ Authorization: `Bearer ${process.env.TVLABS_API_TOKEN}`,
70
+ },
71
+ };
72
+
73
+ const serviceOpts = {
74
+ apiKey: process.env.TVLABS_API_TOKEN,
75
+ }
76
+
75
77
  const service = new TVLabsService(serviceOpts, capabilities, {})
76
78
 
77
79
  // The TV Labs service does not use specs or a cid, pass default values.
@@ -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;YAoBzD,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 = {
@@ -50,9 +56,58 @@ class Logger {
50
56
  formatMessage(level, ...args) {
51
57
  const timestamp = new Date().toISOString();
52
58
  return `${timestamp} ${level.toUpperCase()} ${this.name}: ${args
53
- .map((arg) => typeof arg === 'object' ? JSON.stringify(arg) : String(arg))
59
+ .map((arg) => this.serializeArg(arg))
54
60
  .join(' ')}`;
55
61
  }
62
+ serializeArg(arg) {
63
+ if (typeof arg === 'string' ||
64
+ typeof arg === 'number' ||
65
+ typeof arg === 'boolean') {
66
+ return String(arg);
67
+ }
68
+ if (arg === null || arg === undefined) {
69
+ return String(arg);
70
+ }
71
+ if (arg instanceof Error) {
72
+ return arg.stack || `${arg.name}: ${arg.message}`;
73
+ }
74
+ if (typeof arg === 'object') {
75
+ try {
76
+ const stringified = JSON.stringify(arg, (key, value) => {
77
+ if (value instanceof Error) {
78
+ return `${value.name}: ${value.message}`;
79
+ }
80
+ return value;
81
+ });
82
+ if (stringified === '{}') {
83
+ const keys = Object.getOwnPropertyNames(arg);
84
+ if (keys.length > 0) {
85
+ const props = {};
86
+ keys.forEach((key) => {
87
+ try {
88
+ const value = arg[key];
89
+ if (value instanceof Error) {
90
+ props[key] = `${value.name}: ${value.message}`;
91
+ }
92
+ else {
93
+ props[key] = value;
94
+ }
95
+ }
96
+ catch {
97
+ props[key] = '[unable to access]';
98
+ }
99
+ });
100
+ return JSON.stringify(props);
101
+ }
102
+ }
103
+ return stringified;
104
+ }
105
+ catch {
106
+ return String(arg);
107
+ }
108
+ }
109
+ return String(arg);
110
+ }
56
111
  debug(...args) {
57
112
  if (this.shouldLog('debug')) {
58
113
  console.log(this.formatMessage('debug', ...args));
@@ -81,7 +136,7 @@ class Logger {
81
136
  }
82
137
 
83
138
  var name = "@tvlabs/wdio-service";
84
- var version = "0.1.4";
139
+ var version = "0.1.6";
85
140
  var packageJson = {
86
141
  name: name,
87
142
  version: version};
@@ -99,15 +154,81 @@ function getServiceName() {
99
154
  return packageJson.name;
100
155
  }
101
156
 
102
- class TVLabsChannel {
157
+ class BaseChannel {
103
158
  endpoint;
104
159
  maxReconnectRetries;
105
160
  key;
106
161
  logLevel;
107
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 {
108
230
  lobbyTopic;
109
231
  requestTopic;
110
- log;
111
232
  events = {
112
233
  SESSION_READY: 'session:ready',
113
234
  SESSION_FAILED: 'session:failed',
@@ -117,17 +238,7 @@ class TVLabsChannel {
117
238
  REQUEST_MATCHING: 'request:matching',
118
239
  };
119
240
  constructor(endpoint, maxReconnectRetries, key, logLevel = 'info') {
120
- this.endpoint = endpoint;
121
- this.maxReconnectRetries = maxReconnectRetries;
122
- this.key = key;
123
- this.logLevel = logLevel;
124
- this.log = new Logger('@tvlabs/wdio-channel', this.logLevel);
125
- this.socket = new phoenix.Socket(this.endpoint, {
126
- transport: ws.WebSocket,
127
- params: this.params(),
128
- reconnectAfterMs: this.reconnectAfterMs.bind(this),
129
- });
130
- this.socket.onError((...args) => TVLabsChannel.logSocketError(this.log, ...args));
241
+ super(endpoint, maxReconnectRetries, key, logLevel, '@tvlabs/session-channel');
131
242
  this.lobbyTopic = this.socket.channel('requests:lobby');
132
243
  }
133
244
  async disconnect() {
@@ -139,14 +250,14 @@ class TVLabsChannel {
139
250
  }
140
251
  async connect() {
141
252
  try {
142
- this.log.debug('Connecting to TV Labs...');
253
+ this.log.debug('Connecting to session channel...');
143
254
  this.socket.connect();
144
255
  await this.join(this.lobbyTopic);
145
- this.log.debug('Connected to TV Labs!');
256
+ this.log.debug('Connected to session channel!');
146
257
  }
147
258
  catch (error) {
148
- this.log.error('Error connecting to TV Labs:', error);
149
- 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.');
150
261
  }
151
262
  }
152
263
  async newSession(capabilities, maxRetries, retry = 0) {
@@ -224,58 +335,111 @@ class TVLabsChannel {
224
335
  throw error;
225
336
  }
226
337
  }
227
- async join(topic) {
228
- return new Promise((res, rej) => {
229
- topic
230
- .join()
231
- .receive('ok', (_resp) => {
232
- res();
233
- })
234
- .receive('error', ({ response }) => {
235
- rej('Failed to join topic: ' + response);
236
- })
237
- .receive('timeout', () => {
238
- rej('timeout');
239
- });
240
- });
338
+ tvlabsSessionLink(sessionId) {
339
+ return `https://tvlabs.ai/app/sessions/${sessionId}`;
241
340
  }
242
- async push(topic, event, payload) {
243
- return new Promise((res, rej) => {
244
- topic
245
- .push(event, payload)
246
- .receive('ok', (msg) => {
247
- res(msg);
248
- })
249
- .receive('error', (reason) => {
250
- rej(reason);
251
- })
252
- .receive('timeout', () => {
253
- rej('timeout');
254
- });
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());
255
353
  });
256
354
  }
257
- params() {
258
- const serviceInfo = getServiceInfo();
259
- this.log.debug('Info:', serviceInfo);
260
- return {
261
- ...serviceInfo,
262
- api_key: this.key,
263
- };
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
+ }
264
366
  }
265
- reconnectAfterMs(tries) {
266
- if (tries > this.maxReconnectRetries) {
267
- 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 { url, build_id } = await this.requestUploadUrl(metadata, appSlug);
371
+ this.log.info('Uploading build...');
372
+ await this.uploadToUrl(url, buildPath, metadata);
373
+ const { application_id } = await this.extractBuildInfo();
374
+ this.log.info(`Build "${application_id}" processed successfully`);
375
+ return build_id;
376
+ }
377
+ async requestUploadUrl(metadata, appSlug) {
378
+ try {
379
+ return await this.push(this.lobbyTopic, 'request_upload_url', { metadata, application_slug: appSlug });
380
+ }
381
+ catch (error) {
382
+ this.log.error('Error requesting upload URL:', error);
383
+ throw error;
268
384
  }
269
- const wait = [0, 1000, 3000, 5000][tries] || 10000;
270
- this.log.info(`[${tries}/${this.maxReconnectRetries}] Waiting ${wait}ms before re-attempting to connect...`);
271
- return wait;
272
385
  }
273
- tvlabsSessionLink(sessionId) {
274
- return `https://tvlabs.ai/app/sessions/${sessionId}`;
386
+ async uploadToUrl(url, filePath, metadata) {
387
+ try {
388
+ const response = await fetch(url, {
389
+ method: 'PUT',
390
+ headers: {
391
+ 'Content-Type': metadata.type,
392
+ 'Content-Length': String(metadata.size),
393
+ },
394
+ body: fs__namespace.createReadStream(filePath),
395
+ duplex: 'half',
396
+ });
397
+ if (!response.ok) {
398
+ throw new webdriverio.SevereServiceError(`Failed to upload build to storage, got ${response.status}`);
399
+ }
400
+ this.log.info('Upload complete');
401
+ }
402
+ catch (error) {
403
+ this.log.error('Error uploading build:', error);
404
+ throw error;
405
+ }
275
406
  }
276
- static logSocketError(log, event, _transport, _establishedConnections) {
277
- const error = event.error;
278
- log.error('Socket error:', error || event);
407
+ async extractBuildInfo() {
408
+ this.log.info('Processing uploaded build...');
409
+ try {
410
+ return await this.push(this.lobbyTopic, 'extract_build_info', {});
411
+ }
412
+ catch (error) {
413
+ this.log.error('Error processing build:', error);
414
+ throw error;
415
+ }
416
+ }
417
+ async getFileMetadata(buildPath) {
418
+ const filename = path__namespace.basename(buildPath);
419
+ const size = fs__namespace.statSync(buildPath).size;
420
+ const type = this.detectMimeType(filename);
421
+ const sha256 = await this.computeSha256(buildPath);
422
+ return { filename, type, size, sha256 };
423
+ }
424
+ async computeSha256(buildPath) {
425
+ return new Promise((resolve, reject) => {
426
+ const hash = crypto__namespace.createHash('sha256');
427
+ const stream = fs__namespace.createReadStream(buildPath);
428
+ stream.on('data', (chunk) => hash.update(chunk));
429
+ stream.on('end', () => resolve(hash.digest('hex')));
430
+ stream.on('error', (err) => reject(err));
431
+ });
432
+ }
433
+ detectMimeType(filename) {
434
+ const fileExtension = path__namespace.extname(filename).toLowerCase();
435
+ switch (fileExtension) {
436
+ case '.apk':
437
+ return 'application/vnd.android.package-archive';
438
+ case '.zip':
439
+ return 'application/zip';
440
+ default:
441
+ return 'application/octet-stream';
442
+ }
279
443
  }
280
444
  }
281
445
 
@@ -299,15 +463,22 @@ class TVLabsService {
299
463
  }
300
464
  }
301
465
  async beforeSession(_config, capabilities, _specs, _cid) {
302
- const channel = new TVLabsChannel(this.endpoint(), this.reconnectRetries(), this.apiKey(), this.logLevel());
303
- await channel.connect();
304
- capabilities['tvlabs:session_id'] = await channel.newSession(capabilities, this.retries());
305
- await channel.disconnect();
466
+ const buildPath = this.buildPath();
467
+ if (buildPath) {
468
+ const buildChannel = new BuildChannel(this.buildEndpoint(), this.reconnectRetries(), this.apiKey(), this.logLevel());
469
+ await buildChannel.connect();
470
+ capabilities['tvlabs:build'] = await buildChannel.uploadBuild(buildPath, this.appSlug());
471
+ await buildChannel.disconnect();
472
+ }
473
+ const sessionChannel = new SessionChannel(this.sessionEndpoint(), this.reconnectRetries(), this.apiKey(), this.logLevel());
474
+ await sessionChannel.connect();
475
+ capabilities['tvlabs:session_id'] = await sessionChannel.newSession(capabilities, this.retries());
476
+ await sessionChannel.disconnect();
306
477
  }
307
478
  setupRequestId() {
308
479
  const originalTransformRequest = this._config.transformRequest;
309
480
  this._config.transformRequest = (requestOptions) => {
310
- const requestId = crypto__namespace.randomUUID();
481
+ const requestId = crypto__namespace$1.randomUUID();
311
482
  const originalRequestOptions = typeof originalTransformRequest === 'function'
312
483
  ? originalTransformRequest(requestOptions)
313
484
  : requestOptions;
@@ -332,8 +503,17 @@ class TVLabsService {
332
503
  }
333
504
  }
334
505
  }
335
- endpoint() {
336
- return this._options.endpoint ?? 'wss://tvlabs.ai/appium';
506
+ buildPath() {
507
+ return this._options.buildPath;
508
+ }
509
+ appSlug() {
510
+ return this._options.app;
511
+ }
512
+ sessionEndpoint() {
513
+ return this._options.sessionEndpoint ?? 'wss://tvlabs.ai/appium';
514
+ }
515
+ buildEndpoint() {
516
+ return this._options.buildEndpoint ?? 'wss://tvlabs.ai/cli';
337
517
  }
338
518
  retries() {
339
519
  return this._options.retries ?? 3;
package/cjs/logger.d.ts CHANGED
@@ -5,6 +5,7 @@ export declare class Logger {
5
5
  constructor(name: string, logLevel?: LogLevel);
6
6
  private shouldLog;
7
7
  private formatMessage;
8
+ private serializeArg;
8
9
  debug(...args: unknown[]): void;
9
10
  info(...args: unknown[]): void;
10
11
  warn(...args: unknown[]): void;
@@ -1 +1 @@
1
- {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAc3C,qBAAa,MAAM;IAEf,OAAO,CAAC,IAAI;IACZ,OAAO,CAAC,QAAQ;gBADR,IAAI,EAAE,MAAM,EACZ,QAAQ,GAAE,QAAiB;IAGrC,OAAO,CAAC,SAAS;IAOjB,OAAO,CAAC,aAAa;IASrB,KAAK,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI;IAM/B,IAAI,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI;IAM9B,IAAI,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI;IAM9B,KAAK,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI;IAM/B,KAAK,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI;CAKhC"}
1
+ {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAc3C,qBAAa,MAAM;IAEf,OAAO,CAAC,IAAI;IACZ,OAAO,CAAC,QAAQ;gBADR,IAAI,EAAE,MAAM,EACZ,QAAQ,GAAE,QAAiB;IAGrC,OAAO,CAAC,SAAS;IAOjB,OAAO,CAAC,aAAa;IAOrB,OAAO,CAAC,YAAY;IA2DpB,KAAK,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI;IAM/B,IAAI,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI;IAM9B,IAAI,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI;IAM9B,KAAK,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI;IAM/B,KAAK,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI;CAKhC"}
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,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
@@ -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;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"}