@tvlabs/wdio-service 0.1.9 → 0.1.11
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 +60 -5
- package/cjs/channels/metadata.d.ts +10 -0
- package/cjs/channels/metadata.d.ts.map +1 -0
- package/cjs/index.js +97 -32
- package/cjs/service.d.ts +5 -1
- package/cjs/service.d.ts.map +1 -1
- package/cjs/types.d.ts +18 -0
- package/cjs/types.d.ts.map +1 -1
- package/esm/channels/metadata.d.ts +10 -0
- package/esm/channels/metadata.d.ts.map +1 -0
- package/esm/index.js +84 -19
- package/esm/service.d.ts +5 -1
- package/esm/service.d.ts.map +1 -1
- package/esm/types.d.ts +18 -0
- package/esm/types.d.ts.map +1 -1
- package/package.json +10 -6
- package/scripts/test.ts +65 -0
- package/src/channels/metadata.ts +78 -0
- package/src/service.ts +53 -0
- package/src/types.ts +25 -0
- package/test-integration/README.md +62 -0
- package/test-integration/cjs/require.test.mjs +90 -0
- package/test-integration/esm/import.test.mjs +87 -0
- package/vitest.config.cjs.ts +12 -0
- package/vitest.config.esm.ts +12 -0
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
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
27
|
-
|
|
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.11";
|
|
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;
|
package/cjs/service.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"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
|
package/cjs/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,GAAG,QAAQ,CAAC;AAEhF,MAAM,MAAM,oBAAoB,GAAG;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,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;
|
|
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.11";
|
|
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;
|
package/esm/service.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"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
|
package/esm/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,GAAG,QAAQ,CAAC;AAEhF,MAAM,MAAM,oBAAoB,GAAG;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,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;
|
|
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.
|
|
3
|
+
"version": "0.1.11",
|
|
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": "
|
|
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": "^
|
|
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": "^
|
|
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
|
-
"
|
|
66
|
+
"tsx": "^4.20.6",
|
|
67
|
+
"typescript": "^5.9.3",
|
|
64
68
|
"typescript-eslint": "^8.38.0",
|
|
65
|
-
"vitest": "^
|
|
69
|
+
"vitest": "^4.0.7",
|
|
66
70
|
"webdriverio": "^9.12.1"
|
|
67
71
|
},
|
|
68
72
|
"dependencies": {
|
package/scripts/test.ts
ADDED
|
@@ -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
|
+
});
|