@tvlabs/wdio-service 0.1.9 → 0.1.10

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
@@ -18,7 +18,9 @@ If a build path is provided, the service first makes a build upload request to t
18
18
 
19
19
  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.
20
20
 
21
- 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.
21
+ Additionally, the service automatically injects an `Authorization` header with your API key (formatted as `Bearer ${apiKey}`) into all requests to the TV Labs platform. If you have already set an `Authorization` header in your configuration, the service will respect your existing header and not override it.
22
+
23
+ The service also 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.
22
24
 
23
25
  ## Installation
24
26
 
@@ -66,10 +68,7 @@ async function run() {
66
68
  const wdOpts = {
67
69
  capabilities,
68
70
  hostname: 'appium.tvlabs.ai',
69
- port: 4723,
70
- headers: {
71
- Authorization: `Bearer ${process.env.TVLABS_API_TOKEN}`,
72
- },
71
+ port: 4723
73
72
  };
74
73
 
75
74
  const serviceOpts = {
@@ -177,3 +176,59 @@ const driver = await remote(wdOpts);
177
176
  const requestId = service.lastRequestId();
178
177
  console.log(`Last request ID: ${requestId}`);
179
178
  ```
179
+
180
+ ### `requestMetadata()`
181
+
182
+ - **Parameters:** `appiumSessionId: string, requestIds: string | string[]`
183
+ - **Returns:** `Promise<TVLabsRequestMetadata | TVLabsRequestMetadataResponse>`
184
+ - **Description:** Fetches metadata for one or more Appium request IDs from the TV Labs platform. If a single request ID is provided, returns the metadata for that request. If an array of request IDs is provided, returns a map where keys are request IDs and values are their corresponding metadata.
185
+
186
+ > **Note:** Request metadata is processed asynchronously on the TV Labs platform. To ensure metadata is available, it is recommended to fetch request metadata a few seconds after the request, or after the session has ended.
187
+
188
+ #### Example
189
+
190
+ ```javascript
191
+ import { remote } from 'webdriverio';
192
+ import { TVLabsService } from '@tvlabs/wdio-service';
193
+
194
+ const capabilities = { ... };
195
+ const wdOpts = { ... };
196
+
197
+ const service = new TVLabsService(
198
+ { apiKey: process.env.TVLABS_API_KEY },
199
+ capabilities,
200
+ wdOpts
201
+ );
202
+
203
+ await service.beforeSession(wdOpts, capabilities, [], '');
204
+
205
+ const driver = await remote(wdOpts);
206
+ let requestId;
207
+
208
+ try {
209
+ // Perform some actions that generate requests
210
+ const element = await driver.$('#my-button');
211
+ await element.click();
212
+
213
+ // Get the request ID from the click
214
+ requestId = service.lastRequestId();
215
+ console.log(`Request ID: ${requestId}`);
216
+ } finally {
217
+ await driver.deleteSession();
218
+ }
219
+
220
+ // Fetch metadata after session ends (recommended)
221
+ if (requestId) {
222
+ const metadata = await service.requestMetadata(driver.sessionId, requestId);
223
+ console.log('Request metadata:', metadata);
224
+ }
225
+
226
+ // Fetch metadata for multiple requests
227
+ const multiMetadata = await service.requestMetadata("appium-session-id-1234"[
228
+ 'request-id-123',
229
+ 'request-id-456',
230
+ 'request-id-789'
231
+ ]);
232
+
233
+ console.log('Multiple requests metadata:', multiMetadata);
234
+ ```
@@ -0,0 +1,10 @@
1
+ import { BaseChannel } from './base.js';
2
+ import type { LogLevel, TVLabsRequestMetadataResponse } from '../types.js';
3
+ export declare class MetadataChannel extends BaseChannel {
4
+ private lobbyTopic;
5
+ constructor(endpoint: string, maxReconnectRetries: number, key: string, logLevel?: LogLevel);
6
+ disconnect(): Promise<void>;
7
+ connect(): Promise<void>;
8
+ getRequestMetadata(appiumSessionId: string, requestIds: string[]): Promise<TVLabsRequestMetadataResponse>;
9
+ }
10
+ //# sourceMappingURL=metadata.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metadata.d.ts","sourceRoot":"","sources":["../../src/channels/metadata.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAExC,OAAO,KAAK,EAAE,QAAQ,EAAE,6BAA6B,EAAE,MAAM,aAAa,CAAC;AAE3E,qBAAa,eAAgB,SAAQ,WAAW;IAC9C,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,kBAAkB,CACtB,eAAe,EAAE,MAAM,EACvB,UAAU,EAAE,MAAM,EAAE,GACnB,OAAO,CAAC,6BAA6B,CAAC;CAyB1C"}
package/cjs/index.js CHANGED
@@ -11,20 +11,20 @@ var path = require('node:path');
11
11
  var crypto = require('node:crypto');
12
12
 
13
13
  function _interopNamespaceDefault(e) {
14
- var n = Object.create(null);
15
- if (e) {
16
- Object.keys(e).forEach(function (k) {
17
- if (k !== 'default') {
18
- var d = Object.getOwnPropertyDescriptor(e, k);
19
- Object.defineProperty(n, k, d.get ? d : {
20
- enumerable: true,
21
- get: function () { return e[k]; }
22
- });
23
- }
14
+ var n = Object.create(null);
15
+ if (e) {
16
+ Object.keys(e).forEach(function (k) {
17
+ if (k !== 'default') {
18
+ var d = Object.getOwnPropertyDescriptor(e, k);
19
+ Object.defineProperty(n, k, d.get ? d : {
20
+ enumerable: true,
21
+ get: function () { return e[k]; }
24
22
  });
25
- }
26
- n.default = e;
27
- return Object.freeze(n);
23
+ }
24
+ });
25
+ }
26
+ n.default = e;
27
+ return Object.freeze(n);
28
28
  }
29
29
 
30
30
  var crypto__namespace$1 = /*#__PURE__*/_interopNamespaceDefault(crypto$1);
@@ -32,6 +32,25 @@ var fs__namespace = /*#__PURE__*/_interopNamespaceDefault(fs);
32
32
  var path__namespace = /*#__PURE__*/_interopNamespaceDefault(path);
33
33
  var crypto__namespace = /*#__PURE__*/_interopNamespaceDefault(crypto);
34
34
 
35
+ var name = "@tvlabs/wdio-service";
36
+ var version = "0.1.10";
37
+ var packageJson = {
38
+ name: name,
39
+ version: version};
40
+
41
+ function getServiceInfo() {
42
+ return {
43
+ service_name: getServiceName(),
44
+ service_version: getServiceVersion(),
45
+ };
46
+ }
47
+ function getServiceVersion() {
48
+ return packageJson.version;
49
+ }
50
+ function getServiceName() {
51
+ return packageJson.name;
52
+ }
53
+
35
54
  const LOG_LEVELS = {
36
55
  error: 0,
37
56
  warn: 1,
@@ -135,25 +154,6 @@ class Logger {
135
154
  }
136
155
  }
137
156
 
138
- var name = "@tvlabs/wdio-service";
139
- var version = "0.1.9";
140
- var packageJson = {
141
- name: name,
142
- version: version};
143
-
144
- function getServiceInfo() {
145
- return {
146
- service_name: getServiceName(),
147
- service_version: getServiceVersion(),
148
- };
149
- }
150
- function getServiceVersion() {
151
- return packageJson.version;
152
- }
153
- function getServiceName() {
154
- return packageJson.name;
155
- }
156
-
157
157
  class BaseChannel {
158
158
  endpoint;
159
159
  maxReconnectRetries;
@@ -448,24 +448,83 @@ class BuildChannel extends BaseChannel {
448
448
  }
449
449
  }
450
450
 
451
+ class MetadataChannel extends BaseChannel {
452
+ lobbyTopic;
453
+ constructor(endpoint, maxReconnectRetries, key, logLevel = 'info') {
454
+ super(endpoint, maxReconnectRetries, key, logLevel, '@tvlabs/metadata-channel');
455
+ this.lobbyTopic = this.socket.channel('metadata:lobby');
456
+ }
457
+ async disconnect() {
458
+ return new Promise((res, _rej) => {
459
+ this.lobbyTopic.leave();
460
+ this.socket.disconnect(() => res());
461
+ });
462
+ }
463
+ async connect() {
464
+ try {
465
+ this.log.debug('Connecting to metadata channel...');
466
+ this.socket.connect();
467
+ await this.join(this.lobbyTopic);
468
+ this.log.debug('Connected to metadata channel!');
469
+ }
470
+ catch (error) {
471
+ this.log.error('Error connecting to metadata channel:', error);
472
+ throw new webdriverio.SevereServiceError('Could not connect to metadata channel, please check your connection.');
473
+ }
474
+ }
475
+ async getRequestMetadata(appiumSessionId, requestIds) {
476
+ this.log.debug(`Fetching metadata for session ${appiumSessionId}, requests: ${requestIds.join(', ')}`);
477
+ try {
478
+ const response = await this.push(this.lobbyTopic, 'appium_request_metadata', {
479
+ appium_session_id: appiumSessionId,
480
+ request_ids: requestIds,
481
+ });
482
+ this.log.debug(`Received metadata for ${Object.keys(response).length} request(s)`);
483
+ return response;
484
+ }
485
+ catch (error) {
486
+ this.log.error('Error fetching request metadata:', error);
487
+ throw error;
488
+ }
489
+ }
490
+ }
491
+
451
492
  class TVLabsService {
452
493
  _options;
453
494
  _capabilities;
454
495
  _config;
455
496
  log;
456
497
  requestId;
498
+ sessionId;
499
+ metadataChannel;
457
500
  constructor(_options, _capabilities, _config) {
458
501
  this._options = _options;
459
502
  this._capabilities = _capabilities;
460
503
  this._config = _config;
461
504
  this.log = new Logger('@tvlabs/wdio-service', this._config.logLevel);
505
+ this.injectAuthorizationHeader();
462
506
  if (this.attachRequestId()) {
463
507
  this.setupRequestId();
464
508
  }
509
+ this.log.info(`Instantiated TVLabsService v${getServiceVersion()}`);
465
510
  }
466
511
  lastRequestId() {
467
512
  return this.requestId;
468
513
  }
514
+ async requestMetadata(sessionId, requestIds) {
515
+ const requestIdArray = Array.isArray(requestIds)
516
+ ? requestIds
517
+ : [requestIds];
518
+ if (!this.metadataChannel) {
519
+ this.metadataChannel = new MetadataChannel(this.sessionEndpoint(), this.reconnectRetries(), this.apiKey(), this.logLevel());
520
+ await this.metadataChannel.connect();
521
+ }
522
+ const response = await this.metadataChannel.getRequestMetadata(sessionId, requestIdArray);
523
+ if (!Array.isArray(requestIds)) {
524
+ return response[requestIds];
525
+ }
526
+ return response;
527
+ }
469
528
  onPrepare(_config, param) {
470
529
  try {
471
530
  if (!Array.isArray(param)) {
@@ -500,6 +559,12 @@ class TVLabsService {
500
559
  throw error;
501
560
  }
502
561
  }
562
+ injectAuthorizationHeader() {
563
+ this._config.headers = this._config.headers || {};
564
+ if (!this._config.headers.Authorization) {
565
+ this._config.headers.Authorization = `Bearer ${this.apiKey()}`;
566
+ }
567
+ }
503
568
  setupRequestId() {
504
569
  const originalTransformRequest = this._config.transformRequest;
505
570
  this._config.transformRequest = (requestOptions) => {
package/cjs/service.d.ts CHANGED
@@ -1,15 +1,19 @@
1
1
  import type { Services, Capabilities, Options } from '@wdio/types';
2
- import type { TVLabsCapabilities, TVLabsServiceOptions } from './types.js';
2
+ import type { TVLabsCapabilities, TVLabsServiceOptions, TVLabsRequestMetadata, TVLabsRequestMetadataResponse } from './types.js';
3
3
  export default class TVLabsService implements Services.ServiceInstance {
4
4
  private _options;
5
5
  private _capabilities;
6
6
  private _config;
7
7
  private log;
8
8
  private requestId;
9
+ private sessionId;
10
+ private metadataChannel;
9
11
  constructor(_options: TVLabsServiceOptions, _capabilities: Capabilities.ResolvedTestrunnerCapabilities, _config: Options.WebdriverIO);
10
12
  lastRequestId(): string | undefined;
13
+ requestMetadata(sessionId: string, requestIds: string | string[]): Promise<TVLabsRequestMetadata | TVLabsRequestMetadataResponse>;
11
14
  onPrepare(_config: Options.Testrunner, param: Capabilities.TestrunnerCapabilities): void;
12
15
  beforeSession(_config: Omit<Options.Testrunner, 'capabilities'>, capabilities: TVLabsCapabilities, _specs: string[], _cid: string): Promise<void>;
16
+ private injectAuthorizationHeader;
13
17
  private setupRequestId;
14
18
  private setRequestHeader;
15
19
  private setRequestId;
@@ -1 +1 @@
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;IAKlE,OAAO,CAAC,QAAQ;IAChB,OAAO,CAAC,aAAa;IACrB,OAAO,CAAC,OAAO;IANjB,OAAO,CAAC,GAAG,CAAS;IACpB,OAAO,CAAC,SAAS,CAAqB;gBAG5B,QAAQ,EAAE,oBAAoB,EAC9B,aAAa,EAAE,YAAY,CAAC,8BAA8B,EAC1D,OAAO,EAAE,OAAO,CAAC,WAAW;IAQtC,aAAa,IAAI,MAAM,GAAG,SAAS;IAInC,SAAS,CACP,OAAO,EAAE,OAAO,CAAC,UAAU,EAC3B,KAAK,EAAE,YAAY,CAAC,sBAAsB;IAiBtC,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;IA+Cd,OAAO,CAAC,cAAc;IA4BtB,OAAO,CAAC,gBAAgB;IAgBxB,OAAO,CAAC,YAAY;IAIpB,OAAO,CAAC,eAAe;IAIvB,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"}
1
+ {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,QAAQ,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AACnE,OAAO,KAAK,EACV,kBAAkB,EAClB,oBAAoB,EACpB,qBAAqB,EACrB,6BAA6B,EAE9B,MAAM,YAAY,CAAC;AAEpB,MAAM,CAAC,OAAO,OAAO,aAAc,YAAW,QAAQ,CAAC,eAAe;IAOlE,OAAO,CAAC,QAAQ;IAChB,OAAO,CAAC,aAAa;IACrB,OAAO,CAAC,OAAO;IARjB,OAAO,CAAC,GAAG,CAAS;IACpB,OAAO,CAAC,SAAS,CAAqB;IACtC,OAAO,CAAC,SAAS,CAAqB;IACtC,OAAO,CAAC,eAAe,CAA8B;gBAG3C,QAAQ,EAAE,oBAAoB,EAC9B,aAAa,EAAE,YAAY,CAAC,8BAA8B,EAC1D,OAAO,EAAE,OAAO,CAAC,WAAW;IAatC,aAAa,IAAI,MAAM,GAAG,SAAS;IAI7B,eAAe,CACnB,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,GAAG,MAAM,EAAE,GAC5B,OAAO,CAAC,qBAAqB,GAAG,6BAA6B,CAAC;IA+BjE,SAAS,CACP,OAAO,EAAE,OAAO,CAAC,UAAU,EAC3B,KAAK,EAAE,YAAY,CAAC,sBAAsB;IAiBtC,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;IA+Cd,OAAO,CAAC,yBAAyB;IAQjC,OAAO,CAAC,cAAc;IA4BtB,OAAO,CAAC,gBAAgB;IAgBxB,OAAO,CAAC,YAAY;IAIpB,OAAO,CAAC,eAAe;IAIvB,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
@@ -32,8 +32,20 @@ export type TVLabsSessionRequestUpdate = {
32
32
  session_id: string;
33
33
  reason: string;
34
34
  };
35
+ export type ResponseAnyValue = string | number | boolean | null | ResponseAnyValue[] | {
36
+ [key: string]: ResponseAnyValue;
37
+ };
35
38
  export type TVLabsSessionRequestResponse = {
39
+ status: number;
40
+ path: string;
36
41
  request_id: string;
42
+ method: string;
43
+ req_body: ResponseAnyValue;
44
+ resp_body: ResponseAnyValue;
45
+ requested_at: string | null;
46
+ responded_at: string | null;
47
+ video_start_time: number | null;
48
+ video_end_time: number | null;
37
49
  };
38
50
  export type TVLabsSocketParams = TVLabsServiceInfo & {
39
51
  api_key: string;
@@ -57,4 +69,10 @@ export type TVLabsBuildMetadata = {
57
69
  size: number;
58
70
  sha256: string;
59
71
  };
72
+ export type TVLabsRequestMetadata = {
73
+ [key: string]: unknown;
74
+ };
75
+ export type TVLabsRequestMetadataResponse = {
76
+ [request_id: string]: TVLabsRequestMetadata;
77
+ };
60
78
  //# 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,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;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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import { BaseChannel } from './base.js';
2
+ import type { LogLevel, TVLabsRequestMetadataResponse } from '../types.js';
3
+ export declare class MetadataChannel extends BaseChannel {
4
+ private lobbyTopic;
5
+ constructor(endpoint: string, maxReconnectRetries: number, key: string, logLevel?: LogLevel);
6
+ disconnect(): Promise<void>;
7
+ connect(): Promise<void>;
8
+ getRequestMetadata(appiumSessionId: string, requestIds: string[]): Promise<TVLabsRequestMetadataResponse>;
9
+ }
10
+ //# sourceMappingURL=metadata.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metadata.d.ts","sourceRoot":"","sources":["../../src/channels/metadata.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAExC,OAAO,KAAK,EAAE,QAAQ,EAAE,6BAA6B,EAAE,MAAM,aAAa,CAAC;AAE3E,qBAAa,eAAgB,SAAQ,WAAW;IAC9C,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,kBAAkB,CACtB,eAAe,EAAE,MAAM,EACvB,UAAU,EAAE,MAAM,EAAE,GACnB,OAAO,CAAC,6BAA6B,CAAC;CAyB1C"}
package/esm/index.js CHANGED
@@ -6,6 +6,25 @@ import * as fs from 'node:fs';
6
6
  import * as path from 'node:path';
7
7
  import * as crypto from 'node:crypto';
8
8
 
9
+ var name = "@tvlabs/wdio-service";
10
+ var version = "0.1.10";
11
+ var packageJson = {
12
+ name: name,
13
+ version: version};
14
+
15
+ function getServiceInfo() {
16
+ return {
17
+ service_name: getServiceName(),
18
+ service_version: getServiceVersion(),
19
+ };
20
+ }
21
+ function getServiceVersion() {
22
+ return packageJson.version;
23
+ }
24
+ function getServiceName() {
25
+ return packageJson.name;
26
+ }
27
+
9
28
  const LOG_LEVELS = {
10
29
  error: 0,
11
30
  warn: 1,
@@ -109,25 +128,6 @@ class Logger {
109
128
  }
110
129
  }
111
130
 
112
- var name = "@tvlabs/wdio-service";
113
- var version = "0.1.9";
114
- var packageJson = {
115
- name: name,
116
- version: version};
117
-
118
- function getServiceInfo() {
119
- return {
120
- service_name: getServiceName(),
121
- service_version: getServiceVersion(),
122
- };
123
- }
124
- function getServiceVersion() {
125
- return packageJson.version;
126
- }
127
- function getServiceName() {
128
- return packageJson.name;
129
- }
130
-
131
131
  class BaseChannel {
132
132
  endpoint;
133
133
  maxReconnectRetries;
@@ -422,24 +422,83 @@ class BuildChannel extends BaseChannel {
422
422
  }
423
423
  }
424
424
 
425
+ class MetadataChannel extends BaseChannel {
426
+ lobbyTopic;
427
+ constructor(endpoint, maxReconnectRetries, key, logLevel = 'info') {
428
+ super(endpoint, maxReconnectRetries, key, logLevel, '@tvlabs/metadata-channel');
429
+ this.lobbyTopic = this.socket.channel('metadata:lobby');
430
+ }
431
+ async disconnect() {
432
+ return new Promise((res, _rej) => {
433
+ this.lobbyTopic.leave();
434
+ this.socket.disconnect(() => res());
435
+ });
436
+ }
437
+ async connect() {
438
+ try {
439
+ this.log.debug('Connecting to metadata channel...');
440
+ this.socket.connect();
441
+ await this.join(this.lobbyTopic);
442
+ this.log.debug('Connected to metadata channel!');
443
+ }
444
+ catch (error) {
445
+ this.log.error('Error connecting to metadata channel:', error);
446
+ throw new SevereServiceError('Could not connect to metadata channel, please check your connection.');
447
+ }
448
+ }
449
+ async getRequestMetadata(appiumSessionId, requestIds) {
450
+ this.log.debug(`Fetching metadata for session ${appiumSessionId}, requests: ${requestIds.join(', ')}`);
451
+ try {
452
+ const response = await this.push(this.lobbyTopic, 'appium_request_metadata', {
453
+ appium_session_id: appiumSessionId,
454
+ request_ids: requestIds,
455
+ });
456
+ this.log.debug(`Received metadata for ${Object.keys(response).length} request(s)`);
457
+ return response;
458
+ }
459
+ catch (error) {
460
+ this.log.error('Error fetching request metadata:', error);
461
+ throw error;
462
+ }
463
+ }
464
+ }
465
+
425
466
  class TVLabsService {
426
467
  _options;
427
468
  _capabilities;
428
469
  _config;
429
470
  log;
430
471
  requestId;
472
+ sessionId;
473
+ metadataChannel;
431
474
  constructor(_options, _capabilities, _config) {
432
475
  this._options = _options;
433
476
  this._capabilities = _capabilities;
434
477
  this._config = _config;
435
478
  this.log = new Logger('@tvlabs/wdio-service', this._config.logLevel);
479
+ this.injectAuthorizationHeader();
436
480
  if (this.attachRequestId()) {
437
481
  this.setupRequestId();
438
482
  }
483
+ this.log.info(`Instantiated TVLabsService v${getServiceVersion()}`);
439
484
  }
440
485
  lastRequestId() {
441
486
  return this.requestId;
442
487
  }
488
+ async requestMetadata(sessionId, requestIds) {
489
+ const requestIdArray = Array.isArray(requestIds)
490
+ ? requestIds
491
+ : [requestIds];
492
+ if (!this.metadataChannel) {
493
+ this.metadataChannel = new MetadataChannel(this.sessionEndpoint(), this.reconnectRetries(), this.apiKey(), this.logLevel());
494
+ await this.metadataChannel.connect();
495
+ }
496
+ const response = await this.metadataChannel.getRequestMetadata(sessionId, requestIdArray);
497
+ if (!Array.isArray(requestIds)) {
498
+ return response[requestIds];
499
+ }
500
+ return response;
501
+ }
443
502
  onPrepare(_config, param) {
444
503
  try {
445
504
  if (!Array.isArray(param)) {
@@ -474,6 +533,12 @@ class TVLabsService {
474
533
  throw error;
475
534
  }
476
535
  }
536
+ injectAuthorizationHeader() {
537
+ this._config.headers = this._config.headers || {};
538
+ if (!this._config.headers.Authorization) {
539
+ this._config.headers.Authorization = `Bearer ${this.apiKey()}`;
540
+ }
541
+ }
477
542
  setupRequestId() {
478
543
  const originalTransformRequest = this._config.transformRequest;
479
544
  this._config.transformRequest = (requestOptions) => {
package/esm/service.d.ts CHANGED
@@ -1,15 +1,19 @@
1
1
  import type { Services, Capabilities, Options } from '@wdio/types';
2
- import type { TVLabsCapabilities, TVLabsServiceOptions } from './types.js';
2
+ import type { TVLabsCapabilities, TVLabsServiceOptions, TVLabsRequestMetadata, TVLabsRequestMetadataResponse } from './types.js';
3
3
  export default class TVLabsService implements Services.ServiceInstance {
4
4
  private _options;
5
5
  private _capabilities;
6
6
  private _config;
7
7
  private log;
8
8
  private requestId;
9
+ private sessionId;
10
+ private metadataChannel;
9
11
  constructor(_options: TVLabsServiceOptions, _capabilities: Capabilities.ResolvedTestrunnerCapabilities, _config: Options.WebdriverIO);
10
12
  lastRequestId(): string | undefined;
13
+ requestMetadata(sessionId: string, requestIds: string | string[]): Promise<TVLabsRequestMetadata | TVLabsRequestMetadataResponse>;
11
14
  onPrepare(_config: Options.Testrunner, param: Capabilities.TestrunnerCapabilities): void;
12
15
  beforeSession(_config: Omit<Options.Testrunner, 'capabilities'>, capabilities: TVLabsCapabilities, _specs: string[], _cid: string): Promise<void>;
16
+ private injectAuthorizationHeader;
13
17
  private setupRequestId;
14
18
  private setRequestHeader;
15
19
  private setRequestId;
@@ -1 +1 @@
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;IAKlE,OAAO,CAAC,QAAQ;IAChB,OAAO,CAAC,aAAa;IACrB,OAAO,CAAC,OAAO;IANjB,OAAO,CAAC,GAAG,CAAS;IACpB,OAAO,CAAC,SAAS,CAAqB;gBAG5B,QAAQ,EAAE,oBAAoB,EAC9B,aAAa,EAAE,YAAY,CAAC,8BAA8B,EAC1D,OAAO,EAAE,OAAO,CAAC,WAAW;IAQtC,aAAa,IAAI,MAAM,GAAG,SAAS;IAInC,SAAS,CACP,OAAO,EAAE,OAAO,CAAC,UAAU,EAC3B,KAAK,EAAE,YAAY,CAAC,sBAAsB;IAiBtC,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;IA+Cd,OAAO,CAAC,cAAc;IA4BtB,OAAO,CAAC,gBAAgB;IAgBxB,OAAO,CAAC,YAAY;IAIpB,OAAO,CAAC,eAAe;IAIvB,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"}
1
+ {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,QAAQ,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AACnE,OAAO,KAAK,EACV,kBAAkB,EAClB,oBAAoB,EACpB,qBAAqB,EACrB,6BAA6B,EAE9B,MAAM,YAAY,CAAC;AAEpB,MAAM,CAAC,OAAO,OAAO,aAAc,YAAW,QAAQ,CAAC,eAAe;IAOlE,OAAO,CAAC,QAAQ;IAChB,OAAO,CAAC,aAAa;IACrB,OAAO,CAAC,OAAO;IARjB,OAAO,CAAC,GAAG,CAAS;IACpB,OAAO,CAAC,SAAS,CAAqB;IACtC,OAAO,CAAC,SAAS,CAAqB;IACtC,OAAO,CAAC,eAAe,CAA8B;gBAG3C,QAAQ,EAAE,oBAAoB,EAC9B,aAAa,EAAE,YAAY,CAAC,8BAA8B,EAC1D,OAAO,EAAE,OAAO,CAAC,WAAW;IAatC,aAAa,IAAI,MAAM,GAAG,SAAS;IAI7B,eAAe,CACnB,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,GAAG,MAAM,EAAE,GAC5B,OAAO,CAAC,qBAAqB,GAAG,6BAA6B,CAAC;IA+BjE,SAAS,CACP,OAAO,EAAE,OAAO,CAAC,UAAU,EAC3B,KAAK,EAAE,YAAY,CAAC,sBAAsB;IAiBtC,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;IA+Cd,OAAO,CAAC,yBAAyB;IAQjC,OAAO,CAAC,cAAc;IA4BtB,OAAO,CAAC,gBAAgB;IAgBxB,OAAO,CAAC,YAAY;IAIpB,OAAO,CAAC,eAAe;IAIvB,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
@@ -32,8 +32,20 @@ export type TVLabsSessionRequestUpdate = {
32
32
  session_id: string;
33
33
  reason: string;
34
34
  };
35
+ export type ResponseAnyValue = string | number | boolean | null | ResponseAnyValue[] | {
36
+ [key: string]: ResponseAnyValue;
37
+ };
35
38
  export type TVLabsSessionRequestResponse = {
39
+ status: number;
40
+ path: string;
36
41
  request_id: string;
42
+ method: string;
43
+ req_body: ResponseAnyValue;
44
+ resp_body: ResponseAnyValue;
45
+ requested_at: string | null;
46
+ responded_at: string | null;
47
+ video_start_time: number | null;
48
+ video_end_time: number | null;
37
49
  };
38
50
  export type TVLabsSocketParams = TVLabsServiceInfo & {
39
51
  api_key: string;
@@ -57,4 +69,10 @@ export type TVLabsBuildMetadata = {
57
69
  size: number;
58
70
  sha256: string;
59
71
  };
72
+ export type TVLabsRequestMetadata = {
73
+ [key: string]: unknown;
74
+ };
75
+ export type TVLabsRequestMetadataResponse = {
76
+ [request_id: string]: TVLabsRequestMetadata;
77
+ };
60
78
  //# 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,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;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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tvlabs/wdio-service",
3
- "version": "0.1.9",
3
+ "version": "0.1.10",
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",
@@ -23,7 +23,7 @@
23
23
  },
24
24
  "scripts": {
25
25
  "build": "npm run clean && rollup -c",
26
- "start": "ts-node src/index.ts",
26
+ "start": "tsx --env-file=.env scripts/test.ts",
27
27
  "dev": "ts-node --transpile-only src/index.ts",
28
28
  "clean": "rm -rf cjs && rm -rf esm",
29
29
  "format": "prettier --write .",
@@ -31,6 +31,9 @@
31
31
  "lint": "eslint",
32
32
  "test": "vitest --config vitest.config.ts --coverage --run",
33
33
  "test:watch": "vitest --config vitest.config.ts --coverage",
34
+ "test:integration": "npm run build && npm run test:integration:esm && npm run test:integration:cjs",
35
+ "test:integration:esm": "vitest --config vitest.config.esm.ts --run",
36
+ "test:integration:cjs": "vitest --config vitest.config.cjs.ts --run",
34
37
  "publish:dry": "npm publish --access public --provenance --dry-run"
35
38
  },
36
39
  "type": "module",
@@ -43,7 +46,7 @@
43
46
  "typeScriptVersion": "5.8.2",
44
47
  "devDependencies": {
45
48
  "@eslint/js": "^9.22.0",
46
- "@rollup/plugin-commonjs": "^28.0.6",
49
+ "@rollup/plugin-commonjs": "^29.0.0",
47
50
  "@rollup/plugin-json": "^6.1.0",
48
51
  "@rollup/plugin-node-resolve": "^16.0.1",
49
52
  "@rollup/plugin-typescript": "^12.1.4",
@@ -52,7 +55,7 @@
52
55
  "@types/ws": "^8.18.0",
53
56
  "@typescript-eslint/eslint-plugin": "^8.38.0",
54
57
  "@typescript-eslint/parser": "^8.38.0",
55
- "@vitest/coverage-v8": "^3.0.9",
58
+ "@vitest/coverage-v8": "^4.0.7",
56
59
  "@wdio/globals": "^9.12.1",
57
60
  "@wdio/types": "^9.10.1",
58
61
  "eslint": "^9.30.1",
@@ -60,9 +63,10 @@
60
63
  "jiti": "^2.4.2",
61
64
  "prettier": "^3.5.3",
62
65
  "rollup": "^4.45.1",
63
- "typescript": "^5.8.2",
66
+ "tsx": "^4.20.6",
67
+ "typescript": "^5.9.3",
64
68
  "typescript-eslint": "^8.38.0",
65
- "vitest": "^3.0.9",
69
+ "vitest": "^4.0.7",
66
70
  "webdriverio": "^9.12.1"
67
71
  },
68
72
  "dependencies": {
@@ -0,0 +1,65 @@
1
+ import { remote } from 'webdriverio';
2
+ import { TVLabsService } from '../src/index.ts';
3
+
4
+ const apiKey = process.env.TVLABS_API_TOKEN;
5
+
6
+ const capabilities = {
7
+ 'tvlabs:build': '00000000-0000-0000-0000-000000000000',
8
+ 'tvlabs:teleport_region': 'fake',
9
+ 'tvlabs:constraints': 'platform_key:dev AND model:"Virtual Device"',
10
+ };
11
+
12
+ const wdOpts = {
13
+ hostname: 'localhost',
14
+ port: 4723, // Appium proxy
15
+ logLevel: 'info',
16
+ capabilities,
17
+ };
18
+
19
+ const serviceOpts = {
20
+ apiKey,
21
+ sessionEndpoint: 'ws://localhost:4000/appium',
22
+ };
23
+
24
+ const service = new TVLabsService(serviceOpts, capabilities, wdOpts);
25
+
26
+ async function runTest() {
27
+ await service.beforeSession(wdOpts, capabilities, [], '');
28
+ const driver = await remote(wdOpts);
29
+
30
+ console.log('The session id is: ', driver.sessionId);
31
+
32
+ const requests = [];
33
+
34
+ try {
35
+ for (let i = 0; i < 3; ++i) {
36
+ await driver.execute('fake: getThing', {
37
+ thing: 'thing',
38
+ });
39
+
40
+ requests.push(service.lastRequestId());
41
+ }
42
+ } finally {
43
+ await driver.pause(1000);
44
+ await driver.deleteSession();
45
+ }
46
+
47
+ const allRequests = await service.requestMetadata(driver.sessionId, requests);
48
+ const singleRequest = await service.requestMetadata(
49
+ driver.sessionId,
50
+ requests[0],
51
+ );
52
+
53
+ console.log('Single request', singleRequest);
54
+ console.log('All requests', allRequests);
55
+ }
56
+
57
+ runTest()
58
+ .then(() => {
59
+ console.log('Test completed successfully');
60
+ process.exit(0);
61
+ })
62
+ .catch((error) => {
63
+ console.error('Test failed:', error);
64
+ process.exit(1);
65
+ });
@@ -0,0 +1,78 @@
1
+ import { type Channel } from 'phoenix';
2
+ import { SevereServiceError } from 'webdriverio';
3
+ import { BaseChannel } from './base.js';
4
+
5
+ import type { LogLevel, TVLabsRequestMetadataResponse } from '../types.js';
6
+
7
+ export class MetadataChannel extends BaseChannel {
8
+ private lobbyTopic: Channel;
9
+
10
+ constructor(
11
+ endpoint: string,
12
+ maxReconnectRetries: number,
13
+ key: string,
14
+ logLevel: LogLevel = 'info',
15
+ ) {
16
+ super(
17
+ endpoint,
18
+ maxReconnectRetries,
19
+ key,
20
+ logLevel,
21
+ '@tvlabs/metadata-channel',
22
+ );
23
+ this.lobbyTopic = this.socket.channel('metadata:lobby');
24
+ }
25
+
26
+ async disconnect(): Promise<void> {
27
+ return new Promise((res, _rej) => {
28
+ this.lobbyTopic.leave();
29
+ this.socket.disconnect(() => res());
30
+ });
31
+ }
32
+
33
+ async connect(): Promise<void> {
34
+ try {
35
+ this.log.debug('Connecting to metadata channel...');
36
+
37
+ this.socket.connect();
38
+
39
+ await this.join(this.lobbyTopic);
40
+
41
+ this.log.debug('Connected to metadata channel!');
42
+ } catch (error) {
43
+ this.log.error('Error connecting to metadata channel:', error);
44
+ throw new SevereServiceError(
45
+ 'Could not connect to metadata channel, please check your connection.',
46
+ );
47
+ }
48
+ }
49
+
50
+ async getRequestMetadata(
51
+ appiumSessionId: string,
52
+ requestIds: string[],
53
+ ): Promise<TVLabsRequestMetadataResponse> {
54
+ this.log.debug(
55
+ `Fetching metadata for session ${appiumSessionId}, requests: ${requestIds.join(', ')}`,
56
+ );
57
+
58
+ try {
59
+ const response = await this.push<TVLabsRequestMetadataResponse>(
60
+ this.lobbyTopic,
61
+ 'appium_request_metadata',
62
+ {
63
+ appium_session_id: appiumSessionId,
64
+ request_ids: requestIds,
65
+ },
66
+ );
67
+
68
+ this.log.debug(
69
+ `Received metadata for ${Object.keys(response).length} request(s)`,
70
+ );
71
+
72
+ return response;
73
+ } catch (error) {
74
+ this.log.error('Error fetching request metadata:', error);
75
+ throw error;
76
+ }
77
+ }
78
+ }
package/src/service.ts CHANGED
@@ -1,20 +1,26 @@
1
1
  import { SevereServiceError } from 'webdriverio';
2
2
  import * as crypto from 'crypto';
3
+ import { getServiceVersion } from './utils.js';
3
4
 
4
5
  import { SessionChannel } from './channels/session.js';
5
6
  import { BuildChannel } from './channels/build.js';
7
+ import { MetadataChannel } from './channels/metadata.js';
6
8
  import { Logger } from './logger.js';
7
9
 
8
10
  import type { Services, Capabilities, Options } from '@wdio/types';
9
11
  import type {
10
12
  TVLabsCapabilities,
11
13
  TVLabsServiceOptions,
14
+ TVLabsRequestMetadata,
15
+ TVLabsRequestMetadataResponse,
12
16
  LogLevel,
13
17
  } from './types.js';
14
18
 
15
19
  export default class TVLabsService implements Services.ServiceInstance {
16
20
  private log: Logger;
17
21
  private requestId: string | undefined;
22
+ private sessionId: string | undefined;
23
+ private metadataChannel: MetadataChannel | undefined;
18
24
 
19
25
  constructor(
20
26
  private _options: TVLabsServiceOptions,
@@ -22,15 +28,54 @@ export default class TVLabsService implements Services.ServiceInstance {
22
28
  private _config: Options.WebdriverIO,
23
29
  ) {
24
30
  this.log = new Logger('@tvlabs/wdio-service', this._config.logLevel);
31
+
32
+ this.injectAuthorizationHeader();
33
+
25
34
  if (this.attachRequestId()) {
26
35
  this.setupRequestId();
27
36
  }
37
+
38
+ this.log.info(`Instantiated TVLabsService v${getServiceVersion()}`);
28
39
  }
29
40
 
30
41
  lastRequestId(): string | undefined {
31
42
  return this.requestId;
32
43
  }
33
44
 
45
+ async requestMetadata(
46
+ sessionId: string,
47
+ requestIds: string | string[],
48
+ ): Promise<TVLabsRequestMetadata | TVLabsRequestMetadataResponse> {
49
+ const requestIdArray = Array.isArray(requestIds)
50
+ ? requestIds
51
+ : [requestIds];
52
+
53
+ // Create and connect to metadata channel if not already connected
54
+ if (!this.metadataChannel) {
55
+ this.metadataChannel = new MetadataChannel(
56
+ this.sessionEndpoint(),
57
+ this.reconnectRetries(),
58
+ this.apiKey(),
59
+ this.logLevel(),
60
+ );
61
+
62
+ await this.metadataChannel.connect();
63
+ }
64
+
65
+ const response = await this.metadataChannel.getRequestMetadata(
66
+ sessionId,
67
+ requestIdArray,
68
+ );
69
+
70
+ // If a single request ID was passed, return just that request's metadata
71
+ if (!Array.isArray(requestIds)) {
72
+ return response[requestIds];
73
+ }
74
+
75
+ // Otherwise return the full map
76
+ return response;
77
+ }
78
+
34
79
  onPrepare(
35
80
  _config: Options.Testrunner,
36
81
  param: Capabilities.TestrunnerCapabilities,
@@ -101,6 +146,14 @@ export default class TVLabsService implements Services.ServiceInstance {
101
146
  }
102
147
  }
103
148
 
149
+ private injectAuthorizationHeader() {
150
+ this._config.headers = this._config.headers || {};
151
+
152
+ if (!this._config.headers.Authorization) {
153
+ this._config.headers.Authorization = `Bearer ${this.apiKey()}`;
154
+ }
155
+ }
156
+
104
157
  private setupRequestId() {
105
158
  const originalTransformRequest = this._config.transformRequest;
106
159
 
package/src/types.ts CHANGED
@@ -41,8 +41,25 @@ export type TVLabsSessionRequestUpdate = {
41
41
  reason: string;
42
42
  };
43
43
 
44
+ export type ResponseAnyValue =
45
+ | string
46
+ | number
47
+ | boolean
48
+ | null
49
+ | ResponseAnyValue[]
50
+ | { [key: string]: ResponseAnyValue };
51
+
44
52
  export type TVLabsSessionRequestResponse = {
53
+ status: number;
54
+ path: string;
45
55
  request_id: string;
56
+ method: string;
57
+ req_body: ResponseAnyValue;
58
+ resp_body: ResponseAnyValue;
59
+ requested_at: string | null;
60
+ responded_at: string | null;
61
+ video_start_time: number | null;
62
+ video_end_time: number | null;
46
63
  };
47
64
 
48
65
  export type TVLabsSocketParams = TVLabsServiceInfo & {
@@ -71,3 +88,11 @@ export type TVLabsBuildMetadata = {
71
88
  size: number;
72
89
  sha256: string;
73
90
  };
91
+
92
+ export type TVLabsRequestMetadata = {
93
+ [key: string]: unknown;
94
+ };
95
+
96
+ export type TVLabsRequestMetadataResponse = {
97
+ [request_id: string]: TVLabsRequestMetadata;
98
+ };
@@ -0,0 +1,62 @@
1
+ # Integration Tests
2
+
3
+ These integration tests verify that the compiled ESM and CJS outputs work correctly when imported/required by consumers.
4
+
5
+ ## Directory Structure
6
+
7
+ ```
8
+ test-integration/
9
+ ├── esm/
10
+ │ └── import.test.mjs # ESM module import tests
11
+ └── cjs/
12
+ └── require.test.mjs # CJS require tests (uses createRequire)
13
+ ```
14
+
15
+ ## Running Tests
16
+
17
+ ```bash
18
+ # Run all integration tests (builds first)
19
+ npm run test:integration
20
+
21
+ # Run only ESM integration tests
22
+ npm run test:integration:esm
23
+
24
+ # Run only CJS integration tests
25
+ npm run test:integration:cjs
26
+ ```
27
+
28
+ ## What These Tests Do
29
+
30
+ Unlike unit tests that test source code (`src/`), these integration tests:
31
+
32
+ 1. **Import/require the compiled outputs** (`esm/` and `cjs/`)
33
+ 2. **Verify the module structure** is correct (exports are available)
34
+ 3. **Test basic instantiation** to ensure the code runs in both module systems
35
+ 4. **Validate that the build process** produces functional output
36
+
37
+ ## How It Works
38
+
39
+ ### ESM Tests (`esm/import.test.mjs`)
40
+
41
+ - Uses standard ESM `import` to load the ESM build
42
+ - Imports directly from `../../esm/index.js`
43
+
44
+ ### CJS Tests (`cjs/require.test.mjs`)
45
+
46
+ - Written as an ESM file (`.mjs`) but uses `createRequire` to test CJS output
47
+ - Uses Node's `createRequire` API to dynamically require the CJS build
48
+ - This approach is necessary because Vitest itself is ESM-only
49
+
50
+ ## Prerequisites
51
+
52
+ Integration tests require the project to be built first:
53
+
54
+ ```bash
55
+ npm run build
56
+ ```
57
+
58
+ The `test:integration` script automatically runs the build step.
59
+
60
+ ## CI Integration
61
+
62
+ Integration tests run in CI as part of the Test job after unit tests complete.
@@ -0,0 +1,90 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { createRequire } from 'module';
3
+
4
+ const require = createRequire(import.meta.url);
5
+ const { TVLabsService } = require('../../cjs/index.js');
6
+
7
+ const createService = (options = {}) => {
8
+ const serviceOptions = { apiKey: 'test-key', ...options };
9
+ const capabilities = {};
10
+ const config = { logLevel: 'silent' };
11
+ return new TVLabsService(serviceOptions, capabilities, config);
12
+ };
13
+
14
+ describe('CJS Integration Tests', () => {
15
+ it('should export TVLabsService', () => {
16
+ expect(typeof TVLabsService).toBe('function');
17
+ });
18
+
19
+ it('should be instantiable with valid options', () => {
20
+ const service = createService();
21
+ expect(service).toBeInstanceOf(TVLabsService);
22
+ });
23
+
24
+ it('should have onPrepare method', () => {
25
+ const service = createService();
26
+ expect(typeof service.onPrepare).toBe('function');
27
+ });
28
+
29
+ it('should have beforeSession method', () => {
30
+ const service = createService();
31
+ expect(typeof service.beforeSession).toBe('function');
32
+ });
33
+
34
+ it('should reject multi-remote capabilities in onPrepare', () => {
35
+ const service = createService({ continueOnError: true });
36
+ const multiRemoteConfig = {
37
+ capabilities: {
38
+ browserA: { browserName: 'chrome' },
39
+ browserB: { browserName: 'firefox' },
40
+ },
41
+ };
42
+
43
+ expect(() => service.onPrepare(multiRemoteConfig, {})).toThrow(
44
+ /multi-remote capabilities are not implemented/i,
45
+ );
46
+ });
47
+
48
+ it('should have lastRequestId method', () => {
49
+ const service = createService();
50
+ expect(typeof service.lastRequestId).toBe('function');
51
+ });
52
+
53
+ it('should have requestMetadata method', () => {
54
+ const service = createService();
55
+ expect(typeof service.requestMetadata).toBe('function');
56
+ });
57
+
58
+ describe('Authorization header injection', () => {
59
+ it('should inject Authorization header when not present', () => {
60
+ const config = {};
61
+ new TVLabsService({ apiKey: 'test-api-key' }, {}, config);
62
+
63
+ expect(config.headers).toBeDefined();
64
+ expect(config.headers.Authorization).toBe('Bearer test-api-key');
65
+ });
66
+
67
+ it('should not override existing Authorization header', () => {
68
+ const config = {
69
+ headers: {
70
+ Authorization: 'Bearer existing-token',
71
+ },
72
+ };
73
+ new TVLabsService({ apiKey: 'test-api-key' }, {}, config);
74
+
75
+ expect(config.headers.Authorization).toBe('Bearer existing-token');
76
+ });
77
+
78
+ it('should preserve other headers when injecting Authorization', () => {
79
+ const config = {
80
+ headers: {
81
+ 'X-Custom-Header': 'custom-value',
82
+ },
83
+ };
84
+ new TVLabsService({ apiKey: 'test-api-key' }, {}, config);
85
+
86
+ expect(config.headers['X-Custom-Header']).toBe('custom-value');
87
+ expect(config.headers.Authorization).toBe('Bearer test-api-key');
88
+ });
89
+ });
90
+ });
@@ -0,0 +1,87 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { TVLabsService } from '../../esm/index.js';
3
+
4
+ const createService = (options = {}) => {
5
+ const serviceOptions = { apiKey: 'test-key', ...options };
6
+ const capabilities = {};
7
+ const config = { logLevel: 'silent' };
8
+ return new TVLabsService(serviceOptions, capabilities, config);
9
+ };
10
+
11
+ describe('ESM Integration Tests', () => {
12
+ it('should export TVLabsService', () => {
13
+ expect(typeof TVLabsService).toBe('function');
14
+ });
15
+
16
+ it('should be instantiable with valid options', () => {
17
+ const service = createService();
18
+ expect(service).toBeInstanceOf(TVLabsService);
19
+ });
20
+
21
+ it('should have onPrepare method', () => {
22
+ const service = createService();
23
+ expect(typeof service.onPrepare).toBe('function');
24
+ });
25
+
26
+ it('should have beforeSession method', () => {
27
+ const service = createService();
28
+ expect(typeof service.beforeSession).toBe('function');
29
+ });
30
+
31
+ it('should reject multi-remote capabilities in onPrepare', () => {
32
+ const service = createService({ continueOnError: true });
33
+ const multiRemoteConfig = {
34
+ capabilities: {
35
+ browserA: { browserName: 'chrome' },
36
+ browserB: { browserName: 'firefox' },
37
+ },
38
+ };
39
+
40
+ expect(() => service.onPrepare(multiRemoteConfig, {})).toThrow(
41
+ /multi-remote capabilities are not implemented/i,
42
+ );
43
+ });
44
+
45
+ it('should have lastRequestId method', () => {
46
+ const service = createService();
47
+ expect(typeof service.lastRequestId).toBe('function');
48
+ });
49
+
50
+ it('should have requestMetadata method', () => {
51
+ const service = createService();
52
+ expect(typeof service.requestMetadata).toBe('function');
53
+ });
54
+
55
+ describe('Authorization header injection', () => {
56
+ it('should inject Authorization header when not present', () => {
57
+ const config = {};
58
+ new TVLabsService({ apiKey: 'test-api-key' }, {}, config);
59
+
60
+ expect(config.headers).toBeDefined();
61
+ expect(config.headers.Authorization).toBe('Bearer test-api-key');
62
+ });
63
+
64
+ it('should not override existing Authorization header', () => {
65
+ const config = {
66
+ headers: {
67
+ Authorization: 'Bearer existing-token',
68
+ },
69
+ };
70
+ new TVLabsService({ apiKey: 'test-api-key' }, {}, config);
71
+
72
+ expect(config.headers.Authorization).toBe('Bearer existing-token');
73
+ });
74
+
75
+ it('should preserve other headers when injecting Authorization', () => {
76
+ const config = {
77
+ headers: {
78
+ 'X-Custom-Header': 'custom-value',
79
+ },
80
+ };
81
+ new TVLabsService({ apiKey: 'test-api-key' }, {}, config);
82
+
83
+ expect(config.headers['X-Custom-Header']).toBe('custom-value');
84
+ expect(config.headers.Authorization).toBe('Bearer test-api-key');
85
+ });
86
+ });
87
+ });
@@ -0,0 +1,12 @@
1
+ import { defineConfig } from 'vitest/config';
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ globals: true,
6
+ environment: 'node',
7
+ include: ['test-integration/cjs/**/*.test.mjs'],
8
+ coverage: {
9
+ enabled: false,
10
+ },
11
+ },
12
+ });
@@ -0,0 +1,12 @@
1
+ import { defineConfig } from 'vitest/config';
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ globals: true,
6
+ environment: 'node',
7
+ include: ['test-integration/esm/**/*.test.mjs'],
8
+ coverage: {
9
+ enabled: false,
10
+ },
11
+ },
12
+ });