@tvlabs/wdio-service 0.1.4 → 0.1.6
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 +31 -17
- package/cjs/channels/base.d.ts +20 -0
- package/cjs/channels/base.d.ts.map +1 -0
- package/cjs/channels/build.d.ts +16 -0
- package/cjs/channels/build.d.ts.map +1 -0
- package/cjs/{channel.d.ts → channels/session.d.ts} +4 -14
- package/cjs/channels/session.d.ts.map +1 -0
- package/cjs/index.js +252 -72
- package/cjs/logger.d.ts +1 -0
- package/cjs/logger.d.ts.map +1 -1
- package/cjs/service.d.ts +4 -1
- package/cjs/service.d.ts.map +1 -1
- package/cjs/types.d.ts +17 -1
- package/cjs/types.d.ts.map +1 -1
- package/esm/channels/base.d.ts +20 -0
- package/esm/channels/base.d.ts.map +1 -0
- package/esm/channels/build.d.ts +16 -0
- package/esm/channels/build.d.ts.map +1 -0
- package/esm/{channel.d.ts → channels/session.d.ts} +4 -14
- package/esm/channels/session.d.ts.map +1 -0
- package/esm/index.js +249 -72
- package/esm/logger.d.ts +1 -0
- package/esm/logger.d.ts.map +1 -1
- package/esm/service.d.ts +4 -1
- package/esm/service.d.ts.map +1 -1
- package/esm/types.d.ts +17 -1
- package/esm/types.d.ts.map +1 -1
- package/package.json +9 -8
- package/src/channels/base.ts +110 -0
- package/src/channels/build.ts +174 -0
- package/src/{channel.ts → channels/session.ts} +18 -102
- package/src/logger.ts +60 -3
- package/src/service.ts +41 -8
- package/src/types.ts +20 -1
- package/cjs/channel.d.ts.map +0 -1
- package/esm/channel.d.ts.map +0 -1
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Socket, type Channel } from 'phoenix';
|
|
2
|
+
import { Logger } from '../logger.js';
|
|
3
|
+
import type { LogLevel } from '../types.js';
|
|
4
|
+
export declare abstract class BaseChannel {
|
|
5
|
+
protected endpoint: string;
|
|
6
|
+
protected maxReconnectRetries: number;
|
|
7
|
+
protected key: string;
|
|
8
|
+
protected logLevel: LogLevel;
|
|
9
|
+
protected socket: Socket;
|
|
10
|
+
protected log: Logger;
|
|
11
|
+
constructor(endpoint: string, maxReconnectRetries: number, key: string, logLevel: LogLevel | undefined, loggerName: string);
|
|
12
|
+
abstract connect(): Promise<void>;
|
|
13
|
+
abstract disconnect(): Promise<void>;
|
|
14
|
+
protected join(topic: Channel): Promise<void>;
|
|
15
|
+
protected push<T>(topic: Channel, event: string, payload: object): Promise<T>;
|
|
16
|
+
private params;
|
|
17
|
+
private reconnectAfterMs;
|
|
18
|
+
private static logSocketError;
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=base.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base.d.ts","sourceRoot":"","sources":["../../src/channels/base.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,KAAK,OAAO,EAAE,MAAM,SAAS,CAAC;AAE/C,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAGtC,OAAO,KAAK,EAAsB,QAAQ,EAAE,MAAM,aAAa,CAAC;AAGhE,8BAAsB,WAAW;IAK7B,SAAS,CAAC,QAAQ,EAAE,MAAM;IAC1B,SAAS,CAAC,mBAAmB,EAAE,MAAM;IACrC,SAAS,CAAC,GAAG,EAAE,MAAM;IACrB,SAAS,CAAC,QAAQ,EAAE,QAAQ;IAP9B,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC;IACzB,SAAS,CAAC,GAAG,EAAE,MAAM,CAAC;gBAGV,QAAQ,EAAE,MAAM,EAChB,mBAAmB,EAAE,MAAM,EAC3B,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,QAAQ,YAAS,EACrC,UAAU,EAAE,MAAM;IAepB,QAAQ,CAAC,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IACjC,QAAQ,CAAC,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;cAEpB,IAAI,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;cAgBnC,IAAI,CAAC,CAAC,EACpB,KAAK,EAAE,OAAO,EACd,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,CAAC,CAAC;IAgBb,OAAO,CAAC,MAAM;IAWd,OAAO,CAAC,gBAAgB;IAgBxB,OAAO,CAAC,MAAM,CAAC,cAAc;CAU9B"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { BaseChannel } from './base.js';
|
|
2
|
+
import type { LogLevel } from '../types.js';
|
|
3
|
+
export declare class BuildChannel extends BaseChannel {
|
|
4
|
+
private lobbyTopic;
|
|
5
|
+
constructor(endpoint: string, maxReconnectRetries: number, key: string, logLevel?: LogLevel);
|
|
6
|
+
disconnect(): Promise<void>;
|
|
7
|
+
connect(): Promise<void>;
|
|
8
|
+
uploadBuild(buildPath: string, appSlug?: string): Promise<string>;
|
|
9
|
+
private requestUploadUrl;
|
|
10
|
+
private uploadToUrl;
|
|
11
|
+
private extractBuildInfo;
|
|
12
|
+
private getFileMetadata;
|
|
13
|
+
private computeSha256;
|
|
14
|
+
private detectMimeType;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=build.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../src/channels/build.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAExC,OAAO,KAAK,EAIV,QAAQ,EACT,MAAM,aAAa,CAAC;AAErB,qBAAa,YAAa,SAAQ,WAAW;IAC3C,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,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;YAoBzD,gBAAgB;YAgBhB,WAAW;YA8BX,gBAAgB;YAehB,eAAe;YAWf,aAAa;IAW3B,OAAO,CAAC,cAAc;CAYvB"}
|
|
@@ -1,13 +1,8 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
private maxReconnectRetries;
|
|
5
|
-
private key;
|
|
6
|
-
private logLevel;
|
|
7
|
-
private socket;
|
|
1
|
+
import { BaseChannel } from './base.js';
|
|
2
|
+
import type { TVLabsCapabilities, LogLevel } from '../types.js';
|
|
3
|
+
export declare class SessionChannel extends BaseChannel {
|
|
8
4
|
private lobbyTopic;
|
|
9
5
|
private requestTopic?;
|
|
10
|
-
private log;
|
|
11
6
|
private readonly events;
|
|
12
7
|
constructor(endpoint: string, maxReconnectRetries: number, key: string, logLevel?: LogLevel);
|
|
13
8
|
disconnect(): Promise<void>;
|
|
@@ -17,11 +12,6 @@ export declare class TVLabsChannel {
|
|
|
17
12
|
private observeRequest;
|
|
18
13
|
private unobserveRequest;
|
|
19
14
|
private requestSession;
|
|
20
|
-
private join;
|
|
21
|
-
private push;
|
|
22
|
-
private params;
|
|
23
|
-
private reconnectAfterMs;
|
|
24
15
|
private tvlabsSessionLink;
|
|
25
|
-
private static logSocketError;
|
|
26
16
|
}
|
|
27
|
-
//# sourceMappingURL=
|
|
17
|
+
//# sourceMappingURL=session.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/channels/session.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAExC,OAAO,KAAK,EACV,kBAAkB,EAGlB,QAAQ,EACT,MAAM,aAAa,CAAC;AAErB,qBAAa,cAAe,SAAQ,WAAW;IAC7C,OAAO,CAAC,UAAU,CAAU;IAC5B,OAAO,CAAC,YAAY,CAAC,CAAU;IAE/B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAOZ;gBAGT,QAAQ,EAAE,MAAM,EAChB,mBAAmB,EAAE,MAAM,EAC3B,GAAG,EAAE,MAAM,EACX,QAAQ,GAAE,QAAiB;IAYvB,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;IAuB5B,OAAO,CAAC,iBAAiB;CAG1B"}
|
package/esm/index.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { SevereServiceError } from 'webdriverio';
|
|
2
|
-
import * as crypto from 'crypto';
|
|
2
|
+
import * as crypto$1 from 'crypto';
|
|
3
3
|
import { WebSocket } from 'ws';
|
|
4
4
|
import { Socket } from 'phoenix';
|
|
5
|
+
import * as fs from 'node:fs';
|
|
6
|
+
import * as path from 'node:path';
|
|
7
|
+
import * as crypto from 'node:crypto';
|
|
5
8
|
|
|
6
9
|
const LOG_LEVELS = {
|
|
7
10
|
error: 0,
|
|
@@ -27,9 +30,58 @@ class Logger {
|
|
|
27
30
|
formatMessage(level, ...args) {
|
|
28
31
|
const timestamp = new Date().toISOString();
|
|
29
32
|
return `${timestamp} ${level.toUpperCase()} ${this.name}: ${args
|
|
30
|
-
.map((arg) =>
|
|
33
|
+
.map((arg) => this.serializeArg(arg))
|
|
31
34
|
.join(' ')}`;
|
|
32
35
|
}
|
|
36
|
+
serializeArg(arg) {
|
|
37
|
+
if (typeof arg === 'string' ||
|
|
38
|
+
typeof arg === 'number' ||
|
|
39
|
+
typeof arg === 'boolean') {
|
|
40
|
+
return String(arg);
|
|
41
|
+
}
|
|
42
|
+
if (arg === null || arg === undefined) {
|
|
43
|
+
return String(arg);
|
|
44
|
+
}
|
|
45
|
+
if (arg instanceof Error) {
|
|
46
|
+
return arg.stack || `${arg.name}: ${arg.message}`;
|
|
47
|
+
}
|
|
48
|
+
if (typeof arg === 'object') {
|
|
49
|
+
try {
|
|
50
|
+
const stringified = JSON.stringify(arg, (key, value) => {
|
|
51
|
+
if (value instanceof Error) {
|
|
52
|
+
return `${value.name}: ${value.message}`;
|
|
53
|
+
}
|
|
54
|
+
return value;
|
|
55
|
+
});
|
|
56
|
+
if (stringified === '{}') {
|
|
57
|
+
const keys = Object.getOwnPropertyNames(arg);
|
|
58
|
+
if (keys.length > 0) {
|
|
59
|
+
const props = {};
|
|
60
|
+
keys.forEach((key) => {
|
|
61
|
+
try {
|
|
62
|
+
const value = arg[key];
|
|
63
|
+
if (value instanceof Error) {
|
|
64
|
+
props[key] = `${value.name}: ${value.message}`;
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
props[key] = value;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
props[key] = '[unable to access]';
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
return JSON.stringify(props);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return stringified;
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
return String(arg);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return String(arg);
|
|
84
|
+
}
|
|
33
85
|
debug(...args) {
|
|
34
86
|
if (this.shouldLog('debug')) {
|
|
35
87
|
console.log(this.formatMessage('debug', ...args));
|
|
@@ -58,7 +110,7 @@ class Logger {
|
|
|
58
110
|
}
|
|
59
111
|
|
|
60
112
|
var name = "@tvlabs/wdio-service";
|
|
61
|
-
var version = "0.1.
|
|
113
|
+
var version = "0.1.6";
|
|
62
114
|
var packageJson = {
|
|
63
115
|
name: name,
|
|
64
116
|
version: version};
|
|
@@ -76,15 +128,81 @@ function getServiceName() {
|
|
|
76
128
|
return packageJson.name;
|
|
77
129
|
}
|
|
78
130
|
|
|
79
|
-
class
|
|
131
|
+
class BaseChannel {
|
|
80
132
|
endpoint;
|
|
81
133
|
maxReconnectRetries;
|
|
82
134
|
key;
|
|
83
135
|
logLevel;
|
|
84
136
|
socket;
|
|
137
|
+
log;
|
|
138
|
+
constructor(endpoint, maxReconnectRetries, key, logLevel = 'info', loggerName) {
|
|
139
|
+
this.endpoint = endpoint;
|
|
140
|
+
this.maxReconnectRetries = maxReconnectRetries;
|
|
141
|
+
this.key = key;
|
|
142
|
+
this.logLevel = logLevel;
|
|
143
|
+
this.log = new Logger(loggerName, this.logLevel);
|
|
144
|
+
this.socket = new Socket(this.endpoint, {
|
|
145
|
+
transport: WebSocket,
|
|
146
|
+
params: this.params(),
|
|
147
|
+
reconnectAfterMs: this.reconnectAfterMs.bind(this),
|
|
148
|
+
});
|
|
149
|
+
this.socket.onError((...args) => BaseChannel.logSocketError(this.log, ...args));
|
|
150
|
+
}
|
|
151
|
+
async join(topic) {
|
|
152
|
+
return new Promise((res, rej) => {
|
|
153
|
+
topic
|
|
154
|
+
.join()
|
|
155
|
+
.receive('ok', (_resp) => {
|
|
156
|
+
res();
|
|
157
|
+
})
|
|
158
|
+
.receive('error', ({ response }) => {
|
|
159
|
+
rej('Failed to join topic: ' + response);
|
|
160
|
+
})
|
|
161
|
+
.receive('timeout', () => {
|
|
162
|
+
rej('timeout');
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
async push(topic, event, payload) {
|
|
167
|
+
return new Promise((res, rej) => {
|
|
168
|
+
topic
|
|
169
|
+
.push(event, payload)
|
|
170
|
+
.receive('ok', (msg) => {
|
|
171
|
+
res(msg);
|
|
172
|
+
})
|
|
173
|
+
.receive('error', (reason) => {
|
|
174
|
+
rej(reason);
|
|
175
|
+
})
|
|
176
|
+
.receive('timeout', () => {
|
|
177
|
+
rej('timeout');
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
params() {
|
|
182
|
+
const serviceInfo = getServiceInfo();
|
|
183
|
+
this.log.debug('Info:', serviceInfo);
|
|
184
|
+
return {
|
|
185
|
+
...serviceInfo,
|
|
186
|
+
api_key: this.key,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
reconnectAfterMs(tries) {
|
|
190
|
+
if (tries > this.maxReconnectRetries) {
|
|
191
|
+
throw new SevereServiceError('Could not connect to TV Labs, please check your connection.');
|
|
192
|
+
}
|
|
193
|
+
const wait = [0, 1000, 3000, 5000][tries] || 10000;
|
|
194
|
+
this.log.info(`[${tries}/${this.maxReconnectRetries}] Waiting ${wait}ms before re-attempting to connect...`);
|
|
195
|
+
return wait;
|
|
196
|
+
}
|
|
197
|
+
static logSocketError(log, event, _transport, _establishedConnections) {
|
|
198
|
+
const error = event.error;
|
|
199
|
+
log.error('Socket error:', error || event);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
class SessionChannel extends BaseChannel {
|
|
85
204
|
lobbyTopic;
|
|
86
205
|
requestTopic;
|
|
87
|
-
log;
|
|
88
206
|
events = {
|
|
89
207
|
SESSION_READY: 'session:ready',
|
|
90
208
|
SESSION_FAILED: 'session:failed',
|
|
@@ -94,17 +212,7 @@ class TVLabsChannel {
|
|
|
94
212
|
REQUEST_MATCHING: 'request:matching',
|
|
95
213
|
};
|
|
96
214
|
constructor(endpoint, maxReconnectRetries, key, logLevel = 'info') {
|
|
97
|
-
|
|
98
|
-
this.maxReconnectRetries = maxReconnectRetries;
|
|
99
|
-
this.key = key;
|
|
100
|
-
this.logLevel = logLevel;
|
|
101
|
-
this.log = new Logger('@tvlabs/wdio-channel', this.logLevel);
|
|
102
|
-
this.socket = new Socket(this.endpoint, {
|
|
103
|
-
transport: WebSocket,
|
|
104
|
-
params: this.params(),
|
|
105
|
-
reconnectAfterMs: this.reconnectAfterMs.bind(this),
|
|
106
|
-
});
|
|
107
|
-
this.socket.onError((...args) => TVLabsChannel.logSocketError(this.log, ...args));
|
|
215
|
+
super(endpoint, maxReconnectRetries, key, logLevel, '@tvlabs/session-channel');
|
|
108
216
|
this.lobbyTopic = this.socket.channel('requests:lobby');
|
|
109
217
|
}
|
|
110
218
|
async disconnect() {
|
|
@@ -116,14 +224,14 @@ class TVLabsChannel {
|
|
|
116
224
|
}
|
|
117
225
|
async connect() {
|
|
118
226
|
try {
|
|
119
|
-
this.log.debug('Connecting to
|
|
227
|
+
this.log.debug('Connecting to session channel...');
|
|
120
228
|
this.socket.connect();
|
|
121
229
|
await this.join(this.lobbyTopic);
|
|
122
|
-
this.log.debug('Connected to
|
|
230
|
+
this.log.debug('Connected to session channel!');
|
|
123
231
|
}
|
|
124
232
|
catch (error) {
|
|
125
|
-
this.log.error('Error connecting to
|
|
126
|
-
throw new SevereServiceError('Could not connect to
|
|
233
|
+
this.log.error('Error connecting to session channel:', error);
|
|
234
|
+
throw new SevereServiceError('Could not connect to session channel, please check your connection.');
|
|
127
235
|
}
|
|
128
236
|
}
|
|
129
237
|
async newSession(capabilities, maxRetries, retry = 0) {
|
|
@@ -201,58 +309,111 @@ class TVLabsChannel {
|
|
|
201
309
|
throw error;
|
|
202
310
|
}
|
|
203
311
|
}
|
|
204
|
-
|
|
205
|
-
return
|
|
206
|
-
topic
|
|
207
|
-
.join()
|
|
208
|
-
.receive('ok', (_resp) => {
|
|
209
|
-
res();
|
|
210
|
-
})
|
|
211
|
-
.receive('error', ({ response }) => {
|
|
212
|
-
rej('Failed to join topic: ' + response);
|
|
213
|
-
})
|
|
214
|
-
.receive('timeout', () => {
|
|
215
|
-
rej('timeout');
|
|
216
|
-
});
|
|
217
|
-
});
|
|
312
|
+
tvlabsSessionLink(sessionId) {
|
|
313
|
+
return `https://tvlabs.ai/app/sessions/${sessionId}`;
|
|
218
314
|
}
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
});
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
class BuildChannel extends BaseChannel {
|
|
318
|
+
lobbyTopic;
|
|
319
|
+
constructor(endpoint, maxReconnectRetries, key, logLevel = 'info') {
|
|
320
|
+
super(endpoint, maxReconnectRetries, key, logLevel, '@tvlabs/build-channel');
|
|
321
|
+
this.lobbyTopic = this.socket.channel('upload:lobby');
|
|
322
|
+
}
|
|
323
|
+
async disconnect() {
|
|
324
|
+
return new Promise((res, _rej) => {
|
|
325
|
+
this.lobbyTopic.leave();
|
|
326
|
+
this.socket.disconnect(() => res());
|
|
232
327
|
});
|
|
233
328
|
}
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
}
|
|
329
|
+
async connect() {
|
|
330
|
+
try {
|
|
331
|
+
this.log.debug('Connecting to build channel...');
|
|
332
|
+
this.socket.connect();
|
|
333
|
+
await this.join(this.lobbyTopic);
|
|
334
|
+
this.log.debug('Connected to build channel!');
|
|
335
|
+
}
|
|
336
|
+
catch (error) {
|
|
337
|
+
this.log.error('Error connecting to build channel:', error);
|
|
338
|
+
throw new SevereServiceError('Could not connect to build channel, please check your connection.');
|
|
339
|
+
}
|
|
241
340
|
}
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
341
|
+
async uploadBuild(buildPath, appSlug) {
|
|
342
|
+
const metadata = await this.getFileMetadata(buildPath);
|
|
343
|
+
this.log.info(`Requesting upload for build ${metadata.filename} (${metadata.type}, ${metadata.size} bytes)`);
|
|
344
|
+
const { url, build_id } = await this.requestUploadUrl(metadata, appSlug);
|
|
345
|
+
this.log.info('Uploading build...');
|
|
346
|
+
await this.uploadToUrl(url, buildPath, metadata);
|
|
347
|
+
const { application_id } = await this.extractBuildInfo();
|
|
348
|
+
this.log.info(`Build "${application_id}" processed successfully`);
|
|
349
|
+
return build_id;
|
|
350
|
+
}
|
|
351
|
+
async requestUploadUrl(metadata, appSlug) {
|
|
352
|
+
try {
|
|
353
|
+
return await this.push(this.lobbyTopic, 'request_upload_url', { metadata, application_slug: appSlug });
|
|
354
|
+
}
|
|
355
|
+
catch (error) {
|
|
356
|
+
this.log.error('Error requesting upload URL:', error);
|
|
357
|
+
throw error;
|
|
245
358
|
}
|
|
246
|
-
const wait = [0, 1000, 3000, 5000][tries] || 10000;
|
|
247
|
-
this.log.info(`[${tries}/${this.maxReconnectRetries}] Waiting ${wait}ms before re-attempting to connect...`);
|
|
248
|
-
return wait;
|
|
249
359
|
}
|
|
250
|
-
|
|
251
|
-
|
|
360
|
+
async uploadToUrl(url, filePath, metadata) {
|
|
361
|
+
try {
|
|
362
|
+
const response = await fetch(url, {
|
|
363
|
+
method: 'PUT',
|
|
364
|
+
headers: {
|
|
365
|
+
'Content-Type': metadata.type,
|
|
366
|
+
'Content-Length': String(metadata.size),
|
|
367
|
+
},
|
|
368
|
+
body: fs.createReadStream(filePath),
|
|
369
|
+
duplex: 'half',
|
|
370
|
+
});
|
|
371
|
+
if (!response.ok) {
|
|
372
|
+
throw new SevereServiceError(`Failed to upload build to storage, got ${response.status}`);
|
|
373
|
+
}
|
|
374
|
+
this.log.info('Upload complete');
|
|
375
|
+
}
|
|
376
|
+
catch (error) {
|
|
377
|
+
this.log.error('Error uploading build:', error);
|
|
378
|
+
throw error;
|
|
379
|
+
}
|
|
252
380
|
}
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
381
|
+
async extractBuildInfo() {
|
|
382
|
+
this.log.info('Processing uploaded build...');
|
|
383
|
+
try {
|
|
384
|
+
return await this.push(this.lobbyTopic, 'extract_build_info', {});
|
|
385
|
+
}
|
|
386
|
+
catch (error) {
|
|
387
|
+
this.log.error('Error processing build:', error);
|
|
388
|
+
throw error;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
async getFileMetadata(buildPath) {
|
|
392
|
+
const filename = path.basename(buildPath);
|
|
393
|
+
const size = fs.statSync(buildPath).size;
|
|
394
|
+
const type = this.detectMimeType(filename);
|
|
395
|
+
const sha256 = await this.computeSha256(buildPath);
|
|
396
|
+
return { filename, type, size, sha256 };
|
|
397
|
+
}
|
|
398
|
+
async computeSha256(buildPath) {
|
|
399
|
+
return new Promise((resolve, reject) => {
|
|
400
|
+
const hash = crypto.createHash('sha256');
|
|
401
|
+
const stream = fs.createReadStream(buildPath);
|
|
402
|
+
stream.on('data', (chunk) => hash.update(chunk));
|
|
403
|
+
stream.on('end', () => resolve(hash.digest('hex')));
|
|
404
|
+
stream.on('error', (err) => reject(err));
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
detectMimeType(filename) {
|
|
408
|
+
const fileExtension = path.extname(filename).toLowerCase();
|
|
409
|
+
switch (fileExtension) {
|
|
410
|
+
case '.apk':
|
|
411
|
+
return 'application/vnd.android.package-archive';
|
|
412
|
+
case '.zip':
|
|
413
|
+
return 'application/zip';
|
|
414
|
+
default:
|
|
415
|
+
return 'application/octet-stream';
|
|
416
|
+
}
|
|
256
417
|
}
|
|
257
418
|
}
|
|
258
419
|
|
|
@@ -276,15 +437,22 @@ class TVLabsService {
|
|
|
276
437
|
}
|
|
277
438
|
}
|
|
278
439
|
async beforeSession(_config, capabilities, _specs, _cid) {
|
|
279
|
-
const
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
440
|
+
const buildPath = this.buildPath();
|
|
441
|
+
if (buildPath) {
|
|
442
|
+
const buildChannel = new BuildChannel(this.buildEndpoint(), this.reconnectRetries(), this.apiKey(), this.logLevel());
|
|
443
|
+
await buildChannel.connect();
|
|
444
|
+
capabilities['tvlabs:build'] = await buildChannel.uploadBuild(buildPath, this.appSlug());
|
|
445
|
+
await buildChannel.disconnect();
|
|
446
|
+
}
|
|
447
|
+
const sessionChannel = new SessionChannel(this.sessionEndpoint(), this.reconnectRetries(), this.apiKey(), this.logLevel());
|
|
448
|
+
await sessionChannel.connect();
|
|
449
|
+
capabilities['tvlabs:session_id'] = await sessionChannel.newSession(capabilities, this.retries());
|
|
450
|
+
await sessionChannel.disconnect();
|
|
283
451
|
}
|
|
284
452
|
setupRequestId() {
|
|
285
453
|
const originalTransformRequest = this._config.transformRequest;
|
|
286
454
|
this._config.transformRequest = (requestOptions) => {
|
|
287
|
-
const requestId = crypto.randomUUID();
|
|
455
|
+
const requestId = crypto$1.randomUUID();
|
|
288
456
|
const originalRequestOptions = typeof originalTransformRequest === 'function'
|
|
289
457
|
? originalTransformRequest(requestOptions)
|
|
290
458
|
: requestOptions;
|
|
@@ -309,8 +477,17 @@ class TVLabsService {
|
|
|
309
477
|
}
|
|
310
478
|
}
|
|
311
479
|
}
|
|
312
|
-
|
|
313
|
-
return this._options.
|
|
480
|
+
buildPath() {
|
|
481
|
+
return this._options.buildPath;
|
|
482
|
+
}
|
|
483
|
+
appSlug() {
|
|
484
|
+
return this._options.app;
|
|
485
|
+
}
|
|
486
|
+
sessionEndpoint() {
|
|
487
|
+
return this._options.sessionEndpoint ?? 'wss://tvlabs.ai/appium';
|
|
488
|
+
}
|
|
489
|
+
buildEndpoint() {
|
|
490
|
+
return this._options.buildEndpoint ?? 'wss://tvlabs.ai/cli';
|
|
314
491
|
}
|
|
315
492
|
retries() {
|
|
316
493
|
return this._options.retries ?? 3;
|
package/esm/logger.d.ts
CHANGED
package/esm/logger.d.ts.map
CHANGED
|
@@ -1 +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;
|
|
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;IAOrB,OAAO,CAAC,YAAY;IA2DpB,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/esm/service.d.ts
CHANGED
|
@@ -10,7 +10,10 @@ export default class TVLabsService implements Services.ServiceInstance {
|
|
|
10
10
|
beforeSession(_config: Omit<Options.Testrunner, 'capabilities'>, capabilities: TVLabsCapabilities, _specs: string[], _cid: string): Promise<void>;
|
|
11
11
|
private setupRequestId;
|
|
12
12
|
private setRequestHeader;
|
|
13
|
-
private
|
|
13
|
+
private buildPath;
|
|
14
|
+
private appSlug;
|
|
15
|
+
private sessionEndpoint;
|
|
16
|
+
private buildEndpoint;
|
|
14
17
|
private retries;
|
|
15
18
|
private apiKey;
|
|
16
19
|
private logLevel;
|
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":"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;IAuCd,OAAO,CAAC,cAAc;IA0BtB,OAAO,CAAC,gBAAgB;IAgBxB,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
|
@@ -2,8 +2,11 @@ import type { Capabilities } from '@wdio/types';
|
|
|
2
2
|
export type LogLevel = 'error' | 'warn' | 'info' | 'debug' | 'trace' | 'silent';
|
|
3
3
|
export type TVLabsServiceOptions = {
|
|
4
4
|
apiKey: string;
|
|
5
|
-
|
|
5
|
+
sessionEndpoint?: string;
|
|
6
|
+
buildEndpoint?: string;
|
|
6
7
|
retries?: number;
|
|
8
|
+
buildPath?: string;
|
|
9
|
+
app?: string;
|
|
7
10
|
reconnectRetries?: number;
|
|
8
11
|
attachRequestId?: boolean;
|
|
9
12
|
};
|
|
@@ -38,4 +41,17 @@ export type TVLabsServiceInfo = {
|
|
|
38
41
|
service_version: string;
|
|
39
42
|
service_name: string;
|
|
40
43
|
};
|
|
44
|
+
export type TVLabsRequestUploadUrlResponse = {
|
|
45
|
+
url: string;
|
|
46
|
+
build_id: string;
|
|
47
|
+
};
|
|
48
|
+
export type TVLabsExtractBuildInfoResponse = {
|
|
49
|
+
application_id: string;
|
|
50
|
+
};
|
|
51
|
+
export type TVLabsBuildMetadata = {
|
|
52
|
+
filename: string;
|
|
53
|
+
type: string;
|
|
54
|
+
size: number;
|
|
55
|
+
sha256: string;
|
|
56
|
+
};
|
|
41
57
|
//# 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,
|
|
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;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,kBAAkB,GAAG,iBAAiB,GAAG;IACnD,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,eAAe,EAAE,MAAM,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,8BAA8B,GAAG;IAC3C,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,8BAA8B,GAAG;IAC3C,cAAc,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tvlabs/wdio-service",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
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",
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
},
|
|
10
10
|
"repository": {
|
|
11
11
|
"type": "git",
|
|
12
|
-
"url": "git+https://github.com/tv-labs/wdio-
|
|
12
|
+
"url": "git+https://github.com/tv-labs/wdio-service.git"
|
|
13
13
|
},
|
|
14
14
|
"keywords": [
|
|
15
15
|
"wdio-plugin",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"appium"
|
|
20
20
|
],
|
|
21
21
|
"bugs": {
|
|
22
|
-
"url": "https://github.com/tv-labs/wdio-
|
|
22
|
+
"url": "https://github.com/tv-labs/wdio-service/issues"
|
|
23
23
|
},
|
|
24
24
|
"scripts": {
|
|
25
25
|
"build": "npm run clean && rollup -c",
|
|
@@ -29,7 +29,8 @@
|
|
|
29
29
|
"format": "prettier --write .",
|
|
30
30
|
"format:check": "prettier --check .",
|
|
31
31
|
"lint": "eslint",
|
|
32
|
-
"test": "vitest --config vitest.config.ts --coverage",
|
|
32
|
+
"test": "vitest --config vitest.config.ts --coverage --run",
|
|
33
|
+
"test:watch": "vitest --config vitest.config.ts --coverage",
|
|
33
34
|
"publish:dry": "npm publish --access public --provenance --dry-run"
|
|
34
35
|
},
|
|
35
36
|
"type": "module",
|
|
@@ -46,11 +47,11 @@
|
|
|
46
47
|
"@rollup/plugin-json": "^6.1.0",
|
|
47
48
|
"@rollup/plugin-node-resolve": "^16.0.1",
|
|
48
49
|
"@rollup/plugin-typescript": "^12.1.4",
|
|
49
|
-
"@types/node": "^
|
|
50
|
+
"@types/node": "^24.1.0",
|
|
50
51
|
"@types/phoenix": "^1.6.6",
|
|
51
52
|
"@types/ws": "^8.18.0",
|
|
52
|
-
"@typescript-eslint/eslint-plugin": "^8.
|
|
53
|
-
"@typescript-eslint/parser": "^8.
|
|
53
|
+
"@typescript-eslint/eslint-plugin": "^8.38.0",
|
|
54
|
+
"@typescript-eslint/parser": "^8.38.0",
|
|
54
55
|
"@vitest/coverage-v8": "^3.0.9",
|
|
55
56
|
"@wdio/globals": "^9.12.1",
|
|
56
57
|
"@wdio/types": "^9.10.1",
|
|
@@ -60,7 +61,7 @@
|
|
|
60
61
|
"prettier": "^3.5.3",
|
|
61
62
|
"rollup": "^4.45.1",
|
|
62
63
|
"typescript": "^5.8.2",
|
|
63
|
-
"typescript-eslint": "^8.
|
|
64
|
+
"typescript-eslint": "^8.38.0",
|
|
64
65
|
"vitest": "^3.0.9",
|
|
65
66
|
"webdriverio": "^9.12.1"
|
|
66
67
|
},
|