@tvlabs/wdio-service 0.1.3 → 0.1.4

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/cjs/channel.js DELETED
@@ -1,184 +0,0 @@
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/logger.js DELETED
@@ -1,57 +0,0 @@
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;
package/cjs/service.js DELETED
@@ -1,79 +0,0 @@
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;
package/cjs/types.js DELETED
@@ -1,2 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
package/esm/channel.js DELETED
@@ -1,180 +0,0 @@
1
- import { WebSocket } from 'ws';
2
- import { Socket } from 'phoenix';
3
- import { SevereServiceError } from 'webdriverio';
4
- import { Logger } from './logger.js';
5
- export class TVLabsChannel {
6
- endpoint;
7
- maxReconnectRetries;
8
- key;
9
- logLevel;
10
- socket;
11
- lobbyTopic;
12
- requestTopic;
13
- log;
14
- events = {
15
- SESSION_READY: 'session:ready',
16
- SESSION_FAILED: 'session:failed',
17
- REQUEST_CANCELED: 'request:canceled',
18
- REQUEST_FAILED: 'request:failed',
19
- REQUEST_FILLED: 'request:filled',
20
- REQUEST_MATCHING: 'request:matching',
21
- };
22
- constructor(endpoint, maxReconnectRetries, key, logLevel) {
23
- this.endpoint = endpoint;
24
- this.maxReconnectRetries = maxReconnectRetries;
25
- this.key = key;
26
- this.logLevel = logLevel;
27
- this.log = new Logger('@tvlabs/wdio-channel', this.logLevel);
28
- this.socket = new Socket(this.endpoint, {
29
- transport: WebSocket,
30
- params: this.params(),
31
- reconnectAfterMs: this.reconnectAfterMs.bind(this),
32
- });
33
- this.socket.onError((...args) => TVLabsChannel.logSocketError(this.log, ...args));
34
- this.lobbyTopic = this.socket.channel('requests:lobby');
35
- }
36
- async disconnect() {
37
- return new Promise((res, _rej) => {
38
- this.lobbyTopic.leave();
39
- this.requestTopic?.leave();
40
- this.socket.disconnect(() => res());
41
- });
42
- }
43
- async connect() {
44
- try {
45
- this.log.debug('Connecting to TV Labs...');
46
- this.socket.connect();
47
- await this.join(this.lobbyTopic);
48
- this.log.debug('Connected to TV Labs!');
49
- }
50
- catch (error) {
51
- this.log.error('Error connecting to TV Labs:', error);
52
- throw new SevereServiceError('Could not connect to TV Labs, please check your connection.');
53
- }
54
- }
55
- async newSession(capabilities, maxRetries, retry = 0) {
56
- try {
57
- const requestId = await this.requestSession(capabilities);
58
- const sessionId = await this.observeRequest(requestId);
59
- return sessionId;
60
- }
61
- catch {
62
- return this.handleRetry(capabilities, maxRetries, retry);
63
- }
64
- }
65
- async handleRetry(capabilities, maxRetries, retry) {
66
- if (retry < maxRetries) {
67
- this.log.warn(`Could not create a session, retrying (${retry + 1}/${maxRetries})`);
68
- return this.newSession(capabilities, maxRetries, retry + 1);
69
- }
70
- else {
71
- throw new SevereServiceError(`Could not create a session after ${maxRetries} attempts.`);
72
- }
73
- }
74
- async observeRequest(requestId) {
75
- const cleanup = () => this.unobserveRequest();
76
- return new Promise((res, rej) => {
77
- this.requestTopic = this.socket.channel(`requests:${requestId}`);
78
- const eventHandlers = {
79
- [this.events.REQUEST_MATCHING]: ({ request_id }) => {
80
- this.log.info(`Session request ${request_id} matching...`);
81
- },
82
- [this.events.REQUEST_FILLED]: ({ session_id, request_id }) => {
83
- this.log.info(`Session request ${request_id} filled: ${this.tvlabsSessionLink(session_id)}`);
84
- this.log.info('Waiting for device to be ready...');
85
- },
86
- [this.events.SESSION_FAILED]: ({ session_id, reason }) => {
87
- this.log.error(`Session ${session_id} failed, reason: ${reason}`);
88
- rej(reason);
89
- },
90
- [this.events.REQUEST_CANCELED]: ({ request_id, reason }) => {
91
- this.log.info(`Session request ${request_id} canceled, reason: ${reason}`);
92
- rej(reason);
93
- },
94
- [this.events.REQUEST_FAILED]: ({ request_id, reason }) => {
95
- this.log.info(`Session request ${request_id} failed, reason: ${reason}`);
96
- rej(reason);
97
- },
98
- [this.events.SESSION_READY]: ({ session_id }) => {
99
- this.log.info(`Session ${session_id} ready!`);
100
- res(session_id);
101
- },
102
- };
103
- Object.entries(eventHandlers).forEach(([event, handler]) => {
104
- this.requestTopic?.on(event, handler);
105
- });
106
- this.join(this.requestTopic).catch((err) => {
107
- rej(err);
108
- });
109
- }).finally(cleanup);
110
- }
111
- unobserveRequest() {
112
- Object.values(this.events).forEach((event) => {
113
- this.requestTopic?.off(event);
114
- });
115
- this.requestTopic?.leave();
116
- this.requestTopic = undefined;
117
- }
118
- async requestSession(capabilities) {
119
- this.log.info('Requesting TV Labs session');
120
- try {
121
- const response = await this.push(this.lobbyTopic, 'requests:create', { capabilities });
122
- this.log.info(`Received session request ID: ${response.request_id}. Waiting for a match...`);
123
- return response.request_id;
124
- }
125
- catch (error) {
126
- this.log.error('Error requesting session:', error);
127
- throw error;
128
- }
129
- }
130
- async join(topic) {
131
- return new Promise((res, rej) => {
132
- topic
133
- .join()
134
- .receive('ok', (_resp) => {
135
- res();
136
- })
137
- .receive('error', ({ response }) => {
138
- rej('Failed to join topic: ' + response);
139
- })
140
- .receive('timeout', () => {
141
- rej('timeout');
142
- });
143
- });
144
- }
145
- async push(topic, event, payload) {
146
- return new Promise((res, rej) => {
147
- topic
148
- .push(event, payload)
149
- .receive('ok', (msg) => {
150
- res(msg);
151
- })
152
- .receive('error', (reason) => {
153
- rej(reason);
154
- })
155
- .receive('timeout', () => {
156
- rej('timeout');
157
- });
158
- });
159
- }
160
- params() {
161
- return {
162
- api_key: this.key,
163
- };
164
- }
165
- reconnectAfterMs(tries) {
166
- if (tries > this.maxReconnectRetries) {
167
- throw new SevereServiceError('Could not connect to TV Labs, please check your connection.');
168
- }
169
- const wait = [0, 1000, 3000, 5000][tries] || 10000;
170
- this.log.info(`[${tries}/${this.maxReconnectRetries}] Waiting ${wait}ms before re-attempting to connect...`);
171
- return wait;
172
- }
173
- tvlabsSessionLink(sessionId) {
174
- return `https://tvlabs.ai/app/sessions/${sessionId}`;
175
- }
176
- static logSocketError(log, event, _transport, _establishedConnections) {
177
- const error = event.error;
178
- log.error('Socket error:', error || event);
179
- }
180
- }
package/esm/logger.js DELETED
@@ -1,53 +0,0 @@
1
- const LOG_LEVELS = {
2
- error: 0,
3
- warn: 1,
4
- info: 2,
5
- debug: 3,
6
- trace: 4,
7
- silent: 5,
8
- };
9
- export class Logger {
10
- name;
11
- logLevel;
12
- constructor(name, logLevel = 'info') {
13
- this.name = name;
14
- this.logLevel = logLevel;
15
- }
16
- shouldLog(level) {
17
- if (this.logLevel === 'silent') {
18
- return false;
19
- }
20
- return LOG_LEVELS[level] <= LOG_LEVELS[this.logLevel];
21
- }
22
- formatMessage(level, ...args) {
23
- const timestamp = new Date().toISOString();
24
- return `${timestamp} ${level.toUpperCase()} ${this.name}: ${args
25
- .map((arg) => typeof arg === 'object' ? JSON.stringify(arg) : String(arg))
26
- .join(' ')}`;
27
- }
28
- debug(...args) {
29
- if (this.shouldLog('debug')) {
30
- console.log(this.formatMessage('debug', ...args));
31
- }
32
- }
33
- info(...args) {
34
- if (this.shouldLog('info')) {
35
- console.log(this.formatMessage('info', ...args));
36
- }
37
- }
38
- warn(...args) {
39
- if (this.shouldLog('warn')) {
40
- console.warn(this.formatMessage('warn', ...args));
41
- }
42
- }
43
- error(...args) {
44
- if (this.shouldLog('error')) {
45
- console.error(this.formatMessage('error', ...args));
46
- }
47
- }
48
- trace(...args) {
49
- if (this.shouldLog('trace')) {
50
- console.trace(this.formatMessage('trace', ...args));
51
- }
52
- }
53
- }
package/esm/service.js DELETED
@@ -1,76 +0,0 @@
1
- import { SevereServiceError } from 'webdriverio';
2
- import * as crypto from 'crypto';
3
- import { TVLabsChannel } from './channel.js';
4
- import { Logger } from './logger.js';
5
- export default class TVLabsService {
6
- _options;
7
- _capabilities;
8
- _config;
9
- log;
10
- constructor(_options, _capabilities, _config) {
11
- this._options = _options;
12
- this._capabilities = _capabilities;
13
- this._config = _config;
14
- this.log = new Logger('@tvlabs/wdio-server', this._config.logLevel);
15
- if (this.attachRequestId()) {
16
- this.setupRequestId();
17
- }
18
- }
19
- onPrepare(_config, param) {
20
- if (!Array.isArray(param)) {
21
- throw new SevereServiceError('Multi-remote capabilities are not implemented. Contact TV Labs support if you are interested in this feature.');
22
- }
23
- }
24
- async beforeSession(_config, capabilities, _specs, _cid) {
25
- const channel = new TVLabsChannel(this.endpoint(), this.reconnectRetries(), this.apiKey(), this.logLevel());
26
- await channel.connect();
27
- capabilities['tvlabs:session_id'] = await channel.newSession(capabilities, this.retries());
28
- await channel.disconnect();
29
- }
30
- setupRequestId() {
31
- const originalTransformRequest = this._config.transformRequest;
32
- this._config.transformRequest = (requestOptions) => {
33
- const requestId = crypto.randomUUID();
34
- const originalRequestOptions = typeof originalTransformRequest === 'function'
35
- ? originalTransformRequest(requestOptions)
36
- : requestOptions;
37
- if (typeof originalRequestOptions.headers === 'undefined') {
38
- originalRequestOptions.headers = {};
39
- }
40
- this.setRequestHeader(originalRequestOptions.headers, 'x-request-id', requestId);
41
- this.log.info('ATTACHED REQUEST ID', requestId);
42
- return originalRequestOptions;
43
- };
44
- }
45
- setRequestHeader(headers, header, value) {
46
- if (headers instanceof Headers) {
47
- headers.set(header, value);
48
- }
49
- else if (typeof headers === 'object') {
50
- if (Array.isArray(headers)) {
51
- headers.push([header, value]);
52
- }
53
- else {
54
- headers[header] = value;
55
- }
56
- }
57
- }
58
- endpoint() {
59
- return this._options.endpoint ?? 'wss://tvlabs.ai/appium';
60
- }
61
- retries() {
62
- return this._options.retries ?? 3;
63
- }
64
- apiKey() {
65
- return this._options.apiKey;
66
- }
67
- logLevel() {
68
- return this._config.logLevel ?? 'info';
69
- }
70
- attachRequestId() {
71
- return this._options.attachRequestId ?? true;
72
- }
73
- reconnectRetries() {
74
- return this._options.reconnectRetries ?? 5;
75
- }
76
- }
package/esm/types.js DELETED
@@ -1 +0,0 @@
1
- export {};