@tvlabs/wdio-service 0.1.0 → 0.1.2
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 +13 -6
- package/{dist → cjs}/channel.d.ts +4 -2
- package/cjs/channel.d.ts.map +1 -0
- package/cjs/channel.js +185 -0
- package/cjs/index.js +19 -0
- package/cjs/logger.d.ts +14 -0
- package/cjs/logger.d.ts.map +1 -0
- package/cjs/logger.js +67 -0
- package/cjs/package.json +1 -0
- package/{dist → cjs}/service.d.ts +5 -0
- package/cjs/service.d.ts.map +1 -0
- package/cjs/service.js +80 -0
- package/{dist → cjs}/types.d.ts +2 -0
- package/cjs/types.d.ts.map +1 -0
- package/cjs/types.js +2 -0
- package/esm/channel.d.ts +27 -0
- package/esm/channel.d.ts.map +1 -0
- package/{dist → esm}/channel.js +23 -20
- package/esm/index.d.ts +4 -0
- package/esm/index.d.ts.map +1 -0
- package/esm/logger.d.ts +14 -0
- package/esm/logger.d.ts.map +1 -0
- package/esm/logger.js +63 -0
- package/esm/package.json +1 -0
- package/esm/service.d.ts +20 -0
- package/esm/service.d.ts.map +1 -0
- package/esm/service.js +77 -0
- package/esm/types.d.ts +37 -0
- package/esm/types.d.ts.map +1 -0
- package/package.json +17 -15
- package/src/channel.ts +265 -0
- package/src/index.ts +4 -0
- package/src/logger.ts +77 -0
- package/src/phoenix.d.ts +18 -0
- package/src/service.ts +128 -0
- package/src/types.ts +46 -0
- package/dist/channel.d.ts.map +0 -1
- package/dist/service.d.ts.map +0 -1
- package/dist/service.js +0 -35
- package/dist/types.d.ts.map +0 -1
- /package/{dist → cjs}/index.d.ts +0 -0
- /package/{dist → cjs}/index.d.ts.map +0 -0
- /package/{dist → esm}/index.js +0 -0
- /package/{dist → esm}/types.js +0 -0
package/README.md
CHANGED
|
@@ -14,6 +14,8 @@ The `@tvlabs/wdio-service` package uses a websocket to connect to the TV Labs pl
|
|
|
14
14
|
|
|
15
15
|
The service first 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.
|
|
16
16
|
|
|
17
|
+
Additionally, the service adds a unique request ID for each request made. The service will generate and attach an `x-request-id` header before each request to the TV Labs platform. This can be used to correlate requests in the client side logs to the Appium server logs.
|
|
18
|
+
|
|
17
19
|
## Installation
|
|
18
20
|
|
|
19
21
|
In your WebdriverIO project, run one of the following commands to install:
|
|
@@ -40,12 +42,10 @@ To use this as a WebdriverIO test runner service, include the service in your We
|
|
|
40
42
|
import TVLabsService from '@tvlabs/wdio-service';
|
|
41
43
|
|
|
42
44
|
export const config = {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
// ...
|
|
48
|
-
}
|
|
45
|
+
// ...
|
|
46
|
+
services: [[TVLabsService, { apiKey: process.env.TVLABS_API_KEY }]],
|
|
47
|
+
// ...
|
|
48
|
+
};
|
|
49
49
|
```
|
|
50
50
|
|
|
51
51
|
### WebdriverIO Remote
|
|
@@ -113,3 +113,10 @@ run();
|
|
|
113
113
|
- **Required:** No
|
|
114
114
|
- **Default:** `5`
|
|
115
115
|
- **Description:** Maximum number of attempts to re-connect if the connection to TV Labs is lost.
|
|
116
|
+
|
|
117
|
+
### `attachRequestId`
|
|
118
|
+
|
|
119
|
+
- **Type:** `boolean`
|
|
120
|
+
- **Required:** No
|
|
121
|
+
- **Default:** `true`
|
|
122
|
+
- **Description:** Controls whether or not to attach an `x-request-id` header to each request made to the TV Labs platform.
|
|
@@ -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>;
|
|
@@ -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;IActB,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,cAAc;IAWtB,OAAO,CAAC,iBAAiB;CAG1B"}
|
package/cjs/channel.js
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
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(this.logSocketError);
|
|
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
|
+
logSocketError(event, _transport, _establishedConnections) {
|
|
177
|
+
const error = event.error;
|
|
178
|
+
const code = error && error.code;
|
|
179
|
+
this.log.error('Socket error:', code || error || event);
|
|
180
|
+
}
|
|
181
|
+
tvlabsSessionLink(sessionId) {
|
|
182
|
+
return `https://tvlabs.ai/app/sessions/${sessionId}`;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
exports.TVLabsChannel = TVLabsChannel;
|
package/cjs/index.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
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
|
+
const service_js_1 = require("./service.js");
|
|
18
|
+
exports.default = service_js_1.default;
|
|
19
|
+
__exportStar(require("./types.js"), exports);
|
package/cjs/logger.d.ts
ADDED
|
@@ -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;AAwB3C,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;IAUrB,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,67 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Logger = void 0;
|
|
4
|
+
const chalk_1 = require("chalk");
|
|
5
|
+
const LOG_LEVEL_COLORS = {
|
|
6
|
+
error: chalk_1.default.red,
|
|
7
|
+
warn: chalk_1.default.yellow,
|
|
8
|
+
info: chalk_1.default.cyanBright,
|
|
9
|
+
debug: chalk_1.default.green,
|
|
10
|
+
trace: chalk_1.default.cyan,
|
|
11
|
+
silent: chalk_1.default.gray,
|
|
12
|
+
};
|
|
13
|
+
const LOG_LEVELS = {
|
|
14
|
+
error: 0,
|
|
15
|
+
warn: 1,
|
|
16
|
+
info: 2,
|
|
17
|
+
debug: 3,
|
|
18
|
+
trace: 4,
|
|
19
|
+
silent: 5,
|
|
20
|
+
};
|
|
21
|
+
class Logger {
|
|
22
|
+
name;
|
|
23
|
+
logLevel;
|
|
24
|
+
constructor(name, logLevel = 'info') {
|
|
25
|
+
this.name = name;
|
|
26
|
+
this.logLevel = logLevel;
|
|
27
|
+
}
|
|
28
|
+
shouldLog(level) {
|
|
29
|
+
if (this.logLevel === 'silent') {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
return LOG_LEVELS[level] <= LOG_LEVELS[this.logLevel];
|
|
33
|
+
}
|
|
34
|
+
formatMessage(level, ...args) {
|
|
35
|
+
const timestamp = new Date().toISOString();
|
|
36
|
+
const levelColor = LOG_LEVEL_COLORS[level];
|
|
37
|
+
return `${chalk_1.default.gray(timestamp)} ${levelColor(level.toUpperCase())} ${chalk_1.default.white(this.name)}: ${args
|
|
38
|
+
.map((arg) => typeof arg === 'object' ? JSON.stringify(arg) : String(arg))
|
|
39
|
+
.join(' ')}`;
|
|
40
|
+
}
|
|
41
|
+
debug(...args) {
|
|
42
|
+
if (this.shouldLog('debug')) {
|
|
43
|
+
console.log(this.formatMessage('debug', ...args));
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
info(...args) {
|
|
47
|
+
if (this.shouldLog('info')) {
|
|
48
|
+
console.log(this.formatMessage('info', ...args));
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
warn(...args) {
|
|
52
|
+
if (this.shouldLog('warn')) {
|
|
53
|
+
console.warn(this.formatMessage('warn', ...args));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
error(...args) {
|
|
57
|
+
if (this.shouldLog('error')) {
|
|
58
|
+
console.error(this.formatMessage('error', ...args));
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
trace(...args) {
|
|
62
|
+
if (this.shouldLog('trace')) {
|
|
63
|
+
console.trace(this.formatMessage('trace', ...args));
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
exports.Logger = Logger;
|
package/cjs/package.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"type": "commonjs"}
|
|
@@ -4,12 +4,17 @@ 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>;
|
|
11
|
+
private setupRequestId;
|
|
12
|
+
private setRequestHeader;
|
|
10
13
|
private endpoint;
|
|
11
14
|
private retries;
|
|
12
15
|
private apiKey;
|
|
16
|
+
private logLevel;
|
|
17
|
+
private attachRequestId;
|
|
13
18
|
private reconnectRetries;
|
|
14
19
|
}
|
|
15
20
|
//# sourceMappingURL=service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,QAAQ,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AACnE,OAAO,KAAK,EACV,kBAAkB,EAClB,oBAAoB,EAErB,MAAM,YAAY,CAAC;AAEpB,MAAM,CAAC,OAAO,OAAO,aAAc,YAAW,QAAQ,CAAC,eAAe;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,80 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const webdriverio_1 = require("webdriverio");
|
|
4
|
+
const crypto = require("crypto");
|
|
5
|
+
const chalk_1 = require("chalk");
|
|
6
|
+
const channel_js_1 = require("./channel.js");
|
|
7
|
+
const logger_js_1 = require("./logger.js");
|
|
8
|
+
class TVLabsService {
|
|
9
|
+
_options;
|
|
10
|
+
_capabilities;
|
|
11
|
+
_config;
|
|
12
|
+
log;
|
|
13
|
+
constructor(_options, _capabilities, _config) {
|
|
14
|
+
this._options = _options;
|
|
15
|
+
this._capabilities = _capabilities;
|
|
16
|
+
this._config = _config;
|
|
17
|
+
this.log = new logger_js_1.Logger('@tvlabs/wdio-server', this._config.logLevel);
|
|
18
|
+
if (this.attachRequestId()) {
|
|
19
|
+
this.setupRequestId();
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
onPrepare(_config, param) {
|
|
23
|
+
if (!Array.isArray(param)) {
|
|
24
|
+
throw new webdriverio_1.SevereServiceError('Multi-remote capabilities are not implemented. Contact TV Labs support if you are interested in this feature.');
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
async beforeSession(_config, capabilities, _specs, _cid) {
|
|
28
|
+
const channel = new channel_js_1.TVLabsChannel(this.endpoint(), this.reconnectRetries(), this.apiKey(), this.logLevel());
|
|
29
|
+
await channel.connect();
|
|
30
|
+
capabilities['tvlabs:session_id'] = await channel.newSession(capabilities, this.retries());
|
|
31
|
+
await channel.disconnect();
|
|
32
|
+
}
|
|
33
|
+
setupRequestId() {
|
|
34
|
+
const originalTransformRequest = this._config.transformRequest;
|
|
35
|
+
this._config.transformRequest = (requestOptions) => {
|
|
36
|
+
const requestId = crypto.randomUUID();
|
|
37
|
+
const originalRequestOptions = typeof originalTransformRequest === 'function'
|
|
38
|
+
? originalTransformRequest(requestOptions)
|
|
39
|
+
: requestOptions;
|
|
40
|
+
if (typeof originalRequestOptions.headers === 'undefined') {
|
|
41
|
+
originalRequestOptions.headers = {};
|
|
42
|
+
}
|
|
43
|
+
this.setRequestHeader(originalRequestOptions.headers, 'x-request-id', requestId);
|
|
44
|
+
this.log.info(chalk_1.default.blue('ATTACHED REQUEST ID'), requestId);
|
|
45
|
+
return originalRequestOptions;
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
setRequestHeader(headers, header, value) {
|
|
49
|
+
if (headers instanceof Headers) {
|
|
50
|
+
headers.set(header, value);
|
|
51
|
+
}
|
|
52
|
+
else if (typeof headers === 'object') {
|
|
53
|
+
if (Array.isArray(headers)) {
|
|
54
|
+
headers.push([header, value]);
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
headers[header] = value;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
endpoint() {
|
|
62
|
+
return this._options.endpoint ?? 'wss://tvlabs.ai/appium';
|
|
63
|
+
}
|
|
64
|
+
retries() {
|
|
65
|
+
return this._options.retries ?? 3;
|
|
66
|
+
}
|
|
67
|
+
apiKey() {
|
|
68
|
+
return this._options.apiKey;
|
|
69
|
+
}
|
|
70
|
+
logLevel() {
|
|
71
|
+
return this._config.logLevel ?? 'info';
|
|
72
|
+
}
|
|
73
|
+
attachRequestId() {
|
|
74
|
+
return this._options.attachRequestId ?? true;
|
|
75
|
+
}
|
|
76
|
+
reconnectRetries() {
|
|
77
|
+
return this._options.reconnectRetries ?? 5;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
exports.default = TVLabsService;
|
package/{dist → cjs}/types.d.ts
RENAMED
|
@@ -1,9 +1,11 @@
|
|
|
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;
|
|
5
6
|
retries?: number;
|
|
6
7
|
reconnectRetries?: number;
|
|
8
|
+
attachRequestId?: boolean;
|
|
7
9
|
};
|
|
8
10
|
export type TVLabsCapabilities = Capabilities.RequestedStandaloneCapabilities & {
|
|
9
11
|
'tvlabs:session_id'?: 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
package/esm/channel.d.ts
ADDED
|
@@ -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 logSocketError;
|
|
25
|
+
private tvlabsSessionLink;
|
|
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;IActB,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,cAAc;IAWtB,OAAO,CAAC,iBAAiB;CAG1B"}
|
package/{dist → esm}/channel.js
RENAMED
|
@@ -1,15 +1,16 @@
|
|
|
1
|
+
import { WebSocket } from 'ws';
|
|
1
2
|
import { Socket } from 'phoenix';
|
|
2
|
-
import WebSocket from 'ws';
|
|
3
|
-
import logger from '@wdio/logger';
|
|
4
3
|
import { SevereServiceError } from 'webdriverio';
|
|
5
|
-
|
|
4
|
+
import { Logger } from './logger.js';
|
|
6
5
|
export class TVLabsChannel {
|
|
7
6
|
endpoint;
|
|
8
7
|
maxReconnectRetries;
|
|
9
8
|
key;
|
|
9
|
+
logLevel;
|
|
10
10
|
socket;
|
|
11
11
|
lobbyTopic;
|
|
12
12
|
requestTopic;
|
|
13
|
+
log;
|
|
13
14
|
events = {
|
|
14
15
|
SESSION_READY: 'session:ready',
|
|
15
16
|
SESSION_FAILED: 'session:failed',
|
|
@@ -18,10 +19,12 @@ export class TVLabsChannel {
|
|
|
18
19
|
REQUEST_FILLED: 'request:filled',
|
|
19
20
|
REQUEST_MATCHING: 'request:matching',
|
|
20
21
|
};
|
|
21
|
-
constructor(endpoint, maxReconnectRetries, key) {
|
|
22
|
+
constructor(endpoint, maxReconnectRetries, key, logLevel) {
|
|
22
23
|
this.endpoint = endpoint;
|
|
23
24
|
this.maxReconnectRetries = maxReconnectRetries;
|
|
24
25
|
this.key = key;
|
|
26
|
+
this.logLevel = logLevel;
|
|
27
|
+
this.log = new Logger('@tvlabs/wdio-channel', this.logLevel);
|
|
25
28
|
this.socket = new Socket(this.endpoint, {
|
|
26
29
|
transport: WebSocket,
|
|
27
30
|
params: this.params(),
|
|
@@ -39,13 +42,13 @@ export class TVLabsChannel {
|
|
|
39
42
|
}
|
|
40
43
|
async connect() {
|
|
41
44
|
try {
|
|
42
|
-
log.debug('Connecting to TV Labs...');
|
|
45
|
+
this.log.debug('Connecting to TV Labs...');
|
|
43
46
|
this.socket.connect();
|
|
44
47
|
await this.join(this.lobbyTopic);
|
|
45
|
-
log.debug('Connected to TV Labs!');
|
|
48
|
+
this.log.debug('Connected to TV Labs!');
|
|
46
49
|
}
|
|
47
50
|
catch (error) {
|
|
48
|
-
log.error('Error connecting to TV Labs:', error);
|
|
51
|
+
this.log.error('Error connecting to TV Labs:', error);
|
|
49
52
|
throw new SevereServiceError('Could not connect to TV Labs, please check your connection.');
|
|
50
53
|
}
|
|
51
54
|
}
|
|
@@ -61,7 +64,7 @@ export class TVLabsChannel {
|
|
|
61
64
|
}
|
|
62
65
|
async handleRetry(capabilities, maxRetries, retry) {
|
|
63
66
|
if (retry < maxRetries) {
|
|
64
|
-
log.warn(`Could not create a session, retrying (${retry + 1}/${maxRetries})`);
|
|
67
|
+
this.log.warn(`Could not create a session, retrying (${retry + 1}/${maxRetries})`);
|
|
65
68
|
return this.newSession(capabilities, maxRetries, retry + 1);
|
|
66
69
|
}
|
|
67
70
|
else {
|
|
@@ -74,26 +77,26 @@ export class TVLabsChannel {
|
|
|
74
77
|
this.requestTopic = this.socket.channel(`requests:${requestId}`);
|
|
75
78
|
const eventHandlers = {
|
|
76
79
|
[this.events.REQUEST_MATCHING]: ({ request_id }) => {
|
|
77
|
-
log.info(`Session request ${request_id} matching...`);
|
|
80
|
+
this.log.info(`Session request ${request_id} matching...`);
|
|
78
81
|
},
|
|
79
82
|
[this.events.REQUEST_FILLED]: ({ session_id, request_id }) => {
|
|
80
|
-
log.info(`Session request ${request_id} filled: ${this.tvlabsSessionLink(session_id)}`);
|
|
81
|
-
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...');
|
|
82
85
|
},
|
|
83
86
|
[this.events.SESSION_FAILED]: ({ session_id, reason }) => {
|
|
84
|
-
log.error(`Session ${session_id} failed, reason: ${reason}`);
|
|
87
|
+
this.log.error(`Session ${session_id} failed, reason: ${reason}`);
|
|
85
88
|
rej(reason);
|
|
86
89
|
},
|
|
87
90
|
[this.events.REQUEST_CANCELED]: ({ request_id, reason }) => {
|
|
88
|
-
log.info(`Session request ${request_id} canceled, reason: ${reason}`);
|
|
91
|
+
this.log.info(`Session request ${request_id} canceled, reason: ${reason}`);
|
|
89
92
|
rej(reason);
|
|
90
93
|
},
|
|
91
94
|
[this.events.REQUEST_FAILED]: ({ request_id, reason }) => {
|
|
92
|
-
log.info(`Session request ${request_id} failed, reason: ${reason}`);
|
|
95
|
+
this.log.info(`Session request ${request_id} failed, reason: ${reason}`);
|
|
93
96
|
rej(reason);
|
|
94
97
|
},
|
|
95
98
|
[this.events.SESSION_READY]: ({ session_id }) => {
|
|
96
|
-
log.info(`Session ${session_id} ready!`);
|
|
99
|
+
this.log.info(`Session ${session_id} ready!`);
|
|
97
100
|
res(session_id);
|
|
98
101
|
},
|
|
99
102
|
};
|
|
@@ -113,14 +116,14 @@ export class TVLabsChannel {
|
|
|
113
116
|
this.requestTopic = undefined;
|
|
114
117
|
}
|
|
115
118
|
async requestSession(capabilities) {
|
|
116
|
-
log.info('Requesting TV Labs session');
|
|
119
|
+
this.log.info('Requesting TV Labs session');
|
|
117
120
|
try {
|
|
118
121
|
const response = await this.push(this.lobbyTopic, 'requests:create', { capabilities });
|
|
119
|
-
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...`);
|
|
120
123
|
return response.request_id;
|
|
121
124
|
}
|
|
122
125
|
catch (error) {
|
|
123
|
-
log.error('Error requesting session:', error);
|
|
126
|
+
this.log.error('Error requesting session:', error);
|
|
124
127
|
throw error;
|
|
125
128
|
}
|
|
126
129
|
}
|
|
@@ -164,13 +167,13 @@ export class TVLabsChannel {
|
|
|
164
167
|
throw new SevereServiceError('Could not connect to TV Labs, please check your connection.');
|
|
165
168
|
}
|
|
166
169
|
const wait = [0, 1000, 3000, 5000][tries] || 10000;
|
|
167
|
-
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...`);
|
|
168
171
|
return wait;
|
|
169
172
|
}
|
|
170
173
|
logSocketError(event, _transport, _establishedConnections) {
|
|
171
174
|
const error = event.error;
|
|
172
175
|
const code = error && error.code;
|
|
173
|
-
log.error('Socket error:', code || error || event);
|
|
176
|
+
this.log.error('Socket error:', code || error || event);
|
|
174
177
|
}
|
|
175
178
|
tvlabsSessionLink(sessionId) {
|
|
176
179
|
return `https://tvlabs.ai/app/sessions/${sessionId}`;
|
package/esm/index.d.ts
ADDED
|
@@ -0,0 +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"}
|
package/esm/logger.d.ts
ADDED
|
@@ -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
|