@yoqa/runner 1.0.2 → 1.0.3
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/index.js +1756 -419
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -10112,7 +10112,7 @@ var require_bignumber = __commonJS((exports, module) => {
|
|
|
10112
10112
|
return arr.reverse();
|
|
10113
10113
|
}
|
|
10114
10114
|
return function(str, baseIn, baseOut, sign, callerIsToString) {
|
|
10115
|
-
var alphabet, d, e, k, r, x, xc, y, i = str.indexOf("."), dp = DECIMAL_PLACES,
|
|
10115
|
+
var alphabet, d, e, k, r, x, xc, y, i = str.indexOf("."), dp = DECIMAL_PLACES, rm6 = ROUNDING_MODE;
|
|
10116
10116
|
if (i >= 0) {
|
|
10117
10117
|
k = POW_PRECISION;
|
|
10118
10118
|
POW_PRECISION = 0;
|
|
@@ -10135,7 +10135,7 @@ var require_bignumber = __commonJS((exports, module) => {
|
|
|
10135
10135
|
x.c = xc;
|
|
10136
10136
|
x.e = e;
|
|
10137
10137
|
x.s = sign;
|
|
10138
|
-
x = div(x, y, dp,
|
|
10138
|
+
x = div(x, y, dp, rm6, baseOut);
|
|
10139
10139
|
xc = x.c;
|
|
10140
10140
|
r = x.r;
|
|
10141
10141
|
e = x.e;
|
|
@@ -10144,7 +10144,7 @@ var require_bignumber = __commonJS((exports, module) => {
|
|
|
10144
10144
|
i = xc[d];
|
|
10145
10145
|
k = baseOut / 2;
|
|
10146
10146
|
r = r || d < 0 || xc[d + 1] != null;
|
|
10147
|
-
r =
|
|
10147
|
+
r = rm6 < 4 ? (i != null || r) && (rm6 == 0 || rm6 == (x.s < 0 ? 3 : 2)) : i > k || i == k && (rm6 == 4 || r || rm6 == 6 && xc[d - 1] & 1 || rm6 == (x.s < 0 ? 8 : 7));
|
|
10148
10148
|
if (d < 1 || !xc[0]) {
|
|
10149
10149
|
str = r ? toFixedPoint(alphabet.charAt(1), -dp, alphabet.charAt(0)) : alphabet.charAt(0);
|
|
10150
10150
|
} else {
|
|
@@ -10206,7 +10206,7 @@ var require_bignumber = __commonJS((exports, module) => {
|
|
|
10206
10206
|
for (;!a[0] && a.length > 1; a.splice(0, 1))
|
|
10207
10207
|
;
|
|
10208
10208
|
}
|
|
10209
|
-
return function(x, y, dp,
|
|
10209
|
+
return function(x, y, dp, rm6, base) {
|
|
10210
10210
|
var cmp, e, i, more, n, prod, prodL, q, qc, rem, remL, rem0, xi, xL, yc0, yL, yz, s = x.s == y.s ? 1 : -1, xc = x.c, yc = y.c;
|
|
10211
10211
|
if (!xc || !xc[0] || !yc || !yc[0]) {
|
|
10212
10212
|
return new BigNumber2(!x.s || !y.s || (xc ? yc && xc[0] == yc[0] : !yc) ? NaN : xc && xc[0] == 0 || !yc ? s * 0 : s / 0);
|
|
@@ -10306,7 +10306,7 @@ var require_bignumber = __commonJS((exports, module) => {
|
|
|
10306
10306
|
if (base == BASE) {
|
|
10307
10307
|
for (i = 1, s = qc[0];s >= 10; s /= 10, i++)
|
|
10308
10308
|
;
|
|
10309
|
-
round(q, dp + (q.e = i + e * LOG_BASE - 1) + 1,
|
|
10309
|
+
round(q, dp + (q.e = i + e * LOG_BASE - 1) + 1, rm6, more);
|
|
10310
10310
|
} else {
|
|
10311
10311
|
q.e = e;
|
|
10312
10312
|
q.r = +more;
|
|
@@ -10314,12 +10314,12 @@ var require_bignumber = __commonJS((exports, module) => {
|
|
|
10314
10314
|
return q;
|
|
10315
10315
|
};
|
|
10316
10316
|
}();
|
|
10317
|
-
function format(n, i,
|
|
10317
|
+
function format(n, i, rm6, id) {
|
|
10318
10318
|
var c0, e, ne2, len, str;
|
|
10319
|
-
if (
|
|
10320
|
-
|
|
10319
|
+
if (rm6 == null)
|
|
10320
|
+
rm6 = ROUNDING_MODE;
|
|
10321
10321
|
else
|
|
10322
|
-
intCheck(
|
|
10322
|
+
intCheck(rm6, 0, 8);
|
|
10323
10323
|
if (!n.c)
|
|
10324
10324
|
return n.toString();
|
|
10325
10325
|
c0 = n.c[0];
|
|
@@ -10328,7 +10328,7 @@ var require_bignumber = __commonJS((exports, module) => {
|
|
|
10328
10328
|
str = coeffToString(n.c);
|
|
10329
10329
|
str = id == 1 || id == 2 && (ne2 <= TO_EXP_NEG || ne2 >= TO_EXP_POS) ? toExponential(str, ne2) : toFixedPoint(str, ne2, "0");
|
|
10330
10330
|
} else {
|
|
10331
|
-
n = round(new BigNumber2(n), i,
|
|
10331
|
+
n = round(new BigNumber2(n), i, rm6);
|
|
10332
10332
|
e = n.e;
|
|
10333
10333
|
str = coeffToString(n.c);
|
|
10334
10334
|
len = str.length;
|
|
@@ -10409,7 +10409,7 @@ var require_bignumber = __commonJS((exports, module) => {
|
|
|
10409
10409
|
x.c = x.e = null;
|
|
10410
10410
|
};
|
|
10411
10411
|
}();
|
|
10412
|
-
function round(x, sd,
|
|
10412
|
+
function round(x, sd, rm6, r) {
|
|
10413
10413
|
var d, i, j, k, n, ni, rd, xc = x.c, pows10 = POWS_TEN;
|
|
10414
10414
|
if (xc) {
|
|
10415
10415
|
out: {
|
|
@@ -10444,7 +10444,7 @@ var require_bignumber = __commonJS((exports, module) => {
|
|
|
10444
10444
|
}
|
|
10445
10445
|
}
|
|
10446
10446
|
r = r || sd < 0 || xc[ni + 1] != null || (j < 0 ? n : n % pows10[d - j - 1]);
|
|
10447
|
-
r =
|
|
10447
|
+
r = rm6 < 4 ? (rd || r) && (rm6 == 0 || rm6 == (x.s < 0 ? 3 : 2)) : rd > 5 || rd == 5 && (rm6 == 4 || r || rm6 == 6 && (i > 0 ? j > 0 ? n / pows10[d - j] : 0 : xc[ni - 1]) % 10 & 1 || rm6 == (x.s < 0 ? 8 : 7));
|
|
10448
10448
|
if (sd < 1 || !xc[0]) {
|
|
10449
10449
|
xc.length = 0;
|
|
10450
10450
|
if (r) {
|
|
@@ -10516,15 +10516,15 @@ var require_bignumber = __commonJS((exports, module) => {
|
|
|
10516
10516
|
P.comparedTo = function(y, b) {
|
|
10517
10517
|
return compare(this, new BigNumber2(y, b));
|
|
10518
10518
|
};
|
|
10519
|
-
P.decimalPlaces = P.dp = function(dp,
|
|
10519
|
+
P.decimalPlaces = P.dp = function(dp, rm6) {
|
|
10520
10520
|
var c, n, v, x = this;
|
|
10521
10521
|
if (dp != null) {
|
|
10522
10522
|
intCheck(dp, 0, MAX);
|
|
10523
|
-
if (
|
|
10524
|
-
|
|
10523
|
+
if (rm6 == null)
|
|
10524
|
+
rm6 = ROUNDING_MODE;
|
|
10525
10525
|
else
|
|
10526
|
-
intCheck(
|
|
10527
|
-
return round(new BigNumber2(x), dp + x.e + 1,
|
|
10526
|
+
intCheck(rm6, 0, 8);
|
|
10527
|
+
return round(new BigNumber2(x), dp + x.e + 1, rm6);
|
|
10528
10528
|
}
|
|
10529
10529
|
if (!(c = x.c))
|
|
10530
10530
|
return null;
|
|
@@ -10623,13 +10623,13 @@ var require_bignumber = __commonJS((exports, module) => {
|
|
|
10623
10623
|
y = ONE.div(y);
|
|
10624
10624
|
return m ? y.mod(m) : k ? round(y, POW_PRECISION, ROUNDING_MODE, more) : y;
|
|
10625
10625
|
};
|
|
10626
|
-
P.integerValue = function(
|
|
10626
|
+
P.integerValue = function(rm6) {
|
|
10627
10627
|
var n = new BigNumber2(this);
|
|
10628
|
-
if (
|
|
10629
|
-
|
|
10628
|
+
if (rm6 == null)
|
|
10629
|
+
rm6 = ROUNDING_MODE;
|
|
10630
10630
|
else
|
|
10631
|
-
intCheck(
|
|
10632
|
-
return round(n, n.e + 1,
|
|
10631
|
+
intCheck(rm6, 0, 8);
|
|
10632
|
+
return round(n, n.e + 1, rm6);
|
|
10633
10633
|
};
|
|
10634
10634
|
P.isEqualTo = P.eq = function(y, b) {
|
|
10635
10635
|
return compare(this, new BigNumber2(y, b)) === 0;
|
|
@@ -10866,15 +10866,15 @@ var require_bignumber = __commonJS((exports, module) => {
|
|
|
10866
10866
|
}
|
|
10867
10867
|
return normalise(y, xc, ye);
|
|
10868
10868
|
};
|
|
10869
|
-
P.precision = P.sd = function(sd,
|
|
10869
|
+
P.precision = P.sd = function(sd, rm6) {
|
|
10870
10870
|
var c, n, v, x = this;
|
|
10871
10871
|
if (sd != null && sd !== !!sd) {
|
|
10872
10872
|
intCheck(sd, 1, MAX);
|
|
10873
|
-
if (
|
|
10874
|
-
|
|
10873
|
+
if (rm6 == null)
|
|
10874
|
+
rm6 = ROUNDING_MODE;
|
|
10875
10875
|
else
|
|
10876
|
-
intCheck(
|
|
10877
|
-
return round(new BigNumber2(x), sd,
|
|
10876
|
+
intCheck(rm6, 0, 8);
|
|
10877
|
+
return round(new BigNumber2(x), sd, rm6);
|
|
10878
10878
|
}
|
|
10879
10879
|
if (!(c = x.c))
|
|
10880
10880
|
return null;
|
|
@@ -10951,36 +10951,36 @@ var require_bignumber = __commonJS((exports, module) => {
|
|
|
10951
10951
|
}
|
|
10952
10952
|
return round(r, r.e + DECIMAL_PLACES + 1, ROUNDING_MODE, m);
|
|
10953
10953
|
};
|
|
10954
|
-
P.toExponential = function(dp,
|
|
10954
|
+
P.toExponential = function(dp, rm6) {
|
|
10955
10955
|
if (dp != null) {
|
|
10956
10956
|
intCheck(dp, 0, MAX);
|
|
10957
10957
|
dp++;
|
|
10958
10958
|
}
|
|
10959
|
-
return format(this, dp,
|
|
10959
|
+
return format(this, dp, rm6, 1);
|
|
10960
10960
|
};
|
|
10961
|
-
P.toFixed = function(dp,
|
|
10961
|
+
P.toFixed = function(dp, rm6) {
|
|
10962
10962
|
if (dp != null) {
|
|
10963
10963
|
intCheck(dp, 0, MAX);
|
|
10964
10964
|
dp = dp + this.e + 1;
|
|
10965
10965
|
}
|
|
10966
|
-
return format(this, dp,
|
|
10966
|
+
return format(this, dp, rm6);
|
|
10967
10967
|
};
|
|
10968
|
-
P.toFormat = function(dp,
|
|
10968
|
+
P.toFormat = function(dp, rm6, format2) {
|
|
10969
10969
|
var str, x = this;
|
|
10970
10970
|
if (format2 == null) {
|
|
10971
|
-
if (dp != null &&
|
|
10972
|
-
format2 =
|
|
10973
|
-
|
|
10971
|
+
if (dp != null && rm6 && typeof rm6 == "object") {
|
|
10972
|
+
format2 = rm6;
|
|
10973
|
+
rm6 = null;
|
|
10974
10974
|
} else if (dp && typeof dp == "object") {
|
|
10975
10975
|
format2 = dp;
|
|
10976
|
-
dp =
|
|
10976
|
+
dp = rm6 = null;
|
|
10977
10977
|
} else {
|
|
10978
10978
|
format2 = FORMAT;
|
|
10979
10979
|
}
|
|
10980
10980
|
} else if (typeof format2 != "object") {
|
|
10981
10981
|
throw Error(bignumberError + "Argument not an object: " + format2);
|
|
10982
10982
|
}
|
|
10983
|
-
str = x.toFixed(dp,
|
|
10983
|
+
str = x.toFixed(dp, rm6);
|
|
10984
10984
|
if (x.c) {
|
|
10985
10985
|
var i, arr = str.split("."), g1 = +format2.groupSize, g2 = +format2.secondaryGroupSize, groupSeparator = format2.groupSeparator || "", intPart = arr[0], fractionPart = arr[1], isNeg = x.s < 0, intDigits = isNeg ? intPart.slice(1) : intPart, len = intDigits.length;
|
|
10986
10986
|
if (g2) {
|
|
@@ -11048,10 +11048,10 @@ var require_bignumber = __commonJS((exports, module) => {
|
|
|
11048
11048
|
P.toNumber = function() {
|
|
11049
11049
|
return +valueOf(this);
|
|
11050
11050
|
};
|
|
11051
|
-
P.toPrecision = function(sd,
|
|
11051
|
+
P.toPrecision = function(sd, rm6) {
|
|
11052
11052
|
if (sd != null)
|
|
11053
11053
|
intCheck(sd, 1, MAX);
|
|
11054
|
-
return format(this, sd,
|
|
11054
|
+
return format(this, sd, rm6, 2);
|
|
11055
11055
|
};
|
|
11056
11056
|
P.toString = function(b) {
|
|
11057
11057
|
var str, n = this, s = n.s, e = n.e;
|
|
@@ -14526,7 +14526,7 @@ var require_getCredentials = __commonJS((exports) => {
|
|
|
14526
14526
|
var fs = __require("fs");
|
|
14527
14527
|
var util_1 = __require("util");
|
|
14528
14528
|
var errorWithCode_1 = require_errorWithCode();
|
|
14529
|
-
var
|
|
14529
|
+
var readFile4 = fs.readFile ? (0, util_1.promisify)(fs.readFile) : async () => {
|
|
14530
14530
|
throw new errorWithCode_1.ErrorWithCode("use key rather than keyFile.", "MISSING_CREDENTIALS");
|
|
14531
14531
|
};
|
|
14532
14532
|
var ExtensionFiles;
|
|
@@ -14545,7 +14545,7 @@ var require_getCredentials = __commonJS((exports) => {
|
|
|
14545
14545
|
this.keyFilePath = keyFilePath;
|
|
14546
14546
|
}
|
|
14547
14547
|
async getCredentials() {
|
|
14548
|
-
const key = await
|
|
14548
|
+
const key = await readFile4(this.keyFilePath, "utf8");
|
|
14549
14549
|
let body;
|
|
14550
14550
|
try {
|
|
14551
14551
|
body = JSON.parse(key);
|
|
@@ -14568,7 +14568,7 @@ var require_getCredentials = __commonJS((exports) => {
|
|
|
14568
14568
|
this.keyFilePath = keyFilePath;
|
|
14569
14569
|
}
|
|
14570
14570
|
async getCredentials() {
|
|
14571
|
-
const privateKey = await
|
|
14571
|
+
const privateKey = await readFile4(this.keyFilePath, "utf8");
|
|
14572
14572
|
return { privateKey };
|
|
14573
14573
|
}
|
|
14574
14574
|
}
|
|
@@ -15766,7 +15766,7 @@ var require_filesubjecttokensupplier = __commonJS((exports) => {
|
|
|
15766
15766
|
exports.FileSubjectTokenSupplier = undefined;
|
|
15767
15767
|
var util_1 = __require("util");
|
|
15768
15768
|
var fs = __require("fs");
|
|
15769
|
-
var
|
|
15769
|
+
var readFile4 = (0, util_1.promisify)(fs.readFile ?? (() => {}));
|
|
15770
15770
|
var realpath = (0, util_1.promisify)(fs.realpath ?? (() => {}));
|
|
15771
15771
|
var lstat = (0, util_1.promisify)(fs.lstat ?? (() => {}));
|
|
15772
15772
|
|
|
@@ -15793,7 +15793,7 @@ var require_filesubjecttokensupplier = __commonJS((exports) => {
|
|
|
15793
15793
|
throw err;
|
|
15794
15794
|
}
|
|
15795
15795
|
let subjectToken;
|
|
15796
|
-
const rawText = await
|
|
15796
|
+
const rawText = await readFile4(parsedFilePath, { encoding: "utf8" });
|
|
15797
15797
|
if (this.formatType === "text") {
|
|
15798
15798
|
subjectToken = rawText;
|
|
15799
15799
|
} else if (this.formatType === "json" && this.subjectTokenFieldName) {
|
|
@@ -22280,6 +22280,7 @@ var apps = sqliteTable("apps", {
|
|
|
22280
22280
|
iosBundleId: text("ios_bundle_id").notNull().default(""),
|
|
22281
22281
|
iosAppStoreId: text("ios_app_store_id").notNull().default(""),
|
|
22282
22282
|
androidApplicationId: text("android_application_id").notNull().default(""),
|
|
22283
|
+
webUrl: text("web_url").notNull().default(""),
|
|
22283
22284
|
appiumCaps: text("appium_caps").notNull().default("[]"),
|
|
22284
22285
|
createdAt: integer("created_at").notNull(),
|
|
22285
22286
|
updatedAt: integer("updated_at").notNull()
|
|
@@ -22338,6 +22339,8 @@ var runs = sqliteTable("runs", {
|
|
|
22338
22339
|
requestedLane: text("requested_lane"),
|
|
22339
22340
|
lane: text("lane"),
|
|
22340
22341
|
laneWarning: text("lane_warning"),
|
|
22342
|
+
browser: text("browser"),
|
|
22343
|
+
viewport: text("viewport"),
|
|
22341
22344
|
recordVideo: integer("record_video").notNull().default(0),
|
|
22342
22345
|
error: text("error"),
|
|
22343
22346
|
createdAt: integer("created_at").notNull(),
|
|
@@ -22389,6 +22392,7 @@ function ensureSchema(sqlite) {
|
|
|
22389
22392
|
ios_bundle_id TEXT NOT NULL DEFAULT '',
|
|
22390
22393
|
ios_app_store_id TEXT NOT NULL DEFAULT '',
|
|
22391
22394
|
android_application_id TEXT NOT NULL DEFAULT '',
|
|
22395
|
+
web_url TEXT NOT NULL DEFAULT '',
|
|
22392
22396
|
appium_caps TEXT NOT NULL DEFAULT '[]',
|
|
22393
22397
|
created_at INTEGER NOT NULL,
|
|
22394
22398
|
updated_at INTEGER NOT NULL
|
|
@@ -22516,6 +22520,7 @@ function ensureSchema(sqlite) {
|
|
|
22516
22520
|
migrateProvidersTable(sqlite);
|
|
22517
22521
|
migrateAppsPrefix(sqlite);
|
|
22518
22522
|
migrateAppsKnowledge(sqlite);
|
|
22523
|
+
migrateAppsWebUrl(sqlite);
|
|
22519
22524
|
migrateCaseScripts(sqlite);
|
|
22520
22525
|
migrateRunExecutionMode(sqlite);
|
|
22521
22526
|
migrateRunScreenMode(sqlite);
|
|
@@ -22558,6 +22563,8 @@ function migrateRunLane(sqlite) {
|
|
|
22558
22563
|
addColumnIfMissing(sqlite, "runs", "requested_lane", "requested_lane TEXT", runCols);
|
|
22559
22564
|
addColumnIfMissing(sqlite, "runs", "lane", "lane TEXT", runCols);
|
|
22560
22565
|
addColumnIfMissing(sqlite, "runs", "lane_warning", "lane_warning TEXT", runCols);
|
|
22566
|
+
addColumnIfMissing(sqlite, "runs", "browser", "browser TEXT", runCols);
|
|
22567
|
+
addColumnIfMissing(sqlite, "runs", "viewport", "viewport TEXT", runCols);
|
|
22561
22568
|
}
|
|
22562
22569
|
function migrateRecording(sqlite) {
|
|
22563
22570
|
const caseCols = tableColumns(sqlite, "cases");
|
|
@@ -22684,6 +22691,12 @@ function migrateAppsKnowledge(sqlite) {
|
|
|
22684
22691
|
return;
|
|
22685
22692
|
addColumnIfMissing(sqlite, "apps", "knowledge", "knowledge TEXT NOT NULL DEFAULT ''", existing);
|
|
22686
22693
|
}
|
|
22694
|
+
function migrateAppsWebUrl(sqlite) {
|
|
22695
|
+
const existing = tableColumns(sqlite, "apps");
|
|
22696
|
+
if (existing.size === 0)
|
|
22697
|
+
return;
|
|
22698
|
+
addColumnIfMissing(sqlite, "apps", "web_url", "web_url TEXT NOT NULL DEFAULT ''", existing);
|
|
22699
|
+
}
|
|
22687
22700
|
function getCatalogDbPath() {
|
|
22688
22701
|
return process.env.YOQA_DB_PATH ?? DEFAULT_DB_PATH;
|
|
22689
22702
|
}
|
|
@@ -26688,13 +26701,20 @@ var healthResponseSchema = exports_external.object({
|
|
|
26688
26701
|
version: exports_external.string(),
|
|
26689
26702
|
uptimeMs: exports_external.number().nonnegative()
|
|
26690
26703
|
});
|
|
26691
|
-
var devicePlatformSchema = exports_external.union([
|
|
26704
|
+
var devicePlatformSchema = exports_external.union([
|
|
26705
|
+
exports_external.literal("ios"),
|
|
26706
|
+
exports_external.literal("android"),
|
|
26707
|
+
exports_external.literal("web")
|
|
26708
|
+
]);
|
|
26692
26709
|
var laneNameSchema = exports_external.literal("direct");
|
|
26693
26710
|
var deviceKindSchema = exports_external.union([
|
|
26694
26711
|
exports_external.literal("physical"),
|
|
26695
26712
|
exports_external.literal("simulator"),
|
|
26696
|
-
exports_external.literal("emulator")
|
|
26713
|
+
exports_external.literal("emulator"),
|
|
26714
|
+
exports_external.literal("browser")
|
|
26697
26715
|
]);
|
|
26716
|
+
var VIEWPORT_PRESET_NAMES = ["desktop", "mobile"];
|
|
26717
|
+
var viewportPresetSchema = exports_external.enum(VIEWPORT_PRESET_NAMES);
|
|
26698
26718
|
var deviceSchema = exports_external.object({
|
|
26699
26719
|
id: exports_external.string().min(1),
|
|
26700
26720
|
name: exports_external.string().min(1),
|
|
@@ -26834,13 +26854,15 @@ var catalogAppSchema = exports_external.object({
|
|
|
26834
26854
|
iosBundleId: exports_external.string(),
|
|
26835
26855
|
iosAppStoreId: exports_external.string(),
|
|
26836
26856
|
androidApplicationId: exports_external.string(),
|
|
26857
|
+
webUrl: exports_external.string().default(""),
|
|
26837
26858
|
capabilities: exports_external.array(capabilitySchema),
|
|
26838
26859
|
createdAt: exports_external.number().int().nonnegative(),
|
|
26839
26860
|
updatedAt: exports_external.number().int().nonnegative()
|
|
26840
26861
|
});
|
|
26841
26862
|
var createAppRequestSchema = exports_external.object({
|
|
26842
26863
|
name: exports_external.string().min(1),
|
|
26843
|
-
prefix: exports_external.string().min(1).optional()
|
|
26864
|
+
prefix: exports_external.string().min(1).optional(),
|
|
26865
|
+
webUrl: exports_external.string().optional()
|
|
26844
26866
|
});
|
|
26845
26867
|
var updateAppRequestSchema = exports_external.object({
|
|
26846
26868
|
name: exports_external.string().min(1).optional(),
|
|
@@ -26850,6 +26872,7 @@ var updateAppRequestSchema = exports_external.object({
|
|
|
26850
26872
|
iosBundleId: exports_external.string().optional(),
|
|
26851
26873
|
iosAppStoreId: exports_external.string().optional(),
|
|
26852
26874
|
androidApplicationId: exports_external.string().optional(),
|
|
26875
|
+
webUrl: exports_external.string().optional(),
|
|
26853
26876
|
capabilities: exports_external.array(capabilitySchema).optional()
|
|
26854
26877
|
});
|
|
26855
26878
|
var listAppsResponseSchema = exports_external.object({
|
|
@@ -26898,6 +26921,25 @@ var caseScriptDragSchema = exports_external.object({
|
|
|
26898
26921
|
durationMs: exports_external.number().min(50).max(5000).optional(),
|
|
26899
26922
|
reason: exports_external.string().optional()
|
|
26900
26923
|
});
|
|
26924
|
+
var KEY_NAMES = [
|
|
26925
|
+
"Enter",
|
|
26926
|
+
"Tab",
|
|
26927
|
+
"Escape",
|
|
26928
|
+
"Backspace",
|
|
26929
|
+
"Delete",
|
|
26930
|
+
"ArrowUp",
|
|
26931
|
+
"ArrowDown",
|
|
26932
|
+
"ArrowLeft",
|
|
26933
|
+
"ArrowRight",
|
|
26934
|
+
"Home",
|
|
26935
|
+
"End",
|
|
26936
|
+
"PageUp",
|
|
26937
|
+
"PageDown",
|
|
26938
|
+
"Space"
|
|
26939
|
+
];
|
|
26940
|
+
var keyNameSchema = exports_external.enum(KEY_NAMES);
|
|
26941
|
+
var KEY_MODIFIERS = ["Shift", "Control", "Alt", "Meta"];
|
|
26942
|
+
var keyModifierSchema = exports_external.enum(KEY_MODIFIERS);
|
|
26901
26943
|
function caseScriptAppIdSchema(type) {
|
|
26902
26944
|
return exports_external.object({
|
|
26903
26945
|
type: exports_external.literal(type),
|
|
@@ -26905,60 +26947,99 @@ function caseScriptAppIdSchema(type) {
|
|
|
26905
26947
|
reason: exports_external.string().optional()
|
|
26906
26948
|
});
|
|
26907
26949
|
}
|
|
26950
|
+
var caseScriptTypeSchema = exports_external.object({
|
|
26951
|
+
type: exports_external.literal("type"),
|
|
26952
|
+
text: exports_external.string(),
|
|
26953
|
+
reason: exports_external.string().optional()
|
|
26954
|
+
});
|
|
26955
|
+
var caseScriptWaitSchema = exports_external.object({
|
|
26956
|
+
type: exports_external.literal("wait"),
|
|
26957
|
+
ms: exports_external.number().min(0).max(1e4),
|
|
26958
|
+
reason: exports_external.string().optional()
|
|
26959
|
+
});
|
|
26960
|
+
var caseScriptAssertionSchema = exports_external.union([exports_external.literal("visible"), exports_external.literal("not-visible")]).default("visible");
|
|
26961
|
+
var caseScriptAssertSchema = exports_external.object({
|
|
26962
|
+
type: exports_external.literal("assert"),
|
|
26963
|
+
assertion: caseScriptAssertionSchema,
|
|
26964
|
+
text: exports_external.string().min(1),
|
|
26965
|
+
timeoutMs: exports_external.number().min(0).max(120000).optional(),
|
|
26966
|
+
reason: exports_external.string().optional()
|
|
26967
|
+
});
|
|
26968
|
+
var caseScriptAlertSchema = exports_external.object({
|
|
26969
|
+
type: exports_external.literal("alert"),
|
|
26970
|
+
alertAction: exports_external.union([exports_external.literal("accept"), exports_external.literal("dismiss")]).default("accept"),
|
|
26971
|
+
reason: exports_external.string().optional()
|
|
26972
|
+
});
|
|
26973
|
+
var caseScriptBackgroundAppSchema = exports_external.object({
|
|
26974
|
+
type: exports_external.literal("background-app"),
|
|
26975
|
+
seconds: exports_external.number().min(0).max(120).optional(),
|
|
26976
|
+
reason: exports_external.string().optional()
|
|
26977
|
+
});
|
|
26978
|
+
var caseScriptOpenUrlSchema = exports_external.object({
|
|
26979
|
+
type: exports_external.literal("open-url"),
|
|
26980
|
+
url: exports_external.string().min(1),
|
|
26981
|
+
reason: exports_external.string().optional()
|
|
26982
|
+
});
|
|
26983
|
+
var caseScriptKeySchema = exports_external.object({
|
|
26984
|
+
type: exports_external.literal("key"),
|
|
26985
|
+
key: keyNameSchema,
|
|
26986
|
+
modifiers: exports_external.array(keyModifierSchema).min(1).optional(),
|
|
26987
|
+
reason: exports_external.string().optional()
|
|
26988
|
+
});
|
|
26989
|
+
var caseScriptGestureSchema = exports_external.union([
|
|
26990
|
+
caseScriptTapSchema,
|
|
26991
|
+
caseScriptSwipeSchema,
|
|
26992
|
+
caseScriptDragSchema,
|
|
26993
|
+
caseScriptTypeSchema,
|
|
26994
|
+
caseScriptWaitSchema,
|
|
26995
|
+
caseScriptAssertSchema,
|
|
26996
|
+
caseScriptAlertSchema,
|
|
26997
|
+
caseScriptAppIdSchema("activate-app"),
|
|
26998
|
+
caseScriptAppIdSchema("terminate-app"),
|
|
26999
|
+
caseScriptAppIdSchema("restart-app"),
|
|
27000
|
+
caseScriptBackgroundAppSchema,
|
|
27001
|
+
caseScriptOpenUrlSchema,
|
|
27002
|
+
caseScriptKeySchema
|
|
27003
|
+
]);
|
|
27004
|
+
var BRANCH_DEFAULT_TIMEOUT_MS = 2000;
|
|
27005
|
+
var caseScriptStepsSchema = exports_external.array(exports_external.lazy(() => caseScriptActionSchema));
|
|
27006
|
+
var caseScriptBranchSchema = exports_external.object({
|
|
27007
|
+
type: exports_external.literal("branch"),
|
|
27008
|
+
text: exports_external.string().min(1).optional(),
|
|
27009
|
+
id: exports_external.string().min(1).optional(),
|
|
27010
|
+
assertion: caseScriptAssertionSchema,
|
|
27011
|
+
timeoutMs: exports_external.number().min(0).max(120000).optional(),
|
|
27012
|
+
then: caseScriptStepsSchema.min(1),
|
|
27013
|
+
else: caseScriptStepsSchema.optional().transform((actions) => actions?.length ? actions : undefined),
|
|
27014
|
+
reason: exports_external.string().optional()
|
|
27015
|
+
}).superRefine((value, ctx) => {
|
|
27016
|
+
if (value.text == null === (value.id == null)) {
|
|
27017
|
+
ctx.addIssue({
|
|
27018
|
+
code: exports_external.ZodIssueCode.custom,
|
|
27019
|
+
message: "branch requires exactly one of text or id"
|
|
27020
|
+
});
|
|
27021
|
+
}
|
|
27022
|
+
});
|
|
27023
|
+
var caseScriptActionSchema = exports_external.union([
|
|
27024
|
+
caseScriptGestureSchema,
|
|
27025
|
+
caseScriptBranchSchema
|
|
27026
|
+
]);
|
|
26908
27027
|
var caseScriptActionSchemaByType = {
|
|
26909
27028
|
tap: caseScriptTapSchema,
|
|
26910
27029
|
swipe: caseScriptSwipeSchema,
|
|
26911
27030
|
drag: caseScriptDragSchema,
|
|
26912
|
-
type:
|
|
26913
|
-
|
|
26914
|
-
|
|
26915
|
-
|
|
26916
|
-
}),
|
|
26917
|
-
wait: exports_external.object({
|
|
26918
|
-
type: exports_external.literal("wait"),
|
|
26919
|
-
ms: exports_external.number().min(0).max(1e4),
|
|
26920
|
-
reason: exports_external.string().optional()
|
|
26921
|
-
}),
|
|
26922
|
-
assert: exports_external.object({
|
|
26923
|
-
type: exports_external.literal("assert"),
|
|
26924
|
-
assertion: exports_external.union([exports_external.literal("visible"), exports_external.literal("not-visible")]).default("visible"),
|
|
26925
|
-
text: exports_external.string().min(1),
|
|
26926
|
-
timeoutMs: exports_external.number().min(0).max(120000).optional(),
|
|
26927
|
-
reason: exports_external.string().optional()
|
|
26928
|
-
}),
|
|
26929
|
-
alert: exports_external.object({
|
|
26930
|
-
type: exports_external.literal("alert"),
|
|
26931
|
-
alertAction: exports_external.union([exports_external.literal("accept"), exports_external.literal("dismiss")]).default("accept"),
|
|
26932
|
-
reason: exports_external.string().optional()
|
|
26933
|
-
}),
|
|
27031
|
+
type: caseScriptTypeSchema,
|
|
27032
|
+
wait: caseScriptWaitSchema,
|
|
27033
|
+
assert: caseScriptAssertSchema,
|
|
27034
|
+
alert: caseScriptAlertSchema,
|
|
26934
27035
|
"activate-app": caseScriptAppIdSchema("activate-app"),
|
|
26935
27036
|
"terminate-app": caseScriptAppIdSchema("terminate-app"),
|
|
26936
27037
|
"restart-app": caseScriptAppIdSchema("restart-app"),
|
|
26937
|
-
"background-app":
|
|
26938
|
-
|
|
26939
|
-
|
|
26940
|
-
|
|
26941
|
-
}),
|
|
26942
|
-
"open-url": exports_external.object({
|
|
26943
|
-
type: exports_external.literal("open-url"),
|
|
26944
|
-
url: exports_external.string().min(1),
|
|
26945
|
-
reason: exports_external.string().optional()
|
|
26946
|
-
})
|
|
27038
|
+
"background-app": caseScriptBackgroundAppSchema,
|
|
27039
|
+
"open-url": caseScriptOpenUrlSchema,
|
|
27040
|
+
key: caseScriptKeySchema,
|
|
27041
|
+
branch: caseScriptBranchSchema
|
|
26947
27042
|
};
|
|
26948
|
-
var caseScriptActionSchema = exports_external.union([
|
|
26949
|
-
caseScriptActionSchemaByType.tap,
|
|
26950
|
-
caseScriptActionSchemaByType.swipe,
|
|
26951
|
-
caseScriptActionSchemaByType.drag,
|
|
26952
|
-
caseScriptActionSchemaByType.type,
|
|
26953
|
-
caseScriptActionSchemaByType.wait,
|
|
26954
|
-
caseScriptActionSchemaByType.assert,
|
|
26955
|
-
caseScriptActionSchemaByType.alert,
|
|
26956
|
-
caseScriptActionSchemaByType["activate-app"],
|
|
26957
|
-
caseScriptActionSchemaByType["terminate-app"],
|
|
26958
|
-
caseScriptActionSchemaByType["restart-app"],
|
|
26959
|
-
caseScriptActionSchemaByType["background-app"],
|
|
26960
|
-
caseScriptActionSchemaByType["open-url"]
|
|
26961
|
-
]);
|
|
26962
27043
|
var caseScriptSchema = exports_external.object({
|
|
26963
27044
|
version: exports_external.literal(1),
|
|
26964
27045
|
sourceRunId: exports_external.string().min(1).optional(),
|
|
@@ -27269,6 +27350,8 @@ var runSchema = exports_external.object({
|
|
|
27269
27350
|
screenMode: runScreenModeSchema.optional(),
|
|
27270
27351
|
lane: laneNameSchema.optional(),
|
|
27271
27352
|
laneWarning: exports_external.string().min(1).optional(),
|
|
27353
|
+
browser: exports_external.string().min(1).optional(),
|
|
27354
|
+
viewport: viewportPresetSchema.optional(),
|
|
27272
27355
|
error: exports_external.string().nullable(),
|
|
27273
27356
|
createdAt: exports_external.number().int().nonnegative(),
|
|
27274
27357
|
startedAt: exports_external.number().int().nonnegative().nullable(),
|
|
@@ -27287,7 +27370,8 @@ var connectDeviceRequestSchema = exports_external.object({
|
|
|
27287
27370
|
platform: devicePlatformSchema,
|
|
27288
27371
|
bundleId: exports_external.string().min(1).optional(),
|
|
27289
27372
|
appPackage: exports_external.string().min(1).optional(),
|
|
27290
|
-
lane: exports_external.union([laneNameSchema, exports_external.literal("auto")]).optional()
|
|
27373
|
+
lane: exports_external.union([laneNameSchema, exports_external.literal("auto")]).optional(),
|
|
27374
|
+
viewport: viewportPresetSchema.optional()
|
|
27291
27375
|
});
|
|
27292
27376
|
var streamStateSchema = exports_external.union([
|
|
27293
27377
|
exports_external.literal("ready"),
|
|
@@ -27305,7 +27389,8 @@ var activeDeviceResponseSchema = exports_external.object({
|
|
|
27305
27389
|
heldByRun: exports_external.boolean().optional(),
|
|
27306
27390
|
heldByRunId: exports_external.string().min(1).optional(),
|
|
27307
27391
|
lane: laneNameSchema.optional(),
|
|
27308
|
-
laneWarning: exports_external.string().min(1).optional()
|
|
27392
|
+
laneWarning: exports_external.string().min(1).optional(),
|
|
27393
|
+
viewport: viewportPresetSchema.optional()
|
|
27309
27394
|
});
|
|
27310
27395
|
var retargetDeviceRequestSchema = exports_external.object({
|
|
27311
27396
|
bundleId: exports_external.string().min(1).optional(),
|
|
@@ -27352,7 +27437,8 @@ var actionKindSchema = exports_external.union([
|
|
|
27352
27437
|
exports_external.literal("restart-app"),
|
|
27353
27438
|
exports_external.literal("background-app"),
|
|
27354
27439
|
exports_external.literal("open-url"),
|
|
27355
|
-
exports_external.literal("alert")
|
|
27440
|
+
exports_external.literal("alert"),
|
|
27441
|
+
exports_external.literal("key")
|
|
27356
27442
|
]);
|
|
27357
27443
|
var actionRequestSchema = exports_external.object({
|
|
27358
27444
|
kind: actionKindSchema,
|
|
@@ -27370,6 +27456,8 @@ var actionRequestSchema = exports_external.object({
|
|
|
27370
27456
|
url: exports_external.string().optional(),
|
|
27371
27457
|
alertAction: exports_external.union([exports_external.literal("accept"), exports_external.literal("dismiss")]).optional(),
|
|
27372
27458
|
seconds: exports_external.number().optional(),
|
|
27459
|
+
key: keyNameSchema.optional(),
|
|
27460
|
+
modifiers: exports_external.array(keyModifierSchema).optional(),
|
|
27373
27461
|
screenshot: exports_external.boolean().optional(),
|
|
27374
27462
|
settleMs: exports_external.number().int().min(0).max(1e4).optional(),
|
|
27375
27463
|
fullImage: exports_external.boolean().optional(),
|
|
@@ -27451,6 +27539,58 @@ var listBuildsResponseSchema = exports_external.object({
|
|
|
27451
27539
|
|
|
27452
27540
|
// ../../packages/runner-client/src/wait-for-run.ts
|
|
27453
27541
|
var DEFAULT_WAIT_FOR_RUN_TIMEOUT_MS = 30 * 60 * 1000;
|
|
27542
|
+
|
|
27543
|
+
// ../../node_modules/yaml/dist/index.js
|
|
27544
|
+
var composer = require_composer();
|
|
27545
|
+
var Document = require_Document();
|
|
27546
|
+
var Schema2 = require_Schema();
|
|
27547
|
+
var errors2 = require_errors();
|
|
27548
|
+
var Alias = require_Alias();
|
|
27549
|
+
var identity = require_identity();
|
|
27550
|
+
var Pair = require_Pair();
|
|
27551
|
+
var Scalar = require_Scalar();
|
|
27552
|
+
var YAMLMap = require_YAMLMap();
|
|
27553
|
+
var YAMLSeq = require_YAMLSeq();
|
|
27554
|
+
var cst = require_cst();
|
|
27555
|
+
var lexer = require_lexer();
|
|
27556
|
+
var lineCounter = require_line_counter();
|
|
27557
|
+
var parser = require_parser();
|
|
27558
|
+
var publicApi = require_public_api();
|
|
27559
|
+
var visit = require_visit();
|
|
27560
|
+
var $Composer = composer.Composer;
|
|
27561
|
+
var $Document = Document.Document;
|
|
27562
|
+
var $Schema = Schema2.Schema;
|
|
27563
|
+
var $YAMLError = errors2.YAMLError;
|
|
27564
|
+
var $YAMLParseError = errors2.YAMLParseError;
|
|
27565
|
+
var $YAMLWarning = errors2.YAMLWarning;
|
|
27566
|
+
var $Alias = Alias.Alias;
|
|
27567
|
+
var $isAlias = identity.isAlias;
|
|
27568
|
+
var $isCollection = identity.isCollection;
|
|
27569
|
+
var $isDocument = identity.isDocument;
|
|
27570
|
+
var $isMap = identity.isMap;
|
|
27571
|
+
var $isNode = identity.isNode;
|
|
27572
|
+
var $isPair = identity.isPair;
|
|
27573
|
+
var $isScalar = identity.isScalar;
|
|
27574
|
+
var $isSeq = identity.isSeq;
|
|
27575
|
+
var $Pair = Pair.Pair;
|
|
27576
|
+
var $Scalar = Scalar.Scalar;
|
|
27577
|
+
var $YAMLMap = YAMLMap.YAMLMap;
|
|
27578
|
+
var $YAMLSeq = YAMLSeq.YAMLSeq;
|
|
27579
|
+
var $Lexer = lexer.Lexer;
|
|
27580
|
+
var $LineCounter = lineCounter.LineCounter;
|
|
27581
|
+
var $Parser = parser.Parser;
|
|
27582
|
+
var $parse = publicApi.parse;
|
|
27583
|
+
var $parseAllDocuments = publicApi.parseAllDocuments;
|
|
27584
|
+
var $parseDocument = publicApi.parseDocument;
|
|
27585
|
+
var $stringify = publicApi.stringify;
|
|
27586
|
+
var $visit = visit.visit;
|
|
27587
|
+
var $visitAsync = visit.visitAsync;
|
|
27588
|
+
|
|
27589
|
+
// ../../packages/runner-client/src/keys.ts
|
|
27590
|
+
function formatKeyChord(key, modifiers = []) {
|
|
27591
|
+
return [...modifiers, key].join("+");
|
|
27592
|
+
}
|
|
27593
|
+
|
|
27454
27594
|
// ../../packages/runner-client/src/xml-entities.ts
|
|
27455
27595
|
var NAMED = {
|
|
27456
27596
|
amp: "&",
|
|
@@ -27482,6 +27622,10 @@ function shellSingleQuote(value) {
|
|
|
27482
27622
|
}
|
|
27483
27623
|
function formatActionShellLine(action) {
|
|
27484
27624
|
const parts = ["yoqa", "action", action.kind];
|
|
27625
|
+
if (action.kind === "key" && action.key)
|
|
27626
|
+
parts.push(action.key);
|
|
27627
|
+
if (action.modifiers?.length)
|
|
27628
|
+
parts.push("--modifiers", action.modifiers.join(","));
|
|
27485
27629
|
if (action.id) {
|
|
27486
27630
|
parts.push("--id", shellSingleQuote(action.id));
|
|
27487
27631
|
}
|
|
@@ -27522,8 +27666,14 @@ function formatSleepShellLine(seconds) {
|
|
|
27522
27666
|
const rounded = Number.isInteger(safe) ? String(safe) : String(Number(safe.toFixed(3)));
|
|
27523
27667
|
return `sleep ${rounded}`;
|
|
27524
27668
|
}
|
|
27669
|
+
function assertTargetFlags(target) {
|
|
27670
|
+
return [
|
|
27671
|
+
...target.text != null ? ["--text", shellSingleQuote(target.text.trim())] : [],
|
|
27672
|
+
...target.id != null ? ["--id", shellSingleQuote(target.id.trim())] : []
|
|
27673
|
+
];
|
|
27674
|
+
}
|
|
27525
27675
|
function formatAssertShellLine(input) {
|
|
27526
|
-
const parts = ["yoqa", "assert", input.assertion,
|
|
27676
|
+
const parts = ["yoqa", "assert", input.assertion, ...assertTargetFlags(input)];
|
|
27527
27677
|
const timeout = input.timeoutSeconds ?? 5;
|
|
27528
27678
|
if (timeout !== 5) {
|
|
27529
27679
|
parts.push("--timeout", String(timeout));
|
|
@@ -27551,6 +27701,11 @@ function screenHasText(elements, text2) {
|
|
|
27551
27701
|
return label.includes(needle) || type.includes(needle);
|
|
27552
27702
|
});
|
|
27553
27703
|
}
|
|
27704
|
+
function screenHasTarget(elements, target) {
|
|
27705
|
+
if (target.id != null)
|
|
27706
|
+
return findElementById(elements, target.id) != null;
|
|
27707
|
+
return screenHasText(elements, target.text ?? "");
|
|
27708
|
+
}
|
|
27554
27709
|
function findElementByLabel(elements, label) {
|
|
27555
27710
|
const needle = normalizedLabel(label);
|
|
27556
27711
|
if (!needle)
|
|
@@ -27588,63 +27743,104 @@ function elementCenterNorm(element) {
|
|
|
27588
27743
|
y: Math.round(element.y + element.height / 2)
|
|
27589
27744
|
};
|
|
27590
27745
|
}
|
|
27591
|
-
// ../../node_modules/yaml/dist/index.js
|
|
27592
|
-
var composer = require_composer();
|
|
27593
|
-
var Document = require_Document();
|
|
27594
|
-
var Schema2 = require_Schema();
|
|
27595
|
-
var errors2 = require_errors();
|
|
27596
|
-
var Alias = require_Alias();
|
|
27597
|
-
var identity = require_identity();
|
|
27598
|
-
var Pair = require_Pair();
|
|
27599
|
-
var Scalar = require_Scalar();
|
|
27600
|
-
var YAMLMap = require_YAMLMap();
|
|
27601
|
-
var YAMLSeq = require_YAMLSeq();
|
|
27602
|
-
var cst = require_cst();
|
|
27603
|
-
var lexer = require_lexer();
|
|
27604
|
-
var lineCounter = require_line_counter();
|
|
27605
|
-
var parser = require_parser();
|
|
27606
|
-
var publicApi = require_public_api();
|
|
27607
|
-
var visit = require_visit();
|
|
27608
|
-
var $Composer = composer.Composer;
|
|
27609
|
-
var $Document = Document.Document;
|
|
27610
|
-
var $Schema = Schema2.Schema;
|
|
27611
|
-
var $YAMLError = errors2.YAMLError;
|
|
27612
|
-
var $YAMLParseError = errors2.YAMLParseError;
|
|
27613
|
-
var $YAMLWarning = errors2.YAMLWarning;
|
|
27614
|
-
var $Alias = Alias.Alias;
|
|
27615
|
-
var $isAlias = identity.isAlias;
|
|
27616
|
-
var $isCollection = identity.isCollection;
|
|
27617
|
-
var $isDocument = identity.isDocument;
|
|
27618
|
-
var $isMap = identity.isMap;
|
|
27619
|
-
var $isNode = identity.isNode;
|
|
27620
|
-
var $isPair = identity.isPair;
|
|
27621
|
-
var $isScalar = identity.isScalar;
|
|
27622
|
-
var $isSeq = identity.isSeq;
|
|
27623
|
-
var $Pair = Pair.Pair;
|
|
27624
|
-
var $Scalar = Scalar.Scalar;
|
|
27625
|
-
var $YAMLMap = YAMLMap.YAMLMap;
|
|
27626
|
-
var $YAMLSeq = YAMLSeq.YAMLSeq;
|
|
27627
|
-
var $Lexer = lexer.Lexer;
|
|
27628
|
-
var $LineCounter = lineCounter.LineCounter;
|
|
27629
|
-
var $Parser = parser.Parser;
|
|
27630
|
-
var $parse = publicApi.parse;
|
|
27631
|
-
var $parseAllDocuments = publicApi.parseAllDocuments;
|
|
27632
|
-
var $parseDocument = publicApi.parseDocument;
|
|
27633
|
-
var $stringify = publicApi.stringify;
|
|
27634
|
-
var $visit = visit.visit;
|
|
27635
|
-
var $visitAsync = visit.visitAsync;
|
|
27636
27746
|
|
|
27637
27747
|
// ../../packages/runner-client/src/case-script-file.ts
|
|
27638
|
-
var ACTION_TYPES = Object.keys(caseScriptActionSchemaByType);
|
|
27639
|
-
var
|
|
27748
|
+
var ACTION_TYPES = Object.keys(caseScriptActionSchemaByType).filter((type) => type !== "branch");
|
|
27749
|
+
var BRANCH_KEY = "if";
|
|
27750
|
+
var ITEM_NAMES = [...ACTION_TYPES, BRANCH_KEY];
|
|
27751
|
+
var TEXT_SHORT_FORM_FIELDS = new Set(["label", "text", "alertAction", "url", "appId", "key"]);
|
|
27640
27752
|
var TOP_LEVEL_KEYS = new Set(["version", "name", "sourceRunId", "savedAt", "actions"]);
|
|
27753
|
+
|
|
27754
|
+
// ../../packages/runner-client/src/screen-check.ts
|
|
27755
|
+
var SCREEN_CHECK_POLL_MS = 400;
|
|
27756
|
+
function abortError() {
|
|
27757
|
+
return Object.assign(new Error("Aborted"), { name: "AbortError" });
|
|
27758
|
+
}
|
|
27759
|
+
function describeScreenCheck(check) {
|
|
27760
|
+
return check.id != null ? `${check.assertion} id: ${check.id}` : `${check.assertion}: ${check.text}`;
|
|
27761
|
+
}
|
|
27762
|
+
function screenCheckMissMessage(options) {
|
|
27763
|
+
const seconds = Math.round(options.timeoutMs / 1000);
|
|
27764
|
+
const [kind, target] = options.id != null ? ["id", options.id] : ["text", options.text];
|
|
27765
|
+
return options.assertion === "visible" ? `Expected visible ${kind} not found within ${seconds}s: ${target}` : `Unexpected ${kind} still visible after ${seconds}s: ${target}`;
|
|
27766
|
+
}
|
|
27767
|
+
async function checkScreen(options) {
|
|
27768
|
+
const pollMs = options.pollMs ?? SCREEN_CHECK_POLL_MS;
|
|
27769
|
+
const deadline = options.now() + options.timeoutMs;
|
|
27770
|
+
for (;; ) {
|
|
27771
|
+
if (options.isAborted?.())
|
|
27772
|
+
throw abortError();
|
|
27773
|
+
let readError = null;
|
|
27774
|
+
try {
|
|
27775
|
+
const { elements } = await options.readScreen();
|
|
27776
|
+
const found = screenHasTarget(elements, options);
|
|
27777
|
+
if (options.assertion === "visible" ? found : !found)
|
|
27778
|
+
return { met: true };
|
|
27779
|
+
} catch (error) {
|
|
27780
|
+
if (options.isDeadSession?.(error))
|
|
27781
|
+
throw error;
|
|
27782
|
+
readError = error;
|
|
27783
|
+
}
|
|
27784
|
+
if (options.now() >= deadline) {
|
|
27785
|
+
if (readError) {
|
|
27786
|
+
const seconds = Math.round(options.timeoutMs / 1000);
|
|
27787
|
+
const message = readError instanceof Error ? readError.message : String(readError);
|
|
27788
|
+
throw new Error(`Could not read the screen within ${seconds}s: ${message}`);
|
|
27789
|
+
}
|
|
27790
|
+
return { met: false };
|
|
27791
|
+
}
|
|
27792
|
+
await options.sleep(pollMs);
|
|
27793
|
+
}
|
|
27794
|
+
}
|
|
27795
|
+
|
|
27796
|
+
// ../../packages/runner-client/src/script-format.ts
|
|
27797
|
+
var BRANCH_HELPER = "yoqa_branch";
|
|
27798
|
+
var BRANCH_HELPER_LINES = [
|
|
27799
|
+
"# A Branch's check: 0 runs `then`, 1 runs `else`, anything else stops the script.",
|
|
27800
|
+
`${BRANCH_HELPER}() {`,
|
|
27801
|
+
" local status=0",
|
|
27802
|
+
' "$@" || status=$?',
|
|
27803
|
+
' [ "$status" -le 1 ] || exit "$status"',
|
|
27804
|
+
' return "$status"',
|
|
27805
|
+
"}",
|
|
27806
|
+
""
|
|
27807
|
+
];
|
|
27808
|
+
// ../../packages/runner-client/src/viewport.ts
|
|
27809
|
+
var VIEWPORT_PRESETS = {
|
|
27810
|
+
desktop: { name: "desktop", width: 1280, height: 800, deviceScaleFactor: 1, mobile: false },
|
|
27811
|
+
mobile: { name: "mobile", width: 390, height: 844, deviceScaleFactor: 3, mobile: true }
|
|
27812
|
+
};
|
|
27813
|
+
var DEFAULT_VIEWPORT_PRESET = "desktop";
|
|
27814
|
+
// ../../packages/runner-client/src/app-start-target.ts
|
|
27815
|
+
function appStartTarget(app, platform) {
|
|
27816
|
+
switch (platform) {
|
|
27817
|
+
case "ios":
|
|
27818
|
+
return app?.iosBundleId.trim() || undefined;
|
|
27819
|
+
case "android":
|
|
27820
|
+
return app?.androidApplicationId.trim() || undefined;
|
|
27821
|
+
case "web":
|
|
27822
|
+
return app?.webUrl.trim() || undefined;
|
|
27823
|
+
}
|
|
27824
|
+
}
|
|
27825
|
+
function startTargetRequest(platform, target) {
|
|
27826
|
+
if (!target)
|
|
27827
|
+
return {};
|
|
27828
|
+
switch (platform) {
|
|
27829
|
+
case "ios":
|
|
27830
|
+
return { bundleId: target };
|
|
27831
|
+
case "android":
|
|
27832
|
+
return { appPackage: target };
|
|
27833
|
+
case "web":
|
|
27834
|
+
return {};
|
|
27835
|
+
}
|
|
27836
|
+
}
|
|
27641
27837
|
// src/domains/builds/application.ts
|
|
27642
27838
|
import { basename, extname } from "path";
|
|
27643
27839
|
|
|
27644
27840
|
// src/domains/devices/application.ts
|
|
27645
|
-
import { mkdtemp, readFile, rm } from "fs/promises";
|
|
27646
|
-
import { tmpdir } from "os";
|
|
27647
|
-
import { join as
|
|
27841
|
+
import { mkdtemp as mkdtemp2, readFile as readFile2, rm as rm2 } from "fs/promises";
|
|
27842
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
27843
|
+
import { join as join5 } from "path";
|
|
27648
27844
|
|
|
27649
27845
|
// src/domains/devices/android-identity.ts
|
|
27650
27846
|
function matchAndroidIdentity(deviceId, connected, knownAvds = []) {
|
|
@@ -27672,6 +27868,153 @@ function parseAdbEmuAvdName(stdout) {
|
|
|
27672
27868
|
return name || null;
|
|
27673
27869
|
}
|
|
27674
27870
|
|
|
27871
|
+
// src/domains/devices/chromium-browsers.ts
|
|
27872
|
+
import { constants } from "fs";
|
|
27873
|
+
import { access, mkdtemp, readFile, rm } from "fs/promises";
|
|
27874
|
+
import { homedir as homedir4, tmpdir } from "os";
|
|
27875
|
+
import { join as join4 } from "path";
|
|
27876
|
+
var KNOWN_BROWSERS = [
|
|
27877
|
+
{ id: "web-chrome", name: "Google Chrome", app: "Google Chrome" },
|
|
27878
|
+
{ id: "web-edge", name: "Microsoft Edge", app: "Microsoft Edge" },
|
|
27879
|
+
{ id: "web-brave", name: "Brave Browser", app: "Brave Browser" },
|
|
27880
|
+
{ id: "web-chromium", name: "Chromium", app: "Chromium" }
|
|
27881
|
+
];
|
|
27882
|
+
function installedBrowserIdOf(deviceId) {
|
|
27883
|
+
return KNOWN_BROWSERS.find((browser) => browser.id === deviceId)?.id ?? null;
|
|
27884
|
+
}
|
|
27885
|
+
function discoveryDeps() {
|
|
27886
|
+
return {
|
|
27887
|
+
platform: process.platform,
|
|
27888
|
+
home: homedir4(),
|
|
27889
|
+
isExecutable: (path) => access(path, constants.X_OK).then(() => true, () => false),
|
|
27890
|
+
readText: (path) => readFile(path, "utf8")
|
|
27891
|
+
};
|
|
27892
|
+
}
|
|
27893
|
+
var VERSION_RE = /<key>CFBundleShortVersionString<\/key>\s*<string>([^<]+)<\/string>/;
|
|
27894
|
+
async function bundleVersion(appPath, deps) {
|
|
27895
|
+
const plist = await deps.readText(join4(appPath, "Contents/Info.plist")).catch(() => "");
|
|
27896
|
+
return VERSION_RE.exec(plist)?.[1]?.trim() || null;
|
|
27897
|
+
}
|
|
27898
|
+
async function findInstalledBrowsers(deps = discoveryDeps()) {
|
|
27899
|
+
if (deps.platform !== "darwin")
|
|
27900
|
+
return [];
|
|
27901
|
+
const roots = ["/Applications", join4(deps.home, "Applications")];
|
|
27902
|
+
const found = await Promise.all(KNOWN_BROWSERS.map(async ({ id, name, app }) => {
|
|
27903
|
+
for (const root of roots) {
|
|
27904
|
+
const appPath = join4(root, `${app}.app`);
|
|
27905
|
+
const executable = join4(appPath, "Contents/MacOS", app);
|
|
27906
|
+
if (!await deps.isExecutable(executable))
|
|
27907
|
+
continue;
|
|
27908
|
+
return { id, name, version: await bundleVersion(appPath, deps), executable };
|
|
27909
|
+
}
|
|
27910
|
+
return null;
|
|
27911
|
+
}));
|
|
27912
|
+
return found.filter((browser) => browser !== null);
|
|
27913
|
+
}
|
|
27914
|
+
var running = new Set;
|
|
27915
|
+
async function listWebDevices(options = {}, deps = discoveryDeps()) {
|
|
27916
|
+
const includeUnavailable = options.includeUnavailable ?? true;
|
|
27917
|
+
const devices = [];
|
|
27918
|
+
for (const browser of await findInstalledBrowsers(deps)) {
|
|
27919
|
+
const isRunning = running.has(browser.id);
|
|
27920
|
+
if (!includeUnavailable && !isRunning)
|
|
27921
|
+
continue;
|
|
27922
|
+
devices.push({
|
|
27923
|
+
id: browser.id,
|
|
27924
|
+
name: browser.name,
|
|
27925
|
+
osVersion: browser.version ?? "unknown version",
|
|
27926
|
+
platform: "web",
|
|
27927
|
+
kind: "browser",
|
|
27928
|
+
state: isRunning ? "running" : "not running"
|
|
27929
|
+
});
|
|
27930
|
+
}
|
|
27931
|
+
return devices;
|
|
27932
|
+
}
|
|
27933
|
+
var READY_TIMEOUT_MS = 15000;
|
|
27934
|
+
var READY_POLL_MS = 50;
|
|
27935
|
+
var STOP_GRACE_MS = 2000;
|
|
27936
|
+
var LAUNCH_FLAGS = [
|
|
27937
|
+
"--remote-debugging-port=0",
|
|
27938
|
+
"--no-first-run",
|
|
27939
|
+
"--no-default-browser-check",
|
|
27940
|
+
"--disable-search-engine-choice-screen",
|
|
27941
|
+
"--disable-sync",
|
|
27942
|
+
"--disable-default-apps",
|
|
27943
|
+
"--use-mock-keychain",
|
|
27944
|
+
"--password-store=basic"
|
|
27945
|
+
];
|
|
27946
|
+
function spawnBrowser(command) {
|
|
27947
|
+
return Bun.spawn(command, { stdin: "ignore", stdout: "ignore", stderr: "pipe" });
|
|
27948
|
+
}
|
|
27949
|
+
async function readActivePort(profileDir) {
|
|
27950
|
+
const text2 = await readFile(join4(profileDir, "DevToolsActivePort"), "utf8").catch(() => "");
|
|
27951
|
+
const [port, path] = text2.split(`
|
|
27952
|
+
`).map((line) => line.trim());
|
|
27953
|
+
if (!port || !/^\d+$/.test(port) || !path?.startsWith("/"))
|
|
27954
|
+
return null;
|
|
27955
|
+
return `ws://127.0.0.1:${port}${path}`;
|
|
27956
|
+
}
|
|
27957
|
+
async function launchChromium(browser, deps = {}) {
|
|
27958
|
+
const spawn = deps.spawn ?? spawnBrowser;
|
|
27959
|
+
const readyTimeoutMs = deps.readyTimeoutMs ?? READY_TIMEOUT_MS;
|
|
27960
|
+
const profileDir = await mkdtemp(join4(tmpdir(), "yoqa-web-"));
|
|
27961
|
+
const removeProfile = () => rm(profileDir, { recursive: true, force: true });
|
|
27962
|
+
let proc;
|
|
27963
|
+
try {
|
|
27964
|
+
proc = spawn([
|
|
27965
|
+
browser.executable,
|
|
27966
|
+
`--user-data-dir=${profileDir}`,
|
|
27967
|
+
...LAUNCH_FLAGS,
|
|
27968
|
+
"about:blank"
|
|
27969
|
+
]);
|
|
27970
|
+
} catch (error) {
|
|
27971
|
+
await removeProfile();
|
|
27972
|
+
throw error;
|
|
27973
|
+
}
|
|
27974
|
+
let hasExited = false;
|
|
27975
|
+
const exited = proc.exited.then((code) => {
|
|
27976
|
+
hasExited = true;
|
|
27977
|
+
running.delete(browser.id);
|
|
27978
|
+
return code;
|
|
27979
|
+
});
|
|
27980
|
+
const stderr = proc.stderr ? new Response(proc.stderr).text().catch(() => "") : null;
|
|
27981
|
+
let stopping = null;
|
|
27982
|
+
const stop = () => {
|
|
27983
|
+
stopping ??= (async () => {
|
|
27984
|
+
if (!hasExited) {
|
|
27985
|
+
proc.kill("SIGTERM");
|
|
27986
|
+
const exitedInTime = await Promise.race([
|
|
27987
|
+
exited.then(() => true),
|
|
27988
|
+
Bun.sleep(STOP_GRACE_MS).then(() => false)
|
|
27989
|
+
]);
|
|
27990
|
+
if (!exitedInTime)
|
|
27991
|
+
proc.kill("SIGKILL");
|
|
27992
|
+
}
|
|
27993
|
+
running.delete(browser.id);
|
|
27994
|
+
await removeProfile();
|
|
27995
|
+
})();
|
|
27996
|
+
return stopping;
|
|
27997
|
+
};
|
|
27998
|
+
const deadline = Date.now() + readyTimeoutMs;
|
|
27999
|
+
while (true) {
|
|
28000
|
+
const socketUrl = await readActivePort(profileDir);
|
|
28001
|
+
if (socketUrl) {
|
|
28002
|
+
running.add(browser.id);
|
|
28003
|
+
return { socketUrl, profileDir, exited, stop };
|
|
28004
|
+
}
|
|
28005
|
+
if (hasExited) {
|
|
28006
|
+
await stop();
|
|
28007
|
+
const output = (await stderr ?? "").trim();
|
|
28008
|
+
throw new Error(`${browser.name} exited before it opened a debug port${output ? `: ${output}` : ""}`);
|
|
28009
|
+
}
|
|
28010
|
+
if (Date.now() >= deadline) {
|
|
28011
|
+
await stop();
|
|
28012
|
+
throw new Error(`${browser.name} did not open a debug port within ${readyTimeoutMs} ms`);
|
|
28013
|
+
}
|
|
28014
|
+
await Promise.race([exited, Bun.sleep(READY_POLL_MS)]);
|
|
28015
|
+
}
|
|
28016
|
+
}
|
|
28017
|
+
|
|
27675
28018
|
// src/domains/devices/devicectl.ts
|
|
27676
28019
|
function developerMode(value) {
|
|
27677
28020
|
const key = typeof value === "string" ? value : value && typeof value === "object" ? Object.keys(value)[0] : undefined;
|
|
@@ -27799,11 +28142,11 @@ async function resolveTool(candidates) {
|
|
|
27799
28142
|
}
|
|
27800
28143
|
async function resolveAdb() {
|
|
27801
28144
|
const sdk = androidSdkRoot();
|
|
27802
|
-
return resolveTool([...sdk ? [
|
|
28145
|
+
return resolveTool([...sdk ? [join5(sdk, "platform-tools/adb")] : [], "adb"]);
|
|
27803
28146
|
}
|
|
27804
28147
|
async function resolveEmulator() {
|
|
27805
28148
|
const sdk = androidSdkRoot();
|
|
27806
|
-
return resolveTool([...sdk ? [
|
|
28149
|
+
return resolveTool([...sdk ? [join5(sdk, "emulator/emulator")] : [], "emulator"]);
|
|
27807
28150
|
}
|
|
27808
28151
|
function formatIosVersion2(version2) {
|
|
27809
28152
|
const trimmed = version2.trim();
|
|
@@ -27901,14 +28244,14 @@ async function readDevicectlDevices(jsonPath) {
|
|
|
27901
28244
|
if (exitCode !== 0)
|
|
27902
28245
|
return null;
|
|
27903
28246
|
try {
|
|
27904
|
-
return JSON.parse(await
|
|
28247
|
+
return JSON.parse(await readFile2(jsonPath, "utf8"));
|
|
27905
28248
|
} catch {
|
|
27906
28249
|
return null;
|
|
27907
28250
|
}
|
|
27908
28251
|
}
|
|
27909
28252
|
async function listIosPhysicalDevices(includeUnavailable) {
|
|
27910
|
-
const dir = await
|
|
27911
|
-
const jsonPath =
|
|
28253
|
+
const dir = await mkdtemp2(join5(tmpdir2(), "yoqa-devices-"));
|
|
28254
|
+
const jsonPath = join5(dir, "devices.json");
|
|
27912
28255
|
try {
|
|
27913
28256
|
let parsed = await readDevicectlDevices(jsonPath);
|
|
27914
28257
|
if (parsed === null)
|
|
@@ -27921,7 +28264,7 @@ async function listIosPhysicalDevices(includeUnavailable) {
|
|
|
27921
28264
|
const devices = parseDevicectlDevices(parsed);
|
|
27922
28265
|
return includeUnavailable ? devices : devices.filter((device) => !device.unavailableReason);
|
|
27923
28266
|
} finally {
|
|
27924
|
-
await
|
|
28267
|
+
await rm2(dir, { recursive: true, force: true });
|
|
27925
28268
|
}
|
|
27926
28269
|
}
|
|
27927
28270
|
async function listIosDevices(options = {}) {
|
|
@@ -27972,18 +28315,18 @@ function humanizeModel(model) {
|
|
|
27972
28315
|
}
|
|
27973
28316
|
async function findAvdConfigPath(avdName) {
|
|
27974
28317
|
const home = process.env.HOME ?? "";
|
|
27975
|
-
const roots = [
|
|
28318
|
+
const roots = [join5(home, ".android/avd"), join5(home, "Library/Android/sdk/.android/avd")];
|
|
27976
28319
|
for (const root of roots) {
|
|
27977
|
-
const direct =
|
|
28320
|
+
const direct = join5(root, `${avdName}.avd`, "config.ini");
|
|
27978
28321
|
try {
|
|
27979
|
-
await
|
|
28322
|
+
await readFile2(direct, "utf8");
|
|
27980
28323
|
return direct;
|
|
27981
28324
|
} catch {}
|
|
27982
28325
|
try {
|
|
27983
28326
|
const glob = new Bun.Glob("*.avd/config.ini");
|
|
27984
28327
|
for await (const match of glob.scan({ cwd: root, onlyFiles: true })) {
|
|
27985
|
-
const path =
|
|
27986
|
-
const text2 = await
|
|
28328
|
+
const path = join5(root, match);
|
|
28329
|
+
const text2 = await readFile2(path, "utf8");
|
|
27987
28330
|
const idLine = text2.split(`
|
|
27988
28331
|
`).find((line) => line.startsWith("AvdId="));
|
|
27989
28332
|
const id = idLine?.slice("AvdId=".length).trim();
|
|
@@ -27999,7 +28342,7 @@ async function readAvdConfig(avdName) {
|
|
|
27999
28342
|
if (!path)
|
|
28000
28343
|
return { api: null, deviceName: null };
|
|
28001
28344
|
try {
|
|
28002
|
-
const text2 = await
|
|
28345
|
+
const text2 = await readFile2(path, "utf8");
|
|
28003
28346
|
const imageLine = text2.split(`
|
|
28004
28347
|
`).find((line) => line.includes("system-images") && line.includes("android-"));
|
|
28005
28348
|
const apiMatch = imageLine?.match(/android-([\d.]+)/);
|
|
@@ -28140,9 +28483,14 @@ async function listAndroidDevices(options = {}) {
|
|
|
28140
28483
|
return [...physical, ...emulators];
|
|
28141
28484
|
}
|
|
28142
28485
|
async function listDevices(platform, options = {}) {
|
|
28143
|
-
|
|
28144
|
-
|
|
28145
|
-
|
|
28486
|
+
switch (platform) {
|
|
28487
|
+
case "ios":
|
|
28488
|
+
return listIosDevices(options);
|
|
28489
|
+
case "android":
|
|
28490
|
+
return listAndroidDevices(options);
|
|
28491
|
+
case "web":
|
|
28492
|
+
return listWebDevices(options);
|
|
28493
|
+
}
|
|
28146
28494
|
}
|
|
28147
28495
|
|
|
28148
28496
|
// src/domains/builds/application.ts
|
|
@@ -28252,32 +28600,44 @@ async function resolveBuildForRun(options) {
|
|
|
28252
28600
|
}
|
|
28253
28601
|
async function installBuildOnDevice(options) {
|
|
28254
28602
|
const { build, deviceId, platform } = options;
|
|
28255
|
-
|
|
28256
|
-
|
|
28257
|
-
|
|
28258
|
-
|
|
28259
|
-
|
|
28260
|
-
|
|
28261
|
-
|
|
28262
|
-
|
|
28263
|
-
|
|
28264
|
-
|
|
28265
|
-
|
|
28603
|
+
switch (platform) {
|
|
28604
|
+
case "ios":
|
|
28605
|
+
return installIosBuild(build, deviceId);
|
|
28606
|
+
case "android":
|
|
28607
|
+
return installAndroidBuild(build, deviceId);
|
|
28608
|
+
case "web":
|
|
28609
|
+
throw new Error("A web device has no Builds to install");
|
|
28610
|
+
default:
|
|
28611
|
+
throw new Error(`Cannot install a build on ${platform}`);
|
|
28612
|
+
}
|
|
28613
|
+
}
|
|
28614
|
+
async function installIosBuild(build, deviceId) {
|
|
28615
|
+
if (build.path.endsWith(".app") || build.path.includes(".app")) {
|
|
28616
|
+
const proc = Bun.spawn(["xcrun", "simctl", "install", deviceId, build.path], {
|
|
28617
|
+
stdout: "pipe",
|
|
28618
|
+
stderr: "pipe"
|
|
28619
|
+
});
|
|
28620
|
+
const [stderr, code] = await Promise.all([new Response(proc.stderr).text(), proc.exited]);
|
|
28621
|
+
if (code !== 0) {
|
|
28622
|
+
throw new Error(`simctl install failed: ${stderr.trim() || `exit ${code}`}`);
|
|
28266
28623
|
}
|
|
28267
|
-
|
|
28268
|
-
|
|
28269
|
-
|
|
28270
|
-
|
|
28271
|
-
|
|
28272
|
-
|
|
28273
|
-
|
|
28274
|
-
|
|
28275
|
-
|
|
28276
|
-
|
|
28277
|
-
|
|
28624
|
+
return;
|
|
28625
|
+
}
|
|
28626
|
+
const which = Bun.which("ideviceinstaller");
|
|
28627
|
+
if (which && build.path.endsWith(".ipa")) {
|
|
28628
|
+
const proc = Bun.spawn([which, "-u", deviceId, "-i", build.path], {
|
|
28629
|
+
stdout: "pipe",
|
|
28630
|
+
stderr: "pipe"
|
|
28631
|
+
});
|
|
28632
|
+
const [stderr, code] = await Promise.all([new Response(proc.stderr).text(), proc.exited]);
|
|
28633
|
+
if (code !== 0) {
|
|
28634
|
+
throw new Error(`ideviceinstaller failed: ${stderr.trim() || `exit ${code}`}`);
|
|
28278
28635
|
}
|
|
28279
|
-
|
|
28636
|
+
return;
|
|
28280
28637
|
}
|
|
28638
|
+
throw new Error(`Cannot install iOS build at ${build.path}. Use a .app on simulator or install .ipa via ideviceinstaller.`);
|
|
28639
|
+
}
|
|
28640
|
+
async function installAndroidBuild(build, deviceId) {
|
|
28281
28641
|
const identity2 = await resolveAndroidIdentity(deviceId);
|
|
28282
28642
|
const serial = identity2.udid;
|
|
28283
28643
|
if (!serial) {
|
|
@@ -28491,10 +28851,10 @@ function normalizeVisionJson(value) {
|
|
|
28491
28851
|
}
|
|
28492
28852
|
|
|
28493
28853
|
// src/domains/providers/vision-model.ts
|
|
28494
|
-
import { mkdtemp as
|
|
28495
|
-
import { homedir as
|
|
28496
|
-
import { tmpdir as
|
|
28497
|
-
import { join as
|
|
28854
|
+
import { mkdtemp as mkdtemp3, readFile as readFile3, rm as rm3, writeFile } from "fs/promises";
|
|
28855
|
+
import { homedir as homedir5 } from "os";
|
|
28856
|
+
import { tmpdir as tmpdir3 } from "os";
|
|
28857
|
+
import { join as join6 } from "path";
|
|
28498
28858
|
|
|
28499
28859
|
// ../../node_modules/@ai-sdk/gateway/node_modules/@ai-sdk/provider/dist/index.js
|
|
28500
28860
|
var marker = "vercel.ai.error";
|
|
@@ -51171,9 +51531,9 @@ async function prepareVisionImage(pngBase64) {
|
|
|
51171
51531
|
if (process.platform !== "darwin") {
|
|
51172
51532
|
return { base64: pngBase64, mediaType: "image/png" };
|
|
51173
51533
|
}
|
|
51174
|
-
const dir = await
|
|
51175
|
-
const inPath =
|
|
51176
|
-
const outPath =
|
|
51534
|
+
const dir = await mkdtemp3(join6(tmpdir3(), "yoqa-vision-"));
|
|
51535
|
+
const inPath = join6(dir, "shot.png");
|
|
51536
|
+
const outPath = join6(dir, "shot.jpg");
|
|
51177
51537
|
try {
|
|
51178
51538
|
await writeFile(inPath, new Uint8Array(bytes));
|
|
51179
51539
|
const proc = Bun.spawn([
|
|
@@ -51194,7 +51554,7 @@ async function prepareVisionImage(pngBase64) {
|
|
|
51194
51554
|
if (code !== 0) {
|
|
51195
51555
|
return { base64: pngBase64, mediaType: "image/png" };
|
|
51196
51556
|
}
|
|
51197
|
-
const jpeg = await
|
|
51557
|
+
const jpeg = await readFile3(outPath);
|
|
51198
51558
|
if (jpeg.byteLength === 0 || jpeg.byteLength >= bytes.byteLength) {
|
|
51199
51559
|
return { base64: pngBase64, mediaType: "image/png" };
|
|
51200
51560
|
}
|
|
@@ -51202,7 +51562,7 @@ async function prepareVisionImage(pngBase64) {
|
|
|
51202
51562
|
} catch {
|
|
51203
51563
|
return { base64: pngBase64, mediaType: "image/png" };
|
|
51204
51564
|
} finally {
|
|
51205
|
-
await
|
|
51565
|
+
await rm3(dir, { recursive: true, force: true }).catch(() => {
|
|
51206
51566
|
return;
|
|
51207
51567
|
});
|
|
51208
51568
|
}
|
|
@@ -51389,8 +51749,8 @@ function openCodeAuthPaths() {
|
|
|
51389
51749
|
const paths = [];
|
|
51390
51750
|
const xdg = process.env.XDG_DATA_HOME?.trim();
|
|
51391
51751
|
if (xdg)
|
|
51392
|
-
paths.push(
|
|
51393
|
-
paths.push(
|
|
51752
|
+
paths.push(join6(xdg, "opencode", "auth.json"));
|
|
51753
|
+
paths.push(join6(homedir5(), ".local", "share", "opencode", "auth.json"));
|
|
51394
51754
|
return paths;
|
|
51395
51755
|
}
|
|
51396
51756
|
function apiKeyFromOpenCodeAuthEntry(value) {
|
|
@@ -60599,13 +60959,13 @@ function createAnthropic(options = {}) {
|
|
|
60599
60959
|
var anthropic = createAnthropic();
|
|
60600
60960
|
|
|
60601
60961
|
// src/domains/providers/drivers/probe.ts
|
|
60602
|
-
import { constants } from "fs";
|
|
60603
|
-
import { access } from "fs/promises";
|
|
60604
|
-
import { homedir as
|
|
60605
|
-
import { isAbsolute, join as
|
|
60962
|
+
import { constants as constants2 } from "fs";
|
|
60963
|
+
import { access as access2 } from "fs/promises";
|
|
60964
|
+
import { homedir as homedir6 } from "os";
|
|
60965
|
+
import { isAbsolute, join as join7 } from "path";
|
|
60606
60966
|
async function pathExists(path) {
|
|
60607
60967
|
try {
|
|
60608
|
-
await
|
|
60968
|
+
await access2(path, constants2.F_OK);
|
|
60609
60969
|
return true;
|
|
60610
60970
|
} catch {
|
|
60611
60971
|
return false;
|
|
@@ -60627,13 +60987,13 @@ async function which(binary) {
|
|
|
60627
60987
|
`)[0]?.trim();
|
|
60628
60988
|
return resolved || null;
|
|
60629
60989
|
}
|
|
60630
|
-
function knownHomeBinCandidates(binaryName, home =
|
|
60990
|
+
function knownHomeBinCandidates(binaryName, home = homedir6()) {
|
|
60631
60991
|
return [
|
|
60632
|
-
|
|
60633
|
-
|
|
60634
|
-
|
|
60635
|
-
|
|
60636
|
-
|
|
60992
|
+
join7(home, ".opencode", "bin", binaryName),
|
|
60993
|
+
join7(home, ".grok", "bin", binaryName),
|
|
60994
|
+
join7(home, ".antigravity", "antigravity", "bin", binaryName),
|
|
60995
|
+
join7(home, ".local", "bin", binaryName),
|
|
60996
|
+
join7(home, ".bun", "bin", binaryName),
|
|
60637
60997
|
`/opt/homebrew/bin/${binaryName}`,
|
|
60638
60998
|
`/usr/local/bin/${binaryName}`
|
|
60639
60999
|
];
|
|
@@ -60898,9 +61258,9 @@ function resolveAgyModelId(raw, fallback = ANTIGRAVITY_DEFAULT_VISION_MODEL) {
|
|
|
60898
61258
|
}
|
|
60899
61259
|
|
|
60900
61260
|
// src/domains/providers/drivers/antigravity-vision.ts
|
|
60901
|
-
import { mkdtemp as
|
|
60902
|
-
import { tmpdir as
|
|
60903
|
-
import { join as
|
|
61261
|
+
import { mkdtemp as mkdtemp4, rm as rm4, writeFile as writeFile2 } from "fs/promises";
|
|
61262
|
+
import { tmpdir as tmpdir4 } from "os";
|
|
61263
|
+
import { join as join8 } from "path";
|
|
60904
61264
|
|
|
60905
61265
|
// ../../node_modules/@ai-sdk/google/node_modules/@ai-sdk/provider/dist/index.js
|
|
60906
61266
|
var marker29 = "vercel.ai.error";
|
|
@@ -70375,8 +70735,8 @@ async function completeWithAgyCli(input, repairHint) {
|
|
|
70375
70735
|
const image = input.image ?? await prepareVisionImage(input.imageBase64);
|
|
70376
70736
|
const model = resolveAgyModelId(input.auth.defaultModel);
|
|
70377
70737
|
const ext = image.mediaType === "image/jpeg" ? "jpg" : "png";
|
|
70378
|
-
const dir = await
|
|
70379
|
-
const shotPath =
|
|
70738
|
+
const dir = await mkdtemp4(join8(tmpdir4(), "yoqa-agy-"));
|
|
70739
|
+
const shotPath = join8(dir, `shot.${ext}`);
|
|
70380
70740
|
try {
|
|
70381
70741
|
await writeFile2(shotPath, new Uint8Array(Buffer.from(image.base64, "base64")));
|
|
70382
70742
|
const userText = [
|
|
@@ -70416,7 +70776,7 @@ ${result.stderr}`.trim();
|
|
|
70416
70776
|
}
|
|
70417
70777
|
throw error40;
|
|
70418
70778
|
} finally {
|
|
70419
|
-
await
|
|
70779
|
+
await rm4(dir, { recursive: true, force: true }).catch(() => {
|
|
70420
70780
|
return;
|
|
70421
70781
|
});
|
|
70422
70782
|
}
|
|
@@ -78884,9 +79244,9 @@ ${result.stderr}`.toLowerCase();
|
|
|
78884
79244
|
};
|
|
78885
79245
|
|
|
78886
79246
|
// src/domains/providers/drivers/cursor-vision.ts
|
|
78887
|
-
import { mkdtemp as
|
|
78888
|
-
import { tmpdir as
|
|
78889
|
-
import { join as
|
|
79247
|
+
import { mkdtemp as mkdtemp5, rm as rm5, writeFile as writeFile3 } from "fs/promises";
|
|
79248
|
+
import { tmpdir as tmpdir5 } from "os";
|
|
79249
|
+
import { join as join9 } from "path";
|
|
78890
79250
|
var CURSOR_DEFAULT_VISION_MODEL = "auto";
|
|
78891
79251
|
var JSON_REPAIR_PROMPT3 = "Your previous reply was not valid JSON for this task. Reply again with ONLY one strict JSON object using double quotes for every key and string (no single quotes, no markdown, no prose).";
|
|
78892
79252
|
function resolveCursorApiKey(auth) {
|
|
@@ -78899,8 +79259,8 @@ async function invokeCursorPrint(input) {
|
|
|
78899
79259
|
}
|
|
78900
79260
|
const model = input.auth.defaultModel?.trim() || CURSOR_DEFAULT_VISION_MODEL;
|
|
78901
79261
|
const ext = input.mediaType === "image/jpeg" ? "jpg" : "png";
|
|
78902
|
-
const dir = await
|
|
78903
|
-
const shotPath =
|
|
79262
|
+
const dir = await mkdtemp5(join9(tmpdir5(), "yoqa-cursor-"));
|
|
79263
|
+
const shotPath = join9(dir, `shot.${ext}`);
|
|
78904
79264
|
const apiKey = resolveCursorApiKey(input.auth);
|
|
78905
79265
|
try {
|
|
78906
79266
|
await writeFile3(shotPath, new Uint8Array(Buffer.from(input.imageBase64, "base64")));
|
|
@@ -78937,7 +79297,7 @@ ${result.stderr}`.trim();
|
|
|
78937
79297
|
}
|
|
78938
79298
|
return result.stdout.trim() || combined;
|
|
78939
79299
|
} finally {
|
|
78940
|
-
await
|
|
79300
|
+
await rm5(dir, { recursive: true, force: true }).catch(() => {
|
|
78941
79301
|
return;
|
|
78942
79302
|
});
|
|
78943
79303
|
}
|
|
@@ -99029,8 +99389,8 @@ var openaiDriver = {
|
|
|
99029
99389
|
};
|
|
99030
99390
|
|
|
99031
99391
|
// src/domains/providers/drivers/opencode-config.ts
|
|
99032
|
-
import { homedir as
|
|
99033
|
-
import { join as
|
|
99392
|
+
import { homedir as homedir7 } from "os";
|
|
99393
|
+
import { join as join10 } from "path";
|
|
99034
99394
|
var OPENAI_COMPATIBLE_PACKAGES = new Set([
|
|
99035
99395
|
"@ai-sdk/openai-compatible",
|
|
99036
99396
|
"@ai-sdk/openai",
|
|
@@ -99058,8 +99418,8 @@ function openCodeConfigPaths() {
|
|
|
99058
99418
|
const paths = [];
|
|
99059
99419
|
const xdg = process.env.XDG_CONFIG_HOME?.trim();
|
|
99060
99420
|
if (xdg)
|
|
99061
|
-
paths.push(
|
|
99062
|
-
paths.push(
|
|
99421
|
+
paths.push(join10(xdg, "opencode", "opencode.json"));
|
|
99422
|
+
paths.push(join10(homedir7(), ".config", "opencode", "opencode.json"));
|
|
99063
99423
|
return paths;
|
|
99064
99424
|
}
|
|
99065
99425
|
function parseOpenCodeCompatibleProvider(config2, providerId) {
|
|
@@ -99880,6 +100240,7 @@ var agentDecisionSchema = exports_external.object({
|
|
|
99880
100240
|
exports_external.literal("restart-app"),
|
|
99881
100241
|
exports_external.literal("background-app"),
|
|
99882
100242
|
exports_external.literal("open-url"),
|
|
100243
|
+
exports_external.literal("key"),
|
|
99883
100244
|
exports_external.literal("assert"),
|
|
99884
100245
|
exports_external.literal("verify"),
|
|
99885
100246
|
exports_external.literal("done"),
|
|
@@ -99909,6 +100270,8 @@ var agentDecisionSchema = exports_external.object({
|
|
|
99909
100270
|
appId: exports_external.string().min(1).optional(),
|
|
99910
100271
|
url: exports_external.string().min(1).optional(),
|
|
99911
100272
|
seconds: exports_external.number().optional(),
|
|
100273
|
+
key: keyNameSchema.optional(),
|
|
100274
|
+
modifiers: exports_external.array(keyModifierSchema).optional(),
|
|
99912
100275
|
assertion: exports_external.union([exports_external.literal("visible"), exports_external.literal("not-visible")]).optional(),
|
|
99913
100276
|
timeoutMs: exports_external.number().optional(),
|
|
99914
100277
|
reason: exports_external.string().min(1),
|
|
@@ -99937,6 +100300,9 @@ var agentDecisionSchema = exports_external.object({
|
|
|
99937
100300
|
if (value.type === "open-url" && !value.url?.trim()) {
|
|
99938
100301
|
ctx.addIssue({ code: exports_external.ZodIssueCode.custom, message: "open-url requires url" });
|
|
99939
100302
|
}
|
|
100303
|
+
if (value.type === "key" && !value.key) {
|
|
100304
|
+
ctx.addIssue({ code: exports_external.ZodIssueCode.custom, message: "key requires key" });
|
|
100305
|
+
}
|
|
99940
100306
|
if (value.type === "assert" && !value.text?.trim()) {
|
|
99941
100307
|
ctx.addIssue({ code: exports_external.ZodIssueCode.custom, message: "assert requires text" });
|
|
99942
100308
|
}
|
|
@@ -100000,7 +100366,7 @@ function releaseCanvasPoint(decision, elements) {
|
|
|
100000
100366
|
return point;
|
|
100001
100367
|
}
|
|
100002
100368
|
var SNAP_RADIUS = 100;
|
|
100003
|
-
var TAPPABLE_ROLE_RE = /button|link|cell|switch|checkbox|radio|tab|toggle|textfield|searchfield|segment|menuitem|stepper|slider|picker/i;
|
|
100369
|
+
var TAPPABLE_ROLE_RE = /button|link|cell|switch|checkbox|radio|tab|toggle|textfield|searchfield|segment|menuitem|stepper|slider|picker|textbox|searchbox|combobox|option/i;
|
|
100004
100370
|
function isTappableElement(element) {
|
|
100005
100371
|
if (isFullBleedElement(element))
|
|
100006
100372
|
return false;
|
|
@@ -100223,6 +100589,12 @@ function decisionToActionRequest(decision, options = {}) {
|
|
|
100223
100589
|
};
|
|
100224
100590
|
case "open-url":
|
|
100225
100591
|
return decision.url ? { kind: "open-url", url: decision.url } : null;
|
|
100592
|
+
case "key":
|
|
100593
|
+
return decision.key ? {
|
|
100594
|
+
kind: "key",
|
|
100595
|
+
key: decision.key,
|
|
100596
|
+
...decision.modifiers?.length ? { modifiers: decision.modifiers } : {}
|
|
100597
|
+
} : null;
|
|
100226
100598
|
default:
|
|
100227
100599
|
return null;
|
|
100228
100600
|
}
|
|
@@ -100315,6 +100687,8 @@ var APP_LIFECYCLE_SHAPES = `{"type":"activate-app","appId":"com.example.app","re
|
|
|
100315
100687
|
{"type":"restart-app","appId":"com.example.app","reason":"...","thoughts":"..."}
|
|
100316
100688
|
{"type":"background-app","seconds":3,"reason":"...","thoughts":"..."}
|
|
100317
100689
|
{"type":"open-url","url":"https://example.com","reason":"...","thoughts":"..."}
|
|
100690
|
+
{"type":"key","key":"Enter","reason":"...","thoughts":"..."}
|
|
100691
|
+
{"type":"key","key":"Tab","modifiers":["Shift"],"reason":"...","thoughts":"..."}
|
|
100318
100692
|
{"type":"assert","assertion":"visible|not-visible","text":"...","reason":"...","thoughts":"..."}
|
|
100319
100693
|
{"type":"wait","ms":500-3000,"reason":"...","thoughts":"..."}
|
|
100320
100694
|
{"type":"verify","reason":"...","thoughts":"..."}
|
|
@@ -100332,6 +100706,7 @@ var SWIPE_GUIDANCE = `Swipe direction is the FINGER movement (same as Inspector)
|
|
|
100332
100706
|
- "left" / "right": horizontal paging
|
|
100333
100707
|
When the case says "scroll up", emit direction "down". When it says "scroll down" or "to the bottom", emit direction "up". Never tap to scroll. Never emit wait as a stand-in for scrolling.`;
|
|
100334
100708
|
var SHARED_RULES = `- activate-app / terminate-app / restart-app / open-url / background-app match yoqa action. Omit appId to use the catalog app id provided in the user message.
|
|
100709
|
+
- key presses one named key: ${KEY_NAMES.join("|")}, with optional modifiers ${KEY_MODIFIERS.join("|")}. Use it to submit a form (Enter), move focus (Tab), close a dialog or menu (Escape), or move through a list or combobox (arrows). If the device cannot press that key, the error says so; choose another action.
|
|
100335
100710
|
- To scroll a list or page, emit swipe \u2014 never a tap in the middle of the screen as a stand-in for scrolling.
|
|
100336
100711
|
- "Scroll until you cannot scroll anymore" / "to the bottom": keep emitting swipe (direction "up") until a later screenshot shows the same content. Then verify. Unchanged screens after taps do not count \u2014 taps do not scroll.
|
|
100337
100712
|
- You are given ONE current instruction. Complete only that instruction, then return verify or done. Later instructions are hidden on purpose \u2014 do not invent, skip ahead, or combine them. Earlier finished instructions may be omitted \u2014 do not reconstruct them.
|
|
@@ -100453,6 +100828,9 @@ function formatRecentActions(actions) {
|
|
|
100453
100828
|
if (action.type === "open-url") {
|
|
100454
100829
|
return `${n}. open-url ${action.url ?? ""} \u2014 ${action.reason ?? ""}`;
|
|
100455
100830
|
}
|
|
100831
|
+
if (action.type === "key") {
|
|
100832
|
+
return `${n}. key ${formatKeyChord(action.key ?? "?", action.modifiers)} \u2014 ${action.reason ?? ""}`;
|
|
100833
|
+
}
|
|
100456
100834
|
if (action.type === "wait") {
|
|
100457
100835
|
return `${n}. wait ${action.ms ?? "?"}ms \u2014 ${action.reason ?? ""}`;
|
|
100458
100836
|
}
|
|
@@ -100745,6 +101123,15 @@ function buildScriptFromDecisions(decisions, sourceRunId) {
|
|
|
100745
101123
|
url: decision.url,
|
|
100746
101124
|
reason: decision.reason
|
|
100747
101125
|
});
|
|
101126
|
+
} else if (decision.type === "key") {
|
|
101127
|
+
if (!decision.key)
|
|
101128
|
+
continue;
|
|
101129
|
+
actions.push({
|
|
101130
|
+
type: "key",
|
|
101131
|
+
key: decision.key,
|
|
101132
|
+
...decision.modifiers?.length ? { modifiers: decision.modifiers } : {},
|
|
101133
|
+
reason: decision.reason
|
|
101134
|
+
});
|
|
100748
101135
|
} else if (decision.type === "assert") {
|
|
100749
101136
|
if (!decision.text?.trim())
|
|
100750
101137
|
continue;
|
|
@@ -100807,6 +101194,7 @@ function mapApp(row) {
|
|
|
100807
101194
|
iosBundleId: row.iosBundleId,
|
|
100808
101195
|
iosAppStoreId: row.iosAppStoreId,
|
|
100809
101196
|
androidApplicationId: row.androidApplicationId,
|
|
101197
|
+
webUrl: row.webUrl,
|
|
100810
101198
|
capabilities: [],
|
|
100811
101199
|
createdAt: row.createdAt,
|
|
100812
101200
|
updatedAt: row.updatedAt
|
|
@@ -100981,6 +101369,7 @@ async function createApp(input) {
|
|
|
100981
101369
|
if (!name30) {
|
|
100982
101370
|
throw new CatalogValidationError("Application name is required");
|
|
100983
101371
|
}
|
|
101372
|
+
const webUrl = parseWebUrl(input.webUrl ?? "");
|
|
100984
101373
|
const db = getCatalogDb();
|
|
100985
101374
|
const now = Date.now();
|
|
100986
101375
|
const id = newId2("app");
|
|
@@ -100994,6 +101383,7 @@ async function createApp(input) {
|
|
|
100994
101383
|
iosBundleId: "",
|
|
100995
101384
|
iosAppStoreId: "",
|
|
100996
101385
|
androidApplicationId: "",
|
|
101386
|
+
webUrl,
|
|
100997
101387
|
appiumCaps: "[]",
|
|
100998
101388
|
createdAt: now,
|
|
100999
101389
|
updatedAt: now
|
|
@@ -101004,6 +101394,16 @@ async function createApp(input) {
|
|
|
101004
101394
|
}
|
|
101005
101395
|
return created;
|
|
101006
101396
|
}
|
|
101397
|
+
function parseWebUrl(raw) {
|
|
101398
|
+
const webUrl = raw.trim();
|
|
101399
|
+
if (!webUrl)
|
|
101400
|
+
return "";
|
|
101401
|
+
const protocol = URL.canParse(webUrl) ? new URL(webUrl).protocol : null;
|
|
101402
|
+
if (protocol !== "http:" && protocol !== "https:") {
|
|
101403
|
+
throw new CatalogValidationError(`Web URL must be an http or https URL, not "${webUrl}"`);
|
|
101404
|
+
}
|
|
101405
|
+
return webUrl;
|
|
101406
|
+
}
|
|
101007
101407
|
var MAX_APP_KNOWLEDGE_CHARS2 = 2048;
|
|
101008
101408
|
async function updateApp(appId, input) {
|
|
101009
101409
|
const db = getCatalogDb();
|
|
@@ -101018,6 +101418,7 @@ async function updateApp(appId, input) {
|
|
|
101018
101418
|
if (input.knowledge !== undefined && input.knowledge.length > MAX_APP_KNOWLEDGE_CHARS2) {
|
|
101019
101419
|
throw new CatalogValidationError(`App knowledge must be at most ${MAX_APP_KNOWLEDGE_CHARS2} characters`);
|
|
101020
101420
|
}
|
|
101421
|
+
const webUrl = input.webUrl !== undefined ? parseWebUrl(input.webUrl) : existing.webUrl;
|
|
101021
101422
|
const prefix = input.prefix !== undefined ? await allocatePrefix(input.prefix.trim() || name30, appId) : existing.prefix || await allocatePrefix(name30, appId);
|
|
101022
101423
|
await db.update(apps).set({
|
|
101023
101424
|
name: name30,
|
|
@@ -101027,6 +101428,7 @@ async function updateApp(appId, input) {
|
|
|
101027
101428
|
iosBundleId: input.iosBundleId ?? existing.iosBundleId,
|
|
101028
101429
|
iosAppStoreId: input.iosAppStoreId ?? existing.iosAppStoreId,
|
|
101029
101430
|
androidApplicationId: input.androidApplicationId ?? existing.androidApplicationId,
|
|
101431
|
+
webUrl,
|
|
101030
101432
|
updatedAt: Date.now()
|
|
101031
101433
|
}).where(eq(apps.id, appId));
|
|
101032
101434
|
const updated = await getApp(appId);
|
|
@@ -101279,7 +101681,7 @@ function abortAllMjpegProxies() {
|
|
|
101279
101681
|
|
|
101280
101682
|
// src/domains/devices/android-devtools.ts
|
|
101281
101683
|
import { existsSync as existsSync2 } from "fs";
|
|
101282
|
-
import { join as
|
|
101684
|
+
import { join as join11 } from "path";
|
|
101283
101685
|
function escapeAttr(value) {
|
|
101284
101686
|
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """);
|
|
101285
101687
|
}
|
|
@@ -101307,13 +101709,13 @@ var DEVTOOLS_PACKAGE = "yoqa.android.devtools";
|
|
|
101307
101709
|
var DEVTOOLS_INSTRUMENTATION = `${DEVTOOLS_PACKAGE}/.DevtoolsInstrumentation`;
|
|
101308
101710
|
var DEVTOOLS_VERSION_CODE = 2;
|
|
101309
101711
|
var DEVTOOLS_DEVICE_PORT = 7421;
|
|
101310
|
-
var
|
|
101712
|
+
var READY_TIMEOUT_MS2 = 1e4;
|
|
101311
101713
|
var TREE_TIMEOUT_MS = 5000;
|
|
101312
101714
|
function resolveDevtoolsApk() {
|
|
101313
101715
|
const fromEnv = process.env.YOQA_ANDROID_DEVTOOLS_APK?.trim();
|
|
101314
101716
|
if (fromEnv)
|
|
101315
101717
|
return fromEnv;
|
|
101316
|
-
const built =
|
|
101718
|
+
const built = join11(import.meta.dir, "../../../../../native/android-devtools/app/build/outputs/apk/debug/app-debug.apk");
|
|
101317
101719
|
return existsSync2(built) ? built : null;
|
|
101318
101720
|
}
|
|
101319
101721
|
async function adbOk(adb, args, label) {
|
|
@@ -101422,7 +101824,7 @@ async function startAndroidDevtools(context, deps = {}) {
|
|
|
101422
101824
|
});
|
|
101423
101825
|
};
|
|
101424
101826
|
try {
|
|
101425
|
-
await waitUntilReady(base, instrument, deps.readyTimeoutMs ??
|
|
101827
|
+
await waitUntilReady(base, instrument, deps.readyTimeoutMs ?? READY_TIMEOUT_MS2);
|
|
101426
101828
|
} catch (error40) {
|
|
101427
101829
|
await stop();
|
|
101428
101830
|
throw error40;
|
|
@@ -101435,9 +101837,38 @@ async function startAndroidDevtools(context, deps = {}) {
|
|
|
101435
101837
|
|
|
101436
101838
|
// src/domains/devices/android-direct-lane.ts
|
|
101437
101839
|
import { mkdir } from "fs/promises";
|
|
101438
|
-
import { dirname as dirname2, join as
|
|
101840
|
+
import { dirname as dirname2, join as join13 } from "path";
|
|
101439
101841
|
|
|
101440
101842
|
// src/domains/devices/adb-input.ts
|
|
101843
|
+
var ANDROID_KEYCODES = {
|
|
101844
|
+
Enter: 66,
|
|
101845
|
+
Tab: 61,
|
|
101846
|
+
Escape: 111,
|
|
101847
|
+
Backspace: 67,
|
|
101848
|
+
Delete: 112,
|
|
101849
|
+
ArrowUp: 19,
|
|
101850
|
+
ArrowDown: 20,
|
|
101851
|
+
ArrowLeft: 21,
|
|
101852
|
+
ArrowRight: 22,
|
|
101853
|
+
Home: 122,
|
|
101854
|
+
End: 123,
|
|
101855
|
+
PageUp: 92,
|
|
101856
|
+
PageDown: 93,
|
|
101857
|
+
Space: 62
|
|
101858
|
+
};
|
|
101859
|
+
var ANDROID_MODIFIER_KEYCODES = {
|
|
101860
|
+
Shift: 59,
|
|
101861
|
+
Control: 113,
|
|
101862
|
+
Alt: 57,
|
|
101863
|
+
Meta: 117
|
|
101864
|
+
};
|
|
101865
|
+
function adbKeyArgs(key, modifiers = []) {
|
|
101866
|
+
const code = String(ANDROID_KEYCODES[key]);
|
|
101867
|
+
if (modifiers.length === 0)
|
|
101868
|
+
return ["input", "keyevent", code];
|
|
101869
|
+
const held = modifiers.map((modifier) => String(ANDROID_MODIFIER_KEYCODES[modifier]));
|
|
101870
|
+
return ["input", "keycombination", ...held, code];
|
|
101871
|
+
}
|
|
101441
101872
|
function parseWmSize(stdout) {
|
|
101442
101873
|
const override = stdout.match(/Override size:\s*(\d+)x(\d+)/i);
|
|
101443
101874
|
const physical = stdout.match(/Physical size:\s*(\d+)x(\d+)/i);
|
|
@@ -101546,6 +101977,21 @@ function reportLaneFallback(session, options, warning) {
|
|
|
101546
101977
|
session.laneWarning = joinLaneWarnings(session.laneWarning, warning);
|
|
101547
101978
|
options.onLaneWarning?.(warning);
|
|
101548
101979
|
}
|
|
101980
|
+
|
|
101981
|
+
class UnsupportedKeyError extends Error {
|
|
101982
|
+
constructor(message) {
|
|
101983
|
+
super(message);
|
|
101984
|
+
this.name = "UnsupportedKeyError";
|
|
101985
|
+
}
|
|
101986
|
+
}
|
|
101987
|
+
function assertKeySupported(support, device, key, modifiers = []) {
|
|
101988
|
+
if (!support.keys.includes(key)) {
|
|
101989
|
+
throw new UnsupportedKeyError(`The ${key} key is not available ${device}; it can press ${support.keys.join(", ")}`);
|
|
101990
|
+
}
|
|
101991
|
+
if (modifiers.length > 0 && !support.modifiers) {
|
|
101992
|
+
throw new UnsupportedKeyError(`Modifier keys (${modifiers.join(", ")}) are not available ${device}; press ${key} without them`);
|
|
101993
|
+
}
|
|
101994
|
+
}
|
|
101549
101995
|
var DEAD_SESSION_RE = /session does not exist|invalid session id|no such session|terminated or not started|session is either terminated/i;
|
|
101550
101996
|
|
|
101551
101997
|
class DeadSessionError extends Error {
|
|
@@ -101555,6 +102001,13 @@ class DeadSessionError extends Error {
|
|
|
101555
102001
|
}
|
|
101556
102002
|
}
|
|
101557
102003
|
|
|
102004
|
+
class ActionUnsupportedError extends Error {
|
|
102005
|
+
constructor(message) {
|
|
102006
|
+
super(message);
|
|
102007
|
+
this.name = "ActionUnsupportedError";
|
|
102008
|
+
}
|
|
102009
|
+
}
|
|
102010
|
+
|
|
101558
102011
|
class DeviceUnreachableError extends Error {
|
|
101559
102012
|
unreachableMs;
|
|
101560
102013
|
graceMs;
|
|
@@ -101593,7 +102046,7 @@ function createFrameLoop(capture, options = {}) {
|
|
|
101593
102046
|
const now = () => performance.now();
|
|
101594
102047
|
let latest = null;
|
|
101595
102048
|
let fatal = null;
|
|
101596
|
-
let
|
|
102049
|
+
let running2 = false;
|
|
101597
102050
|
let kicked = false;
|
|
101598
102051
|
let lastRead = 0;
|
|
101599
102052
|
let waiters = [];
|
|
@@ -101632,12 +102085,12 @@ function createFrameLoop(capture, options = {}) {
|
|
|
101632
102085
|
}
|
|
101633
102086
|
};
|
|
101634
102087
|
const run = async () => {
|
|
101635
|
-
|
|
102088
|
+
running2 = true;
|
|
101636
102089
|
while (!fatal && (waiters.length > 0 || now() - lastRead < idleMs)) {
|
|
101637
102090
|
if (!await captureOnce(true) && !fatal)
|
|
101638
102091
|
await Bun.sleep(RETRY_AFTER_ERROR_MS);
|
|
101639
102092
|
}
|
|
101640
|
-
|
|
102093
|
+
running2 = false;
|
|
101641
102094
|
};
|
|
101642
102095
|
return {
|
|
101643
102096
|
read: (after = 0) => {
|
|
@@ -101649,12 +102102,12 @@ function createFrameLoop(capture, options = {}) {
|
|
|
101649
102102
|
const pending = new Promise((resolve7, reject) => {
|
|
101650
102103
|
waiters.push({ after, resolve: resolve7, reject });
|
|
101651
102104
|
});
|
|
101652
|
-
if (!
|
|
102105
|
+
if (!running2)
|
|
101653
102106
|
run();
|
|
101654
102107
|
return pending;
|
|
101655
102108
|
},
|
|
101656
102109
|
kick: () => {
|
|
101657
|
-
if (fatal || !
|
|
102110
|
+
if (fatal || !running2 || kicked)
|
|
101658
102111
|
return;
|
|
101659
102112
|
kicked = true;
|
|
101660
102113
|
captureOnce(false).finally(() => {
|
|
@@ -101705,9 +102158,9 @@ async function exitsWithin(proc, ms) {
|
|
|
101705
102158
|
|
|
101706
102159
|
// src/domains/devices/screenshot-retention.ts
|
|
101707
102160
|
import { readdir, stat, unlink } from "fs/promises";
|
|
101708
|
-
import { homedir as
|
|
101709
|
-
import { join as
|
|
101710
|
-
var SCREENSHOT_DIR =
|
|
102161
|
+
import { homedir as homedir8 } from "os";
|
|
102162
|
+
import { join as join12 } from "path";
|
|
102163
|
+
var SCREENSHOT_DIR = join12(homedir8(), ".yoqa", "runs", "screenshots");
|
|
101711
102164
|
var SHOT_FILE_RE = /^shot_\d+_[0-9a-f-]+\.png$/i;
|
|
101712
102165
|
var DEFAULT_RETENTION_DAYS = 7;
|
|
101713
102166
|
var DAY_MS = 24 * 60 * 60 * 1000;
|
|
@@ -101735,7 +102188,7 @@ async function pruneOldRunScreenshots(options = {}) {
|
|
|
101735
102188
|
const names = (await readdir(dir)).filter((name30) => SHOT_FILE_RE.test(name30));
|
|
101736
102189
|
const entries = [];
|
|
101737
102190
|
for (const name30 of names) {
|
|
101738
|
-
const info = await stat(
|
|
102191
|
+
const info = await stat(join12(dir, name30));
|
|
101739
102192
|
entries.push({ name: name30, mtimeMs: info.mtimeMs });
|
|
101740
102193
|
}
|
|
101741
102194
|
let removed = 0;
|
|
@@ -101744,7 +102197,7 @@ async function pruneOldRunScreenshots(options = {}) {
|
|
|
101744
102197
|
now: options.now ?? Date.now()
|
|
101745
102198
|
})) {
|
|
101746
102199
|
try {
|
|
101747
|
-
await unlink(
|
|
102200
|
+
await unlink(join12(dir, name30));
|
|
101748
102201
|
removed += 1;
|
|
101749
102202
|
} catch {}
|
|
101750
102203
|
}
|
|
@@ -101763,6 +102216,7 @@ async function readRemotePid(stdout) {
|
|
|
101763
102216
|
throw new Error(`screenrecord: could not read its pid (${pid || "empty"})`);
|
|
101764
102217
|
return pid;
|
|
101765
102218
|
}
|
|
102219
|
+
var ANDROID_KEY_SUPPORT = { keys: KEY_NAMES, modifiers: true };
|
|
101766
102220
|
var ADB_LOST_RE = /device '.*' not found|device offline|device not found|no devices\/emulators found/i;
|
|
101767
102221
|
function adbLostDevice(result) {
|
|
101768
102222
|
return result.exitCode === 127 || result.exitCode !== 0 && ADB_LOST_RE.test(result.stderr);
|
|
@@ -101881,7 +102335,7 @@ async function createAndroidDirectSession(options, deps = {}) {
|
|
|
101881
102335
|
const screenshot = async () => {
|
|
101882
102336
|
await mkdir(SCREENSHOT_DIR, { recursive: true });
|
|
101883
102337
|
const frame = await captureFrame();
|
|
101884
|
-
const path =
|
|
102338
|
+
const path = join13(SCREENSHOT_DIR, `shot_${Date.now()}_${crypto.randomUUID()}.png`);
|
|
101885
102339
|
await Bun.write(path, Uint8Array.from(Buffer.from(frame.base64, "base64")));
|
|
101886
102340
|
return { path, base64: frame.base64 };
|
|
101887
102341
|
};
|
|
@@ -101904,9 +102358,9 @@ async function createAndroidDirectSession(options, deps = {}) {
|
|
|
101904
102358
|
await adb(["-s", serial, "shell", "am", "force-stop", DEVTOOLS_PACKAGE]);
|
|
101905
102359
|
}
|
|
101906
102360
|
const stopDevtools = async () => {
|
|
101907
|
-
const
|
|
102361
|
+
const running2 = devtools;
|
|
101908
102362
|
devtools = null;
|
|
101909
|
-
await
|
|
102363
|
+
await running2?.stop().catch(() => {
|
|
101910
102364
|
return;
|
|
101911
102365
|
});
|
|
101912
102366
|
};
|
|
@@ -101980,6 +102434,12 @@ async function createAndroidDirectSession(options, deps = {}) {
|
|
|
101980
102434
|
}
|
|
101981
102435
|
});
|
|
101982
102436
|
};
|
|
102437
|
+
const key = async (name30, modifiers = []) => {
|
|
102438
|
+
assertKeySupported(ANDROID_KEY_SUPPORT, "on Android", name30, modifiers);
|
|
102439
|
+
await lock.withLock(async () => {
|
|
102440
|
+
await shell2(adbKeyArgs(name30, modifiers), "input key");
|
|
102441
|
+
});
|
|
102442
|
+
};
|
|
101983
102443
|
const activateApp = async (appId) => {
|
|
101984
102444
|
await lock.withLock(async () => {
|
|
101985
102445
|
lastAppId = appId;
|
|
@@ -102060,6 +102520,8 @@ async function createAndroidDirectSession(options, deps = {}) {
|
|
|
102060
102520
|
swipe,
|
|
102061
102521
|
drag: (x1, y1, x2, y2, durationMs = 800) => swipe(x1, y1, x2, y2, durationMs),
|
|
102062
102522
|
type,
|
|
102523
|
+
keySupport: ANDROID_KEY_SUPPORT,
|
|
102524
|
+
key,
|
|
102063
102525
|
activateApp,
|
|
102064
102526
|
setTargetApp: (appId) => {
|
|
102065
102527
|
lastAppId = appId;
|
|
@@ -102099,7 +102561,7 @@ async function createAndroidDirectSession(options, deps = {}) {
|
|
|
102099
102561
|
|
|
102100
102562
|
// src/domains/devices/ios-device-lane.ts
|
|
102101
102563
|
import { mkdir as mkdir5 } from "fs/promises";
|
|
102102
|
-
import { join as
|
|
102564
|
+
import { join as join19 } from "path";
|
|
102103
102565
|
|
|
102104
102566
|
// src/domains/devices/error-chain.ts
|
|
102105
102567
|
var MAX_LINKS = 8;
|
|
@@ -102128,9 +102590,9 @@ function errorChain(error40) {
|
|
|
102128
102590
|
}
|
|
102129
102591
|
|
|
102130
102592
|
// src/domains/devices/frame-recorder.ts
|
|
102131
|
-
import { mkdir as mkdir2, mkdtemp as
|
|
102132
|
-
import { tmpdir as
|
|
102133
|
-
import { dirname as dirname3, join as
|
|
102593
|
+
import { mkdir as mkdir2, mkdtemp as mkdtemp6, rm as rm6 } from "fs/promises";
|
|
102594
|
+
import { tmpdir as tmpdir6 } from "os";
|
|
102595
|
+
import { dirname as dirname3, join as join14 } from "path";
|
|
102134
102596
|
var LAST_FRAME_HOLD_S = 0.5;
|
|
102135
102597
|
async function encodeWithFfmpeg(ffmpeg, list, out) {
|
|
102136
102598
|
const proc = Bun.spawn([
|
|
@@ -102178,36 +102640,36 @@ async function recordFrames(source, path, deps = realDeps) {
|
|
|
102178
102640
|
if (!ffmpeg)
|
|
102179
102641
|
throw new Error("ffmpeg not found on PATH");
|
|
102180
102642
|
await mkdir2(dirname3(path), { recursive: true });
|
|
102181
|
-
const dir = await
|
|
102643
|
+
const dir = await mkdtemp6(join14(tmpdir6(), "yoqa-frames-"));
|
|
102182
102644
|
try {
|
|
102183
102645
|
await source.start?.();
|
|
102184
102646
|
} catch (error40) {
|
|
102185
|
-
await
|
|
102647
|
+
await rm6(dir, { recursive: true, force: true }).catch(() => {
|
|
102186
102648
|
return;
|
|
102187
102649
|
});
|
|
102188
102650
|
throw error40;
|
|
102189
102651
|
}
|
|
102190
102652
|
const frames = [];
|
|
102191
|
-
let
|
|
102653
|
+
let running2 = true;
|
|
102192
102654
|
const pullOnce = async () => {
|
|
102193
102655
|
try {
|
|
102194
102656
|
for (const frame of await source.pull()) {
|
|
102195
|
-
const file2 =
|
|
102657
|
+
const file2 = join14(dir, `${String(frames.length).padStart(6, "0")}.${frame.ext}`);
|
|
102196
102658
|
await Bun.write(file2, frame.bytes);
|
|
102197
102659
|
frames.push({ file: file2, at: frame.at });
|
|
102198
102660
|
}
|
|
102199
102661
|
} catch {}
|
|
102200
102662
|
};
|
|
102201
102663
|
const loop = (async () => {
|
|
102202
|
-
while (
|
|
102664
|
+
while (running2) {
|
|
102203
102665
|
await pullOnce();
|
|
102204
|
-
if (
|
|
102666
|
+
if (running2)
|
|
102205
102667
|
await Bun.sleep(source.intervalMs);
|
|
102206
102668
|
}
|
|
102207
102669
|
})();
|
|
102208
102670
|
return {
|
|
102209
102671
|
stop: async () => {
|
|
102210
|
-
|
|
102672
|
+
running2 = false;
|
|
102211
102673
|
await loop;
|
|
102212
102674
|
try {
|
|
102213
102675
|
await pullOnce();
|
|
@@ -102223,13 +102685,13 @@ async function recordFrames(source, path, deps = realDeps) {
|
|
|
102223
102685
|
lines.push(`file '${frame.file}'`, `duration ${Math.max(seconds, 0.001).toFixed(3)}`);
|
|
102224
102686
|
}
|
|
102225
102687
|
lines.push(`file '${frames[frames.length - 1]?.file}'`);
|
|
102226
|
-
const list =
|
|
102688
|
+
const list = join14(dir, "frames.txt");
|
|
102227
102689
|
await Bun.write(list, `${lines.join(`
|
|
102228
102690
|
`)}
|
|
102229
102691
|
`);
|
|
102230
102692
|
await deps.encode(ffmpeg, list, path);
|
|
102231
102693
|
} finally {
|
|
102232
|
-
await
|
|
102694
|
+
await rm6(dir, { recursive: true, force: true }).catch(() => {
|
|
102233
102695
|
return;
|
|
102234
102696
|
});
|
|
102235
102697
|
}
|
|
@@ -102565,16 +103027,16 @@ async function startPhonePreview(send) {
|
|
|
102565
103027
|
}
|
|
102566
103028
|
|
|
102567
103029
|
// src/domains/devices/ios-direct-lane.ts
|
|
102568
|
-
import { mkdir as mkdir3, readFile as
|
|
102569
|
-
import { tmpdir as
|
|
102570
|
-
import { dirname as dirname4, join as
|
|
103030
|
+
import { mkdir as mkdir3, readFile as readFile4 } from "fs/promises";
|
|
103031
|
+
import { tmpdir as tmpdir7 } from "os";
|
|
103032
|
+
import { dirname as dirname4, join as join17 } from "path";
|
|
102571
103033
|
|
|
102572
103034
|
// src/domains/devices/idb-companion.ts
|
|
102573
103035
|
import { existsSync as existsSync3 } from "fs";
|
|
102574
|
-
import { homedir as
|
|
102575
|
-
import { join as
|
|
102576
|
-
var IDB_COMPANION_HOME =
|
|
102577
|
-
var IDB_CLIENT_HOME =
|
|
103036
|
+
import { homedir as homedir9 } from "os";
|
|
103037
|
+
import { join as join15 } from "path";
|
|
103038
|
+
var IDB_COMPANION_HOME = join15(homedir9(), ".yoqa", "idb", "idb_companion");
|
|
103039
|
+
var IDB_CLIENT_HOME = join15(homedir9(), ".yoqa", "idb", "venv", "bin", "idb");
|
|
102578
103040
|
var MISSING_IDB_MESSAGE = "idb_companion not found. Install the official facebook/idb v1.6.5 macOS binary from GitHub releases to ~/.yoqa/idb/idb_companion (or set YOQA_IDB_COMPANION). Do not use `brew trust facebook/fb`.";
|
|
102579
103041
|
function resolveIdbCompanionPath(env = process.env, exists2 = existsSync3, which2 = (name30) => Bun.which(name30)) {
|
|
102580
103042
|
const fromEnv = env.YOQA_IDB_COMPANION?.trim();
|
|
@@ -102756,9 +103218,83 @@ function cleanIdbCompleteSource(raw, window2) {
|
|
|
102756
103218
|
}
|
|
102757
103219
|
return { elements, window: window2 };
|
|
102758
103220
|
}
|
|
103221
|
+
var WEB_STRUCTURAL_ROLES = new Set(["RootWebArea", "InlineTextBox", "LineBreak"]);
|
|
103222
|
+
var WEB_CONTROL_ROLES = new Set([
|
|
103223
|
+
"button",
|
|
103224
|
+
"link",
|
|
103225
|
+
"checkbox",
|
|
103226
|
+
"radio",
|
|
103227
|
+
"switch",
|
|
103228
|
+
"tab",
|
|
103229
|
+
"menuitem",
|
|
103230
|
+
"menuitemcheckbox",
|
|
103231
|
+
"menuitemradio",
|
|
103232
|
+
"option",
|
|
103233
|
+
"textbox",
|
|
103234
|
+
"searchbox",
|
|
103235
|
+
"combobox",
|
|
103236
|
+
"slider",
|
|
103237
|
+
"spinbutton"
|
|
103238
|
+
]);
|
|
103239
|
+
var WEB_TEXT_ROLES = new Set(["StaticText"]);
|
|
103240
|
+
var GENERATED_DOM_ID = /^(:r[0-9a-z]+:|\u00ABr[0-9a-z]+\u00BB)$/i;
|
|
103241
|
+
function axText(value) {
|
|
103242
|
+
return typeof value?.value === "string" ? value.value.replace(/\s+/g, " ").trim() : "";
|
|
103243
|
+
}
|
|
103244
|
+
function webIdOf(node) {
|
|
103245
|
+
const testId = node.testId?.trim();
|
|
103246
|
+
if (testId)
|
|
103247
|
+
return testId;
|
|
103248
|
+
const domId = node.domId?.trim() ?? "";
|
|
103249
|
+
return GENERATED_DOM_ID.test(domId) ? "" : domId;
|
|
103250
|
+
}
|
|
103251
|
+
function cleanWebAxSource(source, window2) {
|
|
103252
|
+
const byId = new Map(source.axTree.map((node) => [node.nodeId, node]));
|
|
103253
|
+
const namedParentLabel = (node) => {
|
|
103254
|
+
let parent = node.parentId ? byId.get(node.parentId) : undefined;
|
|
103255
|
+
while (parent) {
|
|
103256
|
+
const label = axText(parent.name);
|
|
103257
|
+
if (label && !parent.ignored)
|
|
103258
|
+
return label;
|
|
103259
|
+
parent = parent.parentId ? byId.get(parent.parentId) : undefined;
|
|
103260
|
+
}
|
|
103261
|
+
return "";
|
|
103262
|
+
};
|
|
103263
|
+
const elements = [];
|
|
103264
|
+
for (const node of source.axTree) {
|
|
103265
|
+
if (node.ignored || !node.bounds)
|
|
103266
|
+
continue;
|
|
103267
|
+
if (node.bounds.width <= 0 || node.bounds.height <= 0)
|
|
103268
|
+
continue;
|
|
103269
|
+
const type = axText(node.role);
|
|
103270
|
+
if (WEB_STRUCTURAL_ROLES.has(type))
|
|
103271
|
+
continue;
|
|
103272
|
+
const label = axText(node.name) || axText(node.value);
|
|
103273
|
+
if (!label && !WEB_CONTROL_ROLES.has(type))
|
|
103274
|
+
continue;
|
|
103275
|
+
if (WEB_TEXT_ROLES.has(type) && namedParentLabel(node).includes(label))
|
|
103276
|
+
continue;
|
|
103277
|
+
const id = webIdOf(node);
|
|
103278
|
+
const disabled = node.properties?.some((property) => property.name === "disabled" && property.value?.value === true);
|
|
103279
|
+
pushNormalized(elements, window2, {
|
|
103280
|
+
type,
|
|
103281
|
+
label,
|
|
103282
|
+
id: id || undefined,
|
|
103283
|
+
rect: node.bounds,
|
|
103284
|
+
enabled: !disabled
|
|
103285
|
+
});
|
|
103286
|
+
}
|
|
103287
|
+
return { elements, window: window2 };
|
|
103288
|
+
}
|
|
103289
|
+
function isWebAxSource(parsed) {
|
|
103290
|
+
return typeof parsed === "object" && parsed !== null && Array.isArray(parsed.axTree);
|
|
103291
|
+
}
|
|
102759
103292
|
function cleanPageSource(xml, window2) {
|
|
102760
103293
|
const trimmed = xml.trim();
|
|
102761
103294
|
if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
|
|
103295
|
+
const parsed = JSON.parse(trimmed);
|
|
103296
|
+
if (isWebAxSource(parsed))
|
|
103297
|
+
return cleanWebAxSource(parsed, window2);
|
|
102762
103298
|
return cleanIdbCompleteSource(trimmed, window2);
|
|
102763
103299
|
}
|
|
102764
103300
|
const elements = [];
|
|
@@ -102802,7 +103338,7 @@ function cleanPageSource(xml, window2) {
|
|
|
102802
103338
|
|
|
102803
103339
|
// src/domains/devices/yoqa-sim.ts
|
|
102804
103340
|
import { existsSync as existsSync4 } from "fs";
|
|
102805
|
-
import { join as
|
|
103341
|
+
import { join as join16 } from "path";
|
|
102806
103342
|
|
|
102807
103343
|
class YoqaSimUnreachableError extends Error {
|
|
102808
103344
|
constructor(message) {
|
|
@@ -102813,19 +103349,19 @@ class YoqaSimUnreachableError extends Error {
|
|
|
102813
103349
|
function spawnProcess(command) {
|
|
102814
103350
|
return Bun.spawn(command, { stdin: "pipe", stdout: "pipe", stderr: "pipe" });
|
|
102815
103351
|
}
|
|
102816
|
-
var
|
|
103352
|
+
var READY_TIMEOUT_MS3 = 1e4;
|
|
102817
103353
|
var STREAM_READY_TIMEOUT_MS = 500;
|
|
102818
103354
|
var REQUEST_TIMEOUT_MS = 1e4;
|
|
102819
|
-
var
|
|
103355
|
+
var STOP_GRACE_MS2 = 2000;
|
|
102820
103356
|
var API_READY_RE = /^api_ready (http:\/\/127\.0\.0\.1:\d+)\s*$/;
|
|
102821
103357
|
var STREAM_READY_RE = /^stream_ready (http:\/\/127\.0\.0\.1:\d+\/\S+)\s*$/;
|
|
102822
103358
|
function resolveYoqaSimBin() {
|
|
102823
103359
|
const fromEnv = process.env.YOQA_SIM_BIN?.trim();
|
|
102824
103360
|
if (fromEnv)
|
|
102825
103361
|
return fromEnv;
|
|
102826
|
-
const root =
|
|
103362
|
+
const root = join16(import.meta.dir, "../../../../../native/yoqa-sim/.build");
|
|
102827
103363
|
for (const config2 of ["release", "debug"]) {
|
|
102828
|
-
const built =
|
|
103364
|
+
const built = join16(root, config2, "yoqa-sim");
|
|
102829
103365
|
if (existsSync4(built))
|
|
102830
103366
|
return built;
|
|
102831
103367
|
}
|
|
@@ -102885,12 +103421,12 @@ async function spawnYoqaSim(udid, options) {
|
|
|
102885
103421
|
proc.kill("SIGTERM");
|
|
102886
103422
|
const exited = await Promise.race([
|
|
102887
103423
|
proc.exited.then(() => true),
|
|
102888
|
-
Bun.sleep(
|
|
103424
|
+
Bun.sleep(STOP_GRACE_MS2).then(() => false)
|
|
102889
103425
|
]);
|
|
102890
103426
|
if (!exited)
|
|
102891
103427
|
proc.kill("SIGKILL");
|
|
102892
103428
|
};
|
|
102893
|
-
const timeoutMs = options.readyTimeoutMs ??
|
|
103429
|
+
const timeoutMs = options.readyTimeoutMs ?? READY_TIMEOUT_MS3;
|
|
102894
103430
|
let timer;
|
|
102895
103431
|
const announced = await Promise.race([
|
|
102896
103432
|
readAnnouncements(proc.stdout, options.streamReadyTimeoutMs ?? STREAM_READY_TIMEOUT_MS, () => clearTimeout(timer)),
|
|
@@ -102899,7 +103435,7 @@ async function spawnYoqaSim(udid, options) {
|
|
|
102899
103435
|
})
|
|
102900
103436
|
]).finally(() => clearTimeout(timer));
|
|
102901
103437
|
const stderrTail = async () => {
|
|
102902
|
-
const text3 = await Promise.race([stderr, Bun.sleep(
|
|
103438
|
+
const text3 = await Promise.race([stderr, Bun.sleep(STOP_GRACE_MS2).then(() => "")]);
|
|
102903
103439
|
return text3.trim().split(`
|
|
102904
103440
|
`).slice(-3).join(" ");
|
|
102905
103441
|
};
|
|
@@ -102969,6 +103505,23 @@ async function spawnYoqaSim(udid, options) {
|
|
|
102969
103505
|
var IOS_ACCEPT_LABELS = ["Allow While Using App", "Allow Once", "Allow", "OK"];
|
|
102970
103506
|
var IOS_DISMISS_LABELS = ["Don't Allow", "Don\u2019t Allow", "Don't allow"];
|
|
102971
103507
|
var graphemes = new Intl.Segmenter(undefined, { granularity: "grapheme" });
|
|
103508
|
+
var IOS_HID_KEYCODES = {
|
|
103509
|
+
Enter: 40,
|
|
103510
|
+
Escape: 41,
|
|
103511
|
+
Backspace: 42,
|
|
103512
|
+
Tab: 43,
|
|
103513
|
+
Space: 44,
|
|
103514
|
+
Home: 74,
|
|
103515
|
+
PageUp: 75,
|
|
103516
|
+
Delete: 76,
|
|
103517
|
+
End: 77,
|
|
103518
|
+
PageDown: 78,
|
|
103519
|
+
ArrowRight: 79,
|
|
103520
|
+
ArrowLeft: 80,
|
|
103521
|
+
ArrowDown: 81,
|
|
103522
|
+
ArrowUp: 82
|
|
103523
|
+
};
|
|
103524
|
+
var IOS_SIMULATOR_KEY_SUPPORT = { keys: KEY_NAMES, modifiers: false };
|
|
102972
103525
|
var IDB_LOST_RE = /StatusCode\.UNAVAILABLE|failed to connect|Connection refused|companion .*(?:not running|terminated)|(?:device|simulator|target) .*not found|not booted/i;
|
|
102973
103526
|
function idbLostDevice(result) {
|
|
102974
103527
|
return result.exitCode === 127 || result.exitCode !== 0 && IDB_LOST_RE.test(`${result.stderr}
|
|
@@ -103061,7 +103614,7 @@ async function recordViaSimctl(udid, path) {
|
|
|
103061
103614
|
};
|
|
103062
103615
|
}
|
|
103063
103616
|
async function captureViaSimctl(udid) {
|
|
103064
|
-
const dest =
|
|
103617
|
+
const dest = join17(tmpdir7(), `yoqa-simctl-${crypto.randomUUID()}.png`);
|
|
103065
103618
|
const proc = Bun.spawn(["xcrun", "simctl", "io", udid, "screenshot", dest], {
|
|
103066
103619
|
stdout: "pipe",
|
|
103067
103620
|
stderr: "pipe"
|
|
@@ -103070,7 +103623,7 @@ async function captureViaSimctl(udid) {
|
|
|
103070
103623
|
if (exitCode !== 0) {
|
|
103071
103624
|
throw new Error(`simctl screenshot: ${stderr.trim() || `exit ${exitCode}`}`);
|
|
103072
103625
|
}
|
|
103073
|
-
return new Uint8Array(await
|
|
103626
|
+
return new Uint8Array(await readFile4(dest));
|
|
103074
103627
|
}
|
|
103075
103628
|
async function createIosDirectSession(options, deps = {}) {
|
|
103076
103629
|
if (options.platform !== "ios") {
|
|
@@ -103108,17 +103661,17 @@ async function createIosDirectSession(options, deps = {}) {
|
|
|
103108
103661
|
return yoqaSim;
|
|
103109
103662
|
};
|
|
103110
103663
|
const viaYoqaSim = async (use, retryable = false) => {
|
|
103111
|
-
const
|
|
103112
|
-
if (!
|
|
103664
|
+
const running2 = await startHelpers();
|
|
103665
|
+
if (!running2)
|
|
103113
103666
|
return null;
|
|
103114
103667
|
try {
|
|
103115
|
-
return { value: await use(
|
|
103668
|
+
return { value: await use(running2) };
|
|
103116
103669
|
} catch (error40) {
|
|
103117
103670
|
if (!retryable && !(error40 instanceof YoqaSimUnreachableError))
|
|
103118
103671
|
throw error40;
|
|
103119
103672
|
yoqaSimFailed(error40);
|
|
103120
103673
|
yoqaSim = Promise.resolve(null);
|
|
103121
|
-
await
|
|
103674
|
+
await running2.stop().catch(() => {
|
|
103122
103675
|
return;
|
|
103123
103676
|
});
|
|
103124
103677
|
return null;
|
|
@@ -103144,11 +103697,11 @@ async function createIosDirectSession(options, deps = {}) {
|
|
|
103144
103697
|
return lock.withLock(fn);
|
|
103145
103698
|
};
|
|
103146
103699
|
const captureViaIdb = async () => {
|
|
103147
|
-
const dest =
|
|
103700
|
+
const dest = join17(tmpdir7(), `yoqa-idb-${crypto.randomUUID()}.png`);
|
|
103148
103701
|
let bytes;
|
|
103149
103702
|
try {
|
|
103150
103703
|
const result = await run(["screenshot", dest, "--udid", udid], "idb screenshot");
|
|
103151
|
-
bytes = result.stdoutBytes && result.stdoutBytes.byteLength > 8 ? result.stdoutBytes : new Uint8Array(await
|
|
103704
|
+
bytes = result.stdoutBytes && result.stdoutBytes.byteLength > 8 ? result.stdoutBytes : new Uint8Array(await readFile4(dest));
|
|
103152
103705
|
if (bytes.byteLength === 0)
|
|
103153
103706
|
throw new Error("idb screenshot returned an empty frame");
|
|
103154
103707
|
} catch (error40) {
|
|
@@ -103161,8 +103714,8 @@ async function createIosDirectSession(options, deps = {}) {
|
|
|
103161
103714
|
return bytes;
|
|
103162
103715
|
};
|
|
103163
103716
|
const captureFrame = async () => {
|
|
103164
|
-
const fromYoqaSim = await viaYoqaSim(async (
|
|
103165
|
-
const frame = await
|
|
103717
|
+
const fromYoqaSim = await viaYoqaSim(async (running2) => {
|
|
103718
|
+
const frame = await running2.frame();
|
|
103166
103719
|
if (frame.bytes.byteLength === 0)
|
|
103167
103720
|
throw new Error("empty frame");
|
|
103168
103721
|
return frame;
|
|
@@ -103175,7 +103728,7 @@ async function createIosDirectSession(options, deps = {}) {
|
|
|
103175
103728
|
const screenshot = async () => {
|
|
103176
103729
|
await mkdir3(SCREENSHOT_DIR, { recursive: true });
|
|
103177
103730
|
const frame = await captureFrame();
|
|
103178
|
-
const path =
|
|
103731
|
+
const path = join17(SCREENSHOT_DIR, `shot_${Date.now()}_${crypto.randomUUID()}.png`);
|
|
103179
103732
|
await Bun.write(path, Uint8Array.from(Buffer.from(frame.base64, "base64")));
|
|
103180
103733
|
return { path, base64: frame.base64 };
|
|
103181
103734
|
};
|
|
@@ -103203,7 +103756,7 @@ async function createIosDirectSession(options, deps = {}) {
|
|
|
103203
103756
|
const treeViaYoqaAx = async () => {
|
|
103204
103757
|
if (!await yoqaAx)
|
|
103205
103758
|
return null;
|
|
103206
|
-
const hash = (await viaYoqaSim((
|
|
103759
|
+
const hash = (await viaYoqaSim((running2) => running2.frame(), true))?.value.hash;
|
|
103207
103760
|
if (hash && cachedTree?.hash === hash)
|
|
103208
103761
|
return cachedTree.source;
|
|
103209
103762
|
const read2 = await viaYoqaAx(async (ax) => {
|
|
@@ -103231,14 +103784,14 @@ async function createIosDirectSession(options, deps = {}) {
|
|
|
103231
103784
|
await run(["ui", "tap", String(x), String(y), "--udid", udid, "--api", "hid"], "idb ui tap");
|
|
103232
103785
|
};
|
|
103233
103786
|
const tapNorm = async (xNorm, yNorm, tapOptions) => {
|
|
103234
|
-
const sent = await viaYoqaSim((
|
|
103787
|
+
const sent = await viaYoqaSim((running2) => running2.tap(fraction(xNorm), fraction(yNorm), tapOptions?.durationMs));
|
|
103235
103788
|
if (sent)
|
|
103236
103789
|
return;
|
|
103237
103790
|
const size = await getWindowSize();
|
|
103238
103791
|
await tapPx(toPx(xNorm, size.width), toPx(yNorm, size.height));
|
|
103239
103792
|
};
|
|
103240
103793
|
const swipeNorm = async (x1, y1, x2, y2, durationMs) => {
|
|
103241
|
-
const sent = await viaYoqaSim((
|
|
103794
|
+
const sent = await viaYoqaSim((running2) => running2.swipe({ x: fraction(x1), y: fraction(y1) }, { x: fraction(x2), y: fraction(y2) }, durationMs));
|
|
103242
103795
|
if (sent)
|
|
103243
103796
|
return;
|
|
103244
103797
|
const size = await getWindowSize();
|
|
@@ -103266,6 +103819,12 @@ async function createIosDirectSession(options, deps = {}) {
|
|
|
103266
103819
|
}
|
|
103267
103820
|
});
|
|
103268
103821
|
};
|
|
103822
|
+
const key = async (name30, modifiers = []) => {
|
|
103823
|
+
assertKeySupported(IOS_SIMULATOR_KEY_SUPPORT, "on an iOS simulator", name30, modifiers);
|
|
103824
|
+
await withActionLock(async () => {
|
|
103825
|
+
await run(["ui", "key", String(IOS_HID_KEYCODES[name30]), "--udid", udid], "idb ui key");
|
|
103826
|
+
});
|
|
103827
|
+
};
|
|
103269
103828
|
const activateApp = async (appId) => {
|
|
103270
103829
|
await withActionLock(async () => {
|
|
103271
103830
|
lastAppId = appId;
|
|
@@ -103279,7 +103838,7 @@ async function createIosDirectSession(options, deps = {}) {
|
|
|
103279
103838
|
};
|
|
103280
103839
|
const backgroundApp = async (seconds = 3) => {
|
|
103281
103840
|
await withActionLock(async () => {
|
|
103282
|
-
const pressed = await viaYoqaSim((
|
|
103841
|
+
const pressed = await viaYoqaSim((running2) => running2.key("home"));
|
|
103283
103842
|
if (!pressed)
|
|
103284
103843
|
await run(["ui", "button", "HOME", "--udid", udid], "idb HOME");
|
|
103285
103844
|
await Bun.sleep(Math.max(0, seconds) * 1000);
|
|
@@ -103314,10 +103873,10 @@ async function createIosDirectSession(options, deps = {}) {
|
|
|
103314
103873
|
quit: async () => {
|
|
103315
103874
|
quitting = true;
|
|
103316
103875
|
session.stream = null;
|
|
103317
|
-
const [
|
|
103876
|
+
const [running2, ax] = await Promise.all([yoqaSim, yoqaAx]);
|
|
103318
103877
|
yoqaSim = null;
|
|
103319
103878
|
yoqaAx = null;
|
|
103320
|
-
await Promise.all([
|
|
103879
|
+
await Promise.all([running2?.stop(), ax?.stop()]);
|
|
103321
103880
|
},
|
|
103322
103881
|
startRecording: (path) => recordViaSimctl(udid, path),
|
|
103323
103882
|
captureFrame,
|
|
@@ -103328,6 +103887,8 @@ async function createIosDirectSession(options, deps = {}) {
|
|
|
103328
103887
|
swipe,
|
|
103329
103888
|
drag: (x1, y1, x2, y2, durationMs = 800) => swipe(x1, y1, x2, y2, durationMs),
|
|
103330
103889
|
type,
|
|
103890
|
+
keySupport: IOS_SIMULATOR_KEY_SUPPORT,
|
|
103891
|
+
key,
|
|
103331
103892
|
activateApp,
|
|
103332
103893
|
setTargetApp: (appId) => {
|
|
103333
103894
|
lastAppId = appId;
|
|
@@ -103387,10 +103948,10 @@ function yoqaAxTreeToSource(tree, window2) {
|
|
|
103387
103948
|
}));
|
|
103388
103949
|
return JSON.stringify(tree.degraded ? { elements, degraded: true } : { elements });
|
|
103389
103950
|
}
|
|
103390
|
-
function liveStream(
|
|
103391
|
-
if (!
|
|
103951
|
+
function liveStream(running2) {
|
|
103952
|
+
if (!running2?.streamUrl)
|
|
103392
103953
|
return null;
|
|
103393
|
-
const { streamUrl } =
|
|
103954
|
+
const { streamUrl } = running2;
|
|
103394
103955
|
return { ready: true, port: Number(new URL(streamUrl).port), upstreamUrl: streamUrl };
|
|
103395
103956
|
}
|
|
103396
103957
|
|
|
@@ -103616,9 +104177,9 @@ async function connectUsbmux(udid, port, socketPath = USBMUXD_SOCKET) {
|
|
|
103616
104177
|
// src/domains/devices/yoqa-runner.ts
|
|
103617
104178
|
import { X509Certificate, createHash } from "crypto";
|
|
103618
104179
|
import { existsSync as existsSync5, mkdirSync as mkdirSync2, readFileSync, renameSync, rmSync } from "fs";
|
|
103619
|
-
import { copyFile, cp, mkdir as mkdir4, readFile as
|
|
103620
|
-
import { homedir as
|
|
103621
|
-
import { dirname as dirname5, join as
|
|
104180
|
+
import { copyFile, cp, mkdir as mkdir4, readFile as readFile5, readdir as readdir2, rename, rm as rm7, writeFile as writeFile4 } from "fs/promises";
|
|
104181
|
+
import { homedir as homedir10 } from "os";
|
|
104182
|
+
import { dirname as dirname5, join as join18, relative } from "path";
|
|
103622
104183
|
|
|
103623
104184
|
class YoqaRunnerError extends Error {
|
|
103624
104185
|
constructor(message) {
|
|
@@ -103680,7 +104241,7 @@ function normalizeTeamId(value) {
|
|
|
103680
104241
|
}
|
|
103681
104242
|
async function readSavedTeam(settingsPath) {
|
|
103682
104243
|
try {
|
|
103683
|
-
const parsed = JSON.parse(await
|
|
104244
|
+
const parsed = JSON.parse(await readFile5(settingsPath, "utf8"));
|
|
103684
104245
|
return typeof parsed.team === "string" ? normalizeTeamId(parsed.team) : null;
|
|
103685
104246
|
} catch {
|
|
103686
104247
|
return null;
|
|
@@ -103688,7 +104249,7 @@ async function readSavedTeam(settingsPath) {
|
|
|
103688
104249
|
}
|
|
103689
104250
|
async function saveTeam(settingsPath, team) {
|
|
103690
104251
|
if (team === null) {
|
|
103691
|
-
await
|
|
104252
|
+
await rm7(settingsPath, { force: true });
|
|
103692
104253
|
return;
|
|
103693
104254
|
}
|
|
103694
104255
|
await mkdir4(dirname5(settingsPath), { recursive: true });
|
|
@@ -103757,16 +104318,16 @@ async function hashSources(projectDir) {
|
|
|
103757
104318
|
entries = (await readdir2(path)).sort();
|
|
103758
104319
|
} catch {
|
|
103759
104320
|
hash.update(`${relative(projectDir, path)}\x00`);
|
|
103760
|
-
hash.update(await
|
|
104321
|
+
hash.update(await readFile5(path));
|
|
103761
104322
|
return;
|
|
103762
104323
|
}
|
|
103763
104324
|
for (const entry of entries) {
|
|
103764
104325
|
if (!IGNORED.has(entry))
|
|
103765
|
-
await visit9(
|
|
104326
|
+
await visit9(join18(path, entry));
|
|
103766
104327
|
}
|
|
103767
104328
|
};
|
|
103768
104329
|
for (const entry of SOURCE_ENTRIES) {
|
|
103769
|
-
const path =
|
|
104330
|
+
const path = join18(projectDir, entry);
|
|
103770
104331
|
if (existsSync5(path))
|
|
103771
104332
|
await visit9(path);
|
|
103772
104333
|
}
|
|
@@ -103787,11 +104348,11 @@ function parseProfile(plist) {
|
|
|
103787
104348
|
};
|
|
103788
104349
|
}
|
|
103789
104350
|
async function readProfiles(products, exec) {
|
|
103790
|
-
const platform =
|
|
104351
|
+
const platform = join18(products, "Debug-iphoneos");
|
|
103791
104352
|
const apps2 = (await readdir2(platform).catch(() => [])).filter((name30) => name30.endsWith(".app"));
|
|
103792
104353
|
const profiles = [];
|
|
103793
104354
|
for (const app of apps2) {
|
|
103794
|
-
const path =
|
|
104355
|
+
const path = join18(platform, app, "embedded.mobileprovision");
|
|
103795
104356
|
if (!existsSync5(path))
|
|
103796
104357
|
continue;
|
|
103797
104358
|
const decoded = await exec(["security", "cms", "-D", "-i", path]);
|
|
@@ -103806,7 +104367,7 @@ function usableFor(manifest, udid, now) {
|
|
|
103806
104367
|
}
|
|
103807
104368
|
async function readManifest(path) {
|
|
103808
104369
|
try {
|
|
103809
|
-
return JSON.parse(await
|
|
104370
|
+
return JSON.parse(await readFile5(path, "utf8"));
|
|
103810
104371
|
} catch {
|
|
103811
104372
|
return null;
|
|
103812
104373
|
}
|
|
@@ -103814,14 +104375,14 @@ async function readManifest(path) {
|
|
|
103814
104375
|
async function findXctestrun(products) {
|
|
103815
104376
|
const entries = await readdir2(products).catch(() => []);
|
|
103816
104377
|
const name30 = entries.find((entry) => entry.endsWith(".xctestrun"));
|
|
103817
|
-
return name30 ?
|
|
104378
|
+
return name30 ? join18(products, name30) : null;
|
|
103818
104379
|
}
|
|
103819
104380
|
async function brandRunnerApp(products, exec) {
|
|
103820
|
-
const platform =
|
|
103821
|
-
const host =
|
|
103822
|
-
const runner =
|
|
103823
|
-
const staging =
|
|
103824
|
-
const copy =
|
|
104381
|
+
const platform = join18(products, "Debug-iphoneos");
|
|
104382
|
+
const host = join18(platform, "YoqaRunner.app");
|
|
104383
|
+
const runner = join18(platform, "YoqaRunnerUITests-Runner.app");
|
|
104384
|
+
const staging = join18(products, "branding");
|
|
104385
|
+
const copy = join18(staging, "YoqaRunnerUITests-Runner.app");
|
|
103825
104386
|
const run = async (command) => {
|
|
103826
104387
|
const result = await exec(command);
|
|
103827
104388
|
if (result.exitCode !== 0) {
|
|
@@ -103833,10 +104394,10 @@ async function brandRunnerApp(products, exec) {
|
|
|
103833
104394
|
const icons = (await readdir2(host)).filter((name30) => name30.startsWith("AppIcon") || name30 === "Assets.car");
|
|
103834
104395
|
if (!icons.some((name30) => name30.startsWith("AppIcon")))
|
|
103835
104396
|
return "the host app has no AppIcon";
|
|
103836
|
-
await
|
|
104397
|
+
await rm7(staging, { recursive: true, force: true });
|
|
103837
104398
|
await cp(runner, copy, { recursive: true, verbatimSymlinks: true });
|
|
103838
104399
|
for (const name30 of icons)
|
|
103839
|
-
await copyFile(
|
|
104400
|
+
await copyFile(join18(host, name30), join18(copy, name30));
|
|
103840
104401
|
const plist = await run([
|
|
103841
104402
|
"plutil",
|
|
103842
104403
|
"-extract",
|
|
@@ -103844,7 +104405,7 @@ async function brandRunnerApp(products, exec) {
|
|
|
103844
104405
|
"json",
|
|
103845
104406
|
"-o",
|
|
103846
104407
|
"-",
|
|
103847
|
-
|
|
104408
|
+
join18(host, "Info.plist")
|
|
103848
104409
|
]);
|
|
103849
104410
|
await run([
|
|
103850
104411
|
"plutil",
|
|
@@ -103852,11 +104413,11 @@ async function brandRunnerApp(products, exec) {
|
|
|
103852
104413
|
"CFBundleIcons",
|
|
103853
104414
|
"-json",
|
|
103854
104415
|
plist.trim(),
|
|
103855
|
-
|
|
104416
|
+
join18(copy, "Info.plist")
|
|
103856
104417
|
]);
|
|
103857
|
-
const certificates =
|
|
104418
|
+
const certificates = join18(staging, "certificate");
|
|
103858
104419
|
await run(["codesign", "-d", `--extract-certificates=${certificates}`, runner]);
|
|
103859
|
-
const identity2 = createHash("sha1").update(await
|
|
104420
|
+
const identity2 = createHash("sha1").update(await readFile5(`${certificates}0`)).digest("hex").toUpperCase();
|
|
103860
104421
|
await run([
|
|
103861
104422
|
"codesign",
|
|
103862
104423
|
"--force",
|
|
@@ -103866,7 +104427,7 @@ async function brandRunnerApp(products, exec) {
|
|
|
103866
104427
|
"--generate-entitlement-der",
|
|
103867
104428
|
copy
|
|
103868
104429
|
]);
|
|
103869
|
-
const unbranded =
|
|
104430
|
+
const unbranded = join18(staging, "unbranded.app");
|
|
103870
104431
|
await rename(runner, unbranded);
|
|
103871
104432
|
try {
|
|
103872
104433
|
await rename(copy, runner);
|
|
@@ -103878,7 +104439,7 @@ async function brandRunnerApp(products, exec) {
|
|
|
103878
104439
|
} catch (error40) {
|
|
103879
104440
|
return error40 instanceof Error ? error40.message : String(error40);
|
|
103880
104441
|
} finally {
|
|
103881
|
-
await
|
|
104442
|
+
await rm7(staging, { recursive: true, force: true });
|
|
103882
104443
|
}
|
|
103883
104444
|
}
|
|
103884
104445
|
function xcodeVersion(result) {
|
|
@@ -103938,7 +104499,7 @@ function prepareYoqaRunner(udid, deps) {
|
|
|
103938
104499
|
return next;
|
|
103939
104500
|
}
|
|
103940
104501
|
async function prepare(udid, deps) {
|
|
103941
|
-
if (!existsSync5(
|
|
104502
|
+
if (!existsSync5(join18(deps.projectDir, "YoqaRunner.xcodeproj"))) {
|
|
103942
104503
|
throw new YoqaRunnerError(`YoqaRunner's Xcode project is missing from this install (looked in ${deps.projectDir}). Reinstall yoqa, or set YOQA_RUNNER_PROJECT to a native/yoqa-runner checkout.`);
|
|
103943
104504
|
}
|
|
103944
104505
|
const team = await signingTeam(deps);
|
|
@@ -103947,8 +104508,8 @@ async function prepare(udid, deps) {
|
|
|
103947
104508
|
deps.exec(["xcodebuild", "-version"])
|
|
103948
104509
|
]);
|
|
103949
104510
|
const key = createHash("sha256").update(`${sources}\x00${xcodeVersion(xcode)}\x00${team}`).digest("hex").slice(0, 16);
|
|
103950
|
-
const dir =
|
|
103951
|
-
const manifestPath =
|
|
104511
|
+
const dir = join18(deps.cacheRoot, key);
|
|
104512
|
+
const manifestPath = join18(dir, "build.json");
|
|
103952
104513
|
const cached2 = await readManifest(manifestPath);
|
|
103953
104514
|
if (cached2 && usableFor(cached2, udid, deps.now())) {
|
|
103954
104515
|
return {
|
|
@@ -103959,12 +104520,12 @@ async function prepare(udid, deps) {
|
|
|
103959
104520
|
devices: cached2.devices
|
|
103960
104521
|
};
|
|
103961
104522
|
}
|
|
103962
|
-
const derivedData =
|
|
104523
|
+
const derivedData = join18(dir, "DerivedData");
|
|
103963
104524
|
const build = await deps.exec([
|
|
103964
104525
|
"xcodebuild",
|
|
103965
104526
|
"build-for-testing",
|
|
103966
104527
|
"-project",
|
|
103967
|
-
|
|
104528
|
+
join18(deps.projectDir, "YoqaRunner.xcodeproj"),
|
|
103968
104529
|
"-scheme",
|
|
103969
104530
|
"YoqaRunner",
|
|
103970
104531
|
"-destination",
|
|
@@ -103981,7 +104542,7 @@ async function prepare(udid, deps) {
|
|
|
103981
104542
|
throw new YoqaRunnerError(explainBuildFailure(`${build.stdout}
|
|
103982
104543
|
${build.stderr}`, team));
|
|
103983
104544
|
}
|
|
103984
|
-
const products =
|
|
104545
|
+
const products = join18(derivedData, "Build/Products");
|
|
103985
104546
|
const unbranded = await brandRunnerApp(products, deps.exec);
|
|
103986
104547
|
if (unbranded)
|
|
103987
104548
|
console.warn(`[yoqa-runner] the runner keeps Xcode's blank icon: ${unbranded}`);
|
|
@@ -104008,7 +104569,7 @@ ${build.stderr}`, team));
|
|
|
104008
104569
|
devices: manifest.devices
|
|
104009
104570
|
};
|
|
104010
104571
|
}
|
|
104011
|
-
var
|
|
104572
|
+
var READY_TIMEOUT_MS4 = 120000;
|
|
104012
104573
|
var LISTENING_RE = /YOQA_RUNNER_LISTENING port=(\d+)/;
|
|
104013
104574
|
var LAUNCH_FAILED_RE = /IDELaunchReport: .*Finished with error:|Failed to install or launch the test runner/;
|
|
104014
104575
|
var OUTPUT_TAIL_CHARS = 16000;
|
|
@@ -104038,7 +104599,7 @@ function drain(stream, onLine) {
|
|
|
104038
104599
|
}
|
|
104039
104600
|
async function startYoqaRunner(udid, deps) {
|
|
104040
104601
|
const build = await prepareYoqaRunner(udid, deps);
|
|
104041
|
-
const readyTimeoutMs = deps.readyTimeoutMs ??
|
|
104602
|
+
const readyTimeoutMs = deps.readyTimeoutMs ?? READY_TIMEOUT_MS4;
|
|
104042
104603
|
const env = {};
|
|
104043
104604
|
const port = deps.env.YOQA_RUNNER_PORT?.trim();
|
|
104044
104605
|
if (port)
|
|
@@ -104136,13 +104697,13 @@ function resolveYoqaRunnerProject(at) {
|
|
|
104136
104697
|
const fromEnv = at.env.YOQA_RUNNER_PROJECT?.trim();
|
|
104137
104698
|
if (fromEnv)
|
|
104138
104699
|
return fromEnv;
|
|
104139
|
-
if (existsSync5(
|
|
104700
|
+
if (existsSync5(join18(at.checkout, "YoqaRunner.xcodeproj")))
|
|
104140
104701
|
return at.checkout;
|
|
104141
104702
|
if (!existsSync5(at.archive))
|
|
104142
104703
|
return at.checkout;
|
|
104143
104704
|
const key = createHash("sha256").update(readFileSync(at.archive)).digest("hex").slice(0, 16);
|
|
104144
|
-
const dir =
|
|
104145
|
-
if (existsSync5(
|
|
104705
|
+
const dir = join18(at.cacheRoot, "source", key);
|
|
104706
|
+
if (existsSync5(join18(dir, "YoqaRunner.xcodeproj")))
|
|
104146
104707
|
return dir;
|
|
104147
104708
|
const staging = `${dir}.${process.pid}.tmp`;
|
|
104148
104709
|
rmSync(staging, { recursive: true, force: true });
|
|
@@ -104160,7 +104721,7 @@ function resolveYoqaRunnerProject(at) {
|
|
|
104160
104721
|
return dir;
|
|
104161
104722
|
}
|
|
104162
104723
|
function yoqaRunnerDeps() {
|
|
104163
|
-
const cacheRoot =
|
|
104724
|
+
const cacheRoot = join18(homedir10(), ".yoqa", "yoqa-runner");
|
|
104164
104725
|
return {
|
|
104165
104726
|
exec: execCommand,
|
|
104166
104727
|
spawn: (command, env) => Bun.spawn(command, {
|
|
@@ -104172,12 +104733,12 @@ function yoqaRunnerDeps() {
|
|
|
104172
104733
|
env: process.env,
|
|
104173
104734
|
projectDir: resolveYoqaRunnerProject({
|
|
104174
104735
|
env: process.env,
|
|
104175
|
-
checkout:
|
|
104176
|
-
archive:
|
|
104736
|
+
checkout: join18(import.meta.dir, "../../../../../native/yoqa-runner"),
|
|
104737
|
+
archive: join18(dirname5(process.execPath), RUNNER_SOURCE_ARCHIVE),
|
|
104177
104738
|
cacheRoot
|
|
104178
104739
|
}),
|
|
104179
104740
|
cacheRoot,
|
|
104180
|
-
settingsPath:
|
|
104741
|
+
settingsPath: join18(homedir10(), ".yoqa", "ios-signing.json"),
|
|
104181
104742
|
now: () => new Date
|
|
104182
104743
|
};
|
|
104183
104744
|
}
|
|
@@ -104189,6 +104750,15 @@ var SWIPE_MS = 400;
|
|
|
104189
104750
|
var DRAG_PRESS_MS = 500;
|
|
104190
104751
|
var BACKSPACE = String.fromCharCode(8);
|
|
104191
104752
|
var KEY_SEPARATORS = new RegExp(`(\\r?\\n|${BACKSPACE})`);
|
|
104753
|
+
var PHONE_KEYS = {
|
|
104754
|
+
Enter: ["keyboardReturn", {}],
|
|
104755
|
+
Backspace: ["keyboardDelete", {}],
|
|
104756
|
+
Space: ["type", { text: " " }]
|
|
104757
|
+
};
|
|
104758
|
+
var PHONE_KEY_SUPPORT = {
|
|
104759
|
+
keys: Object.keys(PHONE_KEYS),
|
|
104760
|
+
modifiers: false
|
|
104761
|
+
};
|
|
104192
104762
|
var RECORDING_PULL_MS = 400;
|
|
104193
104763
|
var UNREACHABLE_GRACE_MS = 20000;
|
|
104194
104764
|
function errorMessage5(error40) {
|
|
@@ -104412,7 +104982,7 @@ async function createIosDeviceSession(options, deps = iosDeviceDeps()) {
|
|
|
104412
104982
|
const screenshot = async () => {
|
|
104413
104983
|
await mkdir5(SCREENSHOT_DIR, { recursive: true });
|
|
104414
104984
|
const frame = await captureFrame();
|
|
104415
|
-
const path =
|
|
104985
|
+
const path = join19(SCREENSHOT_DIR, `shot_${Date.now()}_${crypto.randomUUID()}.png`);
|
|
104416
104986
|
await Bun.write(path, Uint8Array.from(Buffer.from(frame.base64, "base64")));
|
|
104417
104987
|
return { path, base64: frame.base64 };
|
|
104418
104988
|
};
|
|
@@ -104466,6 +105036,12 @@ async function createIosDeviceSession(options, deps = iosDeviceDeps()) {
|
|
|
104466
105036
|
swipe: (x1, y1, x2, y2, durationMs = SWIPE_MS) => withActionLock(() => dragNorm({ x: x1, y: y1 }, { x: x2, y: y2 }, durationMs, SWIPE_PRESS_MS)),
|
|
104467
105037
|
drag: (x1, y1, x2, y2, durationMs = 800) => withActionLock(() => dragNorm({ x: x1, y: y1 }, { x: x2, y: y2 }, durationMs, DRAG_PRESS_MS)),
|
|
104468
105038
|
type: (text3) => withActionLock(() => typeText(text3)),
|
|
105039
|
+
keySupport: PHONE_KEY_SUPPORT,
|
|
105040
|
+
key: async (name30, modifiers = []) => {
|
|
105041
|
+
assertKeySupported(PHONE_KEY_SUPPORT, "on a cabled iPhone", name30, modifiers);
|
|
105042
|
+
const [command, fields] = PHONE_KEYS[name30];
|
|
105043
|
+
await withActionLock(() => call(command, fields, true));
|
|
105044
|
+
},
|
|
104469
105045
|
activateApp: async (id) => {
|
|
104470
105046
|
await devicectl(["device", "process", "launch", "--device", udid, id], "devicectl launch");
|
|
104471
105047
|
appId = id;
|
|
@@ -104531,21 +105107,645 @@ function directImplementationOrder(implementations, optIn) {
|
|
|
104531
105107
|
var OPT_IN_ENV = {
|
|
104532
105108
|
android: "YOQA_DIRECT_ANDROID",
|
|
104533
105109
|
"ios-simulator": "YOQA_DIRECT_IOS_SIMULATOR",
|
|
104534
|
-
"ios-device": "YOQA_DIRECT_IOS_DEVICE"
|
|
105110
|
+
"ios-device": "YOQA_DIRECT_IOS_DEVICE",
|
|
105111
|
+
web: "YOQA_DIRECT_WEB"
|
|
104535
105112
|
};
|
|
104536
105113
|
function directOptIn(deviceClass, env = process.env) {
|
|
104537
105114
|
const value = env[OPT_IN_ENV[deviceClass]]?.trim();
|
|
104538
105115
|
return value ? value : undefined;
|
|
104539
105116
|
}
|
|
104540
105117
|
|
|
105118
|
+
// src/domains/devices/web-direct-lane.ts
|
|
105119
|
+
import { mkdir as mkdir6 } from "fs/promises";
|
|
105120
|
+
import { join as join20 } from "path";
|
|
105121
|
+
|
|
105122
|
+
// src/domains/devices/cdp-client.ts
|
|
105123
|
+
class CdpClosedError extends Error {
|
|
105124
|
+
constructor(message = "The browser's DevTools connection closed") {
|
|
105125
|
+
super(message);
|
|
105126
|
+
this.name = "CdpClosedError";
|
|
105127
|
+
}
|
|
105128
|
+
}
|
|
105129
|
+
|
|
105130
|
+
class CdpCommandError extends Error {
|
|
105131
|
+
method;
|
|
105132
|
+
code;
|
|
105133
|
+
constructor(method, code, message) {
|
|
105134
|
+
super(`${method}: ${message}`);
|
|
105135
|
+
this.method = method;
|
|
105136
|
+
this.code = code;
|
|
105137
|
+
this.name = "CdpCommandError";
|
|
105138
|
+
}
|
|
105139
|
+
}
|
|
105140
|
+
var COMMAND_TIMEOUT_MS2 = 30000;
|
|
105141
|
+
var CONNECT_TIMEOUT_MS = 5000;
|
|
105142
|
+
async function connectCdp(url2, options = {}) {
|
|
105143
|
+
const connectTimeoutMs = options.connectTimeoutMs ?? CONNECT_TIMEOUT_MS;
|
|
105144
|
+
const socket = new WebSocket(url2);
|
|
105145
|
+
await new Promise((resolve7, reject) => {
|
|
105146
|
+
const timer = setTimeout(() => {
|
|
105147
|
+
socket.close();
|
|
105148
|
+
reject(new Error(`No DevTools socket at ${url2} within ${connectTimeoutMs} ms`));
|
|
105149
|
+
}, connectTimeoutMs);
|
|
105150
|
+
socket.addEventListener("open", () => {
|
|
105151
|
+
clearTimeout(timer);
|
|
105152
|
+
resolve7();
|
|
105153
|
+
});
|
|
105154
|
+
socket.addEventListener("error", () => {
|
|
105155
|
+
clearTimeout(timer);
|
|
105156
|
+
reject(new Error(`Could not open the DevTools socket at ${url2}`));
|
|
105157
|
+
});
|
|
105158
|
+
});
|
|
105159
|
+
const commandTimeoutMs = options.commandTimeoutMs ?? COMMAND_TIMEOUT_MS2;
|
|
105160
|
+
const pending = new Map;
|
|
105161
|
+
const listeners = new Map;
|
|
105162
|
+
let nextId = 1;
|
|
105163
|
+
let isClosed = false;
|
|
105164
|
+
let markClosed = () => {
|
|
105165
|
+
return;
|
|
105166
|
+
};
|
|
105167
|
+
const closed = new Promise((resolve7) => {
|
|
105168
|
+
markClosed = resolve7;
|
|
105169
|
+
});
|
|
105170
|
+
const onClosed = () => {
|
|
105171
|
+
if (isClosed)
|
|
105172
|
+
return;
|
|
105173
|
+
isClosed = true;
|
|
105174
|
+
for (const entry of pending.values()) {
|
|
105175
|
+
clearTimeout(entry.timer);
|
|
105176
|
+
entry.reject(new CdpClosedError);
|
|
105177
|
+
}
|
|
105178
|
+
pending.clear();
|
|
105179
|
+
markClosed();
|
|
105180
|
+
};
|
|
105181
|
+
socket.addEventListener("close", onClosed);
|
|
105182
|
+
socket.addEventListener("error", onClosed);
|
|
105183
|
+
socket.addEventListener("message", (event) => {
|
|
105184
|
+
let message;
|
|
105185
|
+
try {
|
|
105186
|
+
message = JSON.parse(String(event.data));
|
|
105187
|
+
} catch {
|
|
105188
|
+
return;
|
|
105189
|
+
}
|
|
105190
|
+
if (message.id !== undefined) {
|
|
105191
|
+
const entry = pending.get(message.id);
|
|
105192
|
+
if (!entry)
|
|
105193
|
+
return;
|
|
105194
|
+
pending.delete(message.id);
|
|
105195
|
+
clearTimeout(entry.timer);
|
|
105196
|
+
if (message.error) {
|
|
105197
|
+
entry.reject(new CdpCommandError(entry.method, message.error.code, message.error.message ?? "failed"));
|
|
105198
|
+
} else {
|
|
105199
|
+
entry.resolve(message.result ?? {});
|
|
105200
|
+
}
|
|
105201
|
+
return;
|
|
105202
|
+
}
|
|
105203
|
+
if (message.method) {
|
|
105204
|
+
for (const listener of listeners.get(message.method) ?? []) {
|
|
105205
|
+
listener(message.params, message.sessionId);
|
|
105206
|
+
}
|
|
105207
|
+
}
|
|
105208
|
+
});
|
|
105209
|
+
return {
|
|
105210
|
+
send: (method, params, sessionId) => {
|
|
105211
|
+
if (isClosed)
|
|
105212
|
+
return Promise.reject(new CdpClosedError);
|
|
105213
|
+
const id = nextId++;
|
|
105214
|
+
return new Promise((resolve7, reject) => {
|
|
105215
|
+
const timer = setTimeout(() => {
|
|
105216
|
+
pending.delete(id);
|
|
105217
|
+
reject(new Error(`${method}: the browser did not answer within ${commandTimeoutMs} ms`));
|
|
105218
|
+
}, commandTimeoutMs);
|
|
105219
|
+
pending.set(id, {
|
|
105220
|
+
method,
|
|
105221
|
+
resolve: resolve7,
|
|
105222
|
+
reject,
|
|
105223
|
+
timer
|
|
105224
|
+
});
|
|
105225
|
+
socket.send(JSON.stringify({ id, method, params, ...sessionId ? { sessionId } : {} }));
|
|
105226
|
+
});
|
|
105227
|
+
},
|
|
105228
|
+
on: (method, listener) => {
|
|
105229
|
+
const set2 = listeners.get(method) ?? new Set;
|
|
105230
|
+
set2.add(listener);
|
|
105231
|
+
listeners.set(method, set2);
|
|
105232
|
+
return () => {
|
|
105233
|
+
set2.delete(listener);
|
|
105234
|
+
};
|
|
105235
|
+
},
|
|
105236
|
+
closed,
|
|
105237
|
+
close: async () => {
|
|
105238
|
+
if (isClosed)
|
|
105239
|
+
return;
|
|
105240
|
+
socket.close();
|
|
105241
|
+
await closed;
|
|
105242
|
+
}
|
|
105243
|
+
};
|
|
105244
|
+
}
|
|
105245
|
+
|
|
105246
|
+
// src/domains/devices/chromium-attach.ts
|
|
105247
|
+
var ATTACH_ID = /^chromium-cdp-(\d{1,5})$/;
|
|
105248
|
+
var PROBE_TIMEOUT_MS = 2000;
|
|
105249
|
+
function attachPortOf(deviceId) {
|
|
105250
|
+
const match = ATTACH_ID.exec(deviceId);
|
|
105251
|
+
const port = Number(match?.[1]);
|
|
105252
|
+
return match && port >= 1 && port <= 65535 ? port : null;
|
|
105253
|
+
}
|
|
105254
|
+
|
|
105255
|
+
class NoCdpEndpointError extends Error {
|
|
105256
|
+
port;
|
|
105257
|
+
constructor(port, reason) {
|
|
105258
|
+
super(`No Chromium DevTools endpoint on port ${port} (${reason}). Start Chrome, Edge or Brave with --remote-debugging-port=${port} and a non-default --user-data-dir: Chrome 136 and later turn remote debugging off on the default profile.`);
|
|
105259
|
+
this.port = port;
|
|
105260
|
+
this.name = "NoCdpEndpointError";
|
|
105261
|
+
}
|
|
105262
|
+
}
|
|
105263
|
+
async function browserSocketUrl(port, fetchFn = fetch) {
|
|
105264
|
+
let response;
|
|
105265
|
+
try {
|
|
105266
|
+
response = await fetchFn(`http://127.0.0.1:${port}/json/version`, {
|
|
105267
|
+
signal: AbortSignal.timeout(PROBE_TIMEOUT_MS)
|
|
105268
|
+
});
|
|
105269
|
+
} catch (error40) {
|
|
105270
|
+
throw new NoCdpEndpointError(port, error40 instanceof Error ? error40.message : String(error40));
|
|
105271
|
+
}
|
|
105272
|
+
if (!response.ok)
|
|
105273
|
+
throw new NoCdpEndpointError(port, `/json/version answered ${response.status}`);
|
|
105274
|
+
const body = await response.json().catch(() => null);
|
|
105275
|
+
const url2 = body?.webSocketDebuggerUrl;
|
|
105276
|
+
if (typeof url2 !== "string" || !url2.startsWith("ws")) {
|
|
105277
|
+
throw new NoCdpEndpointError(port, "/json/version named no DevTools socket");
|
|
105278
|
+
}
|
|
105279
|
+
return url2;
|
|
105280
|
+
}
|
|
105281
|
+
|
|
105282
|
+
// src/domains/devices/web-direct-lane.ts
|
|
105283
|
+
var NEWLINE = /(\r?\n)/;
|
|
105284
|
+
var DRAG_STEPS = 10;
|
|
105285
|
+
var DEFAULT_DRAG_MS = 800;
|
|
105286
|
+
var DEFAULT_SWIPE_MS = 400;
|
|
105287
|
+
var NAVIGATE_LOAD_MS = 1e4;
|
|
105288
|
+
var MOBILE_USER_AGENT = "Mozilla/5.0 (iPhone; CPU iPhone OS 18_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1";
|
|
105289
|
+
var CDP_KEYS = {
|
|
105290
|
+
Enter: { key: "Enter", code: "Enter", windowsVirtualKeyCode: 13, text: "\r" },
|
|
105291
|
+
Tab: { key: "Tab", code: "Tab", windowsVirtualKeyCode: 9 },
|
|
105292
|
+
Escape: { key: "Escape", code: "Escape", windowsVirtualKeyCode: 27 },
|
|
105293
|
+
Backspace: { key: "Backspace", code: "Backspace", windowsVirtualKeyCode: 8 },
|
|
105294
|
+
Delete: { key: "Delete", code: "Delete", windowsVirtualKeyCode: 46 },
|
|
105295
|
+
ArrowUp: { key: "ArrowUp", code: "ArrowUp", windowsVirtualKeyCode: 38 },
|
|
105296
|
+
ArrowDown: { key: "ArrowDown", code: "ArrowDown", windowsVirtualKeyCode: 40 },
|
|
105297
|
+
ArrowLeft: { key: "ArrowLeft", code: "ArrowLeft", windowsVirtualKeyCode: 37 },
|
|
105298
|
+
ArrowRight: { key: "ArrowRight", code: "ArrowRight", windowsVirtualKeyCode: 39 },
|
|
105299
|
+
Home: { key: "Home", code: "Home", windowsVirtualKeyCode: 36 },
|
|
105300
|
+
End: { key: "End", code: "End", windowsVirtualKeyCode: 35 },
|
|
105301
|
+
PageUp: { key: "PageUp", code: "PageUp", windowsVirtualKeyCode: 33 },
|
|
105302
|
+
PageDown: { key: "PageDown", code: "PageDown", windowsVirtualKeyCode: 34 },
|
|
105303
|
+
Space: { key: " ", code: "Space", windowsVirtualKeyCode: 32, text: " " }
|
|
105304
|
+
};
|
|
105305
|
+
var CDP_MODIFIERS = {
|
|
105306
|
+
Shift: { key: "Shift", code: "ShiftLeft", windowsVirtualKeyCode: 16, bit: 8 },
|
|
105307
|
+
Control: { key: "Control", code: "ControlLeft", windowsVirtualKeyCode: 17, bit: 2 },
|
|
105308
|
+
Alt: { key: "Alt", code: "AltLeft", windowsVirtualKeyCode: 18, bit: 1 },
|
|
105309
|
+
Meta: { key: "Meta", code: "MetaLeft", windowsVirtualKeyCode: 91, bit: 4 }
|
|
105310
|
+
};
|
|
105311
|
+
var WEB_KEY_SUPPORT = { keys: KEY_NAMES, modifiers: true };
|
|
105312
|
+
function webDirectDeps(overrides = {}) {
|
|
105313
|
+
return {
|
|
105314
|
+
browserSocketUrl: (port) => browserSocketUrl(port),
|
|
105315
|
+
connect: (url2) => connectCdp(url2),
|
|
105316
|
+
installedBrowsers: overrides.installedBrowsers ?? (() => findInstalledBrowsers()),
|
|
105317
|
+
launch: (browser) => launchChromium(browser, { spawn: overrides.spawn })
|
|
105318
|
+
};
|
|
105319
|
+
}
|
|
105320
|
+
async function openBrowser(deviceId, deps) {
|
|
105321
|
+
const port = attachPortOf(deviceId);
|
|
105322
|
+
if (port !== null) {
|
|
105323
|
+
return { socketUrl: await deps.browserSocketUrl(port), owned: null, name: null };
|
|
105324
|
+
}
|
|
105325
|
+
const browserId = installedBrowserIdOf(deviceId);
|
|
105326
|
+
if (browserId === null) {
|
|
105327
|
+
throw new Error("A web device is an installed browser (web-chrome, web-edge, web-brave, web-chromium) or a Chromium started with --remote-debugging-port, named chromium-cdp-<port>");
|
|
105328
|
+
}
|
|
105329
|
+
const browser = (await deps.installedBrowsers()).find((installed) => installed.id === browserId);
|
|
105330
|
+
if (!browser) {
|
|
105331
|
+
throw new Error(`${browserId} is not installed. Run yoqa devices web to list the browsers here`);
|
|
105332
|
+
}
|
|
105333
|
+
const owned = await deps.launch(browser);
|
|
105334
|
+
const name30 = browser.version ? `${browser.name} ${browser.version}` : browser.name;
|
|
105335
|
+
return { socketUrl: owned.socketUrl, owned, name: name30 };
|
|
105336
|
+
}
|
|
105337
|
+
async function pickPage(client) {
|
|
105338
|
+
const { targetInfos } = await client.send("Target.getTargets", {});
|
|
105339
|
+
const page = targetInfos.find((target) => target.type === "page" && !target.url.startsWith("devtools://"));
|
|
105340
|
+
if (page)
|
|
105341
|
+
return page.targetId;
|
|
105342
|
+
return (await client.send("Target.createTarget", { url: "about:blank" })).targetId;
|
|
105343
|
+
}
|
|
105344
|
+
function layoutByBackendId(documents, strings) {
|
|
105345
|
+
const layouts = new Map;
|
|
105346
|
+
const doc2 = documents[0];
|
|
105347
|
+
if (!doc2)
|
|
105348
|
+
return layouts;
|
|
105349
|
+
const scrollX = doc2.scrollOffsetX ?? 0;
|
|
105350
|
+
const scrollY = doc2.scrollOffsetY ?? 0;
|
|
105351
|
+
doc2.layout.nodeIndex.forEach((nodeIndex, layoutIndex) => {
|
|
105352
|
+
const backendId = doc2.nodes.backendNodeId?.[nodeIndex];
|
|
105353
|
+
const [x = 0, y = 0, width = 0, height = 0] = doc2.layout.bounds[layoutIndex] ?? [];
|
|
105354
|
+
if (backendId === undefined || layouts.has(backendId))
|
|
105355
|
+
return;
|
|
105356
|
+
const layout = { bounds: { x: x - scrollX, y: y - scrollY, width, height } };
|
|
105357
|
+
const attributes = doc2.nodes.attributes?.[nodeIndex] ?? [];
|
|
105358
|
+
for (let at = 0;at + 1 < attributes.length; at += 2) {
|
|
105359
|
+
const name30 = strings[attributes[at] ?? -1];
|
|
105360
|
+
const value = strings[attributes[at + 1] ?? -1];
|
|
105361
|
+
if (name30 === "data-testid" && value)
|
|
105362
|
+
layout.testId = value;
|
|
105363
|
+
if (name30 === "id" && value)
|
|
105364
|
+
layout.domId = value;
|
|
105365
|
+
}
|
|
105366
|
+
layouts.set(backendId, layout);
|
|
105367
|
+
});
|
|
105368
|
+
return layouts;
|
|
105369
|
+
}
|
|
105370
|
+
function webUrlOf(appId) {
|
|
105371
|
+
const url2 = URL.canParse(appId) ? new URL(appId) : null;
|
|
105372
|
+
if (url2?.protocol !== "http:" && url2?.protocol !== "https:") {
|
|
105373
|
+
throw new Error(`On web an app is its Web URL (http or https), and "${appId}" is not one`);
|
|
105374
|
+
}
|
|
105375
|
+
return url2;
|
|
105376
|
+
}
|
|
105377
|
+
function originOf(url2) {
|
|
105378
|
+
return URL.canParse(url2) ? new URL(url2).origin : null;
|
|
105379
|
+
}
|
|
105380
|
+
async function attachPage(client, targetId, preset) {
|
|
105381
|
+
const { sessionId } = await client.send("Target.attachToTarget", { targetId, flatten: true });
|
|
105382
|
+
await client.send("Page.enable", {}, sessionId);
|
|
105383
|
+
const { width, height, deviceScaleFactor, mobile } = preset;
|
|
105384
|
+
await client.send("Emulation.setDeviceMetricsOverride", { width, height, deviceScaleFactor, mobile }, sessionId);
|
|
105385
|
+
if (mobile) {
|
|
105386
|
+
await client.send("Emulation.setTouchEmulationEnabled", { enabled: true, maxTouchPoints: 5 }, sessionId);
|
|
105387
|
+
await client.send("Emulation.setUserAgentOverride", { userAgent: MOBILE_USER_AGENT }, sessionId);
|
|
105388
|
+
}
|
|
105389
|
+
await client.send("Page.bringToFront", {}, sessionId);
|
|
105390
|
+
return { targetId, sessionId };
|
|
105391
|
+
}
|
|
105392
|
+
async function createWebDirectSession(options, deps = webDirectDeps()) {
|
|
105393
|
+
if (options.platform !== "web") {
|
|
105394
|
+
throw new Error("The web Direct lane drives web devices only");
|
|
105395
|
+
}
|
|
105396
|
+
const preset = VIEWPORT_PRESETS[options.viewport ?? DEFAULT_VIEWPORT_PRESET];
|
|
105397
|
+
const { socketUrl, owned, name: name30 } = await openBrowser(options.deviceId, deps);
|
|
105398
|
+
const client = await deps.connect(socketUrl).catch(async (error40) => {
|
|
105399
|
+
await owned?.stop();
|
|
105400
|
+
throw error40;
|
|
105401
|
+
});
|
|
105402
|
+
let page;
|
|
105403
|
+
let browser;
|
|
105404
|
+
try {
|
|
105405
|
+
page = await attachPage(client, await pickPage(client), preset);
|
|
105406
|
+
await client.send("Target.setDiscoverTargets", { discover: true });
|
|
105407
|
+
browser = name30 ?? (await client.send("Browser.getVersion", {})).product.replace("/", " ");
|
|
105408
|
+
} catch (error40) {
|
|
105409
|
+
await client.close().catch(() => {
|
|
105410
|
+
return;
|
|
105411
|
+
});
|
|
105412
|
+
await owned?.stop();
|
|
105413
|
+
throw error40;
|
|
105414
|
+
}
|
|
105415
|
+
let quitting = false;
|
|
105416
|
+
let sessionEnded = null;
|
|
105417
|
+
const dead = (reason) => {
|
|
105418
|
+
if (!sessionEnded) {
|
|
105419
|
+
sessionEnded = new DeadSessionError(`Device session ended: ${reason}`);
|
|
105420
|
+
if (!quitting) {
|
|
105421
|
+
owned?.stop();
|
|
105422
|
+
options.onSessionDead?.();
|
|
105423
|
+
}
|
|
105424
|
+
}
|
|
105425
|
+
return sessionEnded;
|
|
105426
|
+
};
|
|
105427
|
+
client.closed.then(() => {
|
|
105428
|
+
if (!quitting)
|
|
105429
|
+
dead("the browser closed its DevTools connection");
|
|
105430
|
+
});
|
|
105431
|
+
owned?.exited.then(() => {
|
|
105432
|
+
if (!quitting)
|
|
105433
|
+
dead("the browser quit");
|
|
105434
|
+
});
|
|
105435
|
+
let openDialog = null;
|
|
105436
|
+
const openers = [];
|
|
105437
|
+
let switching = Promise.resolve();
|
|
105438
|
+
const switchPage = (fn) => {
|
|
105439
|
+
switching = switching.then(fn).catch(() => {
|
|
105440
|
+
return;
|
|
105441
|
+
});
|
|
105442
|
+
};
|
|
105443
|
+
client.on("Target.targetCreated", (params) => {
|
|
105444
|
+
const { targetInfo } = params;
|
|
105445
|
+
if (targetInfo.type !== "page" || !targetInfo.openerId)
|
|
105446
|
+
return;
|
|
105447
|
+
switchPage(async () => {
|
|
105448
|
+
if (targetInfo.openerId !== page.targetId || sessionEnded)
|
|
105449
|
+
return;
|
|
105450
|
+
const popup = await attachPage(client, targetInfo.targetId, preset);
|
|
105451
|
+
openers.push(page);
|
|
105452
|
+
page = popup;
|
|
105453
|
+
openDialog = null;
|
|
105454
|
+
});
|
|
105455
|
+
});
|
|
105456
|
+
client.on("Target.detachedFromTarget", (params) => {
|
|
105457
|
+
const { sessionId } = params ?? {};
|
|
105458
|
+
const closedOpener = openers.findIndex((opener2) => opener2.sessionId === sessionId);
|
|
105459
|
+
if (closedOpener >= 0)
|
|
105460
|
+
openers.splice(closedOpener, 1);
|
|
105461
|
+
if (sessionId !== page.sessionId || quitting)
|
|
105462
|
+
return;
|
|
105463
|
+
const opener = openers.pop();
|
|
105464
|
+
if (!opener) {
|
|
105465
|
+
dead("the page was closed");
|
|
105466
|
+
return;
|
|
105467
|
+
}
|
|
105468
|
+
page = opener;
|
|
105469
|
+
openDialog = null;
|
|
105470
|
+
switchPage(() => client.send("Page.bringToFront", {}, opener.sessionId));
|
|
105471
|
+
});
|
|
105472
|
+
client.on("Page.javascriptDialogOpening", (params, eventSession) => {
|
|
105473
|
+
if (eventSession === page.sessionId)
|
|
105474
|
+
openDialog = params;
|
|
105475
|
+
});
|
|
105476
|
+
client.on("Page.javascriptDialogClosed", (_params, eventSession) => {
|
|
105477
|
+
if (eventSession === page.sessionId)
|
|
105478
|
+
openDialog = null;
|
|
105479
|
+
});
|
|
105480
|
+
const guarded = async (fn) => {
|
|
105481
|
+
if (sessionEnded)
|
|
105482
|
+
throw sessionEnded;
|
|
105483
|
+
try {
|
|
105484
|
+
return await fn();
|
|
105485
|
+
} catch (error40) {
|
|
105486
|
+
if (error40 instanceof CdpClosedError && !quitting)
|
|
105487
|
+
throw dead(error40.message);
|
|
105488
|
+
throw error40;
|
|
105489
|
+
}
|
|
105490
|
+
};
|
|
105491
|
+
const call = (method, params) => guarded(async () => {
|
|
105492
|
+
await switching;
|
|
105493
|
+
return client.send(method, params, page.sessionId);
|
|
105494
|
+
});
|
|
105495
|
+
const callBrowser = (method, params) => guarded(() => client.send(method, params));
|
|
105496
|
+
let chain = Promise.resolve();
|
|
105497
|
+
const withActionLock = (fn) => {
|
|
105498
|
+
const after = () => switching.then(fn);
|
|
105499
|
+
const run = chain.then(after, after);
|
|
105500
|
+
chain = run.catch(() => {
|
|
105501
|
+
return;
|
|
105502
|
+
});
|
|
105503
|
+
return run;
|
|
105504
|
+
};
|
|
105505
|
+
const getWindowSize = () => guarded(async () => {
|
|
105506
|
+
await switching;
|
|
105507
|
+
return { width: preset.width, height: preset.height };
|
|
105508
|
+
});
|
|
105509
|
+
const toPage = async (xNorm, yNorm) => {
|
|
105510
|
+
const viewport = await getWindowSize();
|
|
105511
|
+
return { x: xNorm / 1000 * viewport.width, y: yNorm / 1000 * viewport.height };
|
|
105512
|
+
};
|
|
105513
|
+
const mouse = (type, point, held = false) => {
|
|
105514
|
+
if (type === "mouseMoved") {
|
|
105515
|
+
return call("Input.dispatchMouseEvent", {
|
|
105516
|
+
type,
|
|
105517
|
+
...point,
|
|
105518
|
+
button: held ? "left" : "none",
|
|
105519
|
+
buttons: held ? 1 : 0
|
|
105520
|
+
});
|
|
105521
|
+
}
|
|
105522
|
+
return call("Input.dispatchMouseEvent", {
|
|
105523
|
+
type,
|
|
105524
|
+
...point,
|
|
105525
|
+
button: "left",
|
|
105526
|
+
buttons: type === "mousePressed" ? 1 : 0,
|
|
105527
|
+
clickCount: 1
|
|
105528
|
+
});
|
|
105529
|
+
};
|
|
105530
|
+
const touch = (type, point) => call("Input.dispatchTouchEvent", { type, touchPoints: type === "touchEnd" ? [] : [point] });
|
|
105531
|
+
const pointer = preset.mobile ? {
|
|
105532
|
+
press: (point) => touch("touchStart", point),
|
|
105533
|
+
move: (point) => touch("touchMove", point),
|
|
105534
|
+
release: (point) => touch("touchEnd", point)
|
|
105535
|
+
} : {
|
|
105536
|
+
press: async (point) => {
|
|
105537
|
+
await mouse("mouseMoved", point);
|
|
105538
|
+
await mouse("mousePressed", point);
|
|
105539
|
+
},
|
|
105540
|
+
move: (point) => mouse("mouseMoved", point, true),
|
|
105541
|
+
release: (point) => mouse("mouseReleased", point)
|
|
105542
|
+
};
|
|
105543
|
+
const click = async (xNorm, yNorm, holdMs = 0) => {
|
|
105544
|
+
const point = await toPage(xNorm, yNorm);
|
|
105545
|
+
await pointer.press(point);
|
|
105546
|
+
if (holdMs > 0)
|
|
105547
|
+
await Bun.sleep(holdMs);
|
|
105548
|
+
await pointer.release(point);
|
|
105549
|
+
};
|
|
105550
|
+
const stroke = async (from, to, durationMs) => {
|
|
105551
|
+
await pointer.press(from);
|
|
105552
|
+
for (let step = 1;step <= DRAG_STEPS; step++) {
|
|
105553
|
+
await Bun.sleep(durationMs / DRAG_STEPS);
|
|
105554
|
+
const t = step / DRAG_STEPS;
|
|
105555
|
+
await pointer.move({ x: from.x + (to.x - from.x) * t, y: from.y + (to.y - from.y) * t });
|
|
105556
|
+
}
|
|
105557
|
+
await pointer.release(to);
|
|
105558
|
+
};
|
|
105559
|
+
const scroll = async (x1, y1, x2, y2, durationMs = DEFAULT_SWIPE_MS) => {
|
|
105560
|
+
const from = await toPage(x1, y1);
|
|
105561
|
+
const to = await toPage(x2, y2);
|
|
105562
|
+
if (preset.mobile) {
|
|
105563
|
+
await stroke(from, to, durationMs);
|
|
105564
|
+
return;
|
|
105565
|
+
}
|
|
105566
|
+
await call("Input.dispatchMouseEvent", {
|
|
105567
|
+
type: "mouseWheel",
|
|
105568
|
+
...from,
|
|
105569
|
+
deltaX: from.x - to.x,
|
|
105570
|
+
deltaY: from.y - to.y
|
|
105571
|
+
});
|
|
105572
|
+
};
|
|
105573
|
+
const drag = async (x1, y1, x2, y2, durationMs = DEFAULT_DRAG_MS) => {
|
|
105574
|
+
await stroke(await toPage(x1, y1), await toPage(x2, y2), durationMs);
|
|
105575
|
+
};
|
|
105576
|
+
const answerDialog = async (accept) => {
|
|
105577
|
+
const promptText = accept && openDialog?.type === "prompt" ? openDialog.defaultPrompt ?? "" : undefined;
|
|
105578
|
+
try {
|
|
105579
|
+
await call("Page.handleJavaScriptDialog", {
|
|
105580
|
+
accept,
|
|
105581
|
+
...promptText !== undefined ? { promptText } : {}
|
|
105582
|
+
});
|
|
105583
|
+
} catch (error40) {
|
|
105584
|
+
if (error40 instanceof CdpCommandError && /no dialog is showing/i.test(error40.message)) {
|
|
105585
|
+
throw new Error("No JavaScript dialog is open on the page");
|
|
105586
|
+
}
|
|
105587
|
+
throw error40;
|
|
105588
|
+
}
|
|
105589
|
+
openDialog = null;
|
|
105590
|
+
};
|
|
105591
|
+
const navigate = async (url2) => {
|
|
105592
|
+
let loaded = () => {};
|
|
105593
|
+
const load = new Promise((resolve7) => {
|
|
105594
|
+
loaded = resolve7;
|
|
105595
|
+
});
|
|
105596
|
+
const stopListening = client.on("Page.loadEventFired", (_params, eventSession) => {
|
|
105597
|
+
if (eventSession === page.sessionId)
|
|
105598
|
+
loaded();
|
|
105599
|
+
});
|
|
105600
|
+
try {
|
|
105601
|
+
const { errorText, loaderId } = await call("Page.navigate", { url: url2 });
|
|
105602
|
+
if (errorText)
|
|
105603
|
+
throw new Error(`Could not open ${url2}: ${errorText}`);
|
|
105604
|
+
if (loaderId)
|
|
105605
|
+
await Promise.race([load, client.closed, Bun.sleep(NAVIGATE_LOAD_MS)]);
|
|
105606
|
+
} finally {
|
|
105607
|
+
stopListening();
|
|
105608
|
+
}
|
|
105609
|
+
};
|
|
105610
|
+
const closeAppPage = async () => {
|
|
105611
|
+
const appPage = page;
|
|
105612
|
+
const { targetId } = await callBrowser("Target.createTarget", { url: "about:blank" });
|
|
105613
|
+
page = await guarded(() => attachPage(client, targetId, preset));
|
|
105614
|
+
openDialog = null;
|
|
105615
|
+
await callBrowser("Target.closeTarget", { targetId: appPage.targetId });
|
|
105616
|
+
};
|
|
105617
|
+
const openApp = async (url2) => {
|
|
105618
|
+
const { targetInfo } = await callBrowser("Target.getTargetInfo", { targetId: page.targetId });
|
|
105619
|
+
if (originOf(targetInfo.url) === url2.origin)
|
|
105620
|
+
await call("Page.bringToFront", {});
|
|
105621
|
+
else
|
|
105622
|
+
await navigate(url2.href);
|
|
105623
|
+
};
|
|
105624
|
+
const pressKey = async (name50, modifiers = []) => {
|
|
105625
|
+
let held = 0;
|
|
105626
|
+
for (const modifier of modifiers) {
|
|
105627
|
+
const { bit, ...key2 } = CDP_MODIFIERS[modifier];
|
|
105628
|
+
held |= bit;
|
|
105629
|
+
await call("Input.dispatchKeyEvent", { type: "rawKeyDown", ...key2, modifiers: held });
|
|
105630
|
+
}
|
|
105631
|
+
const { text: text3, ...key } = CDP_KEYS[name50];
|
|
105632
|
+
const shortcut = modifiers.some((modifier) => modifier !== "Shift");
|
|
105633
|
+
await call("Input.dispatchKeyEvent", text3 && !shortcut ? { type: "keyDown", ...key, text: text3, modifiers: held } : { type: "rawKeyDown", ...key, modifiers: held });
|
|
105634
|
+
await call("Input.dispatchKeyEvent", { type: "keyUp", ...key, modifiers: held });
|
|
105635
|
+
for (const modifier of [...modifiers].reverse()) {
|
|
105636
|
+
const { bit, ...key2 } = CDP_MODIFIERS[modifier];
|
|
105637
|
+
held &= ~bit;
|
|
105638
|
+
await call("Input.dispatchKeyEvent", { type: "keyUp", ...key2, modifiers: held });
|
|
105639
|
+
}
|
|
105640
|
+
};
|
|
105641
|
+
const typeText = async (text3) => {
|
|
105642
|
+
for (const part2 of text3.split(NEWLINE)) {
|
|
105643
|
+
if (part2 === "")
|
|
105644
|
+
continue;
|
|
105645
|
+
if (part2 === `
|
|
105646
|
+
` || part2 === `\r
|
|
105647
|
+
`)
|
|
105648
|
+
await pressKey("Enter");
|
|
105649
|
+
else
|
|
105650
|
+
await call("Input.insertText", { text: part2 });
|
|
105651
|
+
}
|
|
105652
|
+
};
|
|
105653
|
+
const captureFrame = async () => {
|
|
105654
|
+
const { data } = await call("Page.captureScreenshot", { format: "png" });
|
|
105655
|
+
if (!data)
|
|
105656
|
+
throw new Error("The browser returned an empty screenshot");
|
|
105657
|
+
return { base64: data, mime: "image/png" };
|
|
105658
|
+
};
|
|
105659
|
+
const screenshot = async () => {
|
|
105660
|
+
await mkdir6(SCREENSHOT_DIR, { recursive: true });
|
|
105661
|
+
const frame = await captureFrame();
|
|
105662
|
+
const path = join20(SCREENSHOT_DIR, `shot_${Date.now()}_${crypto.randomUUID()}.png`);
|
|
105663
|
+
await Bun.write(path, Uint8Array.from(Buffer.from(frame.base64, "base64")));
|
|
105664
|
+
return { path, base64: frame.base64 };
|
|
105665
|
+
};
|
|
105666
|
+
const pageSource = async () => {
|
|
105667
|
+
const [{ nodes }, snapshot] = await Promise.all([
|
|
105668
|
+
call("Accessibility.getFullAXTree", {}),
|
|
105669
|
+
call("DOMSnapshot.captureSnapshot", { computedStyles: [] })
|
|
105670
|
+
]);
|
|
105671
|
+
const layouts = layoutByBackendId(snapshot.documents, snapshot.strings);
|
|
105672
|
+
const axTree = nodes.map((node) => {
|
|
105673
|
+
const layout = node.backendDOMNodeId === undefined ? undefined : layouts.get(node.backendDOMNodeId);
|
|
105674
|
+
return layout ? { ...node, ...layout } : node;
|
|
105675
|
+
});
|
|
105676
|
+
return JSON.stringify({ axTree });
|
|
105677
|
+
};
|
|
105678
|
+
let pointerDown = false;
|
|
105679
|
+
return {
|
|
105680
|
+
lane: "direct",
|
|
105681
|
+
stream: null,
|
|
105682
|
+
browser,
|
|
105683
|
+
viewport: preset.name,
|
|
105684
|
+
noRecordingReason: "Run recording is not available on web",
|
|
105685
|
+
quit: async () => {
|
|
105686
|
+
quitting = true;
|
|
105687
|
+
await client.close();
|
|
105688
|
+
await owned?.stop();
|
|
105689
|
+
},
|
|
105690
|
+
captureFrame,
|
|
105691
|
+
screenshot,
|
|
105692
|
+
pageSource,
|
|
105693
|
+
getWindowSize,
|
|
105694
|
+
tap: (x, y, tapOptions) => withActionLock(() => click(x, y, tapOptions?.durationMs)),
|
|
105695
|
+
swipe: (x1, y1, x2, y2, durationMs) => withActionLock(() => scroll(x1, y1, x2, y2, durationMs)),
|
|
105696
|
+
drag: (x1, y1, x2, y2, durationMs) => withActionLock(() => drag(x1, y1, x2, y2, durationMs)),
|
|
105697
|
+
type: (text3) => withActionLock(() => typeText(text3)),
|
|
105698
|
+
keySupport: WEB_KEY_SUPPORT,
|
|
105699
|
+
key: async (name50, modifiers = []) => {
|
|
105700
|
+
assertKeySupported(WEB_KEY_SUPPORT, "on the web", name50, modifiers);
|
|
105701
|
+
await withActionLock(() => pressKey(name50, modifiers));
|
|
105702
|
+
},
|
|
105703
|
+
activateApp: (appId) => withActionLock(() => openApp(webUrlOf(appId))),
|
|
105704
|
+
setTargetApp: () => {
|
|
105705
|
+
return;
|
|
105706
|
+
},
|
|
105707
|
+
terminateApp: (appId) => withActionLock(async () => {
|
|
105708
|
+
webUrlOf(appId);
|
|
105709
|
+
await closeAppPage();
|
|
105710
|
+
}),
|
|
105711
|
+
restartApp: (appId) => withActionLock(async () => {
|
|
105712
|
+
const url2 = webUrlOf(appId);
|
|
105713
|
+
await closeAppPage();
|
|
105714
|
+
await callBrowser("Storage.clearDataForOrigin", {
|
|
105715
|
+
origin: url2.origin,
|
|
105716
|
+
storageTypes: "all"
|
|
105717
|
+
});
|
|
105718
|
+
await navigate(url2.href);
|
|
105719
|
+
}),
|
|
105720
|
+
backgroundApp: () => Promise.reject(new ActionUnsupportedError("background-app is not supported on web: a browser page has no background to send it to")),
|
|
105721
|
+
openUrl: (url2) => withActionLock(() => navigate(url2)),
|
|
105722
|
+
acceptAlert: () => withActionLock(() => answerDialog(true)),
|
|
105723
|
+
dismissAlert: () => withActionLock(() => answerDialog(false)),
|
|
105724
|
+
withActionLock,
|
|
105725
|
+
pointerEvent: async (phase, xNorm, yNorm) => {
|
|
105726
|
+
const point = await toPage(xNorm, yNorm);
|
|
105727
|
+
if (phase === "begin") {
|
|
105728
|
+
pointerDown = true;
|
|
105729
|
+
await pointer.press(point);
|
|
105730
|
+
} else if (phase === "move") {
|
|
105731
|
+
await pointer.move(point);
|
|
105732
|
+
} else {
|
|
105733
|
+
pointerDown = false;
|
|
105734
|
+
await pointer.release(point);
|
|
105735
|
+
}
|
|
105736
|
+
},
|
|
105737
|
+
isPointerActive: () => pointerDown
|
|
105738
|
+
};
|
|
105739
|
+
}
|
|
105740
|
+
|
|
104541
105741
|
// src/domains/devices/yoqa-ax.ts
|
|
104542
105742
|
import { randomBytes } from "crypto";
|
|
104543
105743
|
import { existsSync as existsSync6, rmSync as rmSync2 } from "fs";
|
|
104544
105744
|
import { createServer } from "net";
|
|
104545
|
-
import { join as
|
|
104546
|
-
var
|
|
105745
|
+
import { join as join21 } from "path";
|
|
105746
|
+
var CONNECT_TIMEOUT_MS2 = 1e4;
|
|
104547
105747
|
var REQUEST_TIMEOUT_MS2 = 5000;
|
|
104548
|
-
var
|
|
105748
|
+
var STOP_GRACE_MS3 = 2000;
|
|
104549
105749
|
function spawnProcess2(command) {
|
|
104550
105750
|
return Bun.spawn(command, { stdin: "ignore", stdout: "ignore", stderr: "ignore" });
|
|
104551
105751
|
}
|
|
@@ -104553,9 +105753,9 @@ function resolveYoqaAxBin() {
|
|
|
104553
105753
|
const fromEnv = process.env.YOQA_AX_BIN?.trim();
|
|
104554
105754
|
if (fromEnv)
|
|
104555
105755
|
return fromEnv;
|
|
104556
|
-
const products =
|
|
105756
|
+
const products = join21(import.meta.dir, "../../../../../native/yoqa-ax/.build/out/Products");
|
|
104557
105757
|
for (const config2 of ["Release-iphonesimulator", "Debug-iphonesimulator"]) {
|
|
104558
|
-
const built =
|
|
105758
|
+
const built = join21(products, config2, "yoqa-ax");
|
|
104559
105759
|
if (existsSync6(built))
|
|
104560
105760
|
return built;
|
|
104561
105761
|
}
|
|
@@ -104675,12 +105875,12 @@ async function startYoqaAx(udid, options) {
|
|
|
104675
105875
|
rmSync2(path, { force: true });
|
|
104676
105876
|
const exited = await Promise.race([
|
|
104677
105877
|
proc.exited.then(() => true),
|
|
104678
|
-
Bun.sleep(
|
|
105878
|
+
Bun.sleep(STOP_GRACE_MS3).then(() => false)
|
|
104679
105879
|
]);
|
|
104680
105880
|
if (!exited)
|
|
104681
105881
|
proc.kill("SIGKILL");
|
|
104682
105882
|
};
|
|
104683
|
-
const timeoutMs = options.connectTimeoutMs ??
|
|
105883
|
+
const timeoutMs = options.connectTimeoutMs ?? CONNECT_TIMEOUT_MS2;
|
|
104684
105884
|
let timer;
|
|
104685
105885
|
const outcome = await Promise.race([
|
|
104686
105886
|
connected,
|
|
@@ -104754,9 +105954,14 @@ function createDirectLane(implementations, optIn) {
|
|
|
104754
105954
|
};
|
|
104755
105955
|
}
|
|
104756
105956
|
function deviceClassFor(platform, deviceId) {
|
|
104757
|
-
|
|
104758
|
-
|
|
104759
|
-
|
|
105957
|
+
switch (platform) {
|
|
105958
|
+
case "android":
|
|
105959
|
+
return "android";
|
|
105960
|
+
case "ios":
|
|
105961
|
+
return looksLikePhysicalIosUdid(deviceId) ? "ios-device" : "ios-simulator";
|
|
105962
|
+
case "web":
|
|
105963
|
+
return "web";
|
|
105964
|
+
}
|
|
104760
105965
|
}
|
|
104761
105966
|
var DIRECT_IMPLEMENTATIONS = {
|
|
104762
105967
|
android: [
|
|
@@ -104795,6 +106000,13 @@ var DIRECT_IMPLEMENTATIONS = {
|
|
|
104795
106000
|
promoted: true,
|
|
104796
106001
|
open: (options) => createIosDeviceSession(options)
|
|
104797
106002
|
}
|
|
106003
|
+
],
|
|
106004
|
+
web: [
|
|
106005
|
+
{
|
|
106006
|
+
name: "cdp",
|
|
106007
|
+
promoted: true,
|
|
106008
|
+
open: (options) => createWebDirectSession(options)
|
|
106009
|
+
}
|
|
104798
106010
|
]
|
|
104799
106011
|
};
|
|
104800
106012
|
function defaultDirectLane(platform) {
|
|
@@ -104807,9 +106019,12 @@ function defaultDirectLane(platform) {
|
|
|
104807
106019
|
// src/domains/devices/open-session.ts
|
|
104808
106020
|
var openByDeviceId = new Map;
|
|
104809
106021
|
function defaultLanesFor(platform) {
|
|
104810
|
-
|
|
104811
|
-
|
|
104812
|
-
|
|
106022
|
+
switch (platform) {
|
|
106023
|
+
case "android":
|
|
106024
|
+
case "ios":
|
|
106025
|
+
case "web":
|
|
106026
|
+
return { direct: defaultDirectLane(platform) };
|
|
106027
|
+
}
|
|
104813
106028
|
}
|
|
104814
106029
|
function mergeLanes(platform, lanes) {
|
|
104815
106030
|
const defaults = defaultLanesFor(platform);
|
|
@@ -104905,7 +106120,8 @@ function toInfo(current) {
|
|
|
104905
106120
|
heldByRun: current.heldByRunId != null,
|
|
104906
106121
|
...current.heldByRunId != null ? { heldByRunId: current.heldByRunId } : {},
|
|
104907
106122
|
lane: current.session.lane,
|
|
104908
|
-
...current.session.laneWarning ? { laneWarning: current.session.laneWarning } : {}
|
|
106123
|
+
...current.session.laneWarning ? { laneWarning: current.session.laneWarning } : {},
|
|
106124
|
+
...current.session.viewport ? { viewport: current.session.viewport } : {}
|
|
104909
106125
|
};
|
|
104910
106126
|
}
|
|
104911
106127
|
function getActiveSession() {
|
|
@@ -104941,6 +106157,7 @@ async function createAndRegister(options) {
|
|
|
104941
106157
|
bundleId: options.bundleId,
|
|
104942
106158
|
appPackage: options.appPackage,
|
|
104943
106159
|
requestedLane: options.requestedLane,
|
|
106160
|
+
viewport: options.viewport,
|
|
104944
106161
|
onSessionDead: () => {
|
|
104945
106162
|
abandonActiveSession();
|
|
104946
106163
|
}
|
|
@@ -105038,7 +106255,14 @@ async function acquireSessionForRun(options) {
|
|
|
105038
106255
|
return { session, shared: true };
|
|
105039
106256
|
}
|
|
105040
106257
|
function targetAppFor(options) {
|
|
105041
|
-
|
|
106258
|
+
switch (options.platform) {
|
|
106259
|
+
case "ios":
|
|
106260
|
+
return options.bundleId;
|
|
106261
|
+
case "android":
|
|
106262
|
+
return options.appPackage;
|
|
106263
|
+
case "web":
|
|
106264
|
+
return;
|
|
106265
|
+
}
|
|
105042
106266
|
}
|
|
105043
106267
|
async function releaseSessionFromRun(runId, session, shared) {
|
|
105044
106268
|
if (!shared) {
|
|
@@ -105059,10 +106283,10 @@ import {
|
|
|
105059
106283
|
randomBytes as randomBytes2
|
|
105060
106284
|
} from "crypto";
|
|
105061
106285
|
import { chmodSync, existsSync as existsSync7, mkdirSync as mkdirSync3, readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
105062
|
-
import { homedir as
|
|
105063
|
-
import { dirname as dirname6, join as
|
|
105064
|
-
var YOQA_ROOT2 =
|
|
105065
|
-
var KEY_PATH =
|
|
106286
|
+
import { homedir as homedir11 } from "os";
|
|
106287
|
+
import { dirname as dirname6, join as join22 } from "path";
|
|
106288
|
+
var YOQA_ROOT2 = join22(homedir11(), ".yoqa");
|
|
106289
|
+
var KEY_PATH = join22(YOQA_ROOT2, ".provider-key");
|
|
105066
106290
|
var ALGORITHM = "aes-256-gcm";
|
|
105067
106291
|
var IV_LENGTH = 12;
|
|
105068
106292
|
var KEY_LENGTH = 32;
|
|
@@ -105656,8 +106880,12 @@ async function performAction(session, body, options) {
|
|
|
105656
106880
|
case "restart-app": {
|
|
105657
106881
|
if (!body.appId)
|
|
105658
106882
|
throw new ActionValidationError("restart-app requires appId");
|
|
105659
|
-
|
|
105660
|
-
|
|
106883
|
+
if (session.restartApp) {
|
|
106884
|
+
await session.restartApp(body.appId);
|
|
106885
|
+
} else {
|
|
106886
|
+
await session.terminateApp(body.appId);
|
|
106887
|
+
await session.activateApp(body.appId);
|
|
106888
|
+
}
|
|
105661
106889
|
break;
|
|
105662
106890
|
}
|
|
105663
106891
|
case "background-app": {
|
|
@@ -105670,6 +106898,18 @@ async function performAction(session, body, options) {
|
|
|
105670
106898
|
await session.openUrl(body.url);
|
|
105671
106899
|
break;
|
|
105672
106900
|
}
|
|
106901
|
+
case "key": {
|
|
106902
|
+
if (!body.key)
|
|
106903
|
+
throw new ActionValidationError("key requires a key");
|
|
106904
|
+
try {
|
|
106905
|
+
await session.key(body.key, body.modifiers);
|
|
106906
|
+
} catch (error40) {
|
|
106907
|
+
if (error40 instanceof UnsupportedKeyError)
|
|
106908
|
+
throw new ActionValidationError(error40.message);
|
|
106909
|
+
throw error40;
|
|
106910
|
+
}
|
|
106911
|
+
break;
|
|
106912
|
+
}
|
|
105673
106913
|
case "alert": {
|
|
105674
106914
|
if (body.alertAction === "dismiss") {
|
|
105675
106915
|
await session.dismissAlert();
|
|
@@ -105723,37 +106963,18 @@ async function readCleanedTree(readScreen2, session) {
|
|
|
105723
106963
|
}
|
|
105724
106964
|
}
|
|
105725
106965
|
async function runTextAssert(input) {
|
|
105726
|
-
const
|
|
105727
|
-
|
|
105728
|
-
|
|
105729
|
-
|
|
105730
|
-
|
|
105731
|
-
|
|
105732
|
-
|
|
105733
|
-
|
|
105734
|
-
|
|
105735
|
-
|
|
105736
|
-
|
|
105737
|
-
|
|
105738
|
-
throw error40;
|
|
105739
|
-
readError = error40;
|
|
105740
|
-
}
|
|
105741
|
-
if (elements) {
|
|
105742
|
-
const found = screenHasText(elements, input.text);
|
|
105743
|
-
if (input.assertion === "visible" && found)
|
|
105744
|
-
return;
|
|
105745
|
-
if (input.assertion === "not-visible" && !found)
|
|
105746
|
-
return;
|
|
105747
|
-
}
|
|
105748
|
-
if (input.clock.now() >= deadline) {
|
|
105749
|
-
if (readError) {
|
|
105750
|
-
const message = readError instanceof Error ? readError.message : String(readError);
|
|
105751
|
-
throw new Error(`Could not read the screen within ${seconds}s: ${message}`);
|
|
105752
|
-
}
|
|
105753
|
-
throw new Error(input.assertion === "visible" ? `Expected visible text not found within ${seconds}s: ${input.text}` : `Unexpected text still visible after ${seconds}s: ${input.text}`);
|
|
105754
|
-
}
|
|
105755
|
-
await input.clock.sleep(400);
|
|
105756
|
-
}
|
|
106966
|
+
const { met } = await checkScreen({
|
|
106967
|
+
readScreen: () => input.readScreen(input.session),
|
|
106968
|
+
text: input.text,
|
|
106969
|
+
assertion: input.assertion,
|
|
106970
|
+
timeoutMs: input.timeoutMs,
|
|
106971
|
+
sleep: (ms) => input.clock.sleep(ms),
|
|
106972
|
+
now: () => input.clock.now(),
|
|
106973
|
+
isAborted: input.isAborted,
|
|
106974
|
+
isDeadSession: isDeadSessionError
|
|
106975
|
+
});
|
|
106976
|
+
if (!met)
|
|
106977
|
+
throw new Error(screenCheckMissMessage(input));
|
|
105757
106978
|
}
|
|
105758
106979
|
function isPointTap(decision) {
|
|
105759
106980
|
return decision.type === "tap" && decision.x != null && decision.y != null && !isSystemPermissionLabel(decision.label);
|
|
@@ -105792,11 +107013,61 @@ async function executeScriptCase(deps) {
|
|
|
105792
107013
|
const setCurrentCommand = deps.setCurrentCommand ?? noopSetCurrentCommand;
|
|
105793
107014
|
let stepIdx = 0;
|
|
105794
107015
|
let lastScreenshotUri = null;
|
|
105795
|
-
|
|
105796
|
-
for (const action of
|
|
107016
|
+
const replay = async (actions) => {
|
|
107017
|
+
for (const action of actions) {
|
|
105797
107018
|
if (deps.isAborted()) {
|
|
105798
107019
|
return "cancelled";
|
|
105799
107020
|
}
|
|
107021
|
+
if (action.type === "branch") {
|
|
107022
|
+
const timeoutMs = action.timeoutMs ?? BRANCH_DEFAULT_TIMEOUT_MS;
|
|
107023
|
+
const { assertion } = action;
|
|
107024
|
+
const target = action.id != null ? { id: action.id } : { text: action.text };
|
|
107025
|
+
const command = formatAssertShellLine({
|
|
107026
|
+
assertion,
|
|
107027
|
+
...target,
|
|
107028
|
+
timeoutSeconds: timeoutMs / 1000
|
|
107029
|
+
});
|
|
107030
|
+
const check2 = describeScreenCheck({ assertion, ...target });
|
|
107031
|
+
const checkStarted = clock.now();
|
|
107032
|
+
const { met } = await withCurrentCommand(setCurrentCommand, command, () => checkScreen({
|
|
107033
|
+
readScreen: () => readScreen2(deps.session),
|
|
107034
|
+
...target,
|
|
107035
|
+
assertion,
|
|
107036
|
+
timeoutMs,
|
|
107037
|
+
sleep: (ms) => clock.sleep(ms),
|
|
107038
|
+
now: () => clock.now(),
|
|
107039
|
+
isAborted: deps.isAborted,
|
|
107040
|
+
isDeadSession: isDeadSessionError
|
|
107041
|
+
}));
|
|
107042
|
+
const shot2 = await deps.session.screenshot();
|
|
107043
|
+
lastScreenshotUri = shot2.path;
|
|
107044
|
+
if (deps.isAborted()) {
|
|
107045
|
+
return "cancelled";
|
|
107046
|
+
}
|
|
107047
|
+
const outcome = met ? "then" : "else";
|
|
107048
|
+
await deps.appendStep({
|
|
107049
|
+
idx: stepIdx,
|
|
107050
|
+
action: {
|
|
107051
|
+
type: "branch",
|
|
107052
|
+
...target,
|
|
107053
|
+
assertion,
|
|
107054
|
+
timeoutMs,
|
|
107055
|
+
outcome,
|
|
107056
|
+
reason: action.reason ?? `If ${check2}`,
|
|
107057
|
+
thoughts: "Replaying the saved script without calling the AI agent."
|
|
107058
|
+
},
|
|
107059
|
+
screenshotUri: shot2.path,
|
|
107060
|
+
ok: true,
|
|
107061
|
+
latencyMs: clock.now() - checkStarted,
|
|
107062
|
+
detail: `${check2} \u2192 ${outcome}`,
|
|
107063
|
+
command
|
|
107064
|
+
});
|
|
107065
|
+
stepIdx += 1;
|
|
107066
|
+
const stopped = await replay((met ? action.then : action.else) ?? []);
|
|
107067
|
+
if (stopped)
|
|
107068
|
+
return stopped;
|
|
107069
|
+
continue;
|
|
107070
|
+
}
|
|
105800
107071
|
const shotStarted = clock.now();
|
|
105801
107072
|
const shot = await deps.session.screenshot();
|
|
105802
107073
|
lastScreenshotUri = shot.path;
|
|
@@ -105970,6 +107241,32 @@ async function executeScriptCase(deps) {
|
|
|
105970
107241
|
command
|
|
105971
107242
|
});
|
|
105972
107243
|
});
|
|
107244
|
+
} else if (action.type === "key") {
|
|
107245
|
+
const keyBody = {
|
|
107246
|
+
kind: "key",
|
|
107247
|
+
key: action.key,
|
|
107248
|
+
...action.modifiers?.length ? { modifiers: action.modifiers } : {}
|
|
107249
|
+
};
|
|
107250
|
+
const command = formatActionShellLine(keyBody);
|
|
107251
|
+
await withCurrentCommand(setCurrentCommand, command, async () => {
|
|
107252
|
+
await perform(deps.session, keyBody);
|
|
107253
|
+
await clock.sleep(settleMs);
|
|
107254
|
+
await deps.appendStep({
|
|
107255
|
+
idx: stepIdx,
|
|
107256
|
+
action: {
|
|
107257
|
+
type: "key",
|
|
107258
|
+
key: action.key,
|
|
107259
|
+
modifiers: action.modifiers,
|
|
107260
|
+
reason: action.reason ?? "Replayed saved script key",
|
|
107261
|
+
thoughts: "Replaying the saved script without calling the AI agent."
|
|
107262
|
+
},
|
|
107263
|
+
screenshotUri: shot.path,
|
|
107264
|
+
ok: true,
|
|
107265
|
+
latencyMs,
|
|
107266
|
+
detail: action.reason ?? null,
|
|
107267
|
+
command
|
|
107268
|
+
});
|
|
107269
|
+
});
|
|
105973
107270
|
} else if (action.type === "type") {
|
|
105974
107271
|
const typeBody = { kind: "input", text: action.text };
|
|
105975
107272
|
const command = formatActionShellLine(typeBody);
|
|
@@ -106085,6 +107382,13 @@ async function executeScriptCase(deps) {
|
|
|
106085
107382
|
}
|
|
106086
107383
|
stepIdx += 1;
|
|
106087
107384
|
}
|
|
107385
|
+
return null;
|
|
107386
|
+
};
|
|
107387
|
+
try {
|
|
107388
|
+
const stopped = await replay(deps.script.actions);
|
|
107389
|
+
if (stopped) {
|
|
107390
|
+
return stopped;
|
|
107391
|
+
}
|
|
106088
107392
|
await deps.appendStep({
|
|
106089
107393
|
idx: stepIdx,
|
|
106090
107394
|
action: {
|
|
@@ -106672,12 +107976,12 @@ function reportedLaneWarning(warning) {
|
|
|
106672
107976
|
|
|
106673
107977
|
// src/domains/runs/run-recording.ts
|
|
106674
107978
|
import { existsSync as existsSync8 } from "fs";
|
|
106675
|
-
import { rm as
|
|
106676
|
-
import { homedir as
|
|
106677
|
-
import { join as
|
|
106678
|
-
var RUN_VIDEO_DIR =
|
|
107979
|
+
import { rm as rm8 } from "fs/promises";
|
|
107980
|
+
import { homedir as homedir12 } from "os";
|
|
107981
|
+
import { join as join23 } from "path";
|
|
107982
|
+
var RUN_VIDEO_DIR = join23(homedir12(), ".yoqa", "runs", "videos");
|
|
106679
107983
|
function runVideoPath(runTestId, dir = RUN_VIDEO_DIR) {
|
|
106680
|
-
return
|
|
107984
|
+
return join23(dir, `${runTestId}.mp4`);
|
|
106681
107985
|
}
|
|
106682
107986
|
var STOP_TIMEOUT_MS = 20000;
|
|
106683
107987
|
function errorMessage7(error40) {
|
|
@@ -106685,7 +107989,9 @@ function errorMessage7(error40) {
|
|
|
106685
107989
|
}
|
|
106686
107990
|
async function startRunRecording(session, path) {
|
|
106687
107991
|
if (!session.startRecording) {
|
|
106688
|
-
return {
|
|
107992
|
+
return {
|
|
107993
|
+
unavailable: session.noRecordingReason ?? `The ${session.lane} lane cannot record video`
|
|
107994
|
+
};
|
|
106689
107995
|
}
|
|
106690
107996
|
let recording;
|
|
106691
107997
|
try {
|
|
@@ -106719,7 +108025,7 @@ async function startRunRecording(session, path) {
|
|
|
106719
108025
|
};
|
|
106720
108026
|
}
|
|
106721
108027
|
async function deleteRunVideo(runTestId, dir = RUN_VIDEO_DIR) {
|
|
106722
|
-
await
|
|
108028
|
+
await rm8(runVideoPath(runTestId, dir), { force: true }).catch(() => {
|
|
106723
108029
|
return;
|
|
106724
108030
|
});
|
|
106725
108031
|
}
|
|
@@ -106817,6 +108123,10 @@ function parseRunExecutionMode(value) {
|
|
|
106817
108123
|
function parseRunScreenMode(value) {
|
|
106818
108124
|
return value === "vision" ? "vision" : "tree";
|
|
106819
108125
|
}
|
|
108126
|
+
function viewportOf(value) {
|
|
108127
|
+
const parsed = viewportPresetSchema.safeParse(value);
|
|
108128
|
+
return parsed.success ? { viewport: parsed.data } : {};
|
|
108129
|
+
}
|
|
106820
108130
|
function resolveCaseExecutionMode(runMode, hasScript) {
|
|
106821
108131
|
if (runMode === "agent")
|
|
106822
108132
|
return "agent";
|
|
@@ -106875,6 +108185,8 @@ async function loadRun(runId) {
|
|
|
106875
108185
|
screenMode: parseRunScreenMode(runRow.screenMode),
|
|
106876
108186
|
lane: reportedLane(runRow.lane),
|
|
106877
108187
|
laneWarning: reportedLaneWarning(runRow.laneWarning),
|
|
108188
|
+
...runRow.browser ? { browser: runRow.browser } : {},
|
|
108189
|
+
...viewportOf(runRow.viewport),
|
|
106878
108190
|
error: runRow.error,
|
|
106879
108191
|
createdAt: runRow.createdAt,
|
|
106880
108192
|
startedAt: runRow.startedAt,
|
|
@@ -107047,6 +108359,17 @@ async function executeCase(input) {
|
|
|
107047
108359
|
async function saveCaseRecording(runTestId, state) {
|
|
107048
108360
|
await getCatalogDb().update(runTests).set({ recordingStatus: state.status, recordingNote: state.note ?? null }).where(eq(runTests.id, runTestId));
|
|
107049
108361
|
}
|
|
108362
|
+
async function openStartTarget(session, platform, startTarget) {
|
|
108363
|
+
switch (platform) {
|
|
108364
|
+
case "ios":
|
|
108365
|
+
case "android":
|
|
108366
|
+
return;
|
|
108367
|
+
case "web":
|
|
108368
|
+
if (startTarget)
|
|
108369
|
+
await session.openUrl(startTarget);
|
|
108370
|
+
return;
|
|
108371
|
+
}
|
|
108372
|
+
}
|
|
107050
108373
|
async function executeRun(runId) {
|
|
107051
108374
|
const db = getCatalogDb();
|
|
107052
108375
|
const run = await loadRun(runId);
|
|
@@ -107071,6 +108394,10 @@ async function executeRun(runId) {
|
|
|
107071
108394
|
if (!app) {
|
|
107072
108395
|
throw new RunValidationError("App not found");
|
|
107073
108396
|
}
|
|
108397
|
+
const startTarget = appStartTarget(app, run.platform);
|
|
108398
|
+
if (run.platform === "web" && !startTarget) {
|
|
108399
|
+
throw new RunValidationError(`${app.name} has no Web URL, so a Run on web has nothing to open. Set one with: yoqa apps update ${app.prefix} --web-url <url>`);
|
|
108400
|
+
}
|
|
107074
108401
|
const caseModes = [];
|
|
107075
108402
|
for (const test of run.tests) {
|
|
107076
108403
|
const catalogCase = await getCase(test.caseId);
|
|
@@ -107101,8 +108428,7 @@ async function executeRun(runId) {
|
|
|
107101
108428
|
runId,
|
|
107102
108429
|
deviceId: run.deviceId,
|
|
107103
108430
|
platform: run.platform,
|
|
107104
|
-
|
|
107105
|
-
appPackage: app.androidApplicationId || undefined,
|
|
108431
|
+
...startTargetRequest(run.platform, startTarget),
|
|
107106
108432
|
requestedLane: requestedLane === "direct" || requestedLane === "auto" ? requestedLane : undefined
|
|
107107
108433
|
});
|
|
107108
108434
|
const activeSession = acquired.session;
|
|
@@ -107110,8 +108436,11 @@ async function executeRun(runId) {
|
|
|
107110
108436
|
sharedSession = acquired.shared;
|
|
107111
108437
|
await db.update(runs).set({
|
|
107112
108438
|
lane: session.lane,
|
|
107113
|
-
laneWarning: session.laneWarning ?? null
|
|
108439
|
+
laneWarning: session.laneWarning ?? null,
|
|
108440
|
+
browser: session.browser ?? null,
|
|
108441
|
+
viewport: session.viewport ?? null
|
|
107114
108442
|
}).where(eq(runs.id, runId));
|
|
108443
|
+
await openStartTarget(session, run.platform, startTarget);
|
|
107115
108444
|
if (isAborted2(runId)) {
|
|
107116
108445
|
await persistCancelled(runId);
|
|
107117
108446
|
return;
|
|
@@ -107146,7 +108475,7 @@ async function executeRun(runId) {
|
|
|
107146
108475
|
auth,
|
|
107147
108476
|
caseMode,
|
|
107148
108477
|
screenMode: run.screenMode ?? "tree",
|
|
107149
|
-
defaultAppId:
|
|
108478
|
+
defaultAppId: startTarget
|
|
107150
108479
|
}));
|
|
107151
108480
|
if (!await loadRun(runId))
|
|
107152
108481
|
await deleteRunVideo(test.id);
|
|
@@ -107333,6 +108662,8 @@ async function listRuns(appId) {
|
|
|
107333
108662
|
screenMode: parseRunScreenMode(runRow.screenMode),
|
|
107334
108663
|
lane: reportedLane(runRow.lane),
|
|
107335
108664
|
laneWarning: reportedLaneWarning(runRow.laneWarning),
|
|
108665
|
+
...runRow.browser ? { browser: runRow.browser } : {},
|
|
108666
|
+
...viewportOf(runRow.viewport),
|
|
107336
108667
|
error: runRow.error,
|
|
107337
108668
|
createdAt: runRow.createdAt,
|
|
107338
108669
|
startedAt: runRow.startedAt,
|
|
@@ -109384,7 +110715,7 @@ function createDevicesRoutes() {
|
|
|
109384
110715
|
const platformParam = c.req.query("platform");
|
|
109385
110716
|
const parsedPlatform = devicePlatformSchema.safeParse(platformParam);
|
|
109386
110717
|
if (!parsedPlatform.success) {
|
|
109387
|
-
return c.json({ error: "Query param platform is required and must be 'ios' or '
|
|
110718
|
+
return c.json({ error: "Query param platform is required and must be 'ios', 'android' or 'web'" }, 400);
|
|
109388
110719
|
}
|
|
109389
110720
|
const includeUnavailable = c.req.query("all") !== "0";
|
|
109390
110721
|
const platform = parsedPlatform.data;
|
|
@@ -109399,12 +110730,12 @@ function createDevicesRoutes() {
|
|
|
109399
110730
|
}
|
|
109400
110731
|
|
|
109401
110732
|
// src/domains/doctor/application.ts
|
|
109402
|
-
import { homedir as
|
|
109403
|
-
import { join as
|
|
110733
|
+
import { homedir as homedir13 } from "os";
|
|
110734
|
+
import { join as join24 } from "path";
|
|
109404
110735
|
// package.json
|
|
109405
110736
|
var package_default = {
|
|
109406
110737
|
name: "@yoqa/runner",
|
|
109407
|
-
version: "1.0.
|
|
110738
|
+
version: "1.0.3",
|
|
109408
110739
|
description: "Yoqa local runner \u2014 device control + catalog HTTP API (Bun). Start with yoqa serve or the yoqa-runner bin.",
|
|
109409
110740
|
license: "UNLICENSED",
|
|
109410
110741
|
type: "module",
|
|
@@ -109603,7 +110934,7 @@ async function probeJava() {
|
|
|
109603
110934
|
let detail = javaBin ?? "java not on PATH";
|
|
109604
110935
|
if (javaHome)
|
|
109605
110936
|
detail = `JAVA_HOME=${javaHome}`;
|
|
109606
|
-
else if (await Bun.file(
|
|
110937
|
+
else if (await Bun.file(join24(studioJbr, "bin/java")).exists()) {
|
|
109607
110938
|
detail = `Android Studio JBR available at ${studioJbr} (JAVA_HOME unset)`;
|
|
109608
110939
|
}
|
|
109609
110940
|
const ok = Boolean(javaBin || javaHome);
|
|
@@ -109634,7 +110965,7 @@ async function probeXcodeSelect() {
|
|
|
109634
110965
|
};
|
|
109635
110966
|
}
|
|
109636
110967
|
async function probeAdbDevices() {
|
|
109637
|
-
const adb = await which2("adb") ??
|
|
110968
|
+
const adb = await which2("adb") ?? join24(homedir13(), "Library/Android/sdk/platform-tools/adb");
|
|
109638
110969
|
if (!await Bun.file(adb).exists() && !await which2("adb")) {
|
|
109639
110970
|
return {
|
|
109640
110971
|
id: "adb-devices",
|
|
@@ -110051,7 +111382,7 @@ function createProviderRoutes() {
|
|
|
110051
111382
|
}
|
|
110052
111383
|
|
|
110053
111384
|
// src/interfaces/http/runs.ts
|
|
110054
|
-
import { readFile as
|
|
111385
|
+
import { readFile as readFile6 } from "fs/promises";
|
|
110055
111386
|
function parseByteRange(header, size) {
|
|
110056
111387
|
const match2 = header?.match(/^bytes=(\d*)-(\d*)$/);
|
|
110057
111388
|
if (!match2)
|
|
@@ -110205,7 +111536,7 @@ function createRunsRoutes() {
|
|
|
110205
111536
|
const stepId = c.req.param("stepId");
|
|
110206
111537
|
try {
|
|
110207
111538
|
const path = await getRunStepScreenshotPath(runId, stepId);
|
|
110208
|
-
const bytes = await
|
|
111539
|
+
const bytes = await readFile6(path);
|
|
110209
111540
|
return new Response(bytes, {
|
|
110210
111541
|
status: 200,
|
|
110211
111542
|
headers: {
|
|
@@ -110268,17 +111599,17 @@ function createServersRoutes() {
|
|
|
110268
111599
|
}
|
|
110269
111600
|
|
|
110270
111601
|
// src/interfaces/http/session.ts
|
|
110271
|
-
import { mkdir as
|
|
111602
|
+
import { mkdir as mkdir8, rename as rename2 } from "fs/promises";
|
|
110272
111603
|
import { dirname as dirname7 } from "path";
|
|
110273
111604
|
|
|
110274
111605
|
// src/domains/devices/action-result.ts
|
|
110275
|
-
import { mkdir as
|
|
110276
|
-
import { join as
|
|
111606
|
+
import { mkdir as mkdir7 } from "fs/promises";
|
|
111607
|
+
import { join as join26 } from "path";
|
|
110277
111608
|
|
|
110278
111609
|
// src/domains/devices/agent-image.ts
|
|
110279
|
-
import { mkdtemp as
|
|
110280
|
-
import { tmpdir as
|
|
110281
|
-
import { join as
|
|
111610
|
+
import { mkdtemp as mkdtemp7, readFile as readFile7, rm as rm9, writeFile as writeFile5 } from "fs/promises";
|
|
111611
|
+
import { tmpdir as tmpdir8 } from "os";
|
|
111612
|
+
import { join as join25 } from "path";
|
|
110282
111613
|
var DEFAULT_AGENT_IMAGE_EDGE = 1000;
|
|
110283
111614
|
function targetEdge(pngBase64, options) {
|
|
110284
111615
|
if (options.full || options.scale === 1)
|
|
@@ -110297,9 +111628,9 @@ async function prepareAgentImage(pngBase64, options = {}) {
|
|
|
110297
111628
|
if (bytes.byteLength === 0 || edge == null || process.platform !== "darwin") {
|
|
110298
111629
|
return { base64: pngBase64, mediaType: "image/png", downscaled: false };
|
|
110299
111630
|
}
|
|
110300
|
-
const dir = await
|
|
110301
|
-
const inPath =
|
|
110302
|
-
const outPath =
|
|
111631
|
+
const dir = await mkdtemp7(join25(tmpdir8(), "yoqa-agent-image-"));
|
|
111632
|
+
const inPath = join25(dir, "shot.png");
|
|
111633
|
+
const outPath = join25(dir, "shot.jpg");
|
|
110303
111634
|
try {
|
|
110304
111635
|
await writeFile5(inPath, new Uint8Array(bytes));
|
|
110305
111636
|
const proc = Bun.spawn([
|
|
@@ -110319,7 +111650,7 @@ async function prepareAgentImage(pngBase64, options = {}) {
|
|
|
110319
111650
|
if (await proc.exited !== 0) {
|
|
110320
111651
|
return { base64: pngBase64, mediaType: "image/png", downscaled: false };
|
|
110321
111652
|
}
|
|
110322
|
-
const jpeg = await
|
|
111653
|
+
const jpeg = await readFile7(outPath);
|
|
110323
111654
|
if (jpeg.byteLength === 0 || jpeg.byteLength >= bytes.byteLength) {
|
|
110324
111655
|
return { base64: pngBase64, mediaType: "image/png", downscaled: false };
|
|
110325
111656
|
}
|
|
@@ -110327,7 +111658,7 @@ async function prepareAgentImage(pngBase64, options = {}) {
|
|
|
110327
111658
|
} catch {
|
|
110328
111659
|
return { base64: pngBase64, mediaType: "image/png", downscaled: false };
|
|
110329
111660
|
} finally {
|
|
110330
|
-
await
|
|
111661
|
+
await rm9(dir, { recursive: true, force: true }).catch(() => {
|
|
110331
111662
|
return;
|
|
110332
111663
|
});
|
|
110333
111664
|
}
|
|
@@ -110388,8 +111719,8 @@ function actionMarks(body, resolved) {
|
|
|
110388
111719
|
return [];
|
|
110389
111720
|
}
|
|
110390
111721
|
async function writeImage(dir, base643, label, ext) {
|
|
110391
|
-
await
|
|
110392
|
-
const path =
|
|
111722
|
+
await mkdir7(dir, { recursive: true });
|
|
111723
|
+
const path = join26(dir, `${label}_${Date.now()}_${crypto.randomUUID()}.${ext}`);
|
|
110393
111724
|
await Bun.write(path, Uint8Array.from(Buffer.from(base643, "base64")));
|
|
110394
111725
|
return path;
|
|
110395
111726
|
}
|
|
@@ -110560,6 +111891,9 @@ function createSessionRoutes() {
|
|
|
110560
111891
|
}
|
|
110561
111892
|
const parsed = connectDeviceRequestSchema.safeParse(json3);
|
|
110562
111893
|
if (!parsed.success) {
|
|
111894
|
+
if (parsed.error.issues.some((issue2) => issue2.path[0] === "viewport")) {
|
|
111895
|
+
return c.json({ error: `viewport must be ${VIEWPORT_PRESET_NAMES.join(" or ")}` }, 400);
|
|
111896
|
+
}
|
|
110563
111897
|
return c.json({ error: "Body must include deviceId and platform" }, 400);
|
|
110564
111898
|
}
|
|
110565
111899
|
try {
|
|
@@ -110654,7 +111988,7 @@ function createSessionRoutes() {
|
|
|
110654
111988
|
const shot = await session.screenshot();
|
|
110655
111989
|
let path = shot.path;
|
|
110656
111990
|
if (parsed.data.path) {
|
|
110657
|
-
await
|
|
111991
|
+
await mkdir8(dirname7(parsed.data.path), { recursive: true });
|
|
110658
111992
|
await rename2(shot.path, parsed.data.path);
|
|
110659
111993
|
path = parsed.data.path;
|
|
110660
111994
|
}
|
|
@@ -110799,6 +112133,9 @@ function createSessionRoutes() {
|
|
|
110799
112133
|
if (error40 instanceof ActionNotFoundError) {
|
|
110800
112134
|
return c.json({ error: error40.message }, 404);
|
|
110801
112135
|
}
|
|
112136
|
+
if (error40 instanceof ActionUnsupportedError) {
|
|
112137
|
+
return c.json({ error: "Action not supported", detail: error40.message }, 422);
|
|
112138
|
+
}
|
|
110802
112139
|
const message = error40 instanceof Error ? error40.message : String(error40);
|
|
110803
112140
|
return c.json({ error: "Action failed", detail: message }, 500);
|
|
110804
112141
|
}
|