@test-lab-ai/cli 0.2.15 → 0.2.17
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/bin/testlab.mjs +54 -9
- package/lib/config.mjs +2 -4
- package/package.json +1 -1
package/bin/testlab.mjs
CHANGED
|
@@ -13,7 +13,6 @@
|
|
|
13
13
|
* testlab import <path> [--dry-run] Import a plan file or a directory of *.json
|
|
14
14
|
*
|
|
15
15
|
* Auth: --key -> $TESTLAB_API_KEY -> ~/.test-lab/config.json
|
|
16
|
-
* API: https://www.test-lab.ai (fixed)
|
|
17
16
|
*/
|
|
18
17
|
import { parseArgs } from "node:util"
|
|
19
18
|
import fs from "node:fs"
|
|
@@ -41,11 +40,14 @@ Usage:
|
|
|
41
40
|
(credentials, labels, fixtures, plans)
|
|
42
41
|
testlab plans list List your test plans
|
|
43
42
|
testlab plans create -f plan.json Create one plan from JSON
|
|
43
|
+
testlab plans update <id> [-f patch.json] [--prompt P] [--name N]
|
|
44
|
+
Update an existing plan; only the
|
|
45
|
+
fields you pass change
|
|
44
46
|
testlab projects list List your projects
|
|
45
47
|
testlab credentials set <key> --value <value> Set a credential ({{credentials.<key>}})
|
|
46
48
|
testlab credentials list List credential keys (values never shown)
|
|
47
49
|
testlab labels list List your labels
|
|
48
|
-
testlab data list List
|
|
50
|
+
testlab data list List data fixtures + built-in file fixtures
|
|
49
51
|
testlab data create -f fixture.json Create a data fixture ({{data.<fixture>.<field>}})
|
|
50
52
|
testlab scripts upload <file> --plan <id> Upload a local Playwright script for a plan
|
|
51
53
|
(skips paid AI generation; --device optional)
|
|
@@ -217,6 +219,34 @@ async function cmdPlansCreate(flags) {
|
|
|
217
219
|
log(`✓ Created plan #${r.json.testPlan.id}: ${r.json.testPlan.name}`)
|
|
218
220
|
}
|
|
219
221
|
|
|
222
|
+
async function cmdPlansUpdate(flags, args) {
|
|
223
|
+
const { apiKey, apiUrl } = requireAuth(flags)
|
|
224
|
+
const planId = parsePlanIdArg(args[2] ?? flags.plan)
|
|
225
|
+
if (!Number.isInteger(planId)) {
|
|
226
|
+
errExit("usage: testlab plans update <id> [-f patch.json] [--prompt P] [--name N] (numeric plan id; see `testlab plans list`)")
|
|
227
|
+
}
|
|
228
|
+
// Build a partial patch. A JSON file is the full surface (name, prompt,
|
|
229
|
+
// testType, agentType, devices, paused, projectId); --prompt / --name are
|
|
230
|
+
// quick overrides for the two common edits. Only the fields present are sent,
|
|
231
|
+
// and the server changes only those.
|
|
232
|
+
let patch = {}
|
|
233
|
+
if (flags.file) {
|
|
234
|
+
try {
|
|
235
|
+
patch = JSON.parse(fs.readFileSync(flags.file, "utf8"))
|
|
236
|
+
} catch (e) {
|
|
237
|
+
errExit(`could not read ${flags.file}: ${e.message}`)
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
if (flags.prompt !== undefined) patch.prompt = flags.prompt
|
|
241
|
+
if (flags.name !== undefined) patch.name = flags.name
|
|
242
|
+
if (Object.keys(patch).length === 0) {
|
|
243
|
+
errExit("nothing to update: pass -f <patch.json> and/or --prompt / --name. Editable fields: name, prompt, testType, agentType, devices, paused, projectId")
|
|
244
|
+
}
|
|
245
|
+
const r = await apiFetch(apiUrl, apiKey, "PUT", `/api/v1/test-plans/${planId}`, patch)
|
|
246
|
+
if (!r.ok) errExit(`${r.status}: ${r.json?.error || ""}`)
|
|
247
|
+
log(`✓ Updated plan #${r.json.testPlan.id}: ${r.json.testPlan.name}`)
|
|
248
|
+
}
|
|
249
|
+
|
|
220
250
|
async function cmdCredentialsSet(flags, args) {
|
|
221
251
|
const { apiKey, apiUrl } = requireAuth(flags)
|
|
222
252
|
const key = args[2]
|
|
@@ -338,13 +368,27 @@ async function cmdDataList(flags) {
|
|
|
338
368
|
const fixtures = r.json?.fixtures || []
|
|
339
369
|
if (fixtures.length === 0) {
|
|
340
370
|
log("No data fixtures yet.")
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
371
|
+
} else {
|
|
372
|
+
log("Data fixtures (reference as {{data.<fixture>.<field>}}):")
|
|
373
|
+
for (const fx of fixtures) {
|
|
374
|
+
const fields = (fx.fields || []).map((f) => f.key).join(", ")
|
|
375
|
+
log(` ${fx.key}${fx.label ? ` (${fx.label})` : ""}${fields ? ` - ${fields}` : ""}`)
|
|
376
|
+
}
|
|
377
|
+
log(` ${fixtures.length} data fixture(s)`)
|
|
378
|
+
}
|
|
379
|
+
// Built-in upload-file fixtures (reference in an upload step as {{file.<name>}}).
|
|
380
|
+
const files = r.json?.files || []
|
|
381
|
+
if (files.length > 0) {
|
|
382
|
+
log("\nFile fixtures (reference in an upload step as {{file.<name>}}):")
|
|
383
|
+
const byCat = {}
|
|
384
|
+
for (const f of files) (byCat[f.category] ||= []).push(f)
|
|
385
|
+
for (const cat of Object.keys(byCat).sort()) {
|
|
386
|
+
const defaults = byCat[cat].filter((f) => f.default).map((f) => `{{${f.token}}}`)
|
|
387
|
+
const shown = defaults.length ? defaults : byCat[cat].map((f) => `{{${f.token}}}`)
|
|
388
|
+
log(` ${cat}: ${shown.join(" ")}`)
|
|
389
|
+
}
|
|
390
|
+
log(` ${files.length} file fixture(s) (append a size, e.g. {{file.pdf.25mb}})`)
|
|
346
391
|
}
|
|
347
|
-
log(`\n${fixtures.length} fixture(s)`)
|
|
348
392
|
}
|
|
349
393
|
|
|
350
394
|
async function cmdDataCreate(flags) {
|
|
@@ -626,7 +670,8 @@ async function main() {
|
|
|
626
670
|
case "plans":
|
|
627
671
|
if (args[1] === "list") return cmdPlansList(flags)
|
|
628
672
|
if (args[1] === "create") return cmdPlansCreate(flags)
|
|
629
|
-
|
|
673
|
+
if (args[1] === "update") return cmdPlansUpdate(flags, args)
|
|
674
|
+
return errExit("usage: testlab plans <list|create|update>")
|
|
630
675
|
case "projects":
|
|
631
676
|
if (args[1] === "list") return cmdProjectsList(flags)
|
|
632
677
|
return errExit("usage: testlab projects list")
|
package/lib/config.mjs
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
* Config + auth resolution for the testlab CLI.
|
|
3
3
|
*
|
|
4
4
|
* API key: --key flag -> $TESTLAB_API_KEY -> ~/.test-lab/config.json
|
|
5
|
-
* API base: always https://www.test-lab.ai (not configurable)
|
|
6
5
|
*/
|
|
7
6
|
import fs from "node:fs"
|
|
8
7
|
import os from "node:os"
|
|
@@ -35,8 +34,7 @@ export function saveConfig(cfg) {
|
|
|
35
34
|
export function resolveAuth(flags) {
|
|
36
35
|
const cfg = loadConfig()
|
|
37
36
|
const apiKey = flags.key || process.env.TESTLAB_API_KEY || cfg.apiKey || null
|
|
38
|
-
//
|
|
39
|
-
//
|
|
40
|
-
// never sent to an arbitrary host.
|
|
37
|
+
// The base URL used to be overridable (--api-url / TESTLAB_API_URL / config);
|
|
38
|
+
// removed so an API key can't be sent to an arbitrary host.
|
|
41
39
|
return { apiKey, apiUrl: DEFAULT_API_URL }
|
|
42
40
|
}
|