@tvlabs/wdio-service 0.1.1 → 0.1.3

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.
Files changed (46) hide show
  1. package/README.md +3 -3
  2. package/{dist → cjs}/channel.d.ts +5 -3
  3. package/cjs/channel.d.ts.map +1 -0
  4. package/cjs/channel.js +184 -0
  5. package/cjs/index.d.ts +5 -0
  6. package/{dist → cjs}/index.d.ts.map +1 -1
  7. package/cjs/index.js +21 -0
  8. package/cjs/logger.d.ts +14 -0
  9. package/cjs/logger.d.ts.map +1 -0
  10. package/cjs/logger.js +57 -0
  11. package/cjs/package.json +1 -0
  12. package/{dist → cjs}/service.d.ts +2 -0
  13. package/cjs/service.d.ts.map +1 -0
  14. package/cjs/service.js +79 -0
  15. package/{dist → cjs}/types.d.ts +1 -0
  16. package/cjs/types.d.ts.map +1 -0
  17. package/cjs/types.js +2 -0
  18. package/esm/channel.d.ts +27 -0
  19. package/esm/channel.d.ts.map +1 -0
  20. package/{dist → esm}/channel.js +27 -24
  21. package/esm/index.d.ts +5 -0
  22. package/esm/index.d.ts.map +1 -0
  23. package/{dist → esm}/index.js +1 -0
  24. package/esm/logger.d.ts +14 -0
  25. package/esm/logger.d.ts.map +1 -0
  26. package/esm/logger.js +53 -0
  27. package/esm/package.json +1 -0
  28. package/esm/service.d.ts +20 -0
  29. package/esm/service.d.ts.map +1 -0
  30. package/{dist → esm}/service.js +9 -5
  31. package/esm/types.d.ts +37 -0
  32. package/esm/types.d.ts.map +1 -0
  33. package/package.json +16 -16
  34. package/src/channel.ts +267 -0
  35. package/{dist/index.d.ts → src/index.ts} +2 -1
  36. package/src/logger.ts +66 -0
  37. package/src/phoenix.d.ts +18 -0
  38. package/src/service.ts +127 -0
  39. package/src/types.ts +46 -0
  40. package/dist/channel.d.ts.map +0 -1
  41. package/dist/logger.d.ts +0 -3
  42. package/dist/logger.d.ts.map +0 -1
  43. package/dist/logger.js +0 -2
  44. package/dist/service.d.ts.map +0 -1
  45. package/dist/types.d.ts.map +0 -1
  46. /package/{dist → esm}/types.js +0 -0
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  <p align="center">
2
2
  <a href="https://tvlabs.ai">
3
- <img alt="TV Labs Logo" width="200" src="https://tvlabs.ai/images/tvlabs.svg">
3
+ <img alt="TV Labs Logo" width="200" src="https://tvlabs.ai/images/tvlabs.svg" />
4
4
  </a>
5
5
  </p>
6
6
 
@@ -39,7 +39,7 @@ yarn add @tvlabs/wdio-service
39
39
  To use this as a WebdriverIO test runner service, include the service in your WebdriverIO configuration file (e.g. `wdio.conf.ts`) with your TV Labs API key set in the options.
40
40
 
41
41
  ```javascript
42
- import TVLabsService from '@tvlabs/wdio-service';
42
+ import { TVLabsService } from '@tvlabs/wdio-service';
43
43
 
44
44
  export const config = {
45
45
  // ...
@@ -54,7 +54,7 @@ To use this with WebdriverIO remote but without the test runner, call the before
54
54
 
55
55
  ```javascript
56
56
  import { remote } from 'webdriverio';
57
- import TVLabsService from '@tvlabs/wdio-service';
57
+ import { TVLabsService } from '@tvlabs/wdio-service';
58
58
 
59
59
  const capabilities = { ... };
60
60
 
@@ -1,13 +1,15 @@
1
- import type { TVLabsCapabilities } from './types.js';
1
+ import type { TVLabsCapabilities, LogLevel } from './types.js';
2
2
  export declare class TVLabsChannel {
3
3
  private endpoint;
4
4
  private maxReconnectRetries;
5
5
  private key;
6
+ private logLevel;
6
7
  private socket;
7
8
  private lobbyTopic;
8
9
  private requestTopic?;
10
+ private log;
9
11
  private readonly events;
10
- constructor(endpoint: string, maxReconnectRetries: number, key: string);
12
+ constructor(endpoint: string, maxReconnectRetries: number, key: string, logLevel: LogLevel);
11
13
  disconnect(): Promise<void>;
12
14
  connect(): Promise<void>;
13
15
  newSession(capabilities: TVLabsCapabilities, maxRetries: number, retry?: number): Promise<string>;
@@ -19,7 +21,7 @@ export declare class TVLabsChannel {
19
21
  private push;
20
22
  private params;
21
23
  private reconnectAfterMs;
22
- private logSocketError;
23
24
  private tvlabsSessionLink;
25
+ private static logSocketError;
24
26
  }
25
27
  //# sourceMappingURL=channel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"channel.d.ts","sourceRoot":"","sources":["../src/channel.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACV,kBAAkB,EAIlB,QAAQ,EACT,MAAM,YAAY,CAAC;AAGpB,qBAAa,aAAa;IAgBtB,OAAO,CAAC,QAAQ;IAChB,OAAO,CAAC,mBAAmB;IAC3B,OAAO,CAAC,GAAG;IACX,OAAO,CAAC,QAAQ;IAlBlB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,UAAU,CAAU;IAC5B,OAAO,CAAC,YAAY,CAAC,CAAU;IAC/B,OAAO,CAAC,GAAG,CAAS;IAEpB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAOZ;gBAGD,QAAQ,EAAE,MAAM,EAChB,mBAAmB,EAAE,MAAM,EAC3B,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,QAAQ;IAgBtB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAQ3B,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAiBxB,UAAU,CACd,YAAY,EAAE,kBAAkB,EAChC,UAAU,EAAE,MAAM,EAClB,KAAK,SAAI,GACR,OAAO,CAAC,MAAM,CAAC;YAWJ,WAAW;YAkBX,cAAc;IAsD5B,OAAO,CAAC,gBAAgB;YAUV,cAAc;YAuBd,IAAI;YAgBJ,IAAI;IAoBlB,OAAO,CAAC,MAAM;IAMd,OAAO,CAAC,gBAAgB;IAgBxB,OAAO,CAAC,iBAAiB;IAIzB,OAAO,CAAC,MAAM,CAAC,cAAc;CAU9B"}
package/cjs/channel.js ADDED
@@ -0,0 +1,184 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TVLabsChannel = void 0;
4
+ const ws_1 = require("ws");
5
+ const phoenix_1 = require("phoenix");
6
+ const webdriverio_1 = require("webdriverio");
7
+ const logger_js_1 = require("./logger.js");
8
+ class TVLabsChannel {
9
+ endpoint;
10
+ maxReconnectRetries;
11
+ key;
12
+ logLevel;
13
+ socket;
14
+ lobbyTopic;
15
+ requestTopic;
16
+ log;
17
+ events = {
18
+ SESSION_READY: 'session:ready',
19
+ SESSION_FAILED: 'session:failed',
20
+ REQUEST_CANCELED: 'request:canceled',
21
+ REQUEST_FAILED: 'request:failed',
22
+ REQUEST_FILLED: 'request:filled',
23
+ REQUEST_MATCHING: 'request:matching',
24
+ };
25
+ constructor(endpoint, maxReconnectRetries, key, logLevel) {
26
+ this.endpoint = endpoint;
27
+ this.maxReconnectRetries = maxReconnectRetries;
28
+ this.key = key;
29
+ this.logLevel = logLevel;
30
+ this.log = new logger_js_1.Logger('@tvlabs/wdio-channel', this.logLevel);
31
+ this.socket = new phoenix_1.Socket(this.endpoint, {
32
+ transport: ws_1.WebSocket,
33
+ params: this.params(),
34
+ reconnectAfterMs: this.reconnectAfterMs.bind(this),
35
+ });
36
+ this.socket.onError((...args) => TVLabsChannel.logSocketError(this.log, ...args));
37
+ this.lobbyTopic = this.socket.channel('requests:lobby');
38
+ }
39
+ async disconnect() {
40
+ return new Promise((res, _rej) => {
41
+ this.lobbyTopic.leave();
42
+ this.requestTopic?.leave();
43
+ this.socket.disconnect(() => res());
44
+ });
45
+ }
46
+ async connect() {
47
+ try {
48
+ this.log.debug('Connecting to TV Labs...');
49
+ this.socket.connect();
50
+ await this.join(this.lobbyTopic);
51
+ this.log.debug('Connected to TV Labs!');
52
+ }
53
+ catch (error) {
54
+ this.log.error('Error connecting to TV Labs:', error);
55
+ throw new webdriverio_1.SevereServiceError('Could not connect to TV Labs, please check your connection.');
56
+ }
57
+ }
58
+ async newSession(capabilities, maxRetries, retry = 0) {
59
+ try {
60
+ const requestId = await this.requestSession(capabilities);
61
+ const sessionId = await this.observeRequest(requestId);
62
+ return sessionId;
63
+ }
64
+ catch {
65
+ return this.handleRetry(capabilities, maxRetries, retry);
66
+ }
67
+ }
68
+ async handleRetry(capabilities, maxRetries, retry) {
69
+ if (retry < maxRetries) {
70
+ this.log.warn(`Could not create a session, retrying (${retry + 1}/${maxRetries})`);
71
+ return this.newSession(capabilities, maxRetries, retry + 1);
72
+ }
73
+ else {
74
+ throw new webdriverio_1.SevereServiceError(`Could not create a session after ${maxRetries} attempts.`);
75
+ }
76
+ }
77
+ async observeRequest(requestId) {
78
+ const cleanup = () => this.unobserveRequest();
79
+ return new Promise((res, rej) => {
80
+ this.requestTopic = this.socket.channel(`requests:${requestId}`);
81
+ const eventHandlers = {
82
+ [this.events.REQUEST_MATCHING]: ({ request_id }) => {
83
+ this.log.info(`Session request ${request_id} matching...`);
84
+ },
85
+ [this.events.REQUEST_FILLED]: ({ session_id, request_id }) => {
86
+ this.log.info(`Session request ${request_id} filled: ${this.tvlabsSessionLink(session_id)}`);
87
+ this.log.info('Waiting for device to be ready...');
88
+ },
89
+ [this.events.SESSION_FAILED]: ({ session_id, reason }) => {
90
+ this.log.error(`Session ${session_id} failed, reason: ${reason}`);
91
+ rej(reason);
92
+ },
93
+ [this.events.REQUEST_CANCELED]: ({ request_id, reason }) => {
94
+ this.log.info(`Session request ${request_id} canceled, reason: ${reason}`);
95
+ rej(reason);
96
+ },
97
+ [this.events.REQUEST_FAILED]: ({ request_id, reason }) => {
98
+ this.log.info(`Session request ${request_id} failed, reason: ${reason}`);
99
+ rej(reason);
100
+ },
101
+ [this.events.SESSION_READY]: ({ session_id }) => {
102
+ this.log.info(`Session ${session_id} ready!`);
103
+ res(session_id);
104
+ },
105
+ };
106
+ Object.entries(eventHandlers).forEach(([event, handler]) => {
107
+ this.requestTopic?.on(event, handler);
108
+ });
109
+ this.join(this.requestTopic).catch((err) => {
110
+ rej(err);
111
+ });
112
+ }).finally(cleanup);
113
+ }
114
+ unobserveRequest() {
115
+ Object.values(this.events).forEach((event) => {
116
+ this.requestTopic?.off(event);
117
+ });
118
+ this.requestTopic?.leave();
119
+ this.requestTopic = undefined;
120
+ }
121
+ async requestSession(capabilities) {
122
+ this.log.info('Requesting TV Labs session');
123
+ try {
124
+ const response = await this.push(this.lobbyTopic, 'requests:create', { capabilities });
125
+ this.log.info(`Received session request ID: ${response.request_id}. Waiting for a match...`);
126
+ return response.request_id;
127
+ }
128
+ catch (error) {
129
+ this.log.error('Error requesting session:', error);
130
+ throw error;
131
+ }
132
+ }
133
+ async join(topic) {
134
+ return new Promise((res, rej) => {
135
+ topic
136
+ .join()
137
+ .receive('ok', (_resp) => {
138
+ res();
139
+ })
140
+ .receive('error', ({ response }) => {
141
+ rej('Failed to join topic: ' + response);
142
+ })
143
+ .receive('timeout', () => {
144
+ rej('timeout');
145
+ });
146
+ });
147
+ }
148
+ async push(topic, event, payload) {
149
+ return new Promise((res, rej) => {
150
+ topic
151
+ .push(event, payload)
152
+ .receive('ok', (msg) => {
153
+ res(msg);
154
+ })
155
+ .receive('error', (reason) => {
156
+ rej(reason);
157
+ })
158
+ .receive('timeout', () => {
159
+ rej('timeout');
160
+ });
161
+ });
162
+ }
163
+ params() {
164
+ return {
165
+ api_key: this.key,
166
+ };
167
+ }
168
+ reconnectAfterMs(tries) {
169
+ if (tries > this.maxReconnectRetries) {
170
+ throw new webdriverio_1.SevereServiceError('Could not connect to TV Labs, please check your connection.');
171
+ }
172
+ const wait = [0, 1000, 3000, 5000][tries] || 10000;
173
+ this.log.info(`[${tries}/${this.maxReconnectRetries}] Waiting ${wait}ms before re-attempting to connect...`);
174
+ return wait;
175
+ }
176
+ tvlabsSessionLink(sessionId) {
177
+ return `https://tvlabs.ai/app/sessions/${sessionId}`;
178
+ }
179
+ static logSocketError(log, event, _transport, _establishedConnections) {
180
+ const error = event.error;
181
+ log.error('Socket error:', error || event);
182
+ }
183
+ }
184
+ exports.TVLabsChannel = TVLabsChannel;
package/cjs/index.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ import TVLabsService from './service.js';
2
+ export { TVLabsService };
3
+ export default TVLabsService;
4
+ export * from './types.js';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,aAAa,MAAM,cAAc,CAAC;AAEzC,eAAe,aAAa,CAAC;AAC7B,cAAc,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,aAAa,MAAM,cAAc,CAAC;AAEzC,OAAO,EAAE,aAAa,EAAE,CAAC;AACzB,eAAe,aAAa,CAAC;AAC7B,cAAc,YAAY,CAAC"}
package/cjs/index.js ADDED
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.TVLabsService = void 0;
18
+ const service_js_1 = require("./service.js");
19
+ exports.TVLabsService = service_js_1.default;
20
+ exports.default = service_js_1.default;
21
+ __exportStar(require("./types.js"), exports);
@@ -0,0 +1,14 @@
1
+ import type { LogLevel } from './types.js';
2
+ export declare class Logger {
3
+ private name;
4
+ private logLevel;
5
+ constructor(name: string, logLevel?: LogLevel);
6
+ private shouldLog;
7
+ private formatMessage;
8
+ debug(...args: unknown[]): void;
9
+ info(...args: unknown[]): void;
10
+ warn(...args: unknown[]): void;
11
+ error(...args: unknown[]): void;
12
+ trace(...args: unknown[]): void;
13
+ }
14
+ //# sourceMappingURL=logger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAc3C,qBAAa,MAAM;IAEf,OAAO,CAAC,IAAI;IACZ,OAAO,CAAC,QAAQ;gBADR,IAAI,EAAE,MAAM,EACZ,QAAQ,GAAE,QAAiB;IAGrC,OAAO,CAAC,SAAS;IAOjB,OAAO,CAAC,aAAa;IASrB,KAAK,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI;IAM/B,IAAI,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI;IAM9B,IAAI,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI;IAM9B,KAAK,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI;IAM/B,KAAK,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI;CAKhC"}
package/cjs/logger.js ADDED
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Logger = void 0;
4
+ const LOG_LEVELS = {
5
+ error: 0,
6
+ warn: 1,
7
+ info: 2,
8
+ debug: 3,
9
+ trace: 4,
10
+ silent: 5,
11
+ };
12
+ class Logger {
13
+ name;
14
+ logLevel;
15
+ constructor(name, logLevel = 'info') {
16
+ this.name = name;
17
+ this.logLevel = logLevel;
18
+ }
19
+ shouldLog(level) {
20
+ if (this.logLevel === 'silent') {
21
+ return false;
22
+ }
23
+ return LOG_LEVELS[level] <= LOG_LEVELS[this.logLevel];
24
+ }
25
+ formatMessage(level, ...args) {
26
+ const timestamp = new Date().toISOString();
27
+ return `${timestamp} ${level.toUpperCase()} ${this.name}: ${args
28
+ .map((arg) => typeof arg === 'object' ? JSON.stringify(arg) : String(arg))
29
+ .join(' ')}`;
30
+ }
31
+ debug(...args) {
32
+ if (this.shouldLog('debug')) {
33
+ console.log(this.formatMessage('debug', ...args));
34
+ }
35
+ }
36
+ info(...args) {
37
+ if (this.shouldLog('info')) {
38
+ console.log(this.formatMessage('info', ...args));
39
+ }
40
+ }
41
+ warn(...args) {
42
+ if (this.shouldLog('warn')) {
43
+ console.warn(this.formatMessage('warn', ...args));
44
+ }
45
+ }
46
+ error(...args) {
47
+ if (this.shouldLog('error')) {
48
+ console.error(this.formatMessage('error', ...args));
49
+ }
50
+ }
51
+ trace(...args) {
52
+ if (this.shouldLog('trace')) {
53
+ console.trace(this.formatMessage('trace', ...args));
54
+ }
55
+ }
56
+ }
57
+ exports.Logger = Logger;
@@ -0,0 +1 @@
1
+ {"type": "commonjs"}
@@ -4,6 +4,7 @@ export default class TVLabsService implements Services.ServiceInstance {
4
4
  private _options;
5
5
  private _capabilities;
6
6
  private _config;
7
+ private log;
7
8
  constructor(_options: TVLabsServiceOptions, _capabilities: Capabilities.ResolvedTestrunnerCapabilities, _config: Options.WebdriverIO);
8
9
  onPrepare(_config: Options.Testrunner, param: Capabilities.TestrunnerCapabilities): void;
9
10
  beforeSession(_config: Omit<Options.Testrunner, 'capabilities'>, capabilities: TVLabsCapabilities, _specs: string[], _cid: string): Promise<void>;
@@ -12,6 +13,7 @@ export default class TVLabsService implements Services.ServiceInstance {
12
13
  private endpoint;
13
14
  private retries;
14
15
  private apiKey;
16
+ private logLevel;
15
17
  private attachRequestId;
16
18
  private reconnectRetries;
17
19
  }
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,QAAQ,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AACnE,OAAO,KAAK,EACV,kBAAkB,EAClB,oBAAoB,EAErB,MAAM,YAAY,CAAC;AAEpB,MAAM,CAAC,OAAO,OAAO,aAAc,YAAW,QAAQ,CAAC,eAAe;IAIlE,OAAO,CAAC,QAAQ;IAChB,OAAO,CAAC,aAAa;IACrB,OAAO,CAAC,OAAO;IALjB,OAAO,CAAC,GAAG,CAAS;gBAGV,QAAQ,EAAE,oBAAoB,EAC9B,aAAa,EAAE,YAAY,CAAC,8BAA8B,EAC1D,OAAO,EAAE,OAAO,CAAC,WAAW;IAQtC,SAAS,CACP,OAAO,EAAE,OAAO,CAAC,UAAU,EAC3B,KAAK,EAAE,YAAY,CAAC,sBAAsB;IAStC,aAAa,CACjB,OAAO,EAAE,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,cAAc,CAAC,EACjD,YAAY,EAAE,kBAAkB,EAChC,MAAM,EAAE,MAAM,EAAE,EAChB,IAAI,EAAE,MAAM;IAmBd,OAAO,CAAC,cAAc;IA0BtB,OAAO,CAAC,gBAAgB;IAgBxB,OAAO,CAAC,QAAQ;IAIhB,OAAO,CAAC,OAAO;IAIf,OAAO,CAAC,MAAM;IAId,OAAO,CAAC,QAAQ;IAIhB,OAAO,CAAC,eAAe;IAIvB,OAAO,CAAC,gBAAgB;CAGzB"}
package/cjs/service.js ADDED
@@ -0,0 +1,79 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const webdriverio_1 = require("webdriverio");
4
+ const crypto = require("crypto");
5
+ const channel_js_1 = require("./channel.js");
6
+ const logger_js_1 = require("./logger.js");
7
+ class TVLabsService {
8
+ _options;
9
+ _capabilities;
10
+ _config;
11
+ log;
12
+ constructor(_options, _capabilities, _config) {
13
+ this._options = _options;
14
+ this._capabilities = _capabilities;
15
+ this._config = _config;
16
+ this.log = new logger_js_1.Logger('@tvlabs/wdio-server', this._config.logLevel);
17
+ if (this.attachRequestId()) {
18
+ this.setupRequestId();
19
+ }
20
+ }
21
+ onPrepare(_config, param) {
22
+ if (!Array.isArray(param)) {
23
+ throw new webdriverio_1.SevereServiceError('Multi-remote capabilities are not implemented. Contact TV Labs support if you are interested in this feature.');
24
+ }
25
+ }
26
+ async beforeSession(_config, capabilities, _specs, _cid) {
27
+ const channel = new channel_js_1.TVLabsChannel(this.endpoint(), this.reconnectRetries(), this.apiKey(), this.logLevel());
28
+ await channel.connect();
29
+ capabilities['tvlabs:session_id'] = await channel.newSession(capabilities, this.retries());
30
+ await channel.disconnect();
31
+ }
32
+ setupRequestId() {
33
+ const originalTransformRequest = this._config.transformRequest;
34
+ this._config.transformRequest = (requestOptions) => {
35
+ const requestId = crypto.randomUUID();
36
+ const originalRequestOptions = typeof originalTransformRequest === 'function'
37
+ ? originalTransformRequest(requestOptions)
38
+ : requestOptions;
39
+ if (typeof originalRequestOptions.headers === 'undefined') {
40
+ originalRequestOptions.headers = {};
41
+ }
42
+ this.setRequestHeader(originalRequestOptions.headers, 'x-request-id', requestId);
43
+ this.log.info('ATTACHED REQUEST ID', requestId);
44
+ return originalRequestOptions;
45
+ };
46
+ }
47
+ setRequestHeader(headers, header, value) {
48
+ if (headers instanceof Headers) {
49
+ headers.set(header, value);
50
+ }
51
+ else if (typeof headers === 'object') {
52
+ if (Array.isArray(headers)) {
53
+ headers.push([header, value]);
54
+ }
55
+ else {
56
+ headers[header] = value;
57
+ }
58
+ }
59
+ }
60
+ endpoint() {
61
+ return this._options.endpoint ?? 'wss://tvlabs.ai/appium';
62
+ }
63
+ retries() {
64
+ return this._options.retries ?? 3;
65
+ }
66
+ apiKey() {
67
+ return this._options.apiKey;
68
+ }
69
+ logLevel() {
70
+ return this._config.logLevel ?? 'info';
71
+ }
72
+ attachRequestId() {
73
+ return this._options.attachRequestId ?? true;
74
+ }
75
+ reconnectRetries() {
76
+ return this._options.reconnectRetries ?? 5;
77
+ }
78
+ }
79
+ exports.default = TVLabsService;
@@ -1,4 +1,5 @@
1
1
  import type { Capabilities } from '@wdio/types';
2
+ export type LogLevel = 'error' | 'warn' | 'info' | 'debug' | 'trace' | 'silent';
2
3
  export type TVLabsServiceOptions = {
3
4
  apiKey: string;
4
5
  endpoint?: string;
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,GAAG,QAAQ,CAAC;AAEhF,MAAM,MAAM,oBAAoB,GAAG;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAC5B,YAAY,CAAC,+BAA+B,GAAG;IAC7C,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,oBAAoB,CAAC,EAAE;QACrB,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,kCAAkC,CAAC,EAAE,MAAM,CAAC;QAC5C,qBAAqB,CAAC,EAAE,OAAO,CAAC;KACjC,CAAC;IACF,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,uBAAuB,CAAC,EAAE,MAAM,CAAC;CAClC,CAAC;AAEJ,MAAM,MAAM,gCAAgC,GAAG,CAC7C,QAAQ,EAAE,0BAA0B,KACjC,IAAI,CAAC;AAEV,MAAM,MAAM,0BAA0B,GAAG;IACvC,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,4BAA4B,GAAG;IACzC,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,0BAA0B,GAAG;IACvC,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC"}
package/cjs/types.js ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,27 @@
1
+ import type { TVLabsCapabilities, LogLevel } from './types.js';
2
+ export declare class TVLabsChannel {
3
+ private endpoint;
4
+ private maxReconnectRetries;
5
+ private key;
6
+ private logLevel;
7
+ private socket;
8
+ private lobbyTopic;
9
+ private requestTopic?;
10
+ private log;
11
+ private readonly events;
12
+ constructor(endpoint: string, maxReconnectRetries: number, key: string, logLevel: LogLevel);
13
+ disconnect(): Promise<void>;
14
+ connect(): Promise<void>;
15
+ newSession(capabilities: TVLabsCapabilities, maxRetries: number, retry?: number): Promise<string>;
16
+ private handleRetry;
17
+ private observeRequest;
18
+ private unobserveRequest;
19
+ private requestSession;
20
+ private join;
21
+ private push;
22
+ private params;
23
+ private reconnectAfterMs;
24
+ private tvlabsSessionLink;
25
+ private static logSocketError;
26
+ }
27
+ //# sourceMappingURL=channel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"channel.d.ts","sourceRoot":"","sources":["../src/channel.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACV,kBAAkB,EAIlB,QAAQ,EACT,MAAM,YAAY,CAAC;AAGpB,qBAAa,aAAa;IAgBtB,OAAO,CAAC,QAAQ;IAChB,OAAO,CAAC,mBAAmB;IAC3B,OAAO,CAAC,GAAG;IACX,OAAO,CAAC,QAAQ;IAlBlB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,UAAU,CAAU;IAC5B,OAAO,CAAC,YAAY,CAAC,CAAU;IAC/B,OAAO,CAAC,GAAG,CAAS;IAEpB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAOZ;gBAGD,QAAQ,EAAE,MAAM,EAChB,mBAAmB,EAAE,MAAM,EAC3B,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,QAAQ;IAgBtB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAQ3B,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAiBxB,UAAU,CACd,YAAY,EAAE,kBAAkB,EAChC,UAAU,EAAE,MAAM,EAClB,KAAK,SAAI,GACR,OAAO,CAAC,MAAM,CAAC;YAWJ,WAAW;YAkBX,cAAc;IAsD5B,OAAO,CAAC,gBAAgB;YAUV,cAAc;YAuBd,IAAI;YAgBJ,IAAI;IAoBlB,OAAO,CAAC,MAAM;IAMd,OAAO,CAAC,gBAAgB;IAgBxB,OAAO,CAAC,iBAAiB;IAIzB,OAAO,CAAC,MAAM,CAAC,cAAc;CAU9B"}
@@ -1,14 +1,16 @@
1
- import WebSocket from 'ws';
1
+ import { WebSocket } from 'ws';
2
2
  import { Socket } from 'phoenix';
3
3
  import { SevereServiceError } from 'webdriverio';
4
- import { log } from './logger.js';
4
+ import { Logger } from './logger.js';
5
5
  export class TVLabsChannel {
6
6
  endpoint;
7
7
  maxReconnectRetries;
8
8
  key;
9
+ logLevel;
9
10
  socket;
10
11
  lobbyTopic;
11
12
  requestTopic;
13
+ log;
12
14
  events = {
13
15
  SESSION_READY: 'session:ready',
14
16
  SESSION_FAILED: 'session:failed',
@@ -17,16 +19,18 @@ export class TVLabsChannel {
17
19
  REQUEST_FILLED: 'request:filled',
18
20
  REQUEST_MATCHING: 'request:matching',
19
21
  };
20
- constructor(endpoint, maxReconnectRetries, key) {
22
+ constructor(endpoint, maxReconnectRetries, key, logLevel) {
21
23
  this.endpoint = endpoint;
22
24
  this.maxReconnectRetries = maxReconnectRetries;
23
25
  this.key = key;
26
+ this.logLevel = logLevel;
27
+ this.log = new Logger('@tvlabs/wdio-channel', this.logLevel);
24
28
  this.socket = new Socket(this.endpoint, {
25
29
  transport: WebSocket,
26
30
  params: this.params(),
27
31
  reconnectAfterMs: this.reconnectAfterMs.bind(this),
28
32
  });
29
- this.socket.onError(this.logSocketError);
33
+ this.socket.onError((...args) => TVLabsChannel.logSocketError(this.log, ...args));
30
34
  this.lobbyTopic = this.socket.channel('requests:lobby');
31
35
  }
32
36
  async disconnect() {
@@ -38,13 +42,13 @@ export class TVLabsChannel {
38
42
  }
39
43
  async connect() {
40
44
  try {
41
- log.debug('Connecting to TV Labs...');
45
+ this.log.debug('Connecting to TV Labs...');
42
46
  this.socket.connect();
43
47
  await this.join(this.lobbyTopic);
44
- log.debug('Connected to TV Labs!');
48
+ this.log.debug('Connected to TV Labs!');
45
49
  }
46
50
  catch (error) {
47
- log.error('Error connecting to TV Labs:', error);
51
+ this.log.error('Error connecting to TV Labs:', error);
48
52
  throw new SevereServiceError('Could not connect to TV Labs, please check your connection.');
49
53
  }
50
54
  }
@@ -60,7 +64,7 @@ export class TVLabsChannel {
60
64
  }
61
65
  async handleRetry(capabilities, maxRetries, retry) {
62
66
  if (retry < maxRetries) {
63
- log.warn(`Could not create a session, retrying (${retry + 1}/${maxRetries})`);
67
+ this.log.warn(`Could not create a session, retrying (${retry + 1}/${maxRetries})`);
64
68
  return this.newSession(capabilities, maxRetries, retry + 1);
65
69
  }
66
70
  else {
@@ -73,26 +77,26 @@ export class TVLabsChannel {
73
77
  this.requestTopic = this.socket.channel(`requests:${requestId}`);
74
78
  const eventHandlers = {
75
79
  [this.events.REQUEST_MATCHING]: ({ request_id }) => {
76
- log.info(`Session request ${request_id} matching...`);
80
+ this.log.info(`Session request ${request_id} matching...`);
77
81
  },
78
82
  [this.events.REQUEST_FILLED]: ({ session_id, request_id }) => {
79
- log.info(`Session request ${request_id} filled: ${this.tvlabsSessionLink(session_id)}`);
80
- log.info('Waiting for device to be ready...');
83
+ this.log.info(`Session request ${request_id} filled: ${this.tvlabsSessionLink(session_id)}`);
84
+ this.log.info('Waiting for device to be ready...');
81
85
  },
82
86
  [this.events.SESSION_FAILED]: ({ session_id, reason }) => {
83
- log.error(`Session ${session_id} failed, reason: ${reason}`);
87
+ this.log.error(`Session ${session_id} failed, reason: ${reason}`);
84
88
  rej(reason);
85
89
  },
86
90
  [this.events.REQUEST_CANCELED]: ({ request_id, reason }) => {
87
- log.info(`Session request ${request_id} canceled, reason: ${reason}`);
91
+ this.log.info(`Session request ${request_id} canceled, reason: ${reason}`);
88
92
  rej(reason);
89
93
  },
90
94
  [this.events.REQUEST_FAILED]: ({ request_id, reason }) => {
91
- log.info(`Session request ${request_id} failed, reason: ${reason}`);
95
+ this.log.info(`Session request ${request_id} failed, reason: ${reason}`);
92
96
  rej(reason);
93
97
  },
94
98
  [this.events.SESSION_READY]: ({ session_id }) => {
95
- log.info(`Session ${session_id} ready!`);
99
+ this.log.info(`Session ${session_id} ready!`);
96
100
  res(session_id);
97
101
  },
98
102
  };
@@ -112,14 +116,14 @@ export class TVLabsChannel {
112
116
  this.requestTopic = undefined;
113
117
  }
114
118
  async requestSession(capabilities) {
115
- log.info('Requesting TV Labs session');
119
+ this.log.info('Requesting TV Labs session');
116
120
  try {
117
121
  const response = await this.push(this.lobbyTopic, 'requests:create', { capabilities });
118
- log.info(`Received session request ID: ${response.request_id}. Waiting for a match...`);
122
+ this.log.info(`Received session request ID: ${response.request_id}. Waiting for a match...`);
119
123
  return response.request_id;
120
124
  }
121
125
  catch (error) {
122
- log.error('Error requesting session:', error);
126
+ this.log.error('Error requesting session:', error);
123
127
  throw error;
124
128
  }
125
129
  }
@@ -163,15 +167,14 @@ export class TVLabsChannel {
163
167
  throw new SevereServiceError('Could not connect to TV Labs, please check your connection.');
164
168
  }
165
169
  const wait = [0, 1000, 3000, 5000][tries] || 10000;
166
- log.info(`[${tries}/${this.maxReconnectRetries}] Waiting ${wait}ms before re-attempting to connect...`);
170
+ this.log.info(`[${tries}/${this.maxReconnectRetries}] Waiting ${wait}ms before re-attempting to connect...`);
167
171
  return wait;
168
172
  }
169
- logSocketError(event, _transport, _establishedConnections) {
170
- const error = event.error;
171
- const code = error && error.code;
172
- log.error('Socket error:', code || error || event);
173
- }
174
173
  tvlabsSessionLink(sessionId) {
175
174
  return `https://tvlabs.ai/app/sessions/${sessionId}`;
176
175
  }
176
+ static logSocketError(log, event, _transport, _establishedConnections) {
177
+ const error = event.error;
178
+ log.error('Socket error:', error || event);
179
+ }
177
180
  }
package/esm/index.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ import TVLabsService from './service.js';
2
+ export { TVLabsService };
3
+ export default TVLabsService;
4
+ export * from './types.js';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,aAAa,MAAM,cAAc,CAAC;AAEzC,OAAO,EAAE,aAAa,EAAE,CAAC;AACzB,eAAe,aAAa,CAAC;AAC7B,cAAc,YAAY,CAAC"}
@@ -1,3 +1,4 @@
1
1
  import TVLabsService from './service.js';
2
+ export { TVLabsService };
2
3
  export default TVLabsService;
3
4
  export * from './types.js';