@tvlabs/wdio-service 0.1.8 → 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 +97 -6
- package/cjs/channels/metadata.d.ts +10 -0
- package/cjs/channels/metadata.d.ts.map +1 -0
- package/cjs/index.js +106 -33
- package/cjs/service.d.ts +8 -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 +93 -20
- package/esm/service.d.ts +8 -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 +65 -1
- 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
|
@@ -10,13 +10,17 @@
|
|
|
10
10
|
|
|
11
11
|
## Introduction
|
|
12
12
|
|
|
13
|
+
[](https://www.npmjs.com/package/@tvlabs/wdio-service)
|
|
14
|
+
|
|
13
15
|
The `@tvlabs/wdio-service` package uses a websocket to connect to the TV Labs platform before an Appium session begins, logging events relating to build upload and session creation as they occur. This offloads the responsibility of creating the TV Labs session from the `POST /session` Webdriver endpoint, leading to more reliable session requests and creation.
|
|
14
16
|
|
|
15
17
|
If a build path is provided, the service first makes a build upload request to the TV Labs platform, and then sets the `tvlabs:build` capability to the newly created build ID.
|
|
16
18
|
|
|
17
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.
|
|
18
20
|
|
|
19
|
-
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.
|
|
20
24
|
|
|
21
25
|
## Installation
|
|
22
26
|
|
|
@@ -64,17 +68,17 @@ async function run() {
|
|
|
64
68
|
const wdOpts = {
|
|
65
69
|
capabilities,
|
|
66
70
|
hostname: 'appium.tvlabs.ai',
|
|
67
|
-
port: 4723
|
|
68
|
-
headers: {
|
|
69
|
-
Authorization: `Bearer ${process.env.TVLABS_API_TOKEN}`,
|
|
70
|
-
},
|
|
71
|
+
port: 4723
|
|
71
72
|
};
|
|
72
73
|
|
|
73
74
|
const serviceOpts = {
|
|
74
75
|
apiKey: process.env.TVLABS_API_TOKEN,
|
|
75
76
|
}
|
|
76
77
|
|
|
77
|
-
|
|
78
|
+
// NOTE: it is important to make sure that
|
|
79
|
+
// the wdOpts passed here are the same reference
|
|
80
|
+
// as the one passed to remote()
|
|
81
|
+
const service = new TVLabsService(serviceOpts, capabilities, wdOpts)
|
|
78
82
|
|
|
79
83
|
// The TV Labs service does not use specs or a cid, pass default values.
|
|
80
84
|
const cid = ""
|
|
@@ -141,3 +145,90 @@ run();
|
|
|
141
145
|
- **Required:** No
|
|
142
146
|
- **Default:** `false`
|
|
143
147
|
- **Description:** Whether to continue the session request if any step fails. When `true`, the session request will still be made with the original provided capabilities. When `false`, the service will exit with a non-zero code.
|
|
148
|
+
|
|
149
|
+
## Methods
|
|
150
|
+
|
|
151
|
+
### `lastRequestId()`
|
|
152
|
+
|
|
153
|
+
- **Returns:** `string | undefined`
|
|
154
|
+
- **Description:** Returns the last request ID that was attached to a request made to the TV Labs platform. This is useful for correlating client-side logs with server-side logs. Returns `undefined` if no request has been made yet or if `attachRequestId` is set to `false`.
|
|
155
|
+
|
|
156
|
+
#### Example
|
|
157
|
+
|
|
158
|
+
```javascript
|
|
159
|
+
import { remote } from 'webdriverio';
|
|
160
|
+
import { TVLabsService } from '@tvlabs/wdio-service';
|
|
161
|
+
|
|
162
|
+
const capabilities = { ... };
|
|
163
|
+
const wdOpts = { ... };
|
|
164
|
+
|
|
165
|
+
const service = new TVLabsService(
|
|
166
|
+
{ apiKey: process.env.TVLABS_API_KEY },
|
|
167
|
+
capabilities,
|
|
168
|
+
wdOpts
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
await service.beforeSession(wdOpts, capabilities, [], '');
|
|
172
|
+
|
|
173
|
+
const driver = await remote(wdOpts);
|
|
174
|
+
|
|
175
|
+
// Get the last request ID
|
|
176
|
+
const requestId = service.lastRequestId();
|
|
177
|
+
console.log(`Last request ID: ${requestId}`);
|
|
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.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.8";
|
|
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,19 +448,82 @@ 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;
|
|
497
|
+
requestId;
|
|
498
|
+
sessionId;
|
|
499
|
+
metadataChannel;
|
|
456
500
|
constructor(_options, _capabilities, _config) {
|
|
457
501
|
this._options = _options;
|
|
458
502
|
this._capabilities = _capabilities;
|
|
459
503
|
this._config = _config;
|
|
460
504
|
this.log = new Logger('@tvlabs/wdio-service', this._config.logLevel);
|
|
505
|
+
this.injectAuthorizationHeader();
|
|
461
506
|
if (this.attachRequestId()) {
|
|
462
507
|
this.setupRequestId();
|
|
463
508
|
}
|
|
509
|
+
this.log.info(`Instantiated TVLabsService v${getServiceVersion()}`);
|
|
510
|
+
}
|
|
511
|
+
lastRequestId() {
|
|
512
|
+
return this.requestId;
|
|
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;
|
|
464
527
|
}
|
|
465
528
|
onPrepare(_config, param) {
|
|
466
529
|
try {
|
|
@@ -496,6 +559,12 @@ class TVLabsService {
|
|
|
496
559
|
throw error;
|
|
497
560
|
}
|
|
498
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
|
+
}
|
|
499
568
|
setupRequestId() {
|
|
500
569
|
const originalTransformRequest = this._config.transformRequest;
|
|
501
570
|
this._config.transformRequest = (requestOptions) => {
|
|
@@ -507,7 +576,8 @@ class TVLabsService {
|
|
|
507
576
|
originalRequestOptions.headers = {};
|
|
508
577
|
}
|
|
509
578
|
this.setRequestHeader(originalRequestOptions.headers, 'x-request-id', requestId);
|
|
510
|
-
this.log.
|
|
579
|
+
this.log.debug('ATTACHED REQUEST ID', requestId);
|
|
580
|
+
this.setRequestId(requestId);
|
|
511
581
|
return originalRequestOptions;
|
|
512
582
|
};
|
|
513
583
|
}
|
|
@@ -524,6 +594,9 @@ class TVLabsService {
|
|
|
524
594
|
}
|
|
525
595
|
}
|
|
526
596
|
}
|
|
597
|
+
setRequestId(id) {
|
|
598
|
+
this.requestId = id;
|
|
599
|
+
}
|
|
527
600
|
continueOnError() {
|
|
528
601
|
return this._options.continueOnError ?? false;
|
|
529
602
|
}
|
package/cjs/service.d.ts
CHANGED
|
@@ -1,15 +1,22 @@
|
|
|
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
|
+
private requestId;
|
|
9
|
+
private sessionId;
|
|
10
|
+
private metadataChannel;
|
|
8
11
|
constructor(_options: TVLabsServiceOptions, _capabilities: Capabilities.ResolvedTestrunnerCapabilities, _config: Options.WebdriverIO);
|
|
12
|
+
lastRequestId(): string | undefined;
|
|
13
|
+
requestMetadata(sessionId: string, requestIds: string | string[]): Promise<TVLabsRequestMetadata | TVLabsRequestMetadataResponse>;
|
|
9
14
|
onPrepare(_config: Options.Testrunner, param: Capabilities.TestrunnerCapabilities): void;
|
|
10
15
|
beforeSession(_config: Omit<Options.Testrunner, 'capabilities'>, capabilities: TVLabsCapabilities, _specs: string[], _cid: string): Promise<void>;
|
|
16
|
+
private injectAuthorizationHeader;
|
|
11
17
|
private setupRequestId;
|
|
12
18
|
private setRequestHeader;
|
|
19
|
+
private setRequestId;
|
|
13
20
|
private continueOnError;
|
|
14
21
|
private buildPath;
|
|
15
22
|
private appSlug;
|
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.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.8";
|
|
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,19 +422,82 @@ 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;
|
|
471
|
+
requestId;
|
|
472
|
+
sessionId;
|
|
473
|
+
metadataChannel;
|
|
430
474
|
constructor(_options, _capabilities, _config) {
|
|
431
475
|
this._options = _options;
|
|
432
476
|
this._capabilities = _capabilities;
|
|
433
477
|
this._config = _config;
|
|
434
478
|
this.log = new Logger('@tvlabs/wdio-service', this._config.logLevel);
|
|
479
|
+
this.injectAuthorizationHeader();
|
|
435
480
|
if (this.attachRequestId()) {
|
|
436
481
|
this.setupRequestId();
|
|
437
482
|
}
|
|
483
|
+
this.log.info(`Instantiated TVLabsService v${getServiceVersion()}`);
|
|
484
|
+
}
|
|
485
|
+
lastRequestId() {
|
|
486
|
+
return this.requestId;
|
|
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;
|
|
438
501
|
}
|
|
439
502
|
onPrepare(_config, param) {
|
|
440
503
|
try {
|
|
@@ -470,6 +533,12 @@ class TVLabsService {
|
|
|
470
533
|
throw error;
|
|
471
534
|
}
|
|
472
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
|
+
}
|
|
473
542
|
setupRequestId() {
|
|
474
543
|
const originalTransformRequest = this._config.transformRequest;
|
|
475
544
|
this._config.transformRequest = (requestOptions) => {
|
|
@@ -481,7 +550,8 @@ class TVLabsService {
|
|
|
481
550
|
originalRequestOptions.headers = {};
|
|
482
551
|
}
|
|
483
552
|
this.setRequestHeader(originalRequestOptions.headers, 'x-request-id', requestId);
|
|
484
|
-
this.log.
|
|
553
|
+
this.log.debug('ATTACHED REQUEST ID', requestId);
|
|
554
|
+
this.setRequestId(requestId);
|
|
485
555
|
return originalRequestOptions;
|
|
486
556
|
};
|
|
487
557
|
}
|
|
@@ -498,6 +568,9 @@ class TVLabsService {
|
|
|
498
568
|
}
|
|
499
569
|
}
|
|
500
570
|
}
|
|
571
|
+
setRequestId(id) {
|
|
572
|
+
this.requestId = id;
|
|
573
|
+
}
|
|
501
574
|
continueOnError() {
|
|
502
575
|
return this._options.continueOnError ?? false;
|
|
503
576
|
}
|
package/esm/service.d.ts
CHANGED
|
@@ -1,15 +1,22 @@
|
|
|
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
|
+
private requestId;
|
|
9
|
+
private sessionId;
|
|
10
|
+
private metadataChannel;
|
|
8
11
|
constructor(_options: TVLabsServiceOptions, _capabilities: Capabilities.ResolvedTestrunnerCapabilities, _config: Options.WebdriverIO);
|
|
12
|
+
lastRequestId(): string | undefined;
|
|
13
|
+
requestMetadata(sessionId: string, requestIds: string | string[]): Promise<TVLabsRequestMetadata | TVLabsRequestMetadataResponse>;
|
|
9
14
|
onPrepare(_config: Options.Testrunner, param: Capabilities.TestrunnerCapabilities): void;
|
|
10
15
|
beforeSession(_config: Omit<Options.Testrunner, 'capabilities'>, capabilities: TVLabsCapabilities, _specs: string[], _cid: string): Promise<void>;
|
|
16
|
+
private injectAuthorizationHeader;
|
|
11
17
|
private setupRequestId;
|
|
12
18
|
private setRequestHeader;
|
|
19
|
+
private setRequestId;
|
|
13
20
|
private continueOnError;
|
|
14
21
|
private buildPath;
|
|
15
22
|
private appSlug;
|
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.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": "
|
|
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,19 +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;
|
|
21
|
+
private requestId: string | undefined;
|
|
22
|
+
private sessionId: string | undefined;
|
|
23
|
+
private metadataChannel: MetadataChannel | undefined;
|
|
17
24
|
|
|
18
25
|
constructor(
|
|
19
26
|
private _options: TVLabsServiceOptions,
|
|
@@ -21,9 +28,52 @@ export default class TVLabsService implements Services.ServiceInstance {
|
|
|
21
28
|
private _config: Options.WebdriverIO,
|
|
22
29
|
) {
|
|
23
30
|
this.log = new Logger('@tvlabs/wdio-service', this._config.logLevel);
|
|
31
|
+
|
|
32
|
+
this.injectAuthorizationHeader();
|
|
33
|
+
|
|
24
34
|
if (this.attachRequestId()) {
|
|
25
35
|
this.setupRequestId();
|
|
26
36
|
}
|
|
37
|
+
|
|
38
|
+
this.log.info(`Instantiated TVLabsService v${getServiceVersion()}`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
lastRequestId(): string | undefined {
|
|
42
|
+
return this.requestId;
|
|
43
|
+
}
|
|
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;
|
|
27
77
|
}
|
|
28
78
|
|
|
29
79
|
onPrepare(
|
|
@@ -96,6 +146,14 @@ export default class TVLabsService implements Services.ServiceInstance {
|
|
|
96
146
|
}
|
|
97
147
|
}
|
|
98
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
|
+
|
|
99
157
|
private setupRequestId() {
|
|
100
158
|
const originalTransformRequest = this._config.transformRequest;
|
|
101
159
|
|
|
@@ -116,7 +174,9 @@ export default class TVLabsService implements Services.ServiceInstance {
|
|
|
116
174
|
requestId,
|
|
117
175
|
);
|
|
118
176
|
|
|
119
|
-
this.log.
|
|
177
|
+
this.log.debug('ATTACHED REQUEST ID', requestId);
|
|
178
|
+
|
|
179
|
+
this.setRequestId(requestId);
|
|
120
180
|
|
|
121
181
|
return originalRequestOptions;
|
|
122
182
|
};
|
|
@@ -138,6 +198,10 @@ export default class TVLabsService implements Services.ServiceInstance {
|
|
|
138
198
|
}
|
|
139
199
|
}
|
|
140
200
|
|
|
201
|
+
private setRequestId(id: string) {
|
|
202
|
+
this.requestId = id;
|
|
203
|
+
}
|
|
204
|
+
|
|
141
205
|
private continueOnError(): boolean {
|
|
142
206
|
return this._options.continueOnError ?? false;
|
|
143
207
|
}
|
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
|
+
});
|