@flowriki/agent 0.1.0-beta.15 → 0.1.0-beta.18
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/dist/{chunk-KY7KDJTV.js → chunk-ZGXVQ2QA.js} +84 -20
- package/dist/cli.js +6 -1
- package/dist/engine.js +1 -1
- package/package.json +1 -1
|
@@ -6,7 +6,7 @@ import os from "os";
|
|
|
6
6
|
import path from "path";
|
|
7
7
|
import { createRequire } from "module";
|
|
8
8
|
import { spawnSync } from "child_process";
|
|
9
|
-
var AGENT_VERSION = "0.1.0-beta.
|
|
9
|
+
var AGENT_VERSION = "0.1.0-beta.18";
|
|
10
10
|
async function prepareBrowsers() {
|
|
11
11
|
if (browsersReady()) {
|
|
12
12
|
return { ok: true, already: true, message: "chromium is already installed" };
|
|
@@ -458,7 +458,7 @@ import path7 from "path";
|
|
|
458
458
|
// ../engine-playwright/dist/index.js
|
|
459
459
|
import path3 from "path";
|
|
460
460
|
|
|
461
|
-
// ../test-model/dist/chunk-
|
|
461
|
+
// ../test-model/dist/chunk-ZRUZU4TI.js
|
|
462
462
|
import { z as z3 } from "zod";
|
|
463
463
|
var LocatorOverrideSchema = z3.object({
|
|
464
464
|
strategy: z3.enum(["css", "xpath", "id", "testId", "text", "label", "placeholder", "altText", "title"]),
|
|
@@ -758,6 +758,20 @@ var FlowDocumentSchema = z3.object({
|
|
|
758
758
|
updatedAt: z3.string(),
|
|
759
759
|
steps: z3.array(RecordedStepSchema),
|
|
760
760
|
environment: ExecutionEnvironmentSchema.optional(),
|
|
761
|
+
/** WHICH DEPLOYMENT THIS WAS RECORDED AGAINST, by name — "staging",
|
|
762
|
+
* "production", whatever the project calls them.
|
|
763
|
+
*
|
|
764
|
+
* A record of fact, not a constraint: the flow can still be run against any
|
|
765
|
+
* environment, which is the point of naming them. What it answers is the
|
|
766
|
+
* question somebody asks of a test that fails only in one place — "where
|
|
767
|
+
* was this made?" — which was previously unanswerable, because the address
|
|
768
|
+
* it used belonged to a setting that had since moved on.
|
|
769
|
+
*
|
|
770
|
+
* Deliberately NOT the address. An origin in a git-tracked file is the
|
|
771
|
+
* mistake documented on `baseUrl` above; a name survives the deployment
|
|
772
|
+
* moving. Absent on everything recorded before this, and on a project that
|
|
773
|
+
* has never named its environments. */
|
|
774
|
+
recordedIn: z3.string().min(1).optional(),
|
|
761
775
|
path: z3.string().optional(),
|
|
762
776
|
actors: z3.array(ActorDefinitionSchema).optional(),
|
|
763
777
|
defaultActorId: z3.string().optional(),
|
|
@@ -1134,17 +1148,25 @@ var AuthProfileSchema = z32.object({
|
|
|
1134
1148
|
updatedAt: z32.string(),
|
|
1135
1149
|
revision: z32.number().int().nonnegative().default(0)
|
|
1136
1150
|
}).strict();
|
|
1137
|
-
var ACCOUNT_BINDING_SCHEMA_VERSION =
|
|
1151
|
+
var ACCOUNT_BINDING_SCHEMA_VERSION = 3;
|
|
1138
1152
|
var AccountBindingSchema = z32.object({
|
|
1139
1153
|
schemaVersion: z32.literal(ACCOUNT_BINDING_SCHEMA_VERSION),
|
|
1140
1154
|
id: z32.string().min(1),
|
|
1141
1155
|
slug: z32.string().regex(/^[a-z0-9][a-z0-9-]*$/),
|
|
1142
1156
|
name: z32.string().min(1),
|
|
1143
1157
|
profileId: z32.string().min(1),
|
|
1144
|
-
/** Which
|
|
1145
|
-
*
|
|
1146
|
-
*
|
|
1147
|
-
|
|
1158
|
+
/** Which environments of the APPLICATION UNDER TEST these credentials work
|
|
1159
|
+
* in — never Flowriki's own dev or staging.
|
|
1160
|
+
*
|
|
1161
|
+
* EMPTY MEANS EVERY ONE, which is what a project with a single deployment
|
|
1162
|
+
* wants and what most accounts will always say.
|
|
1163
|
+
*
|
|
1164
|
+
* A LIST because the same person usually exists in more than one place. One
|
|
1165
|
+
* environment per account was the shape until version 3, and it forced a
|
|
1166
|
+
* duplicate account — same name, same password, same everything — for each
|
|
1167
|
+
* deployment the identity existed in, or a blank "works anywhere" that also
|
|
1168
|
+
* claimed environments it had never been created in. Neither was true. */
|
|
1169
|
+
environments: z32.array(z32.string().min(1)).default([]),
|
|
1148
1170
|
/** Step id → what this identity types there. Ordinary values: a username is
|
|
1149
1171
|
* not a credential, and keeping it here is what lets somebody read an
|
|
1150
1172
|
* account and know who it is. */
|
|
@@ -2125,6 +2147,20 @@ function installRecorder() {
|
|
|
2125
2147
|
},
|
|
2126
2148
|
true
|
|
2127
2149
|
);
|
|
2150
|
+
const SECRET_WORDS = /password|passwd|pass[-_\s]?code|pwd|secret|otp|one[-_\s]?time[-_\s]?(code|pass)|security[-_\s]?(code|answer)|cvv|cvc|\bpin\b/i;
|
|
2151
|
+
const looksSecret = (el, type, name) => {
|
|
2152
|
+
if (type === "password") return true;
|
|
2153
|
+
const auto = String(el.autocomplete || el.getAttribute?.("autocomplete") || "").toLowerCase();
|
|
2154
|
+
if (/current-password|new-password|one-time-code/.test(auto)) return true;
|
|
2155
|
+
try {
|
|
2156
|
+
const masked = getComputedStyle(el).webkitTextSecurity;
|
|
2157
|
+
if (masked && masked !== "none") return true;
|
|
2158
|
+
} catch {
|
|
2159
|
+
}
|
|
2160
|
+
return SECRET_WORDS.test(
|
|
2161
|
+
[name, el.name, el.id, el.getAttribute?.("aria-label"), el.placeholder].filter(Boolean).join(" ")
|
|
2162
|
+
);
|
|
2163
|
+
};
|
|
2128
2164
|
const editedValues = /* @__PURE__ */ new WeakMap();
|
|
2129
2165
|
const onEdit = (e) => {
|
|
2130
2166
|
if (e.isComposing) return;
|
|
@@ -2155,15 +2191,16 @@ function installRecorder() {
|
|
|
2155
2191
|
const signature = JSON.stringify(values ?? value);
|
|
2156
2192
|
if (editedValues.get(el) === signature) return;
|
|
2157
2193
|
editedValues.set(el, signature);
|
|
2194
|
+
const identity = identify(el);
|
|
2158
2195
|
send({
|
|
2159
2196
|
type: type === "range" ? "setRange" : tag === "select" ? "select" : "fill",
|
|
2160
2197
|
role: roleOf(el),
|
|
2161
|
-
...
|
|
2162
|
-
...scoping(el, roleOf(el),
|
|
2198
|
+
...identity,
|
|
2199
|
+
...scoping(el, roleOf(el), identity.name),
|
|
2163
2200
|
legacySelector: cssPath(el),
|
|
2164
2201
|
value,
|
|
2165
2202
|
...values ? { values } : {},
|
|
2166
|
-
secret: type
|
|
2203
|
+
secret: looksSecret(el, type, identity.name)
|
|
2167
2204
|
});
|
|
2168
2205
|
};
|
|
2169
2206
|
document.addEventListener("input", onEdit, true);
|
|
@@ -2985,7 +3022,7 @@ var RunActors = class {
|
|
|
2985
3022
|
this.current = session;
|
|
2986
3023
|
this.currentId = id;
|
|
2987
3024
|
if (this.initialized.has(id)) return;
|
|
2988
|
-
await session.page.goto(startUrl, { waitUntil: "domcontentloaded" });
|
|
3025
|
+
if (startUrl) await session.page.goto(startUrl, { waitUntil: "domcontentloaded" });
|
|
2989
3026
|
if (actor?.startSignedIn) {
|
|
2990
3027
|
if (kept?.state) {
|
|
2991
3028
|
const confirmed = await confirmSignedIn(session.page, kept.auth, this.opts.reuseCheckMs ?? 3e3);
|
|
@@ -3027,7 +3064,7 @@ var RunActors = class {
|
|
|
3027
3064
|
} catch {
|
|
3028
3065
|
}
|
|
3029
3066
|
}
|
|
3030
|
-
await session.page.goto(startUrl, { waitUntil: "domcontentloaded" });
|
|
3067
|
+
if (startUrl) await session.page.goto(startUrl, { waitUntil: "domcontentloaded" });
|
|
3031
3068
|
}
|
|
3032
3069
|
this.initialized.add(id);
|
|
3033
3070
|
}
|
|
@@ -3919,6 +3956,12 @@ var checkStep = (id, proposals) => ({
|
|
|
3919
3956
|
legacySelector: "",
|
|
3920
3957
|
proposals
|
|
3921
3958
|
});
|
|
3959
|
+
function opensItsOwnEntry(steps, opts) {
|
|
3960
|
+
const first = steps.find((step) => !step.disabled);
|
|
3961
|
+
if (!first || first.type !== "signIn" || !first.auth) return false;
|
|
3962
|
+
if (first.auth.from === "here") return false;
|
|
3963
|
+
return opts.resolveAuth?.(first.auth)?.navigation.kind === "open";
|
|
3964
|
+
}
|
|
3922
3965
|
function stepFailure(step, detail) {
|
|
3923
3966
|
return {
|
|
3924
3967
|
stepId: step.id,
|
|
@@ -3940,7 +3983,9 @@ async function runStepsIn(session, steps, opts) {
|
|
|
3940
3983
|
let status2 = "passed";
|
|
3941
3984
|
let error = null;
|
|
3942
3985
|
try {
|
|
3943
|
-
if (opts.startUrl
|
|
3986
|
+
if (opts.startUrl && !opensItsOwnEntry(steps, opts)) {
|
|
3987
|
+
await page.goto(opts.startUrl, { waitUntil: "domcontentloaded" });
|
|
3988
|
+
}
|
|
3944
3989
|
for (const recorded of steps) {
|
|
3945
3990
|
let step = recorded;
|
|
3946
3991
|
if (!recorded.disabled) {
|
|
@@ -4158,8 +4203,9 @@ async function replayFlow(opts) {
|
|
|
4158
4203
|
hydrated: hydrated ? { ...hydrated, id: opts.defaultActorId ?? "default" } : null
|
|
4159
4204
|
});
|
|
4160
4205
|
let prepared = null;
|
|
4206
|
+
const entry = opensItsOwnEntry(steps, opts) ? "" : void 0;
|
|
4161
4207
|
try {
|
|
4162
|
-
await actors.select(opts.defaultActorId ?? "default");
|
|
4208
|
+
await actors.select(opts.defaultActorId ?? "default", entry);
|
|
4163
4209
|
} catch (e) {
|
|
4164
4210
|
prepared = e.message;
|
|
4165
4211
|
}
|
|
@@ -4785,10 +4831,8 @@ async function startRecordingSession(opts) {
|
|
|
4785
4831
|
await page.goto(url, { waitUntil: "domcontentloaded" });
|
|
4786
4832
|
}
|
|
4787
4833
|
await executeStep(page, opts.authentication.step, { ...opts.authentication, baseUrl: url });
|
|
4788
|
-
|
|
4789
|
-
steps.push(opts.authentication.step, resume);
|
|
4834
|
+
steps.push(opts.authentication.step);
|
|
4790
4835
|
onStep?.(opts.authentication.step);
|
|
4791
|
-
onStep?.(resume);
|
|
4792
4836
|
} catch (error) {
|
|
4793
4837
|
const landed = await page.url().replace(/^https?:\/\/[^/]+/, "");
|
|
4794
4838
|
const title = await page.title().catch(() => "");
|
|
@@ -4850,7 +4894,8 @@ async function startRecordingSession(opts) {
|
|
|
4850
4894
|
});
|
|
4851
4895
|
});
|
|
4852
4896
|
try {
|
|
4853
|
-
await page.goto(url, { waitUntil: "domcontentloaded", timeout: 45e3 });
|
|
4897
|
+
if (!opts.authentication) await page.goto(url, { waitUntil: "domcontentloaded", timeout: 45e3 });
|
|
4898
|
+
else await ensureInstalled(page);
|
|
4854
4899
|
} catch (err) {
|
|
4855
4900
|
stopped = true;
|
|
4856
4901
|
await browser.close().catch(() => {
|
|
@@ -5183,7 +5228,11 @@ async function startDebugSession(opts) {
|
|
|
5183
5228
|
publish();
|
|
5184
5229
|
});
|
|
5185
5230
|
await context2.exposeBinding("__flCmd", (_src, msg) => {
|
|
5186
|
-
void handleCommand(msg)
|
|
5231
|
+
void handleCommand(msg).catch((error) => {
|
|
5232
|
+
failure = { stepId: cursor ?? steps[0]?.id ?? "", error: firstLine2(error) };
|
|
5233
|
+
status2 = "paused";
|
|
5234
|
+
void paint(snapshot());
|
|
5235
|
+
});
|
|
5187
5236
|
});
|
|
5188
5237
|
await context2.addInitScript({ content: `${accNameScript2()}
|
|
5189
5238
|
;(${installRecorder.toString()})();
|
|
@@ -6298,7 +6347,7 @@ function toSummary(row) {
|
|
|
6298
6347
|
};
|
|
6299
6348
|
}
|
|
6300
6349
|
|
|
6301
|
-
// ../repository-store/dist/chunk-
|
|
6350
|
+
// ../repository-store/dist/chunk-A4JZPKZT.js
|
|
6302
6351
|
var FLOWRIKI_DIR = ".flowriki";
|
|
6303
6352
|
var FEATURES_DIR = `${FLOWRIKI_DIR}/features`;
|
|
6304
6353
|
var CONFIG_PATH = `${FLOWRIKI_DIR}/config.json`;
|
|
@@ -6339,6 +6388,7 @@ function parseConfig(raw) {
|
|
|
6339
6388
|
return {
|
|
6340
6389
|
baseUrl: typeof source["baseUrl"] === "string" ? source["baseUrl"] : "",
|
|
6341
6390
|
...typeof source["applicationEnvironment"] === "string" ? { applicationEnvironment: source["applicationEnvironment"] } : {},
|
|
6391
|
+
...Array.isArray(source["environments"]) ? { environments: readEnvironments(source["environments"]) } : {},
|
|
6342
6392
|
reuseAuthentication: source["reuseAuthentication"] === true,
|
|
6343
6393
|
headless: source["headless"] === true,
|
|
6344
6394
|
environment: ExecutionEnvironmentSchema.parse(source["environment"] ?? {}),
|
|
@@ -6346,6 +6396,20 @@ function parseConfig(raw) {
|
|
|
6346
6396
|
flakinessRepeat: clampRepeat(source["flakinessRepeat"])
|
|
6347
6397
|
};
|
|
6348
6398
|
}
|
|
6399
|
+
function readEnvironments(raw) {
|
|
6400
|
+
const out = [];
|
|
6401
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6402
|
+
for (const entry of raw) {
|
|
6403
|
+
if (!entry || typeof entry !== "object") continue;
|
|
6404
|
+
const { name, baseUrl } = entry;
|
|
6405
|
+
if (typeof name !== "string" || !name.trim()) continue;
|
|
6406
|
+
const key = name.trim().toLowerCase();
|
|
6407
|
+
if (seen.has(key)) continue;
|
|
6408
|
+
seen.add(key);
|
|
6409
|
+
out.push({ name: name.trim(), baseUrl: typeof baseUrl === "string" ? baseUrl : "" });
|
|
6410
|
+
}
|
|
6411
|
+
return out;
|
|
6412
|
+
}
|
|
6349
6413
|
var FlowNotFound = class extends Error {
|
|
6350
6414
|
constructor(message) {
|
|
6351
6415
|
super(message);
|
package/dist/cli.js
CHANGED
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
resolveCheckouts,
|
|
9
9
|
start,
|
|
10
10
|
status
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-ZGXVQ2QA.js";
|
|
12
12
|
|
|
13
13
|
// src/cli.ts
|
|
14
14
|
import { parseArgs } from "util";
|
|
@@ -111,6 +111,11 @@ Run \`flowriki-agent --help\`.`);
|
|
|
111
111
|
return 0;
|
|
112
112
|
}
|
|
113
113
|
case "start": {
|
|
114
|
+
process.on("unhandledRejection", (reason) => {
|
|
115
|
+
const said = reason instanceof Error ? reason.stack ?? reason.message : String(reason);
|
|
116
|
+
console.error(red(`! a command failed without being handled \u2014 the agent is still running`));
|
|
117
|
+
console.error(dim(said.split("\n").slice(0, 4).join("\n")));
|
|
118
|
+
});
|
|
114
119
|
const started = await start({
|
|
115
120
|
...service ? { service } : {},
|
|
116
121
|
...projects.length ? { projects } : {},
|
package/dist/engine.js
CHANGED
package/package.json
CHANGED