vibeview 0.37.0 → 0.38.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -5
- package/dist/{chunk-6LNOXEPC.js → chunk-AV674D7T.js} +130 -29
- package/dist/{chunk-QRJAFBH3.js → chunk-MYXUGCR5.js} +41 -5
- package/dist/cli.js +385 -183
- package/dist/{server-QRIH34XN.js → server-FMU3HQWF.js} +193 -4
- package/dist/{upload-app-BPTGBDQB.js → upload-app-UHJLFQKZ.js} +1 -1
- package/package.json +2 -1
- package/skills/vibeview-agent/SKILL.md +6 -2
package/README.md
CHANGED
|
@@ -141,11 +141,11 @@ Other MCP clients (Cursor, Claude Desktop, VS Code Copilot) run command
|
|
|
141
141
|
# Upload the build under test (zipped simulator .app for iOS, .apk for Android)
|
|
142
142
|
vibeview upload-app ./MyApp.zip
|
|
143
143
|
|
|
144
|
-
# Run a single test or a whole
|
|
144
|
+
# Run a single test or a whole group
|
|
145
145
|
vibeview run-test <test-id> --output json
|
|
146
146
|
vibeview run-suite <suite-id> --output junit
|
|
147
147
|
|
|
148
|
-
# Run several
|
|
148
|
+
# Run several groups with an aggregated exit code
|
|
149
149
|
vibeview run-suites --tag smoke
|
|
150
150
|
```
|
|
151
151
|
|
|
@@ -181,11 +181,12 @@ Output formats: `human`, `json`, and `junit` for any CI system. The exit code re
|
|
|
181
181
|
| `vibeview list-apps` | List your apps |
|
|
182
182
|
| `vibeview list-builds <app>` | List an app's uploaded builds (ids for `dev --build <id>`, `run-test --build`) |
|
|
183
183
|
| `vibeview list-devices` | List available device types |
|
|
184
|
-
| `vibeview list-suites` | List
|
|
184
|
+
| `vibeview list-suites` | List groups (the CLI calls them suites) |
|
|
185
185
|
| `vibeview create-test [name]` | Create a test case |
|
|
186
|
+
| `vibeview import-maestro <path>` | Import Maestro flows (file, `.zip` or folder) as tests; `--dry-run` previews |
|
|
186
187
|
| `vibeview run-test <test-id>` | Run a single test |
|
|
187
|
-
| `vibeview run-suite <suite-id>` | Run a test
|
|
188
|
-
| `vibeview run-suites` | Run multiple
|
|
188
|
+
| `vibeview run-suite <suite-id>` | Run a test group |
|
|
189
|
+
| `vibeview run-suites` | Run multiple groups |
|
|
189
190
|
|
|
190
191
|
Run any command with `--help` for its options.
|
|
191
192
|
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
resolveArtifact,
|
|
14
14
|
resolveAuth,
|
|
15
15
|
uploadAppBuild
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-MYXUGCR5.js";
|
|
17
17
|
|
|
18
18
|
// src/lib/dev-config.ts
|
|
19
19
|
import { readFileSync, renameSync, rmSync, writeFileSync } from "fs";
|
|
@@ -885,7 +885,7 @@ function rokuDefaults(cwd) {
|
|
|
885
885
|
}
|
|
886
886
|
return { buildCommand: ROKU_ZIP_COMMAND, artifact };
|
|
887
887
|
}
|
|
888
|
-
function defaultsFor(cwd, platform) {
|
|
888
|
+
function defaultsFor(cwd, platform, evaluatedIosName) {
|
|
889
889
|
const flavor = detectFlavor(cwd);
|
|
890
890
|
if (platform === "roku") return flavor === "roku" ? rokuDefaults(cwd) : {};
|
|
891
891
|
if (flavor === "unknown" || flavor === "roku") return {};
|
|
@@ -900,7 +900,7 @@ function defaultsFor(cwd, platform) {
|
|
|
900
900
|
};
|
|
901
901
|
}
|
|
902
902
|
if (isManagedExpo(cwd, "ios")) {
|
|
903
|
-
const name = managedExpoIosName(cwd);
|
|
903
|
+
const name = managedExpoIosName(cwd, evaluatedIosName);
|
|
904
904
|
return {
|
|
905
905
|
buildCommand: name ? managedExpoIosCommand(cwd, name) : void 0,
|
|
906
906
|
artifact: IOS_ARTIFACT,
|
|
@@ -925,10 +925,13 @@ function managedExpoAndroidProductionCommand(cwd) {
|
|
|
925
925
|
function expoSanitizedName(name) {
|
|
926
926
|
return name.replace(/[\W_]+/g, "").normalize("NFD").replace(/[\u0300-\u036f]/g, "");
|
|
927
927
|
}
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
928
|
+
var DYNAMIC_EXPO_CONFIGS = ["app.config.ts", "app.config.js", "app.config.mjs", "app.config.cjs"];
|
|
929
|
+
function dynamicExpoConfig(cwd) {
|
|
930
|
+
return DYNAMIC_EXPO_CONFIGS.find((f) => existsSync3(join5(cwd, f)));
|
|
931
|
+
}
|
|
932
|
+
function managedExpoIosName(cwd, evaluatedName) {
|
|
933
|
+
if (evaluatedName !== void 0) return expoSanitizedName(evaluatedName) || null;
|
|
934
|
+
if (dynamicExpoConfig(cwd)) return null;
|
|
932
935
|
const raw = readJson(cwd, "app.config.json") ?? readJson(cwd, "app.json");
|
|
933
936
|
if (!raw) return null;
|
|
934
937
|
const config = "expo" in raw ? raw.expo : raw;
|
|
@@ -943,7 +946,10 @@ function iosArchiveExportCommand(ws) {
|
|
|
943
946
|
const exportCmd = `xcodebuild -exportArchive -archivePath ${archivePath} -exportPath ios/build -exportOptionsPlist "$VIBEVIEW_EXPORT_OPTIONS_PLIST"`;
|
|
944
947
|
return `${archiveCmd} && ${exportCmd}`;
|
|
945
948
|
}
|
|
946
|
-
function
|
|
949
|
+
function managedExpoIosProductionCommand(cwd, name) {
|
|
950
|
+
return `${installCommand(cwd)} && npx expo prebuild --platform ios && ` + iosArchiveExportCommand(name);
|
|
951
|
+
}
|
|
952
|
+
function productionDefaultsFor(cwd, platform, evaluatedIosName) {
|
|
947
953
|
if (platform === "android" || platform === "androidtv") {
|
|
948
954
|
return {
|
|
949
955
|
buildCommand: isManagedExpo(cwd, "android") ? managedExpoAndroidProductionCommand(cwd) : androidProductionCommand(cwd),
|
|
@@ -955,10 +961,10 @@ function productionDefaultsFor(cwd, platform) {
|
|
|
955
961
|
}
|
|
956
962
|
if (platform === "ios") {
|
|
957
963
|
if (isManagedExpo(cwd, "ios")) {
|
|
958
|
-
const name = managedExpoIosName(cwd);
|
|
964
|
+
const name = managedExpoIosName(cwd, evaluatedIosName);
|
|
959
965
|
if (!name) return { artifact: IOS_PRODUCTION_ARTIFACT };
|
|
960
966
|
return {
|
|
961
|
-
buildCommand:
|
|
967
|
+
buildCommand: managedExpoIosProductionCommand(cwd, name),
|
|
962
968
|
artifact: IOS_PRODUCTION_ARTIFACT
|
|
963
969
|
};
|
|
964
970
|
}
|
|
@@ -994,36 +1000,65 @@ function pbxprojText(cwd) {
|
|
|
994
1000
|
return null;
|
|
995
1001
|
}
|
|
996
1002
|
}
|
|
997
|
-
|
|
1003
|
+
function parseExpoConfigJson(stdout) {
|
|
1004
|
+
const lines = stdout.trim().split("\n");
|
|
1005
|
+
for (const text of [stdout, lines[lines.length - 1]]) {
|
|
1006
|
+
try {
|
|
1007
|
+
const parsed = JSON.parse(text);
|
|
1008
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
1009
|
+
return parsed;
|
|
1010
|
+
}
|
|
1011
|
+
} catch {
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
return null;
|
|
1015
|
+
}
|
|
1016
|
+
var EXPO_CONFIG_TIMEOUT_MS = 3e4;
|
|
1017
|
+
var readExpoConfig = (cwd, timeoutMs = EXPO_CONFIG_TIMEOUT_MS) => {
|
|
998
1018
|
const cli = join5(cwd, "node_modules", "expo", "bin", "cli");
|
|
999
1019
|
if (!existsSync3(cli)) {
|
|
1000
|
-
return Promise.reject(
|
|
1020
|
+
return Promise.reject(
|
|
1021
|
+
new Error("Expo isn't installed in this project yet (run your install command first)")
|
|
1022
|
+
);
|
|
1001
1023
|
}
|
|
1002
1024
|
return new Promise((resolve, reject) => {
|
|
1003
|
-
|
|
1025
|
+
let settled = false;
|
|
1026
|
+
const settle = (fn) => {
|
|
1027
|
+
if (settled) return;
|
|
1028
|
+
settled = true;
|
|
1029
|
+
clearTimeout(timer);
|
|
1030
|
+
fn();
|
|
1031
|
+
};
|
|
1032
|
+
const child = execFile(
|
|
1004
1033
|
process.execPath,
|
|
1005
1034
|
[cli, "config", "--json", "--type", "public"],
|
|
1006
1035
|
{
|
|
1007
1036
|
cwd,
|
|
1008
|
-
timeout: 3e4,
|
|
1009
1037
|
maxBuffer: 8 * 1024 * 1024,
|
|
1010
1038
|
env: { ...process.env, EXPO_NO_TELEMETRY: "1", CI: "1" }
|
|
1011
1039
|
},
|
|
1012
1040
|
(err, stdout, stderr) => {
|
|
1013
1041
|
if (err) {
|
|
1014
1042
|
const why = String(stderr || err.message).split("\n").map((l) => l.trim()).find((l) => l.length > 0);
|
|
1015
|
-
reject(new Error(`expo config failed${why ? `: ${why}` : ""}`));
|
|
1043
|
+
settle(() => reject(new Error(`expo config failed${why ? `: ${why}` : ""}`)));
|
|
1016
1044
|
return;
|
|
1017
1045
|
}
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
} catch {
|
|
1023
|
-
reject(new Error("expo config printed something that is not JSON"));
|
|
1024
|
-
}
|
|
1046
|
+
const parsed = parseExpoConfigJson(stdout);
|
|
1047
|
+
settle(
|
|
1048
|
+
() => parsed ? resolve(parsed) : reject(new Error("expo config printed something that is not JSON"))
|
|
1049
|
+
);
|
|
1025
1050
|
}
|
|
1026
1051
|
);
|
|
1052
|
+
child.stdin?.end();
|
|
1053
|
+
const timer = setTimeout(() => {
|
|
1054
|
+
child.kill("SIGKILL");
|
|
1055
|
+
child.stdout?.destroy();
|
|
1056
|
+
child.stderr?.destroy();
|
|
1057
|
+
settle(
|
|
1058
|
+
() => reject(new Error(`expo config timed out after ${Math.round(timeoutMs / 1e3)} s`))
|
|
1059
|
+
);
|
|
1060
|
+
}, timeoutMs);
|
|
1061
|
+
timer.unref();
|
|
1027
1062
|
});
|
|
1028
1063
|
};
|
|
1029
1064
|
async function localAppIdentity(cwd, platform, readConfig = readExpoConfig) {
|
|
@@ -1034,9 +1069,7 @@ async function localAppIdentity(cwd, platform, readConfig = readExpoConfig) {
|
|
|
1034
1069
|
let version = null;
|
|
1035
1070
|
let bundleId = null;
|
|
1036
1071
|
let versionUnreadable = null;
|
|
1037
|
-
const dynamicName =
|
|
1038
|
-
(f) => existsSync3(join5(cwd, f))
|
|
1039
|
-
);
|
|
1072
|
+
const dynamicName = dynamicExpoConfig(cwd);
|
|
1040
1073
|
const staticRaw = readJson(cwd, "app.config.json") ?? readJson(cwd, "app.json");
|
|
1041
1074
|
const isExpo = Boolean(dynamicName) || staticRaw !== null && "expo" in staticRaw || existsSync3(join5(cwd, "node_modules", "expo", "package.json"));
|
|
1042
1075
|
let expo = null;
|
|
@@ -1075,6 +1108,41 @@ async function localAppIdentity(cwd, platform, readConfig = readExpoConfig) {
|
|
|
1075
1108
|
}
|
|
1076
1109
|
return { reactNative, version, bundleId, versionUnreadable: version ? null : versionUnreadable };
|
|
1077
1110
|
}
|
|
1111
|
+
var evaluatedNameCache = /* @__PURE__ */ new Map();
|
|
1112
|
+
function evaluatedExpoName(cwd, readConfig, onEvaluate) {
|
|
1113
|
+
const cached = evaluatedNameCache.get(cwd);
|
|
1114
|
+
if (cached) return cached;
|
|
1115
|
+
onEvaluate?.();
|
|
1116
|
+
const pending = Promise.resolve().then(() => readConfig(cwd)).then(
|
|
1117
|
+
(config) => typeof config.name === "string" ? { name: config.name } : { error: "the resolved config has no name" },
|
|
1118
|
+
(err) => ({
|
|
1119
|
+
error: err instanceof Error ? err.message : String(err)
|
|
1120
|
+
})
|
|
1121
|
+
);
|
|
1122
|
+
evaluatedNameCache.set(cwd, pending);
|
|
1123
|
+
return pending;
|
|
1124
|
+
}
|
|
1125
|
+
async function resolvedDefaultsFor(cwd, platform, opts = {}) {
|
|
1126
|
+
const { production = false, readConfig = (c) => readExpoConfig(c), onEvaluate } = opts;
|
|
1127
|
+
const base = production ? productionDefaultsFor : defaultsFor;
|
|
1128
|
+
const dynamic = dynamicExpoConfig(cwd);
|
|
1129
|
+
if (platform !== "ios" || !dynamic || !isManagedExpo(cwd, "ios")) {
|
|
1130
|
+
return base(cwd, platform);
|
|
1131
|
+
}
|
|
1132
|
+
const evaluated = await evaluatedExpoName(cwd, readConfig, onEvaluate);
|
|
1133
|
+
if ("error" in evaluated) {
|
|
1134
|
+
return { ...base(cwd, platform), evaluatedConfig: dynamic, evaluationError: evaluated.error };
|
|
1135
|
+
}
|
|
1136
|
+
const named = base(cwd, platform, evaluated.name);
|
|
1137
|
+
if (!named.buildCommand) {
|
|
1138
|
+
return {
|
|
1139
|
+
...named,
|
|
1140
|
+
evaluatedConfig: dynamic,
|
|
1141
|
+
evaluationError: `the name "${evaluated.name}" has no letters or digits`
|
|
1142
|
+
};
|
|
1143
|
+
}
|
|
1144
|
+
return { ...named, evaluatedConfig: dynamic };
|
|
1145
|
+
}
|
|
1078
1146
|
|
|
1079
1147
|
// src/lib/dev-setup.ts
|
|
1080
1148
|
function platformChoices(cwd) {
|
|
@@ -1109,6 +1177,37 @@ function fillLaterHint(skipped, platform) {
|
|
|
1109
1177
|
}
|
|
1110
1178
|
return lines.join("\n");
|
|
1111
1179
|
}
|
|
1180
|
+
async function promptDefaults(cwd, platform, opts = {}) {
|
|
1181
|
+
const progress = {};
|
|
1182
|
+
try {
|
|
1183
|
+
return await resolvedDefaultsFor(cwd, platform, {
|
|
1184
|
+
...opts,
|
|
1185
|
+
onEvaluate: () => {
|
|
1186
|
+
progress.spinner = createSpinner("Reading Expo config\u2026").start();
|
|
1187
|
+
}
|
|
1188
|
+
});
|
|
1189
|
+
} finally {
|
|
1190
|
+
progress.spinner?.stop();
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
function evaluatedConfigNote(cwd, defaults) {
|
|
1194
|
+
const file = defaults.evaluatedConfig;
|
|
1195
|
+
if (!file) return;
|
|
1196
|
+
if (defaults.evaluationError) {
|
|
1197
|
+
console.log(chalk2.dim(` Couldn't read the name from ${file}: ${defaults.evaluationError}`));
|
|
1198
|
+
return;
|
|
1199
|
+
}
|
|
1200
|
+
if (notedConfigDirs.has(cwd)) return;
|
|
1201
|
+
notedConfigDirs.add(cwd);
|
|
1202
|
+
console.log(
|
|
1203
|
+
chalk2.dim(
|
|
1204
|
+
` The iOS name was read from ${file} using your local environment.
|
|
1205
|
+
If it depends on environment variables, set the same ones for the cloud build
|
|
1206
|
+
(build.env, or build variables).`
|
|
1207
|
+
)
|
|
1208
|
+
);
|
|
1209
|
+
}
|
|
1210
|
+
var notedConfigDirs = /* @__PURE__ */ new Set();
|
|
1112
1211
|
async function commandSelect(message, detected) {
|
|
1113
1212
|
const choices = [];
|
|
1114
1213
|
if (detected) choices.push({ value: "default", name: `Use detected default: ${detected}` });
|
|
@@ -1117,7 +1216,8 @@ async function commandSelect(message, detected) {
|
|
|
1117
1216
|
return select({ message, choices });
|
|
1118
1217
|
}
|
|
1119
1218
|
async function promptBuildConfig(cwd, platform) {
|
|
1120
|
-
const defaults =
|
|
1219
|
+
const defaults = await promptDefaults(cwd, platform);
|
|
1220
|
+
evaluatedConfigNote(cwd, defaults);
|
|
1121
1221
|
const choice = await commandSelect(`Build command for ${platform}`, defaults.buildCommand);
|
|
1122
1222
|
if (choice === "skip") return null;
|
|
1123
1223
|
if (choice === "default") {
|
|
@@ -1137,7 +1237,8 @@ async function promptBuildConfig(cwd, platform) {
|
|
|
1137
1237
|
return { command, artifact };
|
|
1138
1238
|
}
|
|
1139
1239
|
async function promptProductionBuildConfig(cwd, platform) {
|
|
1140
|
-
const defaults =
|
|
1240
|
+
const defaults = await promptDefaults(cwd, platform, { production: true });
|
|
1241
|
+
evaluatedConfigNote(cwd, defaults);
|
|
1141
1242
|
const choice = await commandSelect(
|
|
1142
1243
|
`Production build command for ${platform}`,
|
|
1143
1244
|
defaults.buildCommand
|
|
@@ -4557,10 +4658,10 @@ var AGENT_COMMANDS = [
|
|
|
4557
4658
|
{
|
|
4558
4659
|
// relaunch_app: {} — terminate the session's app and launch it again the
|
|
4559
4660
|
// way the session launched it (same launch params); its data is kept.
|
|
4560
|
-
// Refused on embed sessions (their watchdog owns launching)
|
|
4661
|
+
// Refused on embed sessions (their watchdog owns launching).
|
|
4561
4662
|
verb: "relaunch-app",
|
|
4562
4663
|
tool: "relaunch_app",
|
|
4563
|
-
summary: "Close the app under test and start it again: a cold start that keeps the app's data, e.g. to check that something persists. Its log keeps streaming across the restart. Not available on
|
|
4664
|
+
summary: "Close the app under test and start it again: a cold start that keeps the app's data, e.g. to check that something persists. Its log keeps streaming across the restart. Not available on embed sessions.",
|
|
4564
4665
|
mutating: true,
|
|
4565
4666
|
params: []
|
|
4566
4667
|
},
|
|
@@ -163,9 +163,16 @@ async function parseJsonBody(resp) {
|
|
|
163
163
|
return withUtcTimestamps(await resp.json());
|
|
164
164
|
}
|
|
165
165
|
var ApiClient = class {
|
|
166
|
-
constructor(baseUrl, token) {
|
|
166
|
+
constructor(baseUrl, token, opts = {}) {
|
|
167
167
|
this.baseUrl = baseUrl;
|
|
168
168
|
this.token = token;
|
|
169
|
+
this.opts = opts;
|
|
170
|
+
}
|
|
171
|
+
extraHeaders() {
|
|
172
|
+
const h = {};
|
|
173
|
+
if (this.opts.client) h["X-VibeView-Client"] = this.opts.client;
|
|
174
|
+
if (process.env.CI) h["X-VibeView-CI"] = "1";
|
|
175
|
+
return h;
|
|
169
176
|
}
|
|
170
177
|
/**
|
|
171
178
|
* fetch() rejects with undici's bare "TypeError: fetch failed" on any
|
|
@@ -189,6 +196,7 @@ var ApiClient = class {
|
|
|
189
196
|
const url = `${this.baseUrl}/api/v1/ci${path}`;
|
|
190
197
|
const headers = {
|
|
191
198
|
Authorization: `Bearer ${this.token}`,
|
|
199
|
+
...this.extraHeaders(),
|
|
192
200
|
"Content-Type": "application/json"
|
|
193
201
|
};
|
|
194
202
|
const resp = await this.fetchOrExplain(url, {
|
|
@@ -231,7 +239,8 @@ var ApiClient = class {
|
|
|
231
239
|
build_id: options?.build,
|
|
232
240
|
metadata: options?.metadata,
|
|
233
241
|
platform: options?.platform,
|
|
234
|
-
app_id: options?.app
|
|
242
|
+
app_id: options?.app,
|
|
243
|
+
retry_failed: options?.retryFailed
|
|
235
244
|
});
|
|
236
245
|
}
|
|
237
246
|
async runTest(testId, options) {
|
|
@@ -246,7 +255,8 @@ var ApiClient = class {
|
|
|
246
255
|
build_id: options?.build,
|
|
247
256
|
metadata: options?.metadata,
|
|
248
257
|
platform: options?.platform,
|
|
249
|
-
app_id: options?.app
|
|
258
|
+
app_id: options?.app,
|
|
259
|
+
retry_failed: options?.retryFailed
|
|
250
260
|
});
|
|
251
261
|
}
|
|
252
262
|
/** Cancel a still-queued CI run by its queue entry id. Idempotent best-effort. */
|
|
@@ -271,6 +281,7 @@ var ApiClient = class {
|
|
|
271
281
|
const url = `${this.baseUrl}/api/v1${path}`;
|
|
272
282
|
const headers = {
|
|
273
283
|
Authorization: `Bearer ${this.token}`,
|
|
284
|
+
...this.extraHeaders(),
|
|
274
285
|
"Content-Type": "application/json"
|
|
275
286
|
};
|
|
276
287
|
const resp = await this.fetchOrExplain(url, {
|
|
@@ -295,7 +306,8 @@ var ApiClient = class {
|
|
|
295
306
|
async requestFormData(method, path, formData) {
|
|
296
307
|
const url = `${this.baseUrl}/api/v1${path}`;
|
|
297
308
|
const headers = {
|
|
298
|
-
Authorization: `Bearer ${this.token}
|
|
309
|
+
Authorization: `Bearer ${this.token}`,
|
|
310
|
+
...this.extraHeaders()
|
|
299
311
|
// Do NOT set Content-Type — let fetch set multipart boundary automatically
|
|
300
312
|
};
|
|
301
313
|
const resp = await this.fetchOrExplain(url, {
|
|
@@ -351,12 +363,35 @@ var ApiClient = class {
|
|
|
351
363
|
}
|
|
352
364
|
return this.requestFormData("POST", `/apps/${appPublicId}/builds`, formData);
|
|
353
365
|
}
|
|
366
|
+
/** POST /api/v1/tests/import/maestro (multipart): preview (`dryRun`) or
|
|
367
|
+
* import Maestro flows as tests of one app. `group` is a group id or name. */
|
|
368
|
+
async importMaestro(upload, opts) {
|
|
369
|
+
const formData = new FormData();
|
|
370
|
+
formData.append("file", new Blob([Buffer.from(upload.bytes)]), upload.name);
|
|
371
|
+
formData.append("app_id", opts.appId);
|
|
372
|
+
if (opts.group) formData.append("group", opts.group);
|
|
373
|
+
formData.append("dry_run", opts.dryRun ? "true" : "false");
|
|
374
|
+
if (opts.skip && opts.skip.length > 0) formData.append("skip", JSON.stringify(opts.skip));
|
|
375
|
+
return this.requestFormData("POST", "/tests/import/maestro", formData);
|
|
376
|
+
}
|
|
354
377
|
async createTest(body) {
|
|
355
378
|
return this.requestRaw("POST", "/tests/cases/", body);
|
|
356
379
|
}
|
|
357
380
|
async runSuites(body) {
|
|
358
381
|
return this.request("POST", "/run-suites", body);
|
|
359
382
|
}
|
|
383
|
+
async startRun(body) {
|
|
384
|
+
return this.requestRaw("POST", "/tests/runs", body);
|
|
385
|
+
}
|
|
386
|
+
async startRunOnSession(sessionId, body) {
|
|
387
|
+
return this.requestRaw("POST", `/sessions/${encodeURIComponent(sessionId)}/runs`, body);
|
|
388
|
+
}
|
|
389
|
+
async getRunBatch(batchId) {
|
|
390
|
+
return this.requestRaw("GET", `/tests/batches/${encodeURIComponent(batchId)}`);
|
|
391
|
+
}
|
|
392
|
+
async resolveRun(runId) {
|
|
393
|
+
return this.requestRaw("GET", `/tests/runs/${encodeURIComponent(runId)}/resolve`);
|
|
394
|
+
}
|
|
360
395
|
async getMultiSuiteRun(aggregateId) {
|
|
361
396
|
return this.request("GET", `/multi-suite-runs/${aggregateId}`);
|
|
362
397
|
}
|
|
@@ -474,7 +509,8 @@ var ApiClient = class {
|
|
|
474
509
|
async downloadCloudBuildArtifact(id, destDir) {
|
|
475
510
|
const url = `${this.baseUrl}/api/v1/builds/${id}/artifact`;
|
|
476
511
|
const headers = {
|
|
477
|
-
Authorization: `Bearer ${this.token}
|
|
512
|
+
Authorization: `Bearer ${this.token}`,
|
|
513
|
+
...this.extraHeaders()
|
|
478
514
|
};
|
|
479
515
|
const resp = await this.fetchOrExplain(url, { method: "GET", headers });
|
|
480
516
|
if (!resp.ok) {
|