@beryl-so/cli 0.48.0 → 0.50.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/dist/beryl-test-skill.js +29 -13
- package/dist/commands/tests.js +5 -5
- package/dist/lint.js +6 -33
- package/dist/schema.generated.js +58 -0
- package/dist/skill-tables.js +2 -0
- package/package.json +1 -1
package/dist/beryl-test-skill.js
CHANGED
|
@@ -181,19 +181,35 @@ The trade is drift — run 200 has 200 of everything run 1 created. So:
|
|
|
181
181
|
- **Assert relatively, never absolutely.** "The row I just created is present"
|
|
182
182
|
(\`expect.persisted\` on a \`{{unique}}\`-named row), not "there are 3 rows" — a count
|
|
183
183
|
assertion goes stale by itself.
|
|
184
|
-
- **
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
184
|
+
- **Cleanup lives in \`after\`, and it returns the account to the exact state you found
|
|
185
|
+
it.** \`after\` runs on pass AND fail; a trailing step inside \`steps\` does not, so
|
|
186
|
+
never put cleanup in \`steps\`.
|
|
187
|
+
- **Created something?** Delete it in \`after\`.
|
|
188
|
+
- **Deleting something?** Create your own victim first (in \`before\` or the opening
|
|
189
|
+
steps, \`{{unique}}\`-named), then delete it and prove \`gone\`; when the deleted thing
|
|
190
|
+
must persist, re-create it in \`after\` as it was. Never aim a delete at standing data:
|
|
191
|
+
run 2 has nothing to delete, and it eats the account's accumulated coverage.
|
|
192
|
+
- **Changed a value (rename, toggle)?** Restore it in \`after\`: set it back to what it
|
|
193
|
+
was. Capture the original BEFORE you change it with \`capture_text\` (or \`capture_value\`
|
|
194
|
+
for a form input), banking it under \`capture_as\`, then \`fill {{handle}}\` in \`after\`
|
|
195
|
+
to put it back.
|
|
196
|
+
- **You label each test \`read\` or \`write\`; Beryl takes your word for it.** Set it with
|
|
197
|
+
\`--access\` (default \`read\`). Beryl does NOT infer it from your selectors, so YOU
|
|
198
|
+
decide, by one test: **would the app be different after this test runs?**
|
|
199
|
+
- **\`write\`** = it leaves a change behind: it creates, edits, deletes, renames, moves,
|
|
200
|
+
uploads or toggles a record or setting that is still different once the test ends. A
|
|
201
|
+
\`write\` must undo itself: end \`after\` with an \`expect\` proving the undo: \`gone\`,
|
|
202
|
+
\`hidden\`, \`have_count\` or \`count_delta\` for a removal, \`have_text\`,
|
|
203
|
+
\`have_value\` or \`persisted\` for a restore.
|
|
204
|
+
- **\`read\`** = it only observes: navigating, searching, filtering, sorting, opening a
|
|
205
|
+
menu or dialog and reading it, or clicking a destructive button and then CANCELLING.
|
|
206
|
+
Nothing is committed. Opening a confirm dialog and cancelling is a \`read\`; a submit
|
|
207
|
+
that stays disabled is a \`read\`.
|
|
208
|
+
A flow that both changes and reads is a \`write\`. Only the \`write\` label is checked
|
|
209
|
+
(its closing teardown proof, and a \`write\` whose teardown removes nothing fails the
|
|
210
|
+
run); \`read\` is taken at your word, so a real write you mislabel \`read\` silently
|
|
211
|
+
drifts the account. Label deliberately, and flip a banked test with
|
|
212
|
+
\`beryl tests set-access <id> read|write\`.
|
|
197
213
|
|
|
198
214
|
### If there is no account yet
|
|
199
215
|
|
package/dist/commands/tests.js
CHANGED
|
@@ -110,8 +110,8 @@ export const testCommands = [
|
|
|
110
110
|
{
|
|
111
111
|
name: "access",
|
|
112
112
|
type: "string",
|
|
113
|
-
description: "The label the plan will be banked with
|
|
114
|
-
"
|
|
113
|
+
description: "The label the plan will be banked with: read (default) or write. It is yours to " +
|
|
114
|
+
"set; Beryl does not infer it. Only a write is checked (its closing teardown proof)",
|
|
115
115
|
},
|
|
116
116
|
],
|
|
117
117
|
examples: [
|
|
@@ -580,9 +580,9 @@ export const testCommands = [
|
|
|
580
580
|
{
|
|
581
581
|
name: "tests set-access",
|
|
582
582
|
summary: "Mark a test read or write",
|
|
583
|
-
description: "read: the test only reads the app. write:
|
|
584
|
-
"
|
|
585
|
-
"the
|
|
583
|
+
description: "read: the test only reads the app. write: it creates, edits, renames, deletes or " +
|
|
584
|
+
"toggles something that persists. The label is yours to set; Beryl does not infer it " +
|
|
585
|
+
"from the plan. A write test must clean up in `after`, ending with a proof expect.",
|
|
586
586
|
scope: "project",
|
|
587
587
|
args: [
|
|
588
588
|
{ name: "test-id", description: "Test id", required: true },
|
package/dist/lint.js
CHANGED
|
@@ -9,9 +9,6 @@ export const MAILBOX_ADDRESS_IN_TEST = "This plan types the standing account's a
|
|
|
9
9
|
"and rides the account's session; one that needs an address the site can mail cites " +
|
|
10
10
|
"{{inbox_address}}, an alias only this test reads. Only the account's own signup and " +
|
|
11
11
|
"sign-in plans (accounts signup, accounts set-login) may fill {{mailbox_address}}.";
|
|
12
|
-
export const WRITE_NEEDS_LABEL = (label, detail) => `${label} (${detail}) changes the app, so this test must be marked access 'write' ` +
|
|
13
|
-
"(tests create --access write, or tests set-access <id> write) and clean up in " +
|
|
14
|
-
"'after'. A 'read' test only reads.";
|
|
15
12
|
export const WRITE_NEEDS_CLOSING_TEARDOWN = (detail) => "A 'write' test must end with an 'after' section that undoes what it changed and " +
|
|
16
13
|
"proves it: the last step of 'after' is an expect with expect_kind 'gone', 'hidden', " +
|
|
17
14
|
"'have_count', 'count_delta' (something removed) or 'have_text', 'have_value', " +
|
|
@@ -177,31 +174,6 @@ function identityIssues(plan, provisioning) {
|
|
|
177
174
|
? [{ path: "(root)", message: MINTED_IDENTITY_NEEDS_TEARDOWN }]
|
|
178
175
|
: [];
|
|
179
176
|
}
|
|
180
|
-
// Same verb list as the server's write_signal (api/app/ta_shared/schemas.py).
|
|
181
|
-
const WRITE_VERBS_RE = /\b(create|save|delete|remove|rename|revoke|enable|disable|invite|add|publish|archive|restore|quarantine|un-quarantine|turn on|turn off|join|decline)\b/i;
|
|
182
|
-
function writeSignal(plan) {
|
|
183
|
-
if (typeof plan !== "object" || plan === null)
|
|
184
|
-
return null;
|
|
185
|
-
const { before, steps: main, after } = plan;
|
|
186
|
-
if (steps(after).length > 0)
|
|
187
|
-
return ["after", "a teardown means something was created"];
|
|
188
|
-
const sectioned = steps(before).length > 0 || steps(after).length > 0;
|
|
189
|
-
const label = (section, i) => (sectioned ? `${section}[${i}]` : `step[${i}]`);
|
|
190
|
-
const all = [
|
|
191
|
-
...steps(before).map((s, i) => [label("before", i), s]),
|
|
192
|
-
...steps(main).map((s, i) => [label("steps", i), s]),
|
|
193
|
-
];
|
|
194
|
-
for (const [where, s] of all) {
|
|
195
|
-
if (s.action === "upload")
|
|
196
|
-
return [where, "upload"];
|
|
197
|
-
if (s.action === "click" && typeof s.selector === "string") {
|
|
198
|
-
const hit = WRITE_VERBS_RE.exec(s.selector);
|
|
199
|
-
if (hit)
|
|
200
|
-
return [where, `click on '${hit[0]}'`];
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
return null;
|
|
204
|
-
}
|
|
205
177
|
const TEARDOWN_PROOF_KINDS = new Set([
|
|
206
178
|
"gone",
|
|
207
179
|
"hidden",
|
|
@@ -211,11 +183,11 @@ const TEARDOWN_PROOF_KINDS = new Set([
|
|
|
211
183
|
"have_value",
|
|
212
184
|
"persisted",
|
|
213
185
|
]);
|
|
186
|
+
// The read/write label is the author's call; Beryl no longer infers it from the plan.
|
|
187
|
+
// Only a `write` label is checked here: it must close its teardown with a proof.
|
|
214
188
|
function accessIssues(plan, access) {
|
|
215
|
-
if (access !== "write")
|
|
216
|
-
|
|
217
|
-
return signal ? [{ path: "(root)", message: WRITE_NEEDS_LABEL(signal[0], signal[1]) }] : [];
|
|
218
|
-
}
|
|
189
|
+
if (access !== "write")
|
|
190
|
+
return [];
|
|
219
191
|
if (typeof plan !== "object" || plan === null)
|
|
220
192
|
return [];
|
|
221
193
|
const after = steps(plan.after);
|
|
@@ -232,7 +204,8 @@ function accessIssues(plan, access) {
|
|
|
232
204
|
}
|
|
233
205
|
// `provisioning`: the plan is an account's own signup or sign-in plan, the one place
|
|
234
206
|
// {{mailbox_address}} is legal (mirrors the server's validate_editable_plan flag).
|
|
235
|
-
// `access`: the label the plan will be banked with
|
|
207
|
+
// `access`: the label the plan will be banked with. It is the author's call (Beryl no
|
|
208
|
+
// longer infers it from the plan); given, only a `write` is checked, for its closing proof.
|
|
236
209
|
export function lintPlan(plan, opts = {}) {
|
|
237
210
|
const issues = validate(plan, ACTION_PLAN_SCHEMA, ACTION_PLAN_SCHEMA, "");
|
|
238
211
|
// The identity rules read a well-formed plan; a shape error is reported alone.
|
package/dist/schema.generated.js
CHANGED
|
@@ -15,6 +15,8 @@ export const ACTION_PLAN_SCHEMA = {
|
|
|
15
15
|
"wait_for",
|
|
16
16
|
"expect",
|
|
17
17
|
"capture_count",
|
|
18
|
+
"capture_text",
|
|
19
|
+
"capture_value",
|
|
18
20
|
"await_email",
|
|
19
21
|
"upload",
|
|
20
22
|
"dialog",
|
|
@@ -325,6 +327,62 @@ export const ACTION_PLAN_SCHEMA = {
|
|
|
325
327
|
]
|
|
326
328
|
}
|
|
327
329
|
},
|
|
330
|
+
{
|
|
331
|
+
"if": {
|
|
332
|
+
"properties": {
|
|
333
|
+
"action": {
|
|
334
|
+
"const": "capture_text"
|
|
335
|
+
}
|
|
336
|
+
},
|
|
337
|
+
"required": [
|
|
338
|
+
"action"
|
|
339
|
+
]
|
|
340
|
+
},
|
|
341
|
+
"then": {
|
|
342
|
+
"properties": {
|
|
343
|
+
"capture_as": {
|
|
344
|
+
"minLength": 1,
|
|
345
|
+
"type": "string"
|
|
346
|
+
},
|
|
347
|
+
"selector": {
|
|
348
|
+
"minLength": 1,
|
|
349
|
+
"type": "string"
|
|
350
|
+
}
|
|
351
|
+
},
|
|
352
|
+
"required": [
|
|
353
|
+
"capture_as",
|
|
354
|
+
"selector"
|
|
355
|
+
]
|
|
356
|
+
}
|
|
357
|
+
},
|
|
358
|
+
{
|
|
359
|
+
"if": {
|
|
360
|
+
"properties": {
|
|
361
|
+
"action": {
|
|
362
|
+
"const": "capture_value"
|
|
363
|
+
}
|
|
364
|
+
},
|
|
365
|
+
"required": [
|
|
366
|
+
"action"
|
|
367
|
+
]
|
|
368
|
+
},
|
|
369
|
+
"then": {
|
|
370
|
+
"properties": {
|
|
371
|
+
"capture_as": {
|
|
372
|
+
"minLength": 1,
|
|
373
|
+
"type": "string"
|
|
374
|
+
},
|
|
375
|
+
"selector": {
|
|
376
|
+
"minLength": 1,
|
|
377
|
+
"type": "string"
|
|
378
|
+
}
|
|
379
|
+
},
|
|
380
|
+
"required": [
|
|
381
|
+
"capture_as",
|
|
382
|
+
"selector"
|
|
383
|
+
]
|
|
384
|
+
}
|
|
385
|
+
},
|
|
328
386
|
{
|
|
329
387
|
"if": {
|
|
330
388
|
"properties": {
|
package/dist/skill-tables.js
CHANGED
|
@@ -27,6 +27,8 @@ export const ACTION_DOCS = {
|
|
|
27
27
|
wait_for: "Wait for the `selector` element to appear",
|
|
28
28
|
expect: "Assert — kinds in the expect table below",
|
|
29
29
|
capture_count: "Bank the live count of `selector` matches under `capture_as` for a later `count_delta`",
|
|
30
|
+
capture_text: "Bank the `selector` element's visible text under `capture_as`, to restore it later via `{{handle}}`",
|
|
31
|
+
capture_value: "Bank the `selector` input's value under `capture_as`, to restore it later via `{{handle}}`",
|
|
30
32
|
await_email: "Await mail in the run inbox, bank the extracted value under `capture_as` (§5)",
|
|
31
33
|
upload: "Set a file input: `value` names a project config file — see below",
|
|
32
34
|
dialog: "Arm a one-shot accept/dismiss handler for the NEXT step's dialog — see below",
|
package/package.json
CHANGED