@flowriki/agent 0.1.0-beta.63 → 0.1.0-beta.65
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.
|
@@ -16,10 +16,12 @@ import os3 from "os";
|
|
|
16
16
|
import path3 from "path";
|
|
17
17
|
import { execFile as execFile2 } from "child_process";
|
|
18
18
|
import fs4 from "fs";
|
|
19
|
-
import os4 from "os";
|
|
20
19
|
import path4 from "path";
|
|
20
|
+
import fs5 from "fs";
|
|
21
|
+
import os4 from "os";
|
|
22
|
+
import path5 from "path";
|
|
21
23
|
|
|
22
|
-
// ../test-model/dist/chunk-
|
|
24
|
+
// ../test-model/dist/chunk-6JIZJVPN.js
|
|
23
25
|
import { z as z2 } from "zod";
|
|
24
26
|
import { z } from "zod";
|
|
25
27
|
var PLATFORMS = ["web", "android", "ios"];
|
|
@@ -69,7 +71,10 @@ var CloudAccountSchema = z.object({
|
|
|
69
71
|
provider: z.enum(CLOUD_PROVIDERS),
|
|
70
72
|
username: z.string().trim().min(1).max(200),
|
|
71
73
|
/** Sauce Labs data centre: "us-west-1", "eu-central-1", "us-east-4". */
|
|
72
|
-
region:
|
|
74
|
+
/** A Sauce Labs region — a word like us-west-1, never an address: it is
|
|
75
|
+
* spelled into the host the farm's key is sent to. Anything else is
|
|
76
|
+
* dropped, and the default region used. */
|
|
77
|
+
region: z.string().trim().max(40).optional().transform((v) => v && /^[a-z]{2}-[a-z]+-\d$/.test(v) ? v : void 0),
|
|
73
78
|
keyRef: z.string().regex(/^[a-z][a-z0-9_]*$/)
|
|
74
79
|
}).strict();
|
|
75
80
|
var DeviceChoiceSchema = z.discriminatedUnion("kind", [
|
|
@@ -629,19 +634,19 @@ var CHECK_TARGET_FIELDS = [
|
|
|
629
634
|
var unset = (value) => value === void 0 || value === null || value === "" || Array.isArray(value) && value.length === 0;
|
|
630
635
|
function mirrorCheckTargets(steps) {
|
|
631
636
|
return steps.map((step) => {
|
|
632
|
-
const
|
|
633
|
-
if (
|
|
634
|
-
const proposals =
|
|
637
|
+
const held2 = step;
|
|
638
|
+
if (held2["type"] !== "assert") return step;
|
|
639
|
+
const proposals = held2["proposals"];
|
|
635
640
|
if (!Array.isArray(proposals)) return step;
|
|
636
641
|
const first = proposals[0];
|
|
637
642
|
const target = first?.["target"];
|
|
638
643
|
if (!target) return step;
|
|
639
644
|
const fill = {};
|
|
640
645
|
for (const field of CHECK_TARGET_FIELDS) {
|
|
641
|
-
if (!unset(
|
|
646
|
+
if (!unset(held2[field]) || unset(target[field])) continue;
|
|
642
647
|
fill[field] = target[field];
|
|
643
648
|
}
|
|
644
|
-
return Object.keys(fill).length === 0 ? step : { ...
|
|
649
|
+
return Object.keys(fill).length === 0 ? step : { ...held2, ...fill };
|
|
645
650
|
});
|
|
646
651
|
}
|
|
647
652
|
|
|
@@ -659,6 +664,10 @@ import { z as z10 } from "zod";
|
|
|
659
664
|
import { z as z11 } from "zod";
|
|
660
665
|
import { z as z12 } from "zod";
|
|
661
666
|
import { z as z13 } from "zod";
|
|
667
|
+
import { z as z14 } from "zod";
|
|
668
|
+
import { z as z15 } from "zod";
|
|
669
|
+
import { z as z16 } from "zod";
|
|
670
|
+
import { z as z17 } from "zod";
|
|
662
671
|
var EngineSchema = z3.enum(["chromium", "firefox", "webkit", "android", "ios"]);
|
|
663
672
|
var CapabilitySchema = z3.object({
|
|
664
673
|
id: z3.string(),
|
|
@@ -867,6 +876,10 @@ var TestCaseSchema = z32.object({
|
|
|
867
876
|
createdAt: z32.string(),
|
|
868
877
|
createdBy: z32.string().default(""),
|
|
869
878
|
updatedAt: z32.string(),
|
|
879
|
+
/** WHO LAST CHANGED IT — a person's id, or "Claude" for the assistant — so
|
|
880
|
+
* a change the assistant made can be told from one a person made. Absent
|
|
881
|
+
* on cases saved before this was kept. Never sent to a machine. */
|
|
882
|
+
updatedBy: z32.string().max(200).optional(),
|
|
870
883
|
/** The same optimistic-concurrency counter a flow carries, so an edit made
|
|
871
884
|
* against a stale copy is refused rather than overwriting. */
|
|
872
885
|
revision: z32.number().int().nonnegative().default(0)
|
|
@@ -1768,6 +1781,8 @@ var CaseUpdateRequestSchema = z9.object({
|
|
|
1768
1781
|
instruction: z9.string().min(1).max(4e3),
|
|
1769
1782
|
cases: z9.array(z9.object({
|
|
1770
1783
|
id: z9.string().min(1),
|
|
1784
|
+
/** Its id as people say it — TC-12. */
|
|
1785
|
+
key: z9.string().max(20).optional(),
|
|
1771
1786
|
name: z9.string(),
|
|
1772
1787
|
feature: z9.string(),
|
|
1773
1788
|
businessSteps: z9.array(BusinessStepSchema)
|
|
@@ -1825,7 +1840,7 @@ function decodeEntities(text) {
|
|
|
1825
1840
|
var nameKey = (name) => decodeEntities(name).toLowerCase().replace(/&/g, " and ").replace(/[^\p{L}\p{N}]+/gu, " ").trim();
|
|
1826
1841
|
var PLATFORM_PART = "(web|android|ios)";
|
|
1827
1842
|
var LEARNED_PATH = new RegExp(`^(explore\\/${PLATFORM_PART}\\.md|memory\\/sessions\\/[a-z0-9][a-z0-9-]{0,120}\\.md)$`);
|
|
1828
|
-
var isLearnedPath = (
|
|
1843
|
+
var isLearnedPath = (path9) => LEARNED_PATH.test(path9);
|
|
1829
1844
|
var LearnedSchema = z11.object({
|
|
1830
1845
|
id: z11.string().regex(/^lrn_[a-z0-9_-]+$/),
|
|
1831
1846
|
projectId: z11.string().min(1),
|
|
@@ -1866,9 +1881,113 @@ var WhatToDoSchema = z13.object({
|
|
|
1866
1881
|
skipped: z13.array(z13.string().min(1).max(80)).max(100),
|
|
1867
1882
|
updatedAt: z13.string()
|
|
1868
1883
|
}).strict();
|
|
1884
|
+
var BLOCKER_KINDS = ["app-bug", "needs-person", "unclear-step", "not-found", "stuck"];
|
|
1885
|
+
var CaseStandingSchema = z14.discriminatedUnion("state", [
|
|
1886
|
+
z14.object({
|
|
1887
|
+
state: z14.literal("blocked"),
|
|
1888
|
+
kind: z14.enum(BLOCKER_KINDS),
|
|
1889
|
+
/** The business step it stopped at, counted from 1 — when it was one. */
|
|
1890
|
+
line: z14.number().int().positive().max(500).optional(),
|
|
1891
|
+
lineText: z14.string().max(500).optional(),
|
|
1892
|
+
detail: z14.string().min(1).max(1500),
|
|
1893
|
+
/** What would unblock it, said for a person. */
|
|
1894
|
+
needs: z14.string().max(600).optional(),
|
|
1895
|
+
platform: z14.enum(["web", "android", "ios"]).optional(),
|
|
1896
|
+
by: z14.string().max(200),
|
|
1897
|
+
at: z14.string()
|
|
1898
|
+
}).strict(),
|
|
1899
|
+
z14.object({
|
|
1900
|
+
state: z14.literal("manual"),
|
|
1901
|
+
/** Why it is run by hand, when somebody said. */
|
|
1902
|
+
reason: z14.string().max(600).optional(),
|
|
1903
|
+
by: z14.string().max(200),
|
|
1904
|
+
at: z14.string()
|
|
1905
|
+
}).strict()
|
|
1906
|
+
]);
|
|
1907
|
+
var CaseRegistrySchema = z14.object({
|
|
1908
|
+
id: z14.string().regex(/^creg_/),
|
|
1909
|
+
projectId: z14.string().min(1),
|
|
1910
|
+
next: z14.object({ case: z14.number().int().positive(), feature: z14.number().int().positive() }).strict(),
|
|
1911
|
+
cases: z14.record(z14.string(), z14.number().int().positive()),
|
|
1912
|
+
features: z14.record(z14.string(), z14.number().int().positive()),
|
|
1913
|
+
reviews: z14.record(z14.string(), z14.object({ hash: z14.string(), by: z14.string().max(200), at: z14.string() }).strict()),
|
|
1914
|
+
/** Blocked and manual cases, by case id. Absent on a project with none. */
|
|
1915
|
+
standing: z14.record(z14.string(), CaseStandingSchema).optional(),
|
|
1916
|
+
updatedAt: z14.string()
|
|
1917
|
+
}).strict();
|
|
1918
|
+
var BranchChoiceSchema = z15.object({
|
|
1919
|
+
id: z15.string().regex(/^bch_/),
|
|
1920
|
+
projectId: z15.string().min(1),
|
|
1921
|
+
userId: z15.string().min(1),
|
|
1922
|
+
/** The branch (a change id), or null for the published version. */
|
|
1923
|
+
changeId: z15.string().min(1).max(100).nullable(),
|
|
1924
|
+
at: z15.string()
|
|
1925
|
+
}).strict();
|
|
1926
|
+
var TokensSchema = z16.object({
|
|
1927
|
+
/** Read fresh — the request, minus what came from the cache. */
|
|
1928
|
+
input: z16.number().int().nonnegative(),
|
|
1929
|
+
output: z16.number().int().nonnegative(),
|
|
1930
|
+
/** Read from the prompt cache: a tenth of the price of fresh input. */
|
|
1931
|
+
cacheRead: z16.number().int().nonnegative(),
|
|
1932
|
+
/** Written to the prompt cache, so later turns can read it cheaply. */
|
|
1933
|
+
cacheWrite: z16.number().int().nonnegative()
|
|
1934
|
+
}).strict();
|
|
1935
|
+
var USAGE_STAGES = ["write", "automate", "setup", "knowledge", "reply"];
|
|
1936
|
+
var USAGE_KINDS = ["automate", "reautomate", "heal", "update", "write", "explore", "chat"];
|
|
1937
|
+
function usageKindOf(shown) {
|
|
1938
|
+
const s = shown.trim();
|
|
1939
|
+
if (/^Heal\b/i.test(s)) return "heal";
|
|
1940
|
+
if (/^Re-automate\b/i.test(s)) return "reautomate";
|
|
1941
|
+
if (/^Automate\b/i.test(s)) return "automate";
|
|
1942
|
+
if (/business steps of .* (just )?changed/i.test(s) || /^Update\b/i.test(s)) return "update";
|
|
1943
|
+
if (/^Explore\b|\bexplore (the|this) (app|site|website)\b/i.test(s)) return "explore";
|
|
1944
|
+
if (/^(Write|Import)\b.*test cases?/i.test(s)) return "write";
|
|
1945
|
+
return "chat";
|
|
1946
|
+
}
|
|
1947
|
+
var UsagePartSchema = TokensSchema.extend({
|
|
1948
|
+
stage: z16.enum(USAGE_STAGES),
|
|
1949
|
+
/** The tool the turn called; null for a turn that called none. */
|
|
1950
|
+
tool: z16.string().max(80).nullable(),
|
|
1951
|
+
/** The test case the work was for, when one was being worked on. */
|
|
1952
|
+
caseName: z16.string().max(300).nullable(),
|
|
1953
|
+
folder: z16.string().max(300).nullable(),
|
|
1954
|
+
/** Its id — TC-12 — when the assistant named it by id. */
|
|
1955
|
+
caseKey: z16.string().max(40).nullable(),
|
|
1956
|
+
/** The flow being recorded, when one was. */
|
|
1957
|
+
flow: z16.string().max(300).nullable(),
|
|
1958
|
+
/** How many turns are in this line. */
|
|
1959
|
+
turns: z16.number().int().nonnegative()
|
|
1960
|
+
}).strict();
|
|
1961
|
+
var UsageRecordSchema = z16.object({
|
|
1962
|
+
id: z16.string().regex(/^use_/),
|
|
1963
|
+
projectId: z16.string().min(1),
|
|
1964
|
+
userId: z16.string().min(1),
|
|
1965
|
+
jobId: z16.string().min(1),
|
|
1966
|
+
kind: z16.enum(USAGE_KINDS),
|
|
1967
|
+
/** The start of what was asked, so a list of jobs reads as one. */
|
|
1968
|
+
asked: z16.string().max(200),
|
|
1969
|
+
platform: z16.enum(["web", "android", "ios"]).nullable(),
|
|
1970
|
+
startedAt: z16.string(),
|
|
1971
|
+
finishedAt: z16.string().nullable(),
|
|
1972
|
+
status: z16.enum(["running", "done", "failed", "stopped"]),
|
|
1973
|
+
/** Every turn added up, from the turns as they happened. */
|
|
1974
|
+
totals: TokensSchema,
|
|
1975
|
+
turns: z16.number().int().nonnegative(),
|
|
1976
|
+
/** What the model said the whole job cost, in US dollars — at API prices;
|
|
1977
|
+
* on a Claude plan it is what the same work would cost there. */
|
|
1978
|
+
costUsd: z16.number().nonnegative().nullable(),
|
|
1979
|
+
durationMs: z16.number().int().nonnegative().nullable(),
|
|
1980
|
+
parts: z16.array(UsagePartSchema).max(600)
|
|
1981
|
+
}).strict();
|
|
1982
|
+
var StepRenameSchema = z17.object({
|
|
1983
|
+
occurrenceId: z17.string().min(1).max(200),
|
|
1984
|
+
stepId: z17.string().min(1).max(200),
|
|
1985
|
+
from: z17.string().min(1).max(500),
|
|
1986
|
+
to: z17.string().trim().min(1).max(200)
|
|
1987
|
+
}).strict();
|
|
1869
1988
|
|
|
1870
1989
|
// ../engine-appium/dist/index.js
|
|
1871
|
-
import
|
|
1990
|
+
import path6 from "path";
|
|
1872
1991
|
|
|
1873
1992
|
// ../core/dist/index.js
|
|
1874
1993
|
function proposeAssertions(before, after) {
|
|
@@ -2201,9 +2320,9 @@ var STEP_PRESENTATION = {
|
|
|
2201
2320
|
};
|
|
2202
2321
|
|
|
2203
2322
|
// ../engine-appium/dist/index.js
|
|
2204
|
-
import path6 from "path";
|
|
2205
|
-
import fs5 from "fs";
|
|
2206
2323
|
import path7 from "path";
|
|
2324
|
+
import fs6 from "fs";
|
|
2325
|
+
import path8 from "path";
|
|
2207
2326
|
import { spawn as spawn3 } from "child_process";
|
|
2208
2327
|
var WebDriverError = class extends Error {
|
|
2209
2328
|
constructor(message, error, status) {
|
|
@@ -2215,10 +2334,10 @@ var WebDriverError = class extends Error {
|
|
|
2215
2334
|
status;
|
|
2216
2335
|
};
|
|
2217
2336
|
var ELEMENT_KEY = "element-6066-11e4-a52e-4f735466cecc";
|
|
2218
|
-
async function call(endpoint, method,
|
|
2337
|
+
async function call(endpoint, method, path9, body, timeoutMs = 12e4) {
|
|
2219
2338
|
const headers = { "content-type": "application/json; charset=utf-8" };
|
|
2220
2339
|
if (endpoint.username) headers["authorization"] = `Basic ${Buffer.from(`${endpoint.username}:${endpoint.password ?? ""}`).toString("base64")}`;
|
|
2221
|
-
const res = await fetch(`${endpoint.url.replace(/\/+$/, "")}${
|
|
2340
|
+
const res = await fetch(`${endpoint.url.replace(/\/+$/, "")}${path9}`, {
|
|
2222
2341
|
method,
|
|
2223
2342
|
headers,
|
|
2224
2343
|
...method === "POST" ? { body: JSON.stringify(body ?? {}) } : {},
|
|
@@ -2257,11 +2376,11 @@ var WebDriverSession = class _WebDriverSession {
|
|
|
2257
2376
|
return new _WebDriverSession(endpoint, value.sessionId, value.capabilities ?? {});
|
|
2258
2377
|
}
|
|
2259
2378
|
/** Any other command — Appium's own extensions live under /appium/. */
|
|
2260
|
-
raw(method,
|
|
2261
|
-
return this.req(method,
|
|
2379
|
+
raw(method, path9, body, timeoutMs) {
|
|
2380
|
+
return this.req(method, path9, body, timeoutMs);
|
|
2262
2381
|
}
|
|
2263
|
-
req(method,
|
|
2264
|
-
return call(this.endpoint, method, `/session/${this.id}${
|
|
2382
|
+
req(method, path9, body, timeoutMs) {
|
|
2383
|
+
return call(this.endpoint, method, `/session/${this.id}${path9}`, body, timeoutMs);
|
|
2265
2384
|
}
|
|
2266
2385
|
source() {
|
|
2267
2386
|
return this.req("GET", "/source");
|
|
@@ -2364,8 +2483,8 @@ function appiumPath() {
|
|
|
2364
2483
|
}
|
|
2365
2484
|
function javaMajor(home) {
|
|
2366
2485
|
try {
|
|
2367
|
-
const
|
|
2368
|
-
const v = /JAVA_VERSION="(\d+)(?:\.(\d+))?/.exec(
|
|
2486
|
+
const release2 = fs.readFileSync(path.join(home, "release"), "utf8");
|
|
2487
|
+
const v = /JAVA_VERSION="(\d+)(?:\.(\d+))?/.exec(release2);
|
|
2369
2488
|
if (!v) return 0;
|
|
2370
2489
|
return v[1] === "1" ? Number(v[2] ?? 0) : Number(v[1]);
|
|
2371
2490
|
} catch {
|
|
@@ -3288,6 +3407,88 @@ async function checkMobileAssertion(session, a, timeoutMs = 3e3) {
|
|
|
3288
3407
|
}
|
|
3289
3408
|
throw last;
|
|
3290
3409
|
}
|
|
3410
|
+
var held = /* @__PURE__ */ new Map();
|
|
3411
|
+
var lockDir = () => path4.join(process.env["FLOWRIKI_HOME"] || AGENT_HOME, "locks");
|
|
3412
|
+
var fileFor = (udid) => path4.join(lockDir(), `device-${udid.replace(/[^A-Za-z0-9._-]/g, "_")}.lock`);
|
|
3413
|
+
var alive = (pid) => {
|
|
3414
|
+
try {
|
|
3415
|
+
process.kill(pid, 0);
|
|
3416
|
+
return true;
|
|
3417
|
+
} catch (error) {
|
|
3418
|
+
return error.code === "EPERM";
|
|
3419
|
+
}
|
|
3420
|
+
};
|
|
3421
|
+
var DeviceInUse = class extends Error {
|
|
3422
|
+
constructor(device, pid) {
|
|
3423
|
+
super(`${device} is being used by another Flowriki job on this computer \u2014 wait for it to finish, or stop it first`);
|
|
3424
|
+
this.device = device;
|
|
3425
|
+
this.pid = pid;
|
|
3426
|
+
this.name = "DeviceInUse";
|
|
3427
|
+
}
|
|
3428
|
+
device;
|
|
3429
|
+
pid;
|
|
3430
|
+
};
|
|
3431
|
+
function lockDevice(udid, name = udid) {
|
|
3432
|
+
const file = fileFor(udid);
|
|
3433
|
+
const mine = held.get(file);
|
|
3434
|
+
if (mine) {
|
|
3435
|
+
held.set(file, mine + 1);
|
|
3436
|
+
return once(() => release(file));
|
|
3437
|
+
}
|
|
3438
|
+
fs4.mkdirSync(lockDir(), { recursive: true, mode: 448 });
|
|
3439
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
3440
|
+
try {
|
|
3441
|
+
const fd = fs4.openSync(file, "wx", 384);
|
|
3442
|
+
fs4.writeSync(fd, JSON.stringify({ pid: process.pid, at: Date.now() }));
|
|
3443
|
+
fs4.closeSync(fd);
|
|
3444
|
+
held.set(file, 1);
|
|
3445
|
+
return once(() => release(file));
|
|
3446
|
+
} catch (error) {
|
|
3447
|
+
if (error.code !== "EEXIST") throw error;
|
|
3448
|
+
let owner = 0;
|
|
3449
|
+
try {
|
|
3450
|
+
owner = Number(JSON.parse(fs4.readFileSync(file, "utf8")).pid) || 0;
|
|
3451
|
+
} catch {
|
|
3452
|
+
}
|
|
3453
|
+
if (owner && owner !== process.pid && alive(owner)) throw new DeviceInUse(name, owner);
|
|
3454
|
+
try {
|
|
3455
|
+
fs4.unlinkSync(file);
|
|
3456
|
+
} catch {
|
|
3457
|
+
}
|
|
3458
|
+
}
|
|
3459
|
+
}
|
|
3460
|
+
throw new DeviceInUse(name, 0);
|
|
3461
|
+
}
|
|
3462
|
+
function release(file) {
|
|
3463
|
+
const count = held.get(file) ?? 0;
|
|
3464
|
+
if (count > 1) {
|
|
3465
|
+
held.set(file, count - 1);
|
|
3466
|
+
return;
|
|
3467
|
+
}
|
|
3468
|
+
held.delete(file);
|
|
3469
|
+
try {
|
|
3470
|
+
const owner = Number(JSON.parse(fs4.readFileSync(file, "utf8")).pid);
|
|
3471
|
+
if (owner === process.pid) fs4.unlinkSync(file);
|
|
3472
|
+
} catch {
|
|
3473
|
+
}
|
|
3474
|
+
}
|
|
3475
|
+
function once(fn) {
|
|
3476
|
+
let done = false;
|
|
3477
|
+
return () => {
|
|
3478
|
+
if (!done) {
|
|
3479
|
+
done = true;
|
|
3480
|
+
fn();
|
|
3481
|
+
}
|
|
3482
|
+
};
|
|
3483
|
+
}
|
|
3484
|
+
process.once("exit", () => {
|
|
3485
|
+
for (const file of held.keys()) {
|
|
3486
|
+
try {
|
|
3487
|
+
fs4.unlinkSync(file);
|
|
3488
|
+
} catch {
|
|
3489
|
+
}
|
|
3490
|
+
}
|
|
3491
|
+
});
|
|
3291
3492
|
var uploaded = /* @__PURE__ */ new Map();
|
|
3292
3493
|
var OPEN = /* @__PURE__ */ new Map();
|
|
3293
3494
|
var openSessionOn = (platform) => OPEN.get(platform) ?? null;
|
|
@@ -3305,6 +3506,8 @@ var MobileSession = class _MobileSession {
|
|
|
3305
3506
|
deviceName;
|
|
3306
3507
|
cloud;
|
|
3307
3508
|
cached = null;
|
|
3509
|
+
/** Lets go of this phone for other Flowriki processes — see device-lock. */
|
|
3510
|
+
releaseDevice = null;
|
|
3308
3511
|
static async open(target, opts = {}) {
|
|
3309
3512
|
const app = appOf(target);
|
|
3310
3513
|
const say = opts.onProgress ?? (() => {
|
|
@@ -3326,11 +3529,11 @@ var MobileSession = class _MobileSession {
|
|
|
3326
3529
|
}
|
|
3327
3530
|
}
|
|
3328
3531
|
say(`Opening ${choice2.device} on ${CLOUD_PROVIDER_NAME[choice2.provider]}\u2026`);
|
|
3329
|
-
const { endpoint
|
|
3330
|
-
const
|
|
3532
|
+
const { endpoint, capabilities } = cloudCapabilities(target, account, key, appRef);
|
|
3533
|
+
const driver = await WebDriverSession.create(endpoint, capabilities).catch((e) => {
|
|
3331
3534
|
throw new Error(`${CLOUD_PROVIDER_NAME[choice2.provider]} could not open ${choice2.device}: ${e.message}`);
|
|
3332
3535
|
});
|
|
3333
|
-
const cloudSession = new _MobileSession(
|
|
3536
|
+
const cloudSession = new _MobileSession(driver, target.platform, app.id, choice2.device, true);
|
|
3334
3537
|
OPEN.set(target.platform, cloudSession);
|
|
3335
3538
|
await cloudSession.settle(2e4);
|
|
3336
3539
|
return cloudSession;
|
|
@@ -3342,23 +3545,30 @@ var MobileSession = class _MobileSession {
|
|
|
3342
3545
|
if (known.platform !== target.platform) throw new Error(`${known.name} is ${known.platform === "ios" ? "an iOS" : "an Android"} device; this flow runs on ${target.platform === "ios" ? "iOS" : "Android"}.`);
|
|
3343
3546
|
if (known.state !== "ready") say(`Starting ${known.name}\u2026`);
|
|
3344
3547
|
const udid = await bootDevice(known);
|
|
3345
|
-
const
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
const
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3548
|
+
const releaseDevice = lockDevice(udid, known.name);
|
|
3549
|
+
try {
|
|
3550
|
+
const endpoint = await appiumServer();
|
|
3551
|
+
const buildFile = app.build ? await localBuild(app.build) : null;
|
|
3552
|
+
say(`Opening the app on ${known.name}\u2026`);
|
|
3553
|
+
const ports = target.platform === "android" ? { system: await freePort(), mjpeg: await freePort() } : { wda: await freePort(), mjpeg: await freePort() };
|
|
3554
|
+
const activity = target.platform === "android" && !app.activity && !buildFile ? await launchActivityOf(udid, app.id) : null;
|
|
3555
|
+
const capabilities = await localCapabilities(target, udid, { buildFile, ports, ...activity ? { activity } : {} });
|
|
3556
|
+
const driver = await WebDriverSession.create(endpoint, capabilities).catch((e) => {
|
|
3557
|
+
const tail = appiumLog().filter((l) => /error/i.test(l)).slice(-2).join(" ");
|
|
3558
|
+
throw new Error(explainStartFailure(e.message, target.platform) + (tail ? ` (${tail.slice(0, 300)})` : ""));
|
|
3559
|
+
});
|
|
3560
|
+
const session = new _MobileSession(driver, target.platform, app.id, known.name, false);
|
|
3561
|
+
session.releaseDevice = releaseDevice;
|
|
3562
|
+
OPEN.set(target.platform, session);
|
|
3563
|
+
await session.terminateApp().catch(() => {
|
|
3564
|
+
});
|
|
3565
|
+
await session.launchApp().catch(() => {
|
|
3566
|
+
});
|
|
3567
|
+
return session;
|
|
3568
|
+
} catch (error) {
|
|
3569
|
+
releaseDevice();
|
|
3570
|
+
throw error;
|
|
3571
|
+
}
|
|
3362
3572
|
}
|
|
3363
3573
|
// ── Reading ────────────────────────────────────────────────────────────
|
|
3364
3574
|
async screen() {
|
|
@@ -3389,8 +3599,8 @@ var MobileSession = class _MobileSession {
|
|
|
3389
3599
|
async screenshot(file) {
|
|
3390
3600
|
const png = Buffer.from(await this.driver.screenshot(), "base64");
|
|
3391
3601
|
if (file) {
|
|
3392
|
-
|
|
3393
|
-
|
|
3602
|
+
fs5.mkdirSync(path5.dirname(file), { recursive: true });
|
|
3603
|
+
fs5.writeFileSync(file, png);
|
|
3394
3604
|
}
|
|
3395
3605
|
return png;
|
|
3396
3606
|
}
|
|
@@ -3591,14 +3801,19 @@ var MobileSession = class _MobileSession {
|
|
|
3591
3801
|
const b64 = await this.driver.raw("POST", "/appium/stop_recording_screen", { options: {} }, 12e4).catch(() => "");
|
|
3592
3802
|
if (!file || !b64) return null;
|
|
3593
3803
|
const bytes = Buffer.from(b64, "base64");
|
|
3594
|
-
|
|
3595
|
-
|
|
3596
|
-
return { relPath:
|
|
3804
|
+
fs5.mkdirSync(path5.dirname(file), { recursive: true });
|
|
3805
|
+
fs5.writeFileSync(file, bytes);
|
|
3806
|
+
return { relPath: path5.basename(file), bytes: bytes.length };
|
|
3597
3807
|
}
|
|
3598
3808
|
async close() {
|
|
3599
3809
|
if (OPEN.get(this.platform) === this) OPEN.delete(this.platform);
|
|
3600
|
-
|
|
3601
|
-
|
|
3810
|
+
try {
|
|
3811
|
+
if (this.recording) await this.stopVideo(null);
|
|
3812
|
+
await this.driver.close();
|
|
3813
|
+
} finally {
|
|
3814
|
+
this.releaseDevice?.();
|
|
3815
|
+
this.releaseDevice = null;
|
|
3816
|
+
}
|
|
3602
3817
|
}
|
|
3603
3818
|
};
|
|
3604
3819
|
async function launchActivityOf(udid, appPackage) {
|
|
@@ -3614,18 +3829,18 @@ async function launchActivityOf(udid, appPackage) {
|
|
|
3614
3829
|
}
|
|
3615
3830
|
async function shrink(png, maxPx) {
|
|
3616
3831
|
if (process.platform !== "darwin") return png;
|
|
3617
|
-
const dir =
|
|
3618
|
-
const file =
|
|
3832
|
+
const dir = fs5.mkdtempSync(path5.join(os4.tmpdir(), "flowriki-shot-"));
|
|
3833
|
+
const file = path5.join(dir, "s.png");
|
|
3619
3834
|
try {
|
|
3620
|
-
|
|
3835
|
+
fs5.writeFileSync(file, png);
|
|
3621
3836
|
await new Promise((resolve, reject) => {
|
|
3622
3837
|
execFile2("/usr/bin/sips", ["-Z", String(maxPx), file], { timeout: 5e3 }, (error) => error ? reject(error) : resolve());
|
|
3623
3838
|
});
|
|
3624
|
-
return
|
|
3839
|
+
return fs5.readFileSync(file);
|
|
3625
3840
|
} catch {
|
|
3626
3841
|
return png;
|
|
3627
3842
|
} finally {
|
|
3628
|
-
|
|
3843
|
+
fs5.rmSync(dir, { recursive: true, force: true });
|
|
3629
3844
|
}
|
|
3630
3845
|
}
|
|
3631
3846
|
var ANDROID_KEYS = {
|
|
@@ -3885,7 +4100,7 @@ async function runMobileSteps(session, steps, opts) {
|
|
|
3885
4100
|
if (opts.evidenceDir && (shots === "always" || shots === "on-failure" && result.status === "failed")) {
|
|
3886
4101
|
const file = `step-${String((opts.stepOffset ?? 0) + results.length + 1).padStart(2, "0")}.png`;
|
|
3887
4102
|
try {
|
|
3888
|
-
await session.screenshot(
|
|
4103
|
+
await session.screenshot(path6.join(opts.evidenceDir, file));
|
|
3889
4104
|
result.evidence = { screenshot: file, url: await session.currentScreenName(), title: session.deviceName };
|
|
3890
4105
|
} catch {
|
|
3891
4106
|
}
|
|
@@ -3909,7 +4124,7 @@ var typesSecrets = (steps) => steps.some((s) => s.secret || Boolean(s.secretRef)
|
|
|
3909
4124
|
var filmsThis = (policy, steps, evidenceDir) => Boolean(evidenceDir) && Boolean(policy) && policy.mode !== "off" && (policy.allowUnmasked === true || !typesSecrets(steps));
|
|
3910
4125
|
async function finishVideo(session, policy, evidenceDir, passed, name = "phone") {
|
|
3911
4126
|
const keep = Boolean(evidenceDir) && (policy?.mode === "always" || policy?.mode === "on-failure" && !passed);
|
|
3912
|
-
const saved = await session.stopVideo(keep ?
|
|
4127
|
+
const saved = await session.stopVideo(keep ? path7.join(evidenceDir, `video-${name}.mp4`) : null).catch(() => null);
|
|
3913
4128
|
return saved ? [{ kind: "video", relPath: saved.relPath, bytes: saved.bytes }] : [];
|
|
3914
4129
|
}
|
|
3915
4130
|
async function chooseDevice(platform, mobile, asked, where) {
|
|
@@ -4038,20 +4253,20 @@ function stream(file, args, env, cwd, say) {
|
|
|
4038
4253
|
child.stdout.on("data", each);
|
|
4039
4254
|
child.stderr.on("data", each);
|
|
4040
4255
|
child.on("error", reject);
|
|
4041
|
-
child.on("close", (code) => code === 0 ? resolve() : reject(new Error(`${
|
|
4256
|
+
child.on("close", (code) => code === 0 ? resolve() : reject(new Error(`${path8.basename(file)} ${args.join(" ")} failed (${code})`)));
|
|
4042
4257
|
});
|
|
4043
4258
|
}
|
|
4044
4259
|
async function mobileSetup(say = console.log) {
|
|
4045
4260
|
const npm = onPath("npm");
|
|
4046
4261
|
if (!npm) throw new Error("npm was not found \u2014 install Node.js 20 or newer.");
|
|
4047
|
-
|
|
4048
|
-
if (!
|
|
4049
|
-
|
|
4262
|
+
fs6.mkdirSync(path8.join(APPIUM_DIR, "home"), { recursive: true });
|
|
4263
|
+
if (!fs6.existsSync(path8.join(APPIUM_DIR, "package.json"))) {
|
|
4264
|
+
fs6.writeFileSync(path8.join(APPIUM_DIR, "package.json"), JSON.stringify({ name: "flowriki-appium", private: true }, null, 2));
|
|
4050
4265
|
}
|
|
4051
|
-
const env = { ...await toolEnv(), APPIUM_HOME:
|
|
4266
|
+
const env = { ...await toolEnv(), APPIUM_HOME: path8.join(APPIUM_DIR, "home") };
|
|
4052
4267
|
say("Installing Appium\u2026");
|
|
4053
4268
|
await stream(npm, ["install", "--no-audit", "--no-fund", "appium@2"], env, APPIUM_DIR, say);
|
|
4054
|
-
const bin =
|
|
4269
|
+
const bin = path8.join(APPIUM_DIR, "node_modules", ".bin", process.platform === "win32" ? "appium.cmd" : "appium");
|
|
4055
4270
|
const have = await installedDrivers();
|
|
4056
4271
|
const wanted = ["uiautomator2", ...process.platform === "darwin" ? ["xcuitest"] : []];
|
|
4057
4272
|
for (const driver of wanted) {
|
|
@@ -4245,6 +4460,7 @@ export {
|
|
|
4245
4460
|
WEB_CLOUD_BROWSER_NAME,
|
|
4246
4461
|
webCloudOf,
|
|
4247
4462
|
nameKey,
|
|
4463
|
+
usageKindOf,
|
|
4248
4464
|
proposeAssertions,
|
|
4249
4465
|
hasObservableChange,
|
|
4250
4466
|
normalizeStartUrl,
|
|
@@ -4291,6 +4507,8 @@ export {
|
|
|
4291
4507
|
CheckError,
|
|
4292
4508
|
textOf,
|
|
4293
4509
|
checkMobileAssertion,
|
|
4510
|
+
DeviceInUse,
|
|
4511
|
+
lockDevice,
|
|
4294
4512
|
openSessionOn,
|
|
4295
4513
|
MobileSession,
|
|
4296
4514
|
shrink,
|