@flowriki/agent 0.1.0-beta.62 → 0.1.0-beta.64
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.
|
@@ -68,7 +68,7 @@ import {
|
|
|
68
68
|
textIndependent,
|
|
69
69
|
validateActors,
|
|
70
70
|
webCloudOf
|
|
71
|
-
} from "./chunk-
|
|
71
|
+
} from "./chunk-RI63WNGU.js";
|
|
72
72
|
|
|
73
73
|
// src/engine/machine.ts
|
|
74
74
|
import fs from "fs";
|
|
@@ -76,7 +76,7 @@ import os from "os";
|
|
|
76
76
|
import path from "path";
|
|
77
77
|
import { createRequire } from "module";
|
|
78
78
|
import { spawnSync } from "child_process";
|
|
79
|
-
var AGENT_VERSION = "0.1.0-beta.
|
|
79
|
+
var AGENT_VERSION = "0.1.0-beta.64";
|
|
80
80
|
async function prepareBrowsers() {
|
|
81
81
|
if (browsersReady()) {
|
|
82
82
|
return { ok: true, already: true, message: "chromium is already installed" };
|
|
@@ -136,7 +136,7 @@ async function checkMachine() {
|
|
|
136
136
|
}
|
|
137
137
|
} catch {
|
|
138
138
|
}
|
|
139
|
-
const { mobileDoctor: mobileDoctor2 } = await import("./dist-
|
|
139
|
+
const { mobileDoctor: mobileDoctor2 } = await import("./dist-FNAQZJYU.js");
|
|
140
140
|
return {
|
|
141
141
|
agentVersion: AGENT_VERSION,
|
|
142
142
|
node: process.versions.node,
|
|
@@ -658,6 +658,8 @@ import { z as z9 } from "zod";
|
|
|
658
658
|
import { z as z10 } from "zod";
|
|
659
659
|
import { z as z11 } from "zod";
|
|
660
660
|
import { z as z12 } from "zod";
|
|
661
|
+
import { z as z13 } from "zod";
|
|
662
|
+
import { z as z14 } from "zod";
|
|
661
663
|
var EngineSchema = z3.enum(["chromium", "firefox", "webkit", "android", "ios"]);
|
|
662
664
|
var CapabilitySchema = z3.object({
|
|
663
665
|
id: z3.string(),
|
|
@@ -866,6 +868,10 @@ var TestCaseSchema = z32.object({
|
|
|
866
868
|
createdAt: z32.string(),
|
|
867
869
|
createdBy: z32.string().default(""),
|
|
868
870
|
updatedAt: z32.string(),
|
|
871
|
+
/** WHO LAST CHANGED IT — a person's id, or "Claude" for the assistant — so
|
|
872
|
+
* a change the assistant made can be told from one a person made. Absent
|
|
873
|
+
* on cases saved before this was kept. Never sent to a machine. */
|
|
874
|
+
updatedBy: z32.string().max(200).optional(),
|
|
869
875
|
/** The same optimistic-concurrency counter a flow carries, so an edit made
|
|
870
876
|
* against a stale copy is refused rather than overwriting. */
|
|
871
877
|
revision: z32.number().int().nonnegative().default(0)
|
|
@@ -1767,6 +1773,8 @@ var CaseUpdateRequestSchema = z9.object({
|
|
|
1767
1773
|
instruction: z9.string().min(1).max(4e3),
|
|
1768
1774
|
cases: z9.array(z9.object({
|
|
1769
1775
|
id: z9.string().min(1),
|
|
1776
|
+
/** Its id as people say it — TC-12. */
|
|
1777
|
+
key: z9.string().max(20).optional(),
|
|
1770
1778
|
name: z9.string(),
|
|
1771
1779
|
feature: z9.string(),
|
|
1772
1780
|
businessSteps: z9.array(BusinessStepSchema)
|
|
@@ -1858,6 +1866,22 @@ var RunMemorySchema = z12.object({
|
|
|
1858
1866
|
buckets: z12.array(Bucket).max(RUN_MEMORY_WEEKS + 1),
|
|
1859
1867
|
updatedAt: z12.number()
|
|
1860
1868
|
}).strict();
|
|
1869
|
+
var WhatToDoSchema = z13.object({
|
|
1870
|
+
id: z13.string().regex(/^wtd_/),
|
|
1871
|
+
projectId: z13.string().min(1),
|
|
1872
|
+
/** Step ids skipped — "we do this differently". */
|
|
1873
|
+
skipped: z13.array(z13.string().min(1).max(80)).max(100),
|
|
1874
|
+
updatedAt: z13.string()
|
|
1875
|
+
}).strict();
|
|
1876
|
+
var CaseRegistrySchema = z14.object({
|
|
1877
|
+
id: z14.string().regex(/^creg_/),
|
|
1878
|
+
projectId: z14.string().min(1),
|
|
1879
|
+
next: z14.object({ case: z14.number().int().positive(), feature: z14.number().int().positive() }).strict(),
|
|
1880
|
+
cases: z14.record(z14.string(), z14.number().int().positive()),
|
|
1881
|
+
features: z14.record(z14.string(), z14.number().int().positive()),
|
|
1882
|
+
reviews: z14.record(z14.string(), z14.object({ hash: z14.string(), by: z14.string().max(200), at: z14.string() }).strict()),
|
|
1883
|
+
updatedAt: z14.string()
|
|
1884
|
+
}).strict();
|
|
1861
1885
|
|
|
1862
1886
|
// ../engine-appium/dist/index.js
|
|
1863
1887
|
import path5 from "path";
|
package/dist/cli.js
CHANGED
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
runtimeValue,
|
|
25
25
|
start,
|
|
26
26
|
status
|
|
27
|
-
} from "./chunk-
|
|
27
|
+
} from "./chunk-2MTLWAXK.js";
|
|
28
28
|
import {
|
|
29
29
|
CLOUD_PROVIDERS,
|
|
30
30
|
CLOUD_PROVIDER_NAME,
|
|
@@ -52,7 +52,7 @@ import {
|
|
|
52
52
|
slugify,
|
|
53
53
|
stepRefusedOn,
|
|
54
54
|
targetFor
|
|
55
|
-
} from "./chunk-
|
|
55
|
+
} from "./chunk-RI63WNGU.js";
|
|
56
56
|
|
|
57
57
|
// src/cli.ts
|
|
58
58
|
import { parseArgs } from "util";
|
|
@@ -308,8 +308,8 @@ var ServiceClient = class {
|
|
|
308
308
|
);
|
|
309
309
|
}
|
|
310
310
|
readCases(projectId, refs) {
|
|
311
|
-
const ids = refs.filter((r) => /^tc_
|
|
312
|
-
const names = refs.filter((r) => !/^tc_
|
|
311
|
+
const ids = refs.filter((r) => /^(tc_|tc-?\d+$|#\d+$)/i.test(r.trim()));
|
|
312
|
+
const names = refs.filter((r) => !/^(tc_|tc-?\d+$|#\d+$)/i.test(r.trim()));
|
|
313
313
|
const q = new URLSearchParams({ projectId, ...ids.length ? { ids: ids.join(",") } : {}, ...names.length ? { names: names.join("|") } : {} });
|
|
314
314
|
return this.call(`/api/agent/cases/read?${q}`);
|
|
315
315
|
}
|
|
@@ -433,6 +433,22 @@ var STEP_TIMEOUT_MS = 1e4;
|
|
|
433
433
|
var SECRET_WORDS = /password|passwd|pass[-_\s]?code|pwd|secret|otp|one[-_\s]?time[-_\s]?(code|pass)|security[-_\s]?(code|answer)|cvv|cvc|\bpin\b/i;
|
|
434
434
|
var TYPED = "typed_";
|
|
435
435
|
var withoutTyped = (steps) => steps.map((st) => st.secretRef?.startsWith(TYPED) ? (({ secretRef: _, ...rest }) => rest)(st) : st);
|
|
436
|
+
var SIGN_UP = /\b(sign[\s-]?up|register|registration|create (an |your )?account|join)\b/i;
|
|
437
|
+
function shapeOf(draft, typed) {
|
|
438
|
+
const fills = draft.steps.filter((st) => st.type === "fill" && !st.disabled);
|
|
439
|
+
const values = {};
|
|
440
|
+
for (const st of fills) {
|
|
441
|
+
const value = st.secret ? st.secretRef ? typed.get(st.secretRef) : void 0 : st.value;
|
|
442
|
+
if (st.name && typeof value === "string") values[st.name] = value;
|
|
443
|
+
}
|
|
444
|
+
const base = { flowName: draft.name, platform: draft.platform ?? "web", values };
|
|
445
|
+
const secret = fills.some((st) => st.secret);
|
|
446
|
+
const fixed = Object.values(values).every((v) => !v.includes("{{"));
|
|
447
|
+
if (!secret || !fixed || Object.keys(values).length !== fills.length) return { kind: null, ...base };
|
|
448
|
+
if (SIGN_UP.test(draft.name) || fills.length > 3) return { kind: "signup", ...base };
|
|
449
|
+
if (draft.signIn || draft.after?.length) return { kind: null, ...base };
|
|
450
|
+
return { kind: "login", ...base };
|
|
451
|
+
}
|
|
436
452
|
var ACTIONS = [
|
|
437
453
|
"click",
|
|
438
454
|
"dblclick",
|
|
@@ -756,6 +772,14 @@ ${view}`;
|
|
|
756
772
|
}
|
|
757
773
|
if (this.liveCase) await this.sendCaseReport();
|
|
758
774
|
}
|
|
775
|
+
/** What the last saved flow was — a login sign-in can be made from, a
|
|
776
|
+
* sign-up — with the values it typed. Taken once. */
|
|
777
|
+
lastSaved = null;
|
|
778
|
+
takeLastSaved() {
|
|
779
|
+
const saved = this.lastSaved;
|
|
780
|
+
this.lastSaved = null;
|
|
781
|
+
return saved;
|
|
782
|
+
}
|
|
759
783
|
// ── The test case a recording is for ────────────────────────────────
|
|
760
784
|
//
|
|
761
785
|
// A PERSON THINKS IN TEST CASES. The flows are how the assistant makes one
|
|
@@ -1181,6 +1205,7 @@ Fix the step that failed (remove_step, then act again) and save again \u2014 or
|
|
|
1181
1205
|
const note = others.length ? `
|
|
1182
1206
|
Claude has ${others.length} other open branch${others.length === 1 ? "" : "es"} on this project \u2014 gather_branches puts them into this one, for one review.` : "";
|
|
1183
1207
|
const after = draft.after?.length ? ` It starts after ${draft.after.map((a) => `"${a.name}"`).join(", ")}.` : "";
|
|
1208
|
+
this.lastSaved = { projectId: draft.projectId, ...shapeOf(draft, this.typed) };
|
|
1184
1209
|
this.dropDraft(draft.projectId, draft.name);
|
|
1185
1210
|
const live = this.liveCase;
|
|
1186
1211
|
if (live && live.projectId === draft.projectId) {
|
|
@@ -1495,12 +1520,36 @@ THE MODEL \u2014 get this right before recording anything:
|
|
|
1495
1520
|
write_test_cases. To automate one, record the flows its steps need, then
|
|
1496
1521
|
save_case with the SAME name in the SAME folder: that fills it, and its
|
|
1497
1522
|
business steps stay. Its Then lines are the checks to switch on.
|
|
1498
|
-
-
|
|
1499
|
-
sign-in
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1523
|
+
- AUTHENTICATION IS YOURS, END TO END. The person records nothing by hand and
|
|
1524
|
+
is not asked to set sign-in up. A flow or case that needs somebody signed in
|
|
1525
|
+
starts signed in with a sign-in profile (start_flow's signIn) \u2014 never typed
|
|
1526
|
+
login steps. Use one from project_catalog made on the SAME platform, with an
|
|
1527
|
+
account for the role. None yet? Make it, without asking:
|
|
1528
|
+
1. A test account, in this order: credentials the person gave you; a demo
|
|
1529
|
+
login the app itself shows; or SIGN UP a dedicated test user yourself \u2014 a
|
|
1530
|
+
fixed address for the role (e.g. flowriki.customer@ the domain of the
|
|
1531
|
+
project's mailbox helper) and a strong password you invent (it is sealed
|
|
1532
|
+
in the vault). Sign it up once; it is reused by every run.
|
|
1533
|
+
2. Record the login as a flow on that platform that ends with a check proving
|
|
1534
|
+
it worked (the account menu, a heading, the URL), then create_sign_in from
|
|
1535
|
+
it \u2014 one per platform per role, e.g. "Customer sign-in" \u2014 and
|
|
1536
|
+
create_account with the test user's values.
|
|
1537
|
+
3. Only what you cannot create yourself \u2014 an admin, a role only staff can
|
|
1538
|
+
grant, a mailbox when sign-up must verify an email and the catalog has no
|
|
1539
|
+
mailbox helper \u2014 is asked of the person: once, saying exactly what you
|
|
1540
|
+
need, while you carry on with everything else.
|
|
1541
|
+
- AUTHENTICATION IS ALSO A PRODUCT AREA TO TEST. Asked to test sign-up,
|
|
1542
|
+
sign-in, accounts or security, cover it as a tester would \u2014 each its own
|
|
1543
|
+
test case, in folders such as "Account \u2013 Sign up", "Account \u2013 Sign in":
|
|
1544
|
+
sign up (valid; an email already taken; a weak password; required fields
|
|
1545
|
+
missing), sign in (valid; wrong password; unknown email; empty fields),
|
|
1546
|
+
password reset (request it, set a new one through the mailbox helper),
|
|
1547
|
+
sign out (and Back cannot return to a signed-in page), a signed-in page
|
|
1548
|
+
opened while signed out goes to sign-in, a session survives a reload,
|
|
1549
|
+
lockout or throttling after repeated wrong passwords (stop at the limit plus
|
|
1550
|
+
one), and roles (a customer cannot reach an admin page). Test the app's own
|
|
1551
|
+
behaviour only: no attacks, no load, no other systems, no real person's
|
|
1552
|
+
account.
|
|
1504
1553
|
- HELPERS (an inbox for email codes, an authenticator, SMS, an API call) are made
|
|
1505
1554
|
with create_helper when the catalog has none that fits.
|
|
1506
1555
|
- SEVERAL ELEMENTS WITH THE SAME NAME (two "Proceed to Checkout" buttons, twelve
|
|
@@ -1510,8 +1559,13 @@ THE MODEL \u2014 get this right before recording anything:
|
|
|
1510
1559
|
- PASSWORDS AND KEYS never go into a flow or a reply. Typed into a password
|
|
1511
1560
|
field, a value is kept as a secret and saved to the project's vault; given to
|
|
1512
1561
|
create_account or create_helper, it is sealed there too and never shown back.
|
|
1513
|
-
Only use credentials the person gave you
|
|
1514
|
-
testing (a demo login)
|
|
1562
|
+
Only use credentials the person gave you, that the app itself shows for
|
|
1563
|
+
testing (a demo login), or of a test user you signed up yourself \u2014 never
|
|
1564
|
+
guess or reuse anyone's real password.
|
|
1565
|
+
- IDS: every test case has an id people use \u2014 TC-12 \u2014 and every feature one \u2014
|
|
1566
|
+
F-3. When the person names one, it is that one: read_test_cases takes ids,
|
|
1567
|
+
and save_case's case fills or redoes exactly that test case. Say the id
|
|
1568
|
+
whenever you name a case back.
|
|
1515
1569
|
- FOLDERS do not nest. Name each one after the product area and what is in it,
|
|
1516
1570
|
the way a tester would look for it \u2014 "Account \u2013 Sign up", "Account \u2013 Sign in",
|
|
1517
1571
|
"Ordering \u2013 Pizza builder", "Checkout \u2013 Payment". Flows (features) and the
|
|
@@ -1651,6 +1705,7 @@ var CASE_INPUT = {
|
|
|
1651
1705
|
}
|
|
1652
1706
|
},
|
|
1653
1707
|
platform: { type: "string", enum: ["web", "android", "ios"], description: "The platform this case is for, when the project tests several: its rows are that platform's flows. Its label \u2014 not words in its name." },
|
|
1708
|
+
case: { type: "string", description: "The test case to fill or redo, by its id \u2014 TC-12 \u2014 wherever it is: automating or re-automating a case you were asked about by id. Default: the case of this name in this folder." },
|
|
1654
1709
|
branch: { type: "string", description: "Optional: save onto this branch (chg_\u2026) instead of the working one." },
|
|
1655
1710
|
projectId: { type: "string" }
|
|
1656
1711
|
},
|
|
@@ -1664,7 +1719,8 @@ var caseSpec = (args) => ({
|
|
|
1664
1719
|
rows: Array.isArray(args["rows"]) ? args["rows"] : [],
|
|
1665
1720
|
...Array.isArray(args["businessSteps"]) ? { businessSteps: args["businessSteps"] } : {},
|
|
1666
1721
|
...args["branch"] ? { changeId: String(args["branch"]) } : {},
|
|
1667
|
-
...args["platform"] === "web" || args["platform"] === "android" || args["platform"] === "ios" ? { platform: args["platform"] } : {}
|
|
1722
|
+
...args["platform"] === "web" || args["platform"] === "android" || args["platform"] === "ios" ? { platform: args["platform"] } : {},
|
|
1723
|
+
...typeof args["case"] === "string" && args["case"].trim() ? { case: args["case"].trim() } : {}
|
|
1668
1724
|
});
|
|
1669
1725
|
var text = (value, isError = false) => ({ content: [{ type: "text", text: value }], ...isError ? { isError } : {} });
|
|
1670
1726
|
var str = (value) => typeof value === "string" ? value : value === void 0 || value === null ? void 0 : String(value);
|
|
@@ -1698,6 +1754,33 @@ function authoringTools(service, session) {
|
|
|
1698
1754
|
const lines = session.caseLinesFor(spec.name);
|
|
1699
1755
|
return lines ? { ...spec, businessSteps: lines } : spec;
|
|
1700
1756
|
};
|
|
1757
|
+
const signInFrom = async (shape) => {
|
|
1758
|
+
if (!shape?.kind) return "";
|
|
1759
|
+
const NAME = { web: "Web", android: "Android", ios: "iOS" };
|
|
1760
|
+
if (shape.kind === "signup") {
|
|
1761
|
+
return `
|
|
1762
|
+
|
|
1763
|
+
That signed up a fixed test user. Next: record the login as that user on ${NAME[shape.platform]} (a flow that types its email or username and password, then checks it is signed in) \u2014 saving it sets sign-in up for the project.`;
|
|
1764
|
+
}
|
|
1765
|
+
try {
|
|
1766
|
+
const catalog = await catalogOf(shape.projectId, true);
|
|
1767
|
+
const same = catalog.signIns.find((si) => (si.platform ?? "web") === shape.platform);
|
|
1768
|
+
if (same) return `
|
|
1769
|
+
|
|
1770
|
+
${NAME[shape.platform]} already has the sign-in "${same.name}" (profileId ${same.profileId}) \u2014 use it with start_flow signIn.`;
|
|
1771
|
+
const made = await service.createSignIn(shape.projectId, { flow: shape.flowName, name: `${NAME[shape.platform]} sign-in` });
|
|
1772
|
+
const values = Object.fromEntries(made.fields.flatMap((f) => shape.values[f.label] !== void 0 ? [[f.label, shape.values[f.label]]] : []));
|
|
1773
|
+
const account = await service.createAccount(shape.projectId, { signIn: made.profileId, name: "Test user", values });
|
|
1774
|
+
catalogs.delete(shape.projectId);
|
|
1775
|
+
return `
|
|
1776
|
+
|
|
1777
|
+
This is a login, so it is now the project's sign-in on ${NAME[shape.platform]}: "${made.name}" (profileId ${made.profileId}) with the account "${account.name}" (accountId ${account.accountId}; its secrets are in the vault). Every flow that needs somebody signed in starts with start_flow signIn {"profileId": "${made.profileId}", "accountId": "${account.accountId}"}.`;
|
|
1778
|
+
} catch (error) {
|
|
1779
|
+
return `
|
|
1780
|
+
|
|
1781
|
+
It looks like a login, but sign-in could not be set up from it (${error.message.split("\n")[0]}) \u2014 create_sign_in and create_account can do it.`;
|
|
1782
|
+
}
|
|
1783
|
+
};
|
|
1701
1784
|
const tools = [
|
|
1702
1785
|
{
|
|
1703
1786
|
name: "list_projects",
|
|
@@ -1770,12 +1853,12 @@ On Claude's working branch "${c.draft.title}" (${c.draft.changeId}), not yet pub
|
|
|
1770
1853
|
...c.draft.features.length ? [` New features: ${c.draft.features.map((f) => `${f.name} (id ${f.id})`).join(", ")}`] : [],
|
|
1771
1854
|
...c.draft.flows.length ? c.draft.flows.map((f) => ` - flow: ${f.name} (${f.steps} steps, feature ${f.featureId}${f.after?.length ? `, after ${f.after.map((a) => `"${a}"`).join(", ")}` : ""})`) : [" (no flows yet)"],
|
|
1772
1855
|
...c.draft.caseFolders?.length ? [` New case folders: ${c.draft.caseFolders.map((f) => f.name).join(", ")}`] : [],
|
|
1773
|
-
...(c.draft.cases ?? []).map((t) => ` - case: ${t.name} (folder ${t.folderId}) \u2014 rows: ${t.rows.join(" \u2192 ")}`)
|
|
1856
|
+
...(c.draft.cases ?? []).map((t) => ` - case: ${t.key ? `${t.key} ` : ""}${t.name} (folder ${t.folderId}) \u2014 rows: ${t.rows.join(" \u2192 ")}`)
|
|
1774
1857
|
] : [],
|
|
1775
1858
|
`
|
|
1776
1859
|
Test cases:${(c.cases ?? []).length ? "" : " none published"}`,
|
|
1777
|
-
...(c.caseFolders ?? []).map((f) => ` - ${f.name} (id ${f.id})
|
|
1778
|
-
${(c.cases ?? []).filter((t) => t.folderId === f.id).map((t) => ` - ${t.name} \u2014 rows: ${t.rows.join(" \u2192 ")}`).join("\n")}`),
|
|
1860
|
+
...(c.caseFolders ?? []).map((f) => ` - ${f.key ? `${f.key} ` : ""}${f.name} (id ${f.id})
|
|
1861
|
+
${(c.cases ?? []).filter((t) => t.folderId === f.id).map((t) => ` - ${t.key ? `${t.key} ` : ""}${t.name} \u2014 ${t.rows.length ? `rows: ${t.rows.join(" \u2192 ")}` : "not automated yet"}`).join("\n")}`),
|
|
1779
1862
|
`
|
|
1780
1863
|
Sign-in profiles:${c.signIns.length ? "" : " none"}`,
|
|
1781
1864
|
...c.signIns.map((s) => ` - ${s.name} (profileId ${s.profileId})${s.fields?.length ? ` \u2014 fields ${s.fields.map((f) => `"${f}"`).join(", ")}` : ""}: ${s.accounts.map((a) => `${a.name} (accountId ${a.accountId})`).join(", ") || "no accounts yet"}`),
|
|
@@ -2027,10 +2110,13 @@ ${page}`);
|
|
|
2027
2110
|
},
|
|
2028
2111
|
additionalProperties: false
|
|
2029
2112
|
},
|
|
2030
|
-
run: async (args) =>
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2113
|
+
run: async (args) => {
|
|
2114
|
+
const saved = await session.save(str(args["title"]), {
|
|
2115
|
+
...str(args["branch"]) ? { changeId: str(args["branch"]) } : {},
|
|
2116
|
+
...args["newBranch"] === true ? { newBranch: true } : {}
|
|
2117
|
+
}, { ...args["force"] === true ? { force: true } : {} });
|
|
2118
|
+
return text(saved + await signInFrom(session.takeLastSaved()));
|
|
2119
|
+
}
|
|
2034
2120
|
},
|
|
2035
2121
|
{
|
|
2036
2122
|
name: "list_my_branches",
|
|
@@ -2198,7 +2284,7 @@ The change document "${doc.name}" \u2014 part ${part} of ${parts}${part < parts
|
|
|
2198
2284
|
${doc.text.slice((part - 1) * PART, part * PART)}
|
|
2199
2285
|
\u2014 end of part ${part} \u2014`] : [],
|
|
2200
2286
|
...r.cases.map((c) => [`
|
|
2201
|
-
${c.feature} \u203A ${c.name} (${c.id})`, ...c.businessSteps.length ? c.businessSteps.map((s) => ` ${s.keyword} ${s.text}`) : [" (no steps written)"]].join("\n"))
|
|
2287
|
+
${c.key ? `${c.key} ` : ""}${c.feature} \u203A ${c.name} (${c.id})`, ...c.businessSteps.length ? c.businessSteps.map((s) => ` ${s.keyword} ${s.text}`) : [" (no steps written)"]].join("\n"))
|
|
2202
2288
|
].join("\n"));
|
|
2203
2289
|
}
|
|
2204
2290
|
},
|
|
@@ -2236,10 +2322,10 @@ ${c.feature} \u203A ${c.name} (${c.id})`, ...c.businessSteps.length ? c.business
|
|
|
2236
2322
|
{
|
|
2237
2323
|
name: "read_test_cases",
|
|
2238
2324
|
title: "Read test cases",
|
|
2239
|
-
description: "Read test cases by id
|
|
2325
|
+
description: "Read test cases by id \u2014 TC-12 as people say it, or tc_\u2026 \u2014 or exact name: their feature, business steps (Given / When / Then), whether they are automated, and the flows they run. Read a case's steps before automating it.",
|
|
2240
2326
|
inputSchema: {
|
|
2241
2327
|
type: "object",
|
|
2242
|
-
properties: { projectId: { type: "string" }, cases: { type: "array", items: { type: "string" }, description: "Ids or names." } },
|
|
2328
|
+
properties: { projectId: { type: "string" }, cases: { type: "array", items: { type: "string" }, description: "Ids (TC-12) or names." } },
|
|
2243
2329
|
required: ["cases"],
|
|
2244
2330
|
additionalProperties: false
|
|
2245
2331
|
},
|
|
@@ -2249,7 +2335,7 @@ ${c.feature} \u203A ${c.name} (${c.id})`, ...c.businessSteps.length ? c.business
|
|
|
2249
2335
|
const refs = Array.isArray(args["cases"]) ? args["cases"].map(String) : [];
|
|
2250
2336
|
const { cases, missing } = await service.readCases(projectId, refs);
|
|
2251
2337
|
const parts = cases.map((c) => [
|
|
2252
|
-
`${c.folder} \u203A ${c.name} (${c.id}) \u2014 ${c.automated ? `automated, rows: ${c.rows.join(" \u2192 ")}` : "not automated yet"}`,
|
|
2338
|
+
`${c.key ? `${c.key} ` : ""}${c.folder} \u203A ${c.name} (${c.id}) \u2014 ${c.automated ? `automated, rows: ${c.rows.join(" \u2192 ")}` : "not automated yet"}`,
|
|
2253
2339
|
...c.description ? [` ${c.description}`] : [],
|
|
2254
2340
|
...c.businessSteps.length ? c.businessSteps.map((s) => ` ${s.keyword} ${s.text}`) : [" (no business steps written)"]
|
|
2255
2341
|
].join("\n"));
|
|
@@ -2310,7 +2396,7 @@ ${c.feature} \u203A ${c.name} (${c.id})`, ...c.businessSteps.length ? c.business
|
|
|
2310
2396
|
const done = await service.writeCases(projectId, groups, str(args["branch"]));
|
|
2311
2397
|
catalogs.delete(projectId);
|
|
2312
2398
|
const skipped = done.skipped.length ? ` Skipped ${done.skipped.length} the folder already had: ${done.skipped.map((s) => `"${s.name}"`).join(", ")}.` : "";
|
|
2313
|
-
return text(`Wrote ${done.created.length} test case${done.created.length === 1 ? "" : "s"}, not automated yet, on Claude's working branch ${done.branch}.${skipped}`);
|
|
2399
|
+
return text(`Wrote ${done.created.length} test case${done.created.length === 1 ? "" : "s"}, not automated yet, on Claude's working branch ${done.branch}${done.created.some((c) => c.key) ? `: ${done.created.map((c) => `${c.key ?? ""} ${c.name}`.trim()).join(", ")}` : ""}.${skipped}`);
|
|
2314
2400
|
}
|
|
2315
2401
|
},
|
|
2316
2402
|
{
|
package/dist/engine.js
CHANGED
package/package.json
CHANGED