flowstride 1.0.20-rc.6 → 1.0.20
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/NATIVE_CI.md +88 -169
- package/README.md +46 -214
- package/dist/adapters/web/playwright.d.ts +4 -1
- package/dist/adapters/web/playwright.js +69 -2
- package/dist/cli/index.js +1 -1
- package/dist/runner/flow_worker.js +39 -34
- package/dist/security/redaction.js +3 -1
- package/dist/sessions/manager.js +3 -1
- package/dist/types/index.d.ts +2 -0
- package/dist/utils/replayRequest.d.ts +8 -0
- package/dist/utils/replayRequest.js +77 -0
- package/package.json +6 -4
- package/src/ui/dist/assets/{index-DVwp0K-J.js → index-B7MuBTWm.js} +15 -17
- package/src/ui/dist/index.html +1 -1
|
@@ -52,6 +52,21 @@ class WebAdapter {
|
|
|
52
52
|
page = null;
|
|
53
53
|
discoveredPages = [];
|
|
54
54
|
dialogHandler = null;
|
|
55
|
+
pendingSessionStorageRestore = null;
|
|
56
|
+
async clearSessionStorageRestore() {
|
|
57
|
+
const pending = this.pendingSessionStorageRestore;
|
|
58
|
+
this.pendingSessionStorageRestore = null;
|
|
59
|
+
if (!pending)
|
|
60
|
+
return;
|
|
61
|
+
pending.page.off('framenavigated', pending.onNavigation);
|
|
62
|
+
try {
|
|
63
|
+
await pending.script.dispose();
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
if (!pending.page.isClosed())
|
|
67
|
+
throw error;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
55
70
|
constructor(config) {
|
|
56
71
|
this.config = config;
|
|
57
72
|
}
|
|
@@ -61,6 +76,7 @@ class WebAdapter {
|
|
|
61
76
|
return this.page;
|
|
62
77
|
}
|
|
63
78
|
async initialise(audioInjectionPath) {
|
|
79
|
+
await this.clearSessionStorageRestore();
|
|
64
80
|
const launchArgs = this.config.headless
|
|
65
81
|
? [
|
|
66
82
|
"--use-fake-ui-for-media-stream",
|
|
@@ -183,9 +199,26 @@ class WebAdapter {
|
|
|
183
199
|
await this.page.bringToFront();
|
|
184
200
|
await this.page.waitForLoadState("domcontentloaded").catch(() => { });
|
|
185
201
|
}
|
|
186
|
-
async injectStorage(cookies, localStorage, fallbackDomain) {
|
|
202
|
+
async injectStorage(cookies, localStorage, fallbackDomain, sessionStorage, storageOrigin) {
|
|
187
203
|
if (!this.context || !this.page)
|
|
188
204
|
return;
|
|
205
|
+
await this.clearSessionStorageRestore();
|
|
206
|
+
const page = this.page;
|
|
207
|
+
const originOf = (value) => {
|
|
208
|
+
try {
|
|
209
|
+
const url = new URL(value || '');
|
|
210
|
+
return ['http:', 'https:'].includes(url.protocol) ? url.origin : undefined;
|
|
211
|
+
}
|
|
212
|
+
catch {
|
|
213
|
+
return undefined;
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
const hasSessionStorage = !!sessionStorage && Object.keys(sessionStorage).length > 0;
|
|
217
|
+
const origin = storageOrigin !== undefined ? originOf(storageOrigin)
|
|
218
|
+
: originOf(page.url()) || originOf(this.config.baseUrl);
|
|
219
|
+
if (hasSessionStorage && !origin) {
|
|
220
|
+
throw new Error('Session storage restoration requires a saved HTTP(S) origin, an open application page, or baseUrl.');
|
|
221
|
+
}
|
|
189
222
|
if (cookies && cookies.length > 0) {
|
|
190
223
|
const formattedCookies = [];
|
|
191
224
|
for (const cookie of cookies) {
|
|
@@ -232,6 +265,32 @@ class WebAdapter {
|
|
|
232
265
|
}, localStorage);
|
|
233
266
|
}
|
|
234
267
|
}
|
|
268
|
+
if (hasSessionStorage) {
|
|
269
|
+
const restore = (data) => {
|
|
270
|
+
if (window !== window.top || window.location.origin !== data.origin)
|
|
271
|
+
return;
|
|
272
|
+
for (const [key, value] of Object.entries(data.values))
|
|
273
|
+
window.sessionStorage.setItem(key, value);
|
|
274
|
+
};
|
|
275
|
+
const data = { origin: origin, values: sessionStorage };
|
|
276
|
+
if (originOf(page.url()) === origin) {
|
|
277
|
+
await page.evaluate(restore, data);
|
|
278
|
+
}
|
|
279
|
+
else {
|
|
280
|
+
// Seed before application scripts run; remove the seed after the first
|
|
281
|
+
// matching document so reloads preserve later application changes.
|
|
282
|
+
const script = await page.addInitScript(restore, data);
|
|
283
|
+
const onNavigation = (frame) => {
|
|
284
|
+
if (frame !== page.mainFrame() || originOf(frame.url()) !== origin)
|
|
285
|
+
return;
|
|
286
|
+
void this.clearSessionStorageRestore().catch(() => {
|
|
287
|
+
console.warn('[Session]: Could not remove the completed session storage initializer.');
|
|
288
|
+
});
|
|
289
|
+
};
|
|
290
|
+
this.pendingSessionStorageRestore = { page, script, onNavigation };
|
|
291
|
+
page.on('framenavigated', onNavigation);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
235
294
|
}
|
|
236
295
|
async getState() {
|
|
237
296
|
const cookies = (await this.context?.cookies()) || [];
|
|
@@ -250,7 +309,14 @@ class WebAdapter {
|
|
|
250
309
|
console.warn("Warning: Could not access storage. Ensure page is loaded.");
|
|
251
310
|
}
|
|
252
311
|
}
|
|
253
|
-
|
|
312
|
+
let storageOrigin;
|
|
313
|
+
try {
|
|
314
|
+
const url = new URL(this.page?.url() || '');
|
|
315
|
+
if (['http:', 'https:'].includes(url.protocol))
|
|
316
|
+
storageOrigin = url.origin;
|
|
317
|
+
}
|
|
318
|
+
catch { }
|
|
319
|
+
return { cookies, localStorage, sessionStorage, storageOrigin };
|
|
254
320
|
}
|
|
255
321
|
getCurrentUrl() {
|
|
256
322
|
return this.page ? this.page.url() : "about:blank";
|
|
@@ -1196,6 +1262,7 @@ class WebAdapter {
|
|
|
1196
1262
|
}
|
|
1197
1263
|
async shutdown(videoTargetPath) {
|
|
1198
1264
|
try {
|
|
1265
|
+
await this.clearSessionStorageRestore();
|
|
1199
1266
|
const video = this.page?.video();
|
|
1200
1267
|
if (this.context) {
|
|
1201
1268
|
await this.context.close();
|
package/dist/cli/index.js
CHANGED
|
@@ -63,7 +63,7 @@ let API_BASE = (0, cloud_1.cloudApiBase)(process.env);
|
|
|
63
63
|
program
|
|
64
64
|
.name("flowstride")
|
|
65
65
|
.description("Flowstride: The Enterprise Flow-First Automation CLI")
|
|
66
|
-
.version("1.0.20
|
|
66
|
+
.version("1.0.20");
|
|
67
67
|
program
|
|
68
68
|
.command("init")
|
|
69
69
|
.description("Initialise a new Flowstride workspace in the current directory")
|
|
@@ -37,6 +37,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
37
37
|
};
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
39
|
exports.FlowWorker = void 0;
|
|
40
|
+
const deadline_1 = require("../ci/deadline");
|
|
41
|
+
const replayRequest_1 = require("../utils/replayRequest");
|
|
40
42
|
const projectPaths_1 = require("../utils/projectPaths");
|
|
41
43
|
const redaction_1 = require("../security/redaction");
|
|
42
44
|
const fs = __importStar(require("fs"));
|
|
@@ -282,6 +284,7 @@ class FlowWorker extends events_1.EventEmitter {
|
|
|
282
284
|
url: cleanUrl,
|
|
283
285
|
requestHeaders: formattedHeaders,
|
|
284
286
|
requestBody: parsedReqBody,
|
|
287
|
+
rawReplayRequest: { method: method.toUpperCase(), url: cleanUrl, headers: formattedHeaders, body: rawBodyString },
|
|
285
288
|
responseStatus: state.status,
|
|
286
289
|
responseHeaders: state.headers,
|
|
287
290
|
responseBody: state.body,
|
|
@@ -304,33 +307,11 @@ class FlowWorker extends events_1.EventEmitter {
|
|
|
304
307
|
});
|
|
305
308
|
}
|
|
306
309
|
async handleReplayRequest(payload) {
|
|
310
|
+
const requestId = typeof payload?.requestId === 'string' ? payload.requestId : undefined;
|
|
307
311
|
try {
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
if (payload.headers)
|
|
312
|
-
parsedHeaders = JSON.parse(payload.headers);
|
|
313
|
-
}
|
|
314
|
-
catch (e) { }
|
|
315
|
-
try {
|
|
316
|
-
if (payload.body)
|
|
317
|
-
parsedBody = JSON.parse(payload.body);
|
|
318
|
-
}
|
|
319
|
-
catch (e) {
|
|
320
|
-
parsedBody = payload.body;
|
|
321
|
-
}
|
|
322
|
-
const fetchOptions = {
|
|
323
|
-
method: payload.method,
|
|
324
|
-
headers: parsedHeaders,
|
|
325
|
-
};
|
|
326
|
-
if (parsedBody && payload.method !== "GET" && payload.method !== "HEAD") {
|
|
327
|
-
fetchOptions.body =
|
|
328
|
-
typeof parsedBody === "string"
|
|
329
|
-
? parsedBody
|
|
330
|
-
: JSON.stringify(parsedBody);
|
|
331
|
-
}
|
|
332
|
-
const response = await fetch(payload.url, fetchOptions);
|
|
333
|
-
const status = response.status;
|
|
312
|
+
const { url, options } = (0, replayRequest_1.buildReplayRequest)(payload);
|
|
313
|
+
options.signal = AbortSignal.timeout(this.config.timeout || 30000);
|
|
314
|
+
const response = await fetch(url, options);
|
|
334
315
|
const text = await response.text();
|
|
335
316
|
let responseBody;
|
|
336
317
|
try {
|
|
@@ -339,17 +320,18 @@ class FlowWorker extends events_1.EventEmitter {
|
|
|
339
320
|
catch {
|
|
340
321
|
responseBody = text;
|
|
341
322
|
}
|
|
342
|
-
this.broadcast(
|
|
343
|
-
status,
|
|
344
|
-
responseBody,
|
|
323
|
+
this.broadcast('REPLAY_RES', {
|
|
324
|
+
status: response.status, responseBody, error: null, requestId,
|
|
345
325
|
fileName: this.currentFileName,
|
|
346
326
|
});
|
|
347
327
|
}
|
|
348
328
|
catch (error) {
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
329
|
+
const code = error instanceof replayRequest_1.ReplayRequestError ? 'REPLAY_INVALID_REQUEST'
|
|
330
|
+
: error?.name === 'TimeoutError' ? 'REPLAY_TIMEOUT' : 'REPLAY_NETWORK_ERROR';
|
|
331
|
+
const message = (0, redaction_1.redactText)(String(error?.message || 'Replay request failed'), [this.config.machineToken || '']);
|
|
332
|
+
this.broadcast('REPLAY_RES', {
|
|
333
|
+
status: null, error: code, requestId,
|
|
334
|
+
responseBody: { error: message, code }, fileName: this.currentFileName,
|
|
353
335
|
});
|
|
354
336
|
}
|
|
355
337
|
}
|
|
@@ -872,6 +854,7 @@ class FlowWorker extends events_1.EventEmitter {
|
|
|
872
854
|
localStorage: stateToSave.localStorage || {},
|
|
873
855
|
sessionStorage: stateToSave.sessionStorage || {},
|
|
874
856
|
fallbackDomain,
|
|
857
|
+
storageOrigin: stateToSave.storageOrigin,
|
|
875
858
|
};
|
|
876
859
|
if (payload.isGlobal) {
|
|
877
860
|
FlowWorker.globalBrowserSessions[payload.name] = hybridState;
|
|
@@ -920,7 +903,7 @@ class FlowWorker extends events_1.EventEmitter {
|
|
|
920
903
|
}
|
|
921
904
|
if (!loadedState)
|
|
922
905
|
throw new Error(`State Error: The session "${payload.name}" does not exist in memory or on disk.`);
|
|
923
|
-
await this.web.injectStorage(loadedState.cookies, loadedState.localStorage, loadedState.fallbackDomain);
|
|
906
|
+
await this.web.injectStorage(loadedState.cookies, loadedState.localStorage, loadedState.fallbackDomain, loadedState.sessionStorage, loadedState.storageOrigin);
|
|
924
907
|
}
|
|
925
908
|
break;
|
|
926
909
|
case "open":
|
|
@@ -1170,6 +1153,27 @@ class FlowWorker extends events_1.EventEmitter {
|
|
|
1170
1153
|
await this.web.expectText(payload.selector, payload.fuzzy, payload.elementType);
|
|
1171
1154
|
else if (expectType === "value")
|
|
1172
1155
|
await this.web.expectValue(payload.selector, payload.text, payload.elementType);
|
|
1156
|
+
else if (expectType === "attribute") {
|
|
1157
|
+
const deadline = Date.now() + this.config.timeout;
|
|
1158
|
+
let actual = null;
|
|
1159
|
+
const failure = () => `Attribute assertion failed: ${payload.attribute} on ${payload.selector}. Expected ${payload.fuzzy ? 'to contain ' : ''}${JSON.stringify(payload.text)}, received ${JSON.stringify(actual)}.`;
|
|
1160
|
+
while (true) {
|
|
1161
|
+
const remaining = deadline - Date.now();
|
|
1162
|
+
if (remaining <= 0)
|
|
1163
|
+
throw new Error(failure());
|
|
1164
|
+
try {
|
|
1165
|
+
actual = await (0, deadline_1.withDeadline)(this.web.getAttribute(payload.selector, payload.attribute, payload.elementType), remaining);
|
|
1166
|
+
}
|
|
1167
|
+
catch (error) {
|
|
1168
|
+
throw new Error(`${failure()}\n${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
1169
|
+
}
|
|
1170
|
+
if (actual !== null && (payload.fuzzy ? actual.includes(payload.text) : actual === payload.text))
|
|
1171
|
+
break;
|
|
1172
|
+
if (Date.now() >= deadline)
|
|
1173
|
+
throw new Error(failure());
|
|
1174
|
+
await new Promise(resolve => setTimeout(resolve, Math.max(0, Math.min(50, deadline - Date.now()))));
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1173
1177
|
else if (expectType === "transcript")
|
|
1174
1178
|
await this.web.expectTranscript(payload.text);
|
|
1175
1179
|
break;
|
|
@@ -1211,6 +1215,7 @@ class FlowWorker extends events_1.EventEmitter {
|
|
|
1211
1215
|
url: request.url(),
|
|
1212
1216
|
requestHeaders: requestHeaders,
|
|
1213
1217
|
requestBody: requestBody,
|
|
1218
|
+
rawReplayRequest: { method: request.method(), url: request.url(), headers: requestHeaders, body: rawPostData },
|
|
1214
1219
|
responseStatus: response.status(),
|
|
1215
1220
|
responseHeaders: response.headers(),
|
|
1216
1221
|
responseBody: responseBody,
|
|
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.redactText = redactText;
|
|
4
4
|
exports.redactPayload = redactPayload;
|
|
5
5
|
exports.redactWorkerPayload = redactWorkerPayload;
|
|
6
|
-
const sensitive = /password|passcode|secret|token|authorization|cookie|api.?key|master.?key|otp|email|curlString|strippedDom/i;
|
|
6
|
+
const sensitive = /password|passcode|secret|token|authorization|cookie|api.?key|master.?key|otp|email|curlString|rawReplayRequest|strippedDom/i;
|
|
7
7
|
function redactText(input, secrets = []) {
|
|
8
8
|
let value = String(input);
|
|
9
9
|
for (const secret of secrets.filter(s => typeof s === 'string' && s.length >= 4).sort((a, b) => b.length - a.length))
|
|
@@ -44,6 +44,8 @@ function redactWorkerPayload(type, payload, secrets, localDebug = false) {
|
|
|
44
44
|
return result;
|
|
45
45
|
if (type === 'LOG_EVENT' && ['REST', 'GRAPHQL'].includes(payload.type) && typeof payload.details?.curlString === 'string') {
|
|
46
46
|
result.details.rawCurlString = payload.details.curlString;
|
|
47
|
+
if (payload.details.rawReplayRequest)
|
|
48
|
+
result.details.rawReplayRequest = payload.details.rawReplayRequest;
|
|
47
49
|
}
|
|
48
50
|
return result;
|
|
49
51
|
}
|
package/dist/sessions/manager.js
CHANGED
|
@@ -35,7 +35,7 @@ class SessionManager {
|
|
|
35
35
|
data.lastUsedAt = Date.now();
|
|
36
36
|
this.vault.save(`session_${name}`, data);
|
|
37
37
|
this.activeSession = data;
|
|
38
|
-
await this.web.injectStorage(data.cookies, data.localStorage);
|
|
38
|
+
await this.web.injectStorage(data.cookies, data.localStorage, undefined, data.sessionStorage, data.storageOrigin);
|
|
39
39
|
const definition = this.definitions.get(name);
|
|
40
40
|
if (definition?.inject?.api) {
|
|
41
41
|
const { headerName, template } = definition.inject.api;
|
|
@@ -64,6 +64,8 @@ class SessionManager {
|
|
|
64
64
|
tokens: this.activeSession?.tokens || {},
|
|
65
65
|
cookies: state.cookies,
|
|
66
66
|
localStorage: state.localStorage,
|
|
67
|
+
sessionStorage: state.sessionStorage,
|
|
68
|
+
storageOrigin: state.storageOrigin,
|
|
67
69
|
createdAt: Date.now(),
|
|
68
70
|
lastUsedAt: Date.now(),
|
|
69
71
|
persist,
|
package/dist/types/index.d.ts
CHANGED
|
@@ -53,6 +53,8 @@ export interface SessionData {
|
|
|
53
53
|
tokens: Record<string, any>;
|
|
54
54
|
cookies?: any[];
|
|
55
55
|
localStorage?: Record<string, string>;
|
|
56
|
+
sessionStorage?: Record<string, string>;
|
|
57
|
+
storageOrigin?: string;
|
|
56
58
|
createdAt: number;
|
|
57
59
|
lastUsedAt: number;
|
|
58
60
|
persist: boolean;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare class ReplayRequestError extends Error {
|
|
2
|
+
constructor(message: string);
|
|
3
|
+
}
|
|
4
|
+
/** Browser protocol metadata is not a portable set of Fetch request headers. */
|
|
5
|
+
export declare function buildReplayRequest(payload: any): {
|
|
6
|
+
url: string;
|
|
7
|
+
options: RequestInit;
|
|
8
|
+
};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ReplayRequestError = void 0;
|
|
4
|
+
exports.buildReplayRequest = buildReplayRequest;
|
|
5
|
+
class ReplayRequestError extends Error {
|
|
6
|
+
constructor(message) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.name = 'ReplayRequestError';
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
exports.ReplayRequestError = ReplayRequestError;
|
|
12
|
+
/** Browser protocol metadata is not a portable set of Fetch request headers. */
|
|
13
|
+
function buildReplayRequest(payload) {
|
|
14
|
+
let url;
|
|
15
|
+
try {
|
|
16
|
+
url = new URL(payload?.url);
|
|
17
|
+
if (!['http:', 'https:'].includes(url.protocol) || url.username || url.password)
|
|
18
|
+
throw new Error();
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
throw new ReplayRequestError('Replay URL must use HTTP or HTTPS without embedded credentials.');
|
|
22
|
+
}
|
|
23
|
+
const method = typeof payload?.method === 'string' ? payload.method.toUpperCase() : 'GET';
|
|
24
|
+
if (!/^[!#$%&'*+.^_`|~0-9A-Z-]+$/.test(method) || ['CONNECT', 'TRACE', 'TRACK'].includes(method)) {
|
|
25
|
+
throw new ReplayRequestError('Replay method is not supported.');
|
|
26
|
+
}
|
|
27
|
+
let supplied = payload?.headers;
|
|
28
|
+
if (typeof supplied === 'string') {
|
|
29
|
+
try {
|
|
30
|
+
supplied = supplied.trim() ? JSON.parse(supplied) : {};
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
throw new ReplayRequestError('Replay headers must be a valid JSON object.');
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
if (supplied === undefined)
|
|
37
|
+
supplied = {};
|
|
38
|
+
if (!supplied || typeof supplied !== 'object' || Array.isArray(supplied)) {
|
|
39
|
+
throw new ReplayRequestError('Replay headers must be a JSON object of string values.');
|
|
40
|
+
}
|
|
41
|
+
// Fetch owns the destination, framing and supported compression. Recompute
|
|
42
|
+
// Content-Length after edits and remove all Connection-nominated fields too.
|
|
43
|
+
const excluded = new Set([
|
|
44
|
+
'host', 'content-length', 'accept-encoding', 'connection', 'keep-alive',
|
|
45
|
+
'proxy-connection', 'proxy-authenticate', 'proxy-authorization', 'te',
|
|
46
|
+
'trailer', 'transfer-encoding', 'upgrade', 'expect',
|
|
47
|
+
]);
|
|
48
|
+
for (const [name, value] of Object.entries(supplied)) {
|
|
49
|
+
if (name.toLowerCase() === 'connection' && typeof value === 'string') {
|
|
50
|
+
for (const field of value.split(','))
|
|
51
|
+
excluded.add(field.trim().toLowerCase());
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const headers = new Headers();
|
|
55
|
+
for (const [name, value] of Object.entries(supplied)) {
|
|
56
|
+
if (name.startsWith(':') || excluded.has(name.toLowerCase()))
|
|
57
|
+
continue;
|
|
58
|
+
if (typeof value !== 'string')
|
|
59
|
+
throw new ReplayRequestError('Replay header values must be strings.');
|
|
60
|
+
try {
|
|
61
|
+
headers.append(name, value);
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
throw new ReplayRequestError('Replay headers contain an invalid name or value.');
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
const options = { method, headers };
|
|
68
|
+
if (!['GET', 'HEAD'].includes(method) && payload?.body !== undefined && payload.body !== null) {
|
|
69
|
+
try {
|
|
70
|
+
options.body = typeof payload.body === 'string' ? payload.body : JSON.stringify(payload.body);
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
throw new ReplayRequestError('Replay body cannot be serialized.');
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return { url: url.href, options };
|
|
77
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "flowstride",
|
|
3
|
-
"version": "1.0.20
|
|
3
|
+
"version": "1.0.20",
|
|
4
4
|
"description": "An opensource flowfirst test automation framework",
|
|
5
5
|
"workspaces": [
|
|
6
6
|
"packages/flowstride_report"
|
|
@@ -27,8 +27,10 @@
|
|
|
27
27
|
"verify:candidate": "node scripts/verify-candidate.cjs",
|
|
28
28
|
"test:dashboard:unit": "node --test tests/dashboard/store.test.cjs",
|
|
29
29
|
"test:dashboard": "node --test --test-concurrency=1 tests/dashboard/*.test.cjs",
|
|
30
|
-
"test:unit": "node --test tests/native/core.test.cjs tests/native/combined.test.cjs tests/native/curl.test.cjs tests/native/upload.test.cjs tests/dashboard/store.test.cjs",
|
|
31
|
-
"test:security": "node --test tests/manual-security.test.cjs tests/native/curl.test.cjs tests/native/combined.test.cjs"
|
|
30
|
+
"test:unit": "node --test tests/native/core.test.cjs tests/native/combined.test.cjs tests/native/curl.test.cjs tests/native/upload.test.cjs tests/dashboard/store.test.cjs tests/native/replay.test.cjs tests/dashboard/replay.test.cjs tests/native/assertions-sessions.test.cjs",
|
|
31
|
+
"test:security": "node --test tests/manual-security.test.cjs tests/native/curl.test.cjs tests/native/combined.test.cjs tests/native/replay.test.cjs tests/native/assertions-sessions.test.cjs",
|
|
32
|
+
"test:replay": "node --test tests/native/replay.test.cjs tests/dashboard/replay.test.cjs tests/dashboard/curl.browser.test.cjs",
|
|
33
|
+
"test:assertions-sessions": "node --test --test-concurrency=1 tests/native/assertions-sessions*.test.cjs"
|
|
32
34
|
},
|
|
33
35
|
"keywords": [
|
|
34
36
|
"automation",
|
|
@@ -69,6 +71,6 @@
|
|
|
69
71
|
"@rolldown/binding-darwin-arm64": "^0.15.1"
|
|
70
72
|
},
|
|
71
73
|
"publishConfig": {
|
|
72
|
-
"tag": "
|
|
74
|
+
"tag": "latest"
|
|
73
75
|
}
|
|
74
76
|
}
|