@skyramp/mcp 0.1.1 → 0.1.2
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/build/prompts/enhance-assertions/contractProviderAssertionsPrompt.js +28 -110
- package/build/prompts/enhance-assertions/integrationAssertionsPrompt.js +35 -128
- package/build/prompts/enhance-assertions/sharedAssertionRules.js +212 -0
- package/build/prompts/enhance-assertions/uiAssertionsPrompt.js +217 -78
- package/build/tools/code-refactor/enhanceAssertionsTool.js +8 -10
- package/build/utils/docker.test.js +1 -1
- package/build/utils/versions.js +1 -1
- package/package.json +2 -2
|
@@ -1,110 +1,28 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
- Do
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
- Do NOT add assertions for **genuinely unpredictable** fields only (random tokens, opaque server-generated IDs without a known format, timestamps without a fixed format). This is NOT a license to skip body assertions on success responses or 4xx/5xx error bodies — every field present in \`expected_response_body\` IS inferable and MUST be asserted.
|
|
30
|
-
- Do NOT use permissive status matchers — never \`checkStatusCode(response, '20x')\`, \`.toMatch(/^2/)\`, \`.toBeGreaterThanOrEqual(200)\`, or any range/pattern check on the status code. Always assert the exact status code from \`expected_response_body\` (e.g. \`expect(response.statusCode).toBe(204)\`).
|
|
31
|
-
- Do NOT use type-only or shape-only assertions as a substitute for content validation. Specifically forbidden: \`Array.isArray(...)\`, \`typeof X === 'number'/'string'/'boolean'/'object'\`, \`X instanceof Array\`, \`Object.keys(X).length > 0\`. When \`expected_response_body\` shows actual values or items, assert exact values (scalars) and per-item fields + exact length (arrays).
|
|
32
|
-
- Do NOT restructure, reformat, or reorder existing code.
|
|
33
|
-
- Do NOT add comments or docstrings.
|
|
34
|
-
- Do NOT change function signatures, imports, or variable names.
|
|
35
|
-
- Do NOT touch \`beforeAll\`, \`afterAll\`, or setup/teardown helpers — only modify test functions.
|
|
36
|
-
- Do NOT reference \`beforeAll\` provisioning data in assertions. Use inline request body literals for all fields.
|
|
37
|
-
|
|
38
|
-
### Assertion Rules [MANDATORY]
|
|
39
|
-
1. **Echo-back EVERY request body field** with the exact sent value.
|
|
40
|
-
❌ BAD: \`expect(getValue(response, "name")).not.toBeNull()\`
|
|
41
|
-
✅ GOOD: \`expect(getValue(response, "name")).toBe("Skyramp Tester")\`
|
|
42
|
-
|
|
43
|
-
2. **Stable response values** — assert exact values for ALL stable response fields. Covers BOTH:
|
|
44
|
-
- **Response-only fields** (e.g. \`filename_download\`, \`type\`, \`size\`, \`url\`, \`mime_type\`) present in \`expected_response_body\` — do NOT stop at \`data.id is not None\` for resource responses
|
|
45
|
-
- **Counts, booleans, status, enums** — never \`>= 0\`, type-only, or null-only checks
|
|
46
|
-
|
|
47
|
-
❌ BAD (range matcher on a known scalar): \`expect(getValue(response, "count")).toBeGreaterThanOrEqual(0)\`
|
|
48
|
-
✅ GOOD: \`expect(getValue(response, "count")).toBe(3)\`
|
|
49
|
-
|
|
50
|
-
❌ BAD (resource response with only id asserted): \`expect(getValue(response, "data.id")).not.toBeNull()\`
|
|
51
|
-
✅ GOOD: also assert response-only stable fields, e.g. \`expect(getValue(response, "data.filename_download")).toBe("invoice.pdf"); expect(getValue(response, "data.type")).toBe("application/pdf")\`
|
|
52
|
-
|
|
53
|
-
❌ BAD (type-only on a scalar, exact value ignored): \`expect(typeof getValue(response, "total_count")).toBe("number")\`
|
|
54
|
-
✅ GOOD: \`expect(getValue(response, "total_count")).toBe(<exact count from expected_response_body>)\`
|
|
55
|
-
|
|
56
|
-
3. **Server-generated IDs** — \`is not None\` only.
|
|
57
|
-
4. **Status code from expected response** — assert the exact status code from \`expected_response_body\` (e.g. \`201\` for create endpoints), not a generic \`200\` or \`20x\`.
|
|
58
|
-
❌ BAD (default status code, ignoring the expected response which is \`201\`): \`expect(response.statusCode).toBe(200)\`
|
|
59
|
-
✅ GOOD: \`expect(response.statusCode).toBe(201)\`
|
|
60
|
-
|
|
61
|
-
❌ BAD (permissive status matcher hides the exact code and skips body assertions): \`checkStatusCode(response, "20x")\` or \`expect(response.statusCode.toString()).toMatch(/^2/)\`
|
|
62
|
-
✅ GOOD: \`expect(response.statusCode).toBe(204)\` — exact code from \`expected_response_body\`, then add the body field assertions on top
|
|
63
|
-
|
|
64
|
-
5. **Arrays** — for every array (data and error):
|
|
65
|
-
- **Empty array in expected response** (\`data: []\`) → assert exact \`length == 0\` AND \`data[0]\` is null/None. NEVER assert \`data.0.<field> is not None\` on an empty array — this is vacuous and forbidden.
|
|
66
|
-
- **Non-empty array in expected response** → assert exact count (or count field), each present item's key fields, next index is null/None
|
|
67
|
-
- if \`orderBy\`/\`sort\` is set, assert ordering across the first two items
|
|
68
|
-
- **Shape-only check is NEVER sufficient** — \`Array.isArray(getValue(response, "results"))\` does not validate contents. When \`expected_response_body\` contains items, you MUST assert exact length AND per-item key fields, even if \`Array.isArray\` already passes.
|
|
69
|
-
|
|
70
|
-
❌ BAD (vacuous null-check on an empty-array expected response, where \`data: []\`): \`expect(getValue(response, "data.0.id")).not.toBeNull()\`
|
|
71
|
-
✅ GOOD: \`expect(getValue(response, "data").length).toBe(0); expect(getValue(response, "data.0")).toBeUndefined()\`
|
|
72
|
-
|
|
73
|
-
❌ BAD (shape-only check on a list endpoint, items never validated): \`expect(Array.isArray(getValue(response, "results"))).toBe(true)\`
|
|
74
|
-
✅ GOOD: \`expect(getValue(response, "results").length).toBe(2); expect(getValue(response, "results.0.id")).toBe(<exact id from expected_response_body>); expect(getValue(response, "results.0.title")).toBe(<exact title>); expect(getValue(response, "results.2")).toBeUndefined()\`
|
|
75
|
-
|
|
76
|
-
6. **Computed numeric fields** — use the exact pre-computed value from \`expected_response_body\` directly; never hardcode without it.
|
|
77
|
-
❌ BAD (hardcoded without checking expected_response_body): \`expect(getValue(response, "total_amount")).toBe(29.99)\`
|
|
78
|
-
✅ GOOD: use the exact value from \`expected_response_body\`
|
|
79
|
-
|
|
80
|
-
7. **Format/type** — dates, UUIDs, enums get pattern or type assertions, not just \`is not None\`.
|
|
81
|
-
8. **Parity** — every assertion derivable from the request/response must appear independently in both the contract and integration tests.
|
|
82
|
-
9. **Query-param constraints** — when the request URL includes \`limit\`, \`pageSize\`, \`offset\`, \`page\`, \`since\`, \`until\`, or \`filter\`, the response MUST be asserted against that constraint:
|
|
83
|
-
- \`limit=N\` → assert returned array length \`<=\` N (or exactly N when \`expected_response_body\` shows it filled)
|
|
84
|
-
- \`offset=N\` → assert pagination metadata reflects N
|
|
85
|
-
- \`filter=k=v\` → assert every returned item satisfies the predicate
|
|
86
|
-
|
|
87
|
-
❌ BAD (\`?limit=10\` request, response length never asserted): \`expect(getValue(response, "data")).not.toBeNull()\`
|
|
88
|
-
✅ GOOD: \`expect(getValue(response, "data").length).toBeLessThanOrEqual(10)\` (use \`.toBe(10)\` if \`expected_response_body\` shows the limit was filled)
|
|
89
|
-
|
|
90
|
-
10. **Error-path** — for every 4xx/5xx with a response body, assert every error body field with exact values (\`error.code\`, \`error.message\`, \`detail\`, \`invalid_rows\`, etc.). Apply rule 5 to error arrays. No-body responses (DELETE 204): assert status code only.
|
|
91
|
-
❌ BAD (status only, error body never inspected): \`expect(getValue(response, "detail")).not.toBeNull()\`
|
|
92
|
-
✅ GOOD: \`expect(getValue(response, "detail")).toBe("Use POST /api/flow_runs/{id}/stop")\`
|
|
93
|
-
|
|
94
|
-
### Verification of enhanced assertions
|
|
95
|
-
1. Every request body field has an exact-value assertion or a documented server-generated reason
|
|
96
|
-
2. No \`is not None\` / \`.not.toBeNull()\` on any field whose exact value was sent in the request
|
|
97
|
-
3. All stable response values (request-mirrored AND response-only — \`filename_download\`, \`type\`, \`size\`, etc., AND counts/booleans/enums) use exact assertions — no leftover \`>= 0\` or type-only checks
|
|
98
|
-
4. Status code asserted matches \`expected_response_body\` exactly (e.g. \`201\` not a default \`200\`)
|
|
99
|
-
5. Every non-empty array has per-item field + next-index-null-guard assertions; every empty-array expected response asserts \`length == 0\` with no per-item field assertions on missing indices
|
|
100
|
-
6. No shape-only or type-only checks remain — every \`Array.isArray(...)\`, \`typeof X === '...'\`, \`instanceof Array\`, or \`Object.keys(...).length > 0\` has been replaced with exact value and per-item field assertions when \`expected_response_body\` contains data
|
|
101
|
-
7. If \`orderBy\`/\`sort\` is set, ordering direction asserted across the first two items
|
|
102
|
-
8. No references to \`beforeAll\` provisioning data — all values inline
|
|
103
|
-
9. Computed fields use exact pre-computed value from \`expected_response_body\`
|
|
104
|
-
10. Format/type fields (dates, UUIDs, enums) asserted with pattern or type check — not just \`is not None\`
|
|
105
|
-
11. Query-param constraints (\`limit\`, \`pageSize\`, \`offset\`, \`filter\`) reflected in response assertions
|
|
106
|
-
12. Every 4xx/5xx with a body has exact error-body assertions; status-only is never used
|
|
107
|
-
13. All field access uses SDK helpers; no dict/attribute access; no import swaps
|
|
108
|
-
|
|
109
|
-
An item passes verification only when the assertion is present AND is a good assertion per the rules above. If any item is not satisfied — assertion missing, OR present but a bad assertion — add or fix it per the rules before completing, preferring the strongest applicable assertion for the scenario.
|
|
110
|
-
`;
|
|
1
|
+
import { getAssertionsPrompt, } from "./sharedAssertionRules.js";
|
|
2
|
+
const specificRules = [
|
|
3
|
+
{
|
|
4
|
+
title: "Chained values from inline request data",
|
|
5
|
+
description: "Every value used in an assertion must come from what is written inline in the test function — the request body, path, or query parameters.",
|
|
6
|
+
subPoints: [
|
|
7
|
+
"Do not reference data set up in `beforeAll` or any other setup helper.",
|
|
8
|
+
"When an ID was sent in the request body, path, or query, assert its exact value in the response. Since the sent value is already known, asserting only that it is non-null is not sufficient.",
|
|
9
|
+
"Reserve `is not None` / `not.toBeNull()` for server-generated IDs only.",
|
|
10
|
+
"Apply the same rule to all other fields and to array items: use inline request body values as the expected values, not setup helper data.",
|
|
11
|
+
"Every assertion that can be derived from `expected_response_body`, the inline request body, or path/query literals must appear in this file — do not rely on the integration test to cover it.",
|
|
12
|
+
],
|
|
13
|
+
examples: [
|
|
14
|
+
{
|
|
15
|
+
language: "javascript",
|
|
16
|
+
code: `expect(getResponseValue(productGetResponse, "id"), 'id').toBe(getResponseValue(productsPostResponse, "id"));`,
|
|
17
|
+
},
|
|
18
|
+
],
|
|
19
|
+
},
|
|
20
|
+
];
|
|
21
|
+
const SCOPE = `### Scope
|
|
22
|
+
- Only modify test functions — do not touch \`beforeAll\`, \`afterAll\`, or any setup or teardown helper.
|
|
23
|
+
- Only add assertions clearly supported by \`expected_response_body\`, inline request / path / query literals, codebase evidence, or the test generation recommendations received for this test. Do not invent constraints.
|
|
24
|
+
- Add new assertions immediately after the existing status-code assertion — do not move or remove anything.
|
|
25
|
+
- Do not reference \`beforeAll\` / \`afterAll\` provisioning data in any assertion — every assertion value must come from the inline request body, path, query, prior response, or \`expected_response_body\`.`;
|
|
26
|
+
export function getContractProviderAssertionsPrompt(testFile, enhanceType) {
|
|
27
|
+
return getAssertionsPrompt(specificRules, SCOPE, testFile, enhanceType);
|
|
28
|
+
}
|
|
@@ -1,128 +1,35 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
-
|
|
31
|
-
-
|
|
32
|
-
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
- Do NOT change function signatures, imports, or variable names.
|
|
37
|
-
|
|
38
|
-
### Assertion Rules [MANDATORY]
|
|
39
|
-
1. **Echo-back EVERY request body field** with the exact sent value. \`is not None\` only for genuinely server-generated fields (timestamps, auto-incremented IDs).
|
|
40
|
-
❌ BAD: \`expect(getValue(response, "name")).not.toBeNull()\`
|
|
41
|
-
✅ GOOD: \`expect(getValue(response, "name")).toBe("Skyramp Tester")\`
|
|
42
|
-
|
|
43
|
-
2. **Stable response values** — assert exact values for ALL stable response fields. Covers BOTH:
|
|
44
|
-
- **Response-only fields** (e.g. \`filename_download\`, \`type\`, \`size\`, \`url\`, \`mime_type\`) — do NOT stop at \`data.id is not None\` for resource responses
|
|
45
|
-
- **Counts, booleans, status, enums** — never \`>= 0\`, type-only, or null-only checks
|
|
46
|
-
|
|
47
|
-
❌ BAD (range matcher on a known scalar): \`expect(getValue(response, "active_session_count")).toBeGreaterThanOrEqual(0)\`
|
|
48
|
-
✅ GOOD: \`expect(getValue(response, "active_session_count")).toBe(3)\`
|
|
49
|
-
|
|
50
|
-
❌ BAD (resource response with only id asserted): \`expect(getValue(response, "data.id")).not.toBeNull()\`
|
|
51
|
-
✅ GOOD: also assert response-only stable fields, e.g. \`expect(getValue(response, "data.filename_download")).toBe("invoice.pdf"); expect(getValue(response, "data.type")).toBe("application/pdf")\`
|
|
52
|
-
|
|
53
|
-
❌ BAD (type-only on a scalar, exact value ignored): \`expect(typeof getValue(response, "total_count")).toBe("number")\`
|
|
54
|
-
✅ GOOD: \`expect(getValue(response, "total_count")).toBe(<exact count from trace>)\`
|
|
55
|
-
|
|
56
|
-
3. **Status code from trace** — assert the exact status code recorded in the trace (e.g. \`201\` for create endpoints), not a generic \`200\` or \`20x\`. If the trace shows \`201\`, asserting \`200\` is a bug.
|
|
57
|
-
❌ BAD (default status code, ignoring the trace which recorded \`201\`): \`expect(response.statusCode).toBe(200)\`
|
|
58
|
-
✅ GOOD: \`expect(response.statusCode).toBe(201)\`
|
|
59
|
-
|
|
60
|
-
❌ BAD (permissive status matcher hides the exact code and skips body assertions): \`checkStatusCode(response, "20x")\` or \`expect(response.statusCode.toString()).toMatch(/^2/)\`
|
|
61
|
-
✅ GOOD: \`expect(response.statusCode).toBe(204)\` — exact code from the recorded trace, then add the body field assertions on top
|
|
62
|
-
|
|
63
|
-
4. **Chained IDs** — extract each POST ID once and reuse it in every subsequent step (path params, request bodies, assertions). Compare by value in later GET/PATCH/PUT responses; never null-check a chained ID.
|
|
64
|
-
❌ BAD (chained ID null-check): \`expect(getValue(get_response, "data.id")).not.toBeNull()\`
|
|
65
|
-
✅ GOOD: \`expect(getValue(get_response, "data.id")).toBe(getValue(post_response, "data.id"))\`
|
|
66
|
-
|
|
67
|
-
5. **Chained non-ID values** — extract response-driven values (\`collection\`, \`slug\`, \`role\`, etc.) from prior responses; never hardcode reused values.
|
|
68
|
-
❌ BAD (hardcoded reused value): \`let collection = "test_coerce_001"\`
|
|
69
|
-
✅ GOOD: \`let collection = getValue(collections_post_response, "data.collection")\`
|
|
70
|
-
|
|
71
|
-
6. **Cross-endpoint invariants** — when one response field encodes the count, identity, or summary of a collection returned by a sibling endpoint, assert the relationship by extracting both values from their respective responses and comparing them. Do not assume the count and the array length will both incidentally match the same hardcoded number.
|
|
72
|
-
❌ BAD (count and array length both hardcoded to the same number — no invariant): \`expect(getValue(me_response, "data.active_session_count")).toBe(3); expect(getValue(sessions_response, "data").length).toBe(3);\`
|
|
73
|
-
✅ GOOD: \`expect(getValue(sessions_response, "data").length).toBe(getValue(me_response, "data.active_session_count"))\`
|
|
74
|
-
|
|
75
|
-
7. **Arrays** — for every array (data and error):
|
|
76
|
-
- **Empty array in trace** (\`data: []\`) → assert exact \`length == 0\` AND \`data[0]\` is null/None. NEVER assert \`data.0.<field> is not None\` on an empty array — this is vacuous and forbidden.
|
|
77
|
-
- **Non-empty array in trace** → assert exact count (or count field), each present item's key fields, next index is null/None
|
|
78
|
-
- if \`orderBy\`/\`sort\` is set, assert ordering across the first two items
|
|
79
|
-
- **Shape-only check is NEVER sufficient** — \`Array.isArray(getValue(response, "results"))\` does not validate contents. When the trace contains items, you MUST assert exact length AND per-item key fields, even if \`Array.isArray\` already passes.
|
|
80
|
-
|
|
81
|
-
❌ BAD (null-check on a session/items array): \`expect(getValue(response, "data")).not.toBeNull()\`
|
|
82
|
-
✅ GOOD: assert exact count, per-item fields, and \`data[N]\` is null
|
|
83
|
-
|
|
84
|
-
❌ BAD (vacuous null-check on an empty-array trace, where \`data: []\`): \`expect(getValue(response, "data.0.id")).not.toBeNull()\`
|
|
85
|
-
✅ GOOD: \`expect(getValue(response, "data").length).toBe(0); expect(getValue(response, "data.0")).toBeUndefined()\`
|
|
86
|
-
|
|
87
|
-
❌ BAD (shape-only check on a list endpoint, items never validated): \`expect(Array.isArray(getValue(response, "results"))).toBe(true)\`
|
|
88
|
-
✅ GOOD: \`expect(getValue(response, "results").length).toBe(2); expect(getValue(response, "results.0.id")).toBe(<exact id from trace>); expect(getValue(response, "results.0.title")).toBe(<exact title>); expect(getValue(response, "results.2")).toBeUndefined()\`
|
|
89
|
-
|
|
90
|
-
8. **Computed numeric fields** — scan the source models/services for the arithmetic formula (e.g. \`total_amount = price * quantity\`), then derive dynamically from prior responses using that formula. Never guess or hardcode a computed number.
|
|
91
|
-
❌ BAD (hardcoded computed number): \`expect(getValue(patch_response, "total_amount")).toBe(29.99)\`
|
|
92
|
-
✅ GOOD: \`expect(getValue(patch_response, "total_amount")).toBe(getValue(product_post_response, "price") * quantitySent)\`
|
|
93
|
-
|
|
94
|
-
9. **Format/type** — dates, UUIDs, enums get pattern or type assertions, not just \`is not None\`.
|
|
95
|
-
10. **Read steps after POST/PATCH** — re-assert chained values, exact stable values, and computed fields; do not reduce to null/type/range checks.
|
|
96
|
-
11. **Parity** — every assertion derivable from the request/response must appear independently in both the contract and integration tests.
|
|
97
|
-
12. **Query-param constraints** — when the request URL includes \`limit\`, \`pageSize\`, \`offset\`, \`page\`, \`since\`, \`until\`, or \`filter\`, the response MUST be asserted against that constraint:
|
|
98
|
-
- \`limit=N\` → assert returned array length \`<=\` N (or exactly N when the trace shows it filled)
|
|
99
|
-
- \`offset=N\` → assert pagination metadata reflects N
|
|
100
|
-
- \`filter=k=v\` → assert every returned item satisfies the predicate
|
|
101
|
-
|
|
102
|
-
❌ BAD (\`?limit=10\` request, response length never asserted): \`expect(getValue(response, "data")).not.toBeNull()\`
|
|
103
|
-
✅ GOOD: \`expect(getValue(response, "data").length).toBeLessThanOrEqual(10)\` (use \`.toBe(10)\` if the trace shows the limit was filled)
|
|
104
|
-
|
|
105
|
-
13. **Error-path** — for every 4xx/5xx with a response body, assert every error body field with exact values (\`error.code\`, \`error.message\`, \`detail\`, \`invalid_rows\`, etc.). Apply rule 7 to error arrays. No-body responses (DELETE 204): assert status code only.
|
|
106
|
-
❌ BAD (status only when the body has \`detail\`/\`errors\`): \`expect(response.statusCode).toBe(422)\`
|
|
107
|
-
✅ GOOD: \`expect(getValue(response, "detail")).toBe("Use POST /api/flow_runs/{id}/stop")\` plus exact assertions for each \`errors[i]\` field
|
|
108
|
-
|
|
109
|
-
### Verification of enhanced assertions
|
|
110
|
-
1. All stable response values (request-mirrored AND response-only — \`filename_download\`, \`type\`, \`size\`, etc., AND counts/booleans/enums) use exact assertions — no leftover \`>= 0\` or type-only checks
|
|
111
|
-
2. Status code asserted matches the recorded trace exactly (e.g. \`201\` not a default \`200\`)
|
|
112
|
-
3. Every non-empty array has per-item field + next-index-null-guard assertions; every empty-array trace asserts \`length == 0\` with no per-item field assertions on missing indices
|
|
113
|
-
4. No shape-only or type-only checks remain — every \`Array.isArray(...)\`, \`typeof X === '...'\`, \`instanceof Array\`, or \`Object.keys(...).length > 0\` has been replaced with exact value and per-item field assertions when the trace contains data
|
|
114
|
-
5. If \`orderBy\`/\`sort\` is set, ordering direction asserted across the first two items
|
|
115
|
-
6. POST IDs chained into all subsequent steps; no hardcoded IDs
|
|
116
|
-
7. Non-ID response values extracted from prior responses; no hardcoded reused values
|
|
117
|
-
8. Cross-endpoint invariants (e.g. count scalar from one endpoint vs. array length from a sibling endpoint) asserted by extract-and-compare; no twin hardcoded values
|
|
118
|
-
9. Read steps re-assert chained/exact/computed fields — no null/type/range fallbacks
|
|
119
|
-
10. Computed fields use source-derived formulas; no hardcoded computed numbers
|
|
120
|
-
11. Every request body field has an exact-value assertion or a documented server-generated reason
|
|
121
|
-
12. No \`is not None\` / \`.not.toBeNull()\` on any field whose exact value was sent in the request
|
|
122
|
-
13. Format/type fields (dates, UUIDs, enums) asserted with pattern or type check — not just \`is not None\`
|
|
123
|
-
14. Query-param constraints (\`limit\`, \`pageSize\`, \`offset\`, \`filter\`) reflected in response assertions
|
|
124
|
-
15. Every 4xx/5xx with a body has exact error-body assertions; status-only is never used
|
|
125
|
-
16. All field access uses SDK helpers; no dict/attribute access; no import swaps
|
|
126
|
-
|
|
127
|
-
An item passes verification only when the assertion is present AND is a good assertion per the rules above. If any item is not satisfied — assertion missing, OR present but a bad assertion — add or fix it per the rules before completing, preferring the strongest applicable assertion for the scenario.
|
|
128
|
-
`;
|
|
1
|
+
import { getAssertionsPrompt, } from "./sharedAssertionRules.js";
|
|
2
|
+
const specificRules = [
|
|
3
|
+
{
|
|
4
|
+
title: "Chained values across steps — chain, never hardcode",
|
|
5
|
+
description: "Every ID used in a later step must come from a prior step's response using the SDK helper — never hardcode an ID or declare it as a constant before the response that provides it.",
|
|
6
|
+
subPoints: [
|
|
7
|
+
"After a POST creates a resource, GET and PATCH steps should assert that the returned ID matches the one from the POST response.",
|
|
8
|
+
"When a non-ID response value (such as a collection name, slug, or timestamp) is reused in a later request, extract it from the prior response instead of hardcoding it.",
|
|
9
|
+
"After any POST, PATCH, DELETE, sort, reorder, or bulk operation, re-assert the chained, stable, and computed values on the follow-up read step: a follow-up GET after DELETE should return 404 or show the item absent; a follow-up GET after PATCH should assert the new value; a follow-up GET after sort/reorder should confirm the new ordering with chained IDs.",
|
|
10
|
+
"After a POST creates a resource and a GET retrieves the collection, assert that the created item appears in the list by its chained ID with its exact stable fields — do not stop at a null-check on the array or the id alone.",
|
|
11
|
+
"When one response field describes a count or summary of a collection returned by a related endpoint (for example, `active_session_count` from `/users/me` and the array length from `/users/me/sessions`), assert that relationship by comparing both extracted values — never hardcode the same number in two places.",
|
|
12
|
+
"Every assertion that can be derived from the request and response must appear in this file. If a test name claims a state change (such as \"Updates X\" or \"Increments Y\"), include an assertion that proves that state change across the relevant calls — do not rely on the contract test to cover it.",
|
|
13
|
+
],
|
|
14
|
+
examples: [
|
|
15
|
+
{
|
|
16
|
+
language: "javascript",
|
|
17
|
+
code: `expect(getResponseValue(productGetResponse, "id"), 'id').toBe(getResponseValue(productsPostResponse, "id"));
|
|
18
|
+
const collection = getValue(collectionsPostResponse, "data.collection");
|
|
19
|
+
expect(getValue(sessionsResponse, "data").length).toBe(getValue(meResponse, "data.active_session_count"));`,
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
language: "javascript",
|
|
23
|
+
code: `const orderId = getResponseValue(ordersPostResponse, "id");
|
|
24
|
+
await skyramp.sendRequest(\`/orders/\${orderId}\`);`,
|
|
25
|
+
},
|
|
26
|
+
],
|
|
27
|
+
},
|
|
28
|
+
];
|
|
29
|
+
const SCOPE = `### Scope
|
|
30
|
+
- Apply to every \`send_request\` / \`sendRequest\` call that returns a body.
|
|
31
|
+
- Only add assertions clearly supported by the request body, prior response values, field names, codebase evidence, or the test generation recommendations received for this test. Do not invent constraints.
|
|
32
|
+
- Add new assertions immediately after the existing status-code assertion — do not move or remove anything.`;
|
|
33
|
+
export function getIntegrationAssertionsPrompt(testFile, enhanceType) {
|
|
34
|
+
return getAssertionsPrompt(specificRules, SCOPE, testFile, enhanceType);
|
|
35
|
+
}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { getPersonaPrefix } from "../personas.js";
|
|
2
|
+
export const MAINTENANCE_SCOPE_NOTE = "Apply only to new test functions you are adding and existing test functions affected by changes in the diff. Do NOT modify test functions unrelated to the diff.";
|
|
3
|
+
export function maintenanceTaskSuffix(enhanceType) {
|
|
4
|
+
return enhanceType === "maintenance" ? ` ${MAINTENANCE_SCOPE_NOTE}` : "";
|
|
5
|
+
}
|
|
6
|
+
const SHARED_RULES = [
|
|
7
|
+
{
|
|
8
|
+
title: "Echo-back the request fields",
|
|
9
|
+
description: "For every field returned unchanged from the request body, assert the exact sent value.",
|
|
10
|
+
subPoints: [
|
|
11
|
+
"`is not None` / `not.toBeNull()` is only acceptable when the value is genuinely unknown — for server-generated timestamps or opaque IDs. This rule does not apply to computed fields.",
|
|
12
|
+
"When a response field's value equals the value sent in the request body, path, or query, assert that exact value rather than a null-check — the sent value is known and reproducible.",
|
|
13
|
+
"Also applies to response-only fields the server always returns the same value for (`filename_download`, `content_type`, `size`, `url`, enum status after creation). Asserting only the id on a multi-key resource response is not sufficient.",
|
|
14
|
+
"Range matchers like `toBeGreaterThanOrEqual(0)` and type-only checks like `typeof X === 'number'` are not acceptable for fields whose exact values are known.",
|
|
15
|
+
"Assert the exact status code from the recorded trace or `expected_response_body` — for example, `201` for create endpoints, not a generic `200`. Permissive status matchers are never acceptable.",
|
|
16
|
+
],
|
|
17
|
+
examples: [
|
|
18
|
+
{
|
|
19
|
+
language: "javascript",
|
|
20
|
+
code: `expect(getValue(response, "data.filename_download")).toBe("invoice.pdf");
|
|
21
|
+
expect(getValue(response, "data.type")).toBe("application/pdf");
|
|
22
|
+
expect(response.statusCode).toBe(201);`,
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
language: "javascript",
|
|
26
|
+
code: `expect(getResponseValue(productsPostResponse, "name"), 'name').toBe("Skyramp Tester");`,
|
|
27
|
+
},
|
|
28
|
+
],
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
title: "Error path (HTTP 4xx/5xx responses)",
|
|
32
|
+
description: "For every HTTP 4xx/5xx response that includes a response body, assert every error body field with its exact value — including `error.code`, `error.message`, `detail`, `errors[0].message`, and `errors[0].extensions.code`.",
|
|
33
|
+
subPoints: [
|
|
34
|
+
"Also apply the array-validation rule to any errors array.",
|
|
35
|
+
"Asserting only the status code is never sufficient when a body is present.",
|
|
36
|
+
"For no-body responses such as a successful DELETE (204): assert the status code only.",
|
|
37
|
+
],
|
|
38
|
+
examples: [
|
|
39
|
+
{
|
|
40
|
+
language: "javascript",
|
|
41
|
+
code: `expect(response.statusCode).toBe(404);
|
|
42
|
+
expect(getValue(response, "errors.0.message")).toBe("Item not found");
|
|
43
|
+
expect(getValue(response, "errors.0.extensions.code")).toBe("RECORD_NOT_FOUND");
|
|
44
|
+
expect(getValue(response, "errors.1")).toBeUndefined();`,
|
|
45
|
+
},
|
|
46
|
+
],
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
title: "Value ranges",
|
|
50
|
+
description: "For numeric fields where a realistic range is inferable from the field name, domain, or OpenAPI schema (`minimum` / `maximum`), assert the value falls within the expected range.",
|
|
51
|
+
examples: [
|
|
52
|
+
{
|
|
53
|
+
language: "javascript",
|
|
54
|
+
code: `expect(getResponseValue(productsPostResponse, "price")).toBeGreaterThanOrEqual(0);`,
|
|
55
|
+
},
|
|
56
|
+
],
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
title: "Specific known values",
|
|
60
|
+
description: "For enum or status fields where only one outcome is valid for this flow, assert the exact expected value.",
|
|
61
|
+
subPoints: [
|
|
62
|
+
"When the OpenAPI schema defines allowed values, assert the schema-defined value.",
|
|
63
|
+
"Also assert cross-field invariants when one field encodes the count or summary of another (e.g. `total_count == results.length`).",
|
|
64
|
+
],
|
|
65
|
+
examples: [
|
|
66
|
+
{
|
|
67
|
+
language: "javascript",
|
|
68
|
+
code: `expect(getResponseValue(ordersPostResponse, "status"), 'status').toBe("pending");`,
|
|
69
|
+
},
|
|
70
|
+
],
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
title: "Array / Items validation",
|
|
74
|
+
description: "Only assert indices that exist in the recorded response — never infer array length from the request or scenario name.",
|
|
75
|
+
subPoints: [
|
|
76
|
+
"When the response is an empty array: assert the length is 0 and that the first index is absent. Never assert a field on index 0 of an empty array.",
|
|
77
|
+
"When the response is a non-empty array: assert the exact length, key fields on each item, and that the index after the last item is absent.",
|
|
78
|
+
"Shape-only checks such as `Array.isArray` or `typeof` are not sufficient when the response contains actual values.",
|
|
79
|
+
"If the response is sorted or ordered: assert the ordering direction across the first two items.",
|
|
80
|
+
"When the response is an empty or minimal body (an empty object, empty array, null, or only a few keys): assert the empty or minimal shape and the absence of error fields — do not stop at the status code.",
|
|
81
|
+
"When the request includes pagination or filter parameters, assert the response reflects them.",
|
|
82
|
+
],
|
|
83
|
+
examples: [
|
|
84
|
+
{
|
|
85
|
+
language: "javascript",
|
|
86
|
+
code: `expect(getResponseValue(patchResponse, "items.0.product_id")).toBe(getResponseValue(productPostResponse, "id"));
|
|
87
|
+
expect(getResponseValue(patchResponse, "items.0.quantity")).toBe(2);
|
|
88
|
+
expect(getResponseValue(patchResponse, "items.1.product_id")).toBeNull();
|
|
89
|
+
expect(getValue(response, "data").length).toBeLessThanOrEqual(10); // request sent limit=10`,
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
language: "javascript",
|
|
93
|
+
code: `expect(getValue(response, "results").length).toBe(2);
|
|
94
|
+
expect(getValue(response, "results.0.id")).toBe("prod_001");
|
|
95
|
+
expect(getValue(response, "results.2")).toBeUndefined();`,
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
language: "javascript",
|
|
99
|
+
code: `expect(getValue(response, "data").length).toBe(0);
|
|
100
|
+
expect(getValue(response, "data.0")).toBeUndefined();`,
|
|
101
|
+
},
|
|
102
|
+
],
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
title: "Format for server-generated fields",
|
|
106
|
+
description: "For fields whose exact value varies across runs — UUIDs, auto-incremented IDs, ISO timestamps, IP addresses, emails, and URLs — assert the format or pattern rather than the exact value.",
|
|
107
|
+
subPoints: [
|
|
108
|
+
"UUID matches `/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i`.",
|
|
109
|
+
"Auto-incremented ID is greater than 0.",
|
|
110
|
+
"ISO timestamp matches `/^\\d{4}-\\d{2}-\\d{2}T/`.",
|
|
111
|
+
"IP address matches IPv4 or IPv6 format.",
|
|
112
|
+
"Email contains `@` and a domain.",
|
|
113
|
+
"URL starts with `http://` or `https://`.",
|
|
114
|
+
"Use `not.toBeNull()` / `is not None` only for truly opaque random tokens where no format is recognizable.",
|
|
115
|
+
"Use format info from the OpenAPI schema (`format: uuid`, `format: date-time`) to identify these fields.",
|
|
116
|
+
],
|
|
117
|
+
examples: [
|
|
118
|
+
{
|
|
119
|
+
language: "javascript",
|
|
120
|
+
code: `expect(getResponseValue(productsPostResponse, "id")).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i);
|
|
121
|
+
expect(getResponseValue(productsPostResponse, "created_at")).toMatch(/^\\d{4}-\\d{2}-\\d{2}T/);`,
|
|
122
|
+
},
|
|
123
|
+
],
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
title: "Computed response fields, derived using dynamic formulas",
|
|
127
|
+
description: "When the response contains a field whose value is derived from a calculation, assert the result using a formula built from prior response values and request inputs, not a hardcoded literal.",
|
|
128
|
+
subPoints: [
|
|
129
|
+
"Use a dynamic formula when the formula is visible in source.",
|
|
130
|
+
"Fall back to the exact pre-computed value from `expected_response_body` only when no formula is knowable — all inputs must come from the inline request body or the response.",
|
|
131
|
+
],
|
|
132
|
+
examples: [
|
|
133
|
+
{
|
|
134
|
+
language: "javascript",
|
|
135
|
+
code: `expect(getResponseValue(patchResponse, "discount_amount")).toBe(getResponseValue(patchResponse, "total_amount") * (getResponseValue(patchResponse, "discount_value") / 100));`,
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
language: "javascript",
|
|
139
|
+
code: `expect(getResponseValue(patchResponse, "total_amount")).toBe(getResponseValue(productPostResponse, "price") * quantitySent);`,
|
|
140
|
+
},
|
|
141
|
+
],
|
|
142
|
+
},
|
|
143
|
+
];
|
|
144
|
+
export function renderRule(index, rule) {
|
|
145
|
+
const subPoints = rule.subPoints && rule.subPoints.length > 0
|
|
146
|
+
? "\n" + rule.subPoints.map((p) => `- ${p}`).join("\n")
|
|
147
|
+
: "";
|
|
148
|
+
const examplesBlock = rule.examples.length > 0
|
|
149
|
+
? "\n" +
|
|
150
|
+
rule.examples
|
|
151
|
+
.map((ex) => `<example language="${ex.language}">
|
|
152
|
+
${ex.code}
|
|
153
|
+
</example>`)
|
|
154
|
+
.join("\n")
|
|
155
|
+
: "";
|
|
156
|
+
return `${index}. ${rule.title}\n${rule.description}${subPoints}${examplesBlock}`;
|
|
157
|
+
}
|
|
158
|
+
function renderRules(rules) {
|
|
159
|
+
return rules.map((rule, i) => renderRule(i + 1, rule)).join("\n\n");
|
|
160
|
+
}
|
|
161
|
+
export function getAssertionsPrompt(specificRules, scope, testFile, enhanceType) {
|
|
162
|
+
const allRules = [...SHARED_RULES, ...specificRules];
|
|
163
|
+
const ruleChecklistKeys = allRules
|
|
164
|
+
.map((rule) => ` "${rule.title}": []`)
|
|
165
|
+
.join(",\n");
|
|
166
|
+
return `${getPersonaPrefix()}Your task is to enhance response body assertions in the given test file: \`${testFile}\`.${maintenanceTaskSuffix(enhanceType)}
|
|
167
|
+
|
|
168
|
+
### Pre-Edit Assertion Analysis
|
|
169
|
+
Before editing the given file, you must output a \`<thinking>\` block. The aim of the \`<thinking>\` block is to analyze each in-scope response in the given test file and output a JSON array that ensures no assertion rule is overlooked. The JSON array should match the template below — every assertion rule title must appear as a key in the \`rule_checklist\`, even when the value is \`[]\`.
|
|
170
|
+
1. Scan the given test file and expected responses based on the test recommendations for the code change tested.
|
|
171
|
+
2. Classify each response first by its response status type and then assign the applicable assertion rules to the response.
|
|
172
|
+
1. Success with body (2xx with a response body): all assertion rules below may apply — echo-back of request fields, computed response fields, array / items validation, and chained values across steps.
|
|
173
|
+
2. Success with no body (204, or 202 with empty body): assert the status code only. Also apply chained-values rules if a follow-up step uses this response's ID.
|
|
174
|
+
3. Error response (4xx/5xx with a body): assert every error body field with its exact value plus array / items validation on the \`errors[]\` array (exact length + per-item fields + next index undefined). Status code alone is never sufficient when a body is present — for example, also assert \`errors.0.extensions.code == 'INVALID_PAYLOAD'\` and that \`errors.1\` is undefined.
|
|
175
|
+
3. For each in-scope response, output one JSON object using the template below. The output is an array — one object per in-scope response.
|
|
176
|
+
- \`step\`: the HTTP method, path, and response variable name for this request (e.g. \`POST /products → products_POST_response\`).
|
|
177
|
+
- \`response_status\`: one of \`success\`, \`no_body\`, or \`error\` based on the classification in step 2.
|
|
178
|
+
- \`rule_checklist\`: an object that MUST contain every rule title below as a key. For each rule, the value is an array of assertion lines you will add for this response under that rule. Use \`[]\` only when the rule does not apply to this response — every key must still be present. This forces you to consider every rule for every response.
|
|
179
|
+
|
|
180
|
+
\`\`\`json
|
|
181
|
+
[{
|
|
182
|
+
"step": "<METHOD> <path> → <responseVar>",
|
|
183
|
+
"response_status": "success | no_body | error",
|
|
184
|
+
"rule_checklist": {
|
|
185
|
+
${ruleChecklistKeys}
|
|
186
|
+
}
|
|
187
|
+
}]
|
|
188
|
+
\`\`\`
|
|
189
|
+
|
|
190
|
+
### SDK Helpers
|
|
191
|
+
Access response body fields via the SDK helper already imported in the generated file (never dict/attribute access on the response variable): Python \`skyramp.get_response_value(response, "json.path")\`; TypeScript / JavaScript \`getValue(response, "json.path")\` or \`getResponseValue(response, "json.path")\` from \`@skyramp/skyramp\`; Java \`getValue(response, "json.path")\`.
|
|
192
|
+
|
|
193
|
+
### Assertion Rules with Examples
|
|
194
|
+
${renderRules(allRules)}
|
|
195
|
+
|
|
196
|
+
${scope}
|
|
197
|
+
|
|
198
|
+
### What not to do
|
|
199
|
+
- Do not access response fields via dict syntax (\`response["field"]\`) or attribute access (\`response.field\`) — always use the SDK helper.
|
|
200
|
+
- Do not assert \`not.toBeNull()\` / \`is not None\` on a field whose exact value is in the request body, prior response, or trace.
|
|
201
|
+
- Do not skip body assertions citing genuinely unpredictable fields — every assertable field still needs an assertion.
|
|
202
|
+
- Do not use permissive status matchers (\`.toMatch(/^2/)\`, \`.toBeGreaterThanOrEqual(200)\`, \`checkStatusCode(response, '20x')\`).
|
|
203
|
+
- Do not use shape-only or type-only assertions as a substitute for exact value validation. Forbidden patterns: \`Array.isArray(...)\`, \`typeof X === '...'\`, \`X instanceof Array\`, \`Object.keys(X).length > 0\`. When the recorded response contains actual values, assert them exactly.
|
|
204
|
+
- Do not use shape-only, containment-only, range-only, or weak-length as the sole assertion on a populated array.
|
|
205
|
+
- Do not swap between \`getValue\` and \`getResponseValue\` — keep whichever SDK helper the file already imports.
|
|
206
|
+
- Do not restructure, reformat, reorder, or modify existing code; do not add comments or docstrings.
|
|
207
|
+
- Do not change function signatures, imports, or variable names.
|
|
208
|
+
- Do not remove existing assertions.
|
|
209
|
+
|
|
210
|
+
### Verification of Assertions
|
|
211
|
+
After adding all assertion lines in the given test file, verify that every applicable rule has been applied correctly to each in-scope response. If any are missing or weakly applied, fix them before completing.`;
|
|
212
|
+
}
|
|
@@ -1,90 +1,229 @@
|
|
|
1
1
|
import { getPersonaPrefix } from "../personas.js";
|
|
2
|
-
|
|
2
|
+
import { maintenanceTaskSuffix, renderRule, } from "./sharedAssertionRules.js";
|
|
3
|
+
const UI_ASSERTION_CATEGORIES = [
|
|
4
|
+
{
|
|
5
|
+
name: "Critical UI Assertions",
|
|
6
|
+
rules: [
|
|
7
|
+
{
|
|
8
|
+
title: "Selector constraints",
|
|
9
|
+
description: "Every assertion uses a selector already in the file. Never invent `data-testid`, role names, or classes.",
|
|
10
|
+
subPoints: [
|
|
11
|
+
"No tautological assertions — locating an element by text X, then asserting it contains X.",
|
|
12
|
+
],
|
|
13
|
+
examples: [
|
|
14
|
+
{
|
|
15
|
+
language: "javascript",
|
|
16
|
+
code: `await expect(page.getByTestId('badge-count')).toHaveText('3');`,
|
|
17
|
+
},
|
|
18
|
+
],
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
title: "Page errors",
|
|
22
|
+
description: "Register `page.on('pageerror', ...)` before the first navigation and assert `expect(errors).toHaveLength(0)` at the end of the test.",
|
|
23
|
+
examples: [],
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
title: "Correct intended behavior",
|
|
27
|
+
description: "Assertions encode the expected outcome of the action, not the buggy runtime output observed in the trace.",
|
|
28
|
+
examples: [
|
|
29
|
+
{
|
|
30
|
+
language: "javascript",
|
|
31
|
+
code: `await expect(page.locator('[data-testid="session-row"]')).toHaveCount(3);`,
|
|
32
|
+
},
|
|
33
|
+
],
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
title: "Tests target the changed behavior introduced by the pull request",
|
|
37
|
+
description: "At least one assertion targets the changed behavior on populated or updated state, not only empty or zero state.",
|
|
38
|
+
examples: [],
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
title: "Collection / repeated UI elements",
|
|
42
|
+
description: "For every collection page or selector matching multiple rendered nodes (items, rows, cards, badges, tabs, chips, definition rows, nav items), assert the exact count plus per-item content.",
|
|
43
|
+
subPoints: [
|
|
44
|
+
"Use `toHaveCount(N)` where N comes from the trace array length or rendered DOM (`0` for empty state).",
|
|
45
|
+
"Add at least one concrete per-item content assertion (`toHaveText`, `toHaveValue`, `toHaveAttribute`).",
|
|
46
|
+
"Empty trace → `toHaveCount(0)` paired with the empty-state text.",
|
|
47
|
+
],
|
|
48
|
+
examples: [
|
|
49
|
+
{
|
|
50
|
+
language: "javascript",
|
|
51
|
+
code: `await expect(page.getByRole('row')).toHaveCount(2);
|
|
52
|
+
await expect(page.getByRole('cell', { name: 'admin@example.com' })).toHaveText('admin@example.com');`,
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
language: "javascript",
|
|
56
|
+
code: `await expect(page.getByTestId('notification-badge')).toHaveText('3');
|
|
57
|
+
await expect(page.getByTestId('notification-row')).toHaveCount(3);`,
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
language: "javascript",
|
|
61
|
+
code: `await expect(page.getByTestId('definition-row')).toHaveCount(3);
|
|
62
|
+
await expect(page.getByTestId('definition-row').nth(0)).toHaveText('Version 10.2.1');`,
|
|
63
|
+
},
|
|
64
|
+
],
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
title: "Positive-path companion",
|
|
68
|
+
description: "Negative-only checks and URL-only routing tests must include at least one positive rendered-element assertion such as a heading, breadcrumb, title, link, count, or exact text.",
|
|
69
|
+
examples: [
|
|
70
|
+
{
|
|
71
|
+
language: "javascript",
|
|
72
|
+
code: `await expect(page).toHaveURL(/\\/users/);
|
|
73
|
+
await expect(page.getByRole('heading')).toHaveText('User Directory');`,
|
|
74
|
+
},
|
|
75
|
+
],
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
title: "Captured network responses",
|
|
79
|
+
description: "For every `page.waitForResponse`, `page.on`, or `page.route`, assert at least one status, body, or header field. Body values used downstream must also appear as visible UI text.",
|
|
80
|
+
examples: [
|
|
81
|
+
{
|
|
82
|
+
language: "javascript",
|
|
83
|
+
code: `const loginResp = await page.waitForResponse('**/login');
|
|
84
|
+
expect(loginResp.status()).toBe(200);
|
|
85
|
+
expect((await loginResp.json()).user.email).toBe('admin@example.com');`,
|
|
86
|
+
},
|
|
87
|
+
],
|
|
88
|
+
},
|
|
89
|
+
],
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
name: "Computed Values",
|
|
93
|
+
rules: [
|
|
94
|
+
{
|
|
95
|
+
title: "Exact rendered values",
|
|
96
|
+
description: "When the exact text, value, or attribute is knowable from the trace or source, use `toHaveText`, `toHaveValue`, or `toHaveAttribute` — never `toBeVisible` or `toContainText`.",
|
|
97
|
+
subPoints: [
|
|
98
|
+
"Definition / info pages: assert the actual displayed values (version, count, date), not just the static label text.",
|
|
99
|
+
"Images: assert `toHaveAttribute('src', ...)` with the exact src — never visibility alone.",
|
|
100
|
+
],
|
|
101
|
+
examples: [
|
|
102
|
+
{
|
|
103
|
+
language: "javascript",
|
|
104
|
+
code: `await expect(page.getByTestId('status')).toHaveText('Active');`,
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
language: "javascript",
|
|
108
|
+
code: `await expect(page.getByTestId('product-image')).toHaveAttribute('src', /expected-pattern/);`,
|
|
109
|
+
},
|
|
110
|
+
],
|
|
111
|
+
},
|
|
112
|
+
],
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
name: "Post-edit State",
|
|
116
|
+
description: "Apply only when the test contains a state-changing action. Otherwise mark NOT APPLICABLE.",
|
|
117
|
+
rules: [
|
|
118
|
+
{
|
|
119
|
+
title: "Post-action visible state",
|
|
120
|
+
description: "After a state-changing action (form fill+submit, save / delete / create / toggle, checkbox click, hover / mouseout, refresh, reload, JS update, form edit), assert the visible updated outcome via `toHaveText`, `toHaveValue`, `toHaveAttribute`, or `toBeChecked`. The displayed values must reflect the edit.",
|
|
121
|
+
subPoints: [
|
|
122
|
+
"Toggle / checkbox renames: assert the new label or state, not `toBeVisible` on the renamed element.",
|
|
123
|
+
"Hover / mouseout: assert the overlay or state visible / hidden after the event.",
|
|
124
|
+
"List edit: when the test submits N items, assert exactly N rows. When the count is unknown before the action, read it before and assert the delta after.",
|
|
125
|
+
],
|
|
126
|
+
examples: [
|
|
127
|
+
{
|
|
128
|
+
language: "javascript",
|
|
129
|
+
code: `await expect(page.getByTestId('description-checkbox')).toBeChecked();
|
|
130
|
+
await expect(page.getByTestId('description-label')).toHaveText('check_box Description');`,
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
language: "javascript",
|
|
134
|
+
code: `await expect(page.getByTestId('total')).toHaveText('$19.98');`,
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
language: "javascript",
|
|
138
|
+
code: `await expect(page.getByTestId('version')).toHaveText('10.2.1');`,
|
|
139
|
+
},
|
|
140
|
+
],
|
|
141
|
+
},
|
|
142
|
+
],
|
|
143
|
+
},
|
|
144
|
+
];
|
|
145
|
+
function renderCategories(categories) {
|
|
146
|
+
let index = 0;
|
|
147
|
+
return categories
|
|
148
|
+
.map((category) => {
|
|
149
|
+
const rendered = category.rules
|
|
150
|
+
.map((rule) => {
|
|
151
|
+
index += 1;
|
|
152
|
+
return renderRule(index, rule);
|
|
153
|
+
})
|
|
154
|
+
.join("\n\n");
|
|
155
|
+
const header = `#### ${category.name}`;
|
|
156
|
+
return category.description
|
|
157
|
+
? `${header}\n${category.description}\n\n${rendered}`
|
|
158
|
+
: `${header}\n${rendered}`;
|
|
159
|
+
})
|
|
160
|
+
.join("\n\n");
|
|
161
|
+
}
|
|
162
|
+
function renderAssertionCategoriesTemplate(categories) {
|
|
163
|
+
return categories
|
|
164
|
+
.map((category) => {
|
|
165
|
+
const ruleKeys = category.rules
|
|
166
|
+
.map((rule) => ` "${rule.title}": []`)
|
|
167
|
+
.join(",\n");
|
|
168
|
+
return ` "${category.name}": {\n${ruleKeys}\n }`;
|
|
169
|
+
})
|
|
170
|
+
.join(",\n");
|
|
171
|
+
}
|
|
172
|
+
export function getUIAssertionsPrompt(testFile, enhanceType) {
|
|
173
|
+
const categoryTemplate = renderAssertionCategoriesTemplate(UI_ASSERTION_CATEGORIES);
|
|
174
|
+
return `${getPersonaPrefix()}Your task is to enhance assertions for the given UI test file: \`${testFile}\`.${maintenanceTaskSuffix(enhanceType)}
|
|
175
|
+
|
|
3
176
|
### First Check
|
|
4
177
|
If the generated test file has no \`expect()\` assertions, you MUST manually add them before anything else. Use \`import { expect } from '@skyramp/skyramp';\` — never from \`@playwright/test\`. If an existing import pulls \`expect\` from \`@playwright/test\`, move it to \`@skyramp/skyramp\` (keep \`test\` on the playwright line).
|
|
5
178
|
|
|
6
|
-
###
|
|
7
|
-
|
|
8
|
-
1.
|
|
9
|
-
2.
|
|
10
|
-
a.
|
|
11
|
-
b.
|
|
12
|
-
c.
|
|
13
|
-
- If an existing assertion uses the
|
|
14
|
-
- If no assertion exists for the buggy behavior,
|
|
15
|
-
3.
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
9. At least one assertion targets the PR's core changed behavior. If the PR feature only activates with data (e.g. notifications badge, cart count, unread count), the test MUST create or seed that data — never assert only the empty/zero/default state.
|
|
23
|
-
|
|
24
|
-
### Assertion Strength (use the strongest applicable)
|
|
25
|
-
- ✅ STRONG: \`toHaveCount(N)\`, \`toHaveText('Exact Value')\`, \`toHaveValue('input value')\`, \`toHaveAttribute('src', ...)\`
|
|
26
|
-
- ⚠️ PARTIAL: \`toContainText('foo')\` — only when the full string is genuinely dynamic
|
|
27
|
-
- ❌ WEAKEST: \`toBeVisible()\` — only when presence/absence is the actual test
|
|
28
|
-
|
|
29
|
-
### Strategic Placement
|
|
30
|
-
1. Collection pages: exact item/row/card count, OR empty-state text + zero-count, plus at least one concrete content assertion (cell, card, or text).
|
|
31
|
-
❌ BAD (collection page validated only by heading): \`expect(page.getByRole("heading", { name: "Activity Feed" })).toHaveText("Activity Feed")\`
|
|
32
|
-
❌ BAD (collection page validated only by toolbar): \`expect(page.getByRole("button", { name: "search" })).toBeVisible()\`
|
|
33
|
-
✅ GOOD: \`expect(page.getByRole("row")).toHaveCount(2)\` plus \`expect(page.getByRole("cell", { name: "admin@example.com" })).toHaveText("admin@example.com")\`
|
|
34
|
-
2. Read-only collection traces: still required to use the strongest read-only assertions above. Do not fall back to heading-only.
|
|
35
|
-
3. After each state-changing action: assert the visible UPDATED outcome (item appears, total recalculates, count changes).
|
|
36
|
-
❌ BAD (visibility when the recalculated total is known): \`expect(page.getByTestId('total')).toBeVisible()\`
|
|
37
|
-
✅ GOOD: \`expect(page.getByTestId('total')).toHaveText('$19.98')\`
|
|
179
|
+
### Pre-Edit Assertion Analysis
|
|
180
|
+
Before editing the given test file, you must output a \`<thinking>\` block. The aim of the \`<thinking>\` block is to analyze each in-scope item (action, selector, or captured network response) in the given test file and output a JSON array that ensures no assertion rule is overlooked. The JSON array should match the template below — every assertion category and every rule title under it must appear as a key, even when the value is \`[]\`.
|
|
181
|
+
1. Selector inventory — list every selector already present in the generated test file (\`data-testid\`, role + name, text, label, etc.). New assertions may use only selectors from this list. Do not invent \`data-testid\` values, role names, or aria attributes. Also note captured network responses, repeated element patterns, exact rendered text/value/attribute from trace/source, and existing \`toBeVisible()\` assertions whose exact text is knowable.
|
|
182
|
+
2. Process — Replay → Identify → Fix or Add — walk through these three steps explicitly.
|
|
183
|
+
a. Replay the scenario mentally. At each state-changing action (form submit, item add/edit/delete), ask: "What is the EXPECTED outcome based on the action performed?"
|
|
184
|
+
b. Identify expectation mismatches. If the recorded trace shows a result that contradicts the action (e.g. removing 1 of 2 items but the page shows 3, submitting a form but getting a blank page, editing a field but the old value persists), that is an app bug the test should catch. List every mismatch you find.
|
|
185
|
+
c. Fix or add assertions for each mismatch.
|
|
186
|
+
- If an existing assertion uses the wrong (buggy) value, edit it to assert the correct expected value.
|
|
187
|
+
- If no assertion exists for the buggy behavior, add one immediately after the action that triggers it.
|
|
188
|
+
3. Classify each in-scope action / selector / captured response by its applicable assertion category, marking each category APPLICABLE or NOT APPLICABLE.
|
|
189
|
+
- Critical UI Assertions applies when there is a collection / repeated element (\`toHaveCount\`), a pageerror handler, a captured network response, a negative-only or URL-only test needing a positive-path companion, or a tautological locator-by-text + assert-text to replace.
|
|
190
|
+
- Computed Values applies when there is a knowable exact text / value / attribute, including a definition-page displayed value or an image \`src\`.
|
|
191
|
+
- Post-edit State applies only when the test contains a state-changing action (form fill+submit, save / delete / create / toggle, checkbox click, hover / mouseout, refresh, reload, JS update, form edit); otherwise NOT APPLICABLE.
|
|
192
|
+
4. For each in-scope item, output one JSON object using the template below. The output is an array — repeat the object template below once per in-scope item.
|
|
193
|
+
- \`action_or_selector_or_response\`: the selector, action, or captured network response this entry covers.
|
|
194
|
+
- \`assertion_categories\`: an object that MUST contain every category name below as a key. The value of each category is itself an object that MUST contain every rule title under that category as a key. For each rule, the value is an array of assertion lines you will add for this item under that rule. Use \`[]\` only when the rule does not apply to this item — every category key and every rule key must still be present. This forces you to consider every rule for every item.
|
|
38
195
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
✅ GOOD: \`expect(page.getByTestId('notification-badge')).toHaveText('3')\` PLUS \`expect(page.getByTestId('notification-row')).toHaveCount(3)\`
|
|
196
|
+
\`\`\`json
|
|
197
|
+
[{
|
|
198
|
+
"action_or_selector_or_response": "<selector | action | response>",
|
|
199
|
+
"assertion_categories": {
|
|
200
|
+
${categoryTemplate}
|
|
201
|
+
}
|
|
202
|
+
}]
|
|
203
|
+
\`\`\`
|
|
48
204
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
❌ BAD (visibility does not verify the correct image): \`expect(page.getByTestId('product-image')).toBeVisible()\`
|
|
53
|
-
✅ GOOD: \`expect(page.getByTestId('product-image')).toHaveAttribute('src', /expected-pattern/)\`
|
|
54
|
-
10. Dynamic JS updates (no reload): assert the updated value immediately after the action.
|
|
55
|
-
11. Captured network responses (\`page.waitForResponse\`, \`page.on('response', ...)\`, \`page.route()\`): assert at least one field from the captured response — status, body value, or header. A captured-but-unasserted response is a no-op.
|
|
56
|
-
❌ BAD (response captured but never asserted): \`const loginResp = await page.waitForResponse('**/login')\`
|
|
57
|
-
✅ GOOD: \`const loginResp = await page.waitForResponse('**/login'); expect(loginResp.status()).toBe(200); expect((await loginResp.json()).user.email).toBe('admin@example.com')\`
|
|
205
|
+
### Assertion Rules with Examples
|
|
206
|
+
Most-violated patterns — apply every time. (1) repeated elements → \`toHaveCount(N)\` + per-item \`toHaveText\`/\`toHaveValue\`/\`toHaveAttribute\` (Collection / repeated UI elements). (2) post-action state → \`toHaveText\`/\`toHaveValue\`/\`toHaveAttribute\`/\`toBeChecked\`, not \`toBeVisible\` when exact value is knowable (Post-action visible state). (3) routing/URL-only tests → at least one rendered-element exact text (Positive-path companion).
|
|
207
|
+
Strength order. \`toHaveCount\`/\`toHaveText\`/\`toHaveValue\`/\`toHaveAttribute\` > \`toContainText\` (only when string is genuinely dynamic) > \`toBeVisible\` (only when presence is the actual test).
|
|
58
208
|
|
|
59
|
-
|
|
60
|
-
- Static page headings or boilerplate labels
|
|
61
|
-
- Intermediate states (typing, dropdown opening)
|
|
62
|
-
- Values already guaranteed by the action you just took
|
|
63
|
-
- The same value with multiple selectors
|
|
64
|
-
- Tautological assertions — locating an element by text X, then asserting it contains X
|
|
65
|
-
❌ BAD: \`page.getByText('My Activity').toContainText('My Activity')\`
|
|
66
|
-
✅ GOOD: \`expect(page.getByTestId('badge-count')).toHaveText('3')\`
|
|
67
|
-
- Buggy error text or crash output as the expected result (unless the intended UX is explicitly an error state)
|
|
68
|
-
❌ BAD (asserting the bug as expected output): \`expect(page.getByText('s.expires.toISOString is not')).toHaveText('s.expires.toISOString is not a function')\`
|
|
69
|
-
✅ GOOD: assert the correct post-action UI state (e.g. \`expect(page.locator('[data-testid="session-row"]')).toHaveCount(3)\`) and let the test fail when the bug is present
|
|
209
|
+
${renderCategories(UI_ASSERTION_CATEGORIES)}
|
|
70
210
|
|
|
71
|
-
###
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
12. Image assertions use \`toHaveAttribute('src', ...)\` not just \`toBeVisible()\` when the PR affects images
|
|
84
|
-
13. Every captured network response (via \`page.waitForResponse\` / \`page.on('response')\` / \`page.route()\`) has at least one field asserted — status, body value, or header
|
|
85
|
-
14. \`expect\` imported from \`@skyramp/skyramp\`, not \`@playwright/test\`
|
|
211
|
+
### What Not to Do
|
|
212
|
+
- Do not invent selectors, values, attributes, or assertions
|
|
213
|
+
- Do not use \`toBeVisible\` when exact text/value/state is knowable
|
|
214
|
+
- Do not use tautological locator-by-text + assert-text
|
|
215
|
+
- Do not use \`toContainText\` when the full string is known
|
|
216
|
+
- Do not assert static page headings, intermediate states, or values guaranteed by the action you just took
|
|
217
|
+
- Do not assert the same value with multiple selectors
|
|
218
|
+
- Do not import \`expect\` from \`@playwright/test\`
|
|
219
|
+
- Do not restructure, reorder, or modify existing code/imports/signatures
|
|
220
|
+
- Do not add comments or docstrings
|
|
221
|
+
- Do not remove or modify existing assertions
|
|
222
|
+
- Do not assert buggy/error text as expected (unless the intended UX is an error state)
|
|
86
223
|
|
|
87
|
-
|
|
224
|
+
### Verification of Assertions
|
|
225
|
+
After adding all assertion lines in the given test file, verify that every applicable rule has been applied correctly to each in-scope action, selector, and captured response. If any are missing or weakly applied, fix them before completing.
|
|
88
226
|
|
|
89
227
|
The goal is tests that FAIL when the app has bugs, not tests that simply replay what happened.
|
|
90
228
|
`;
|
|
229
|
+
}
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { TestType } from "../../types/TestTypes.js";
|
|
3
3
|
import { AnalyticsService } from "../../services/AnalyticsService.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
4
|
+
import { getContractProviderAssertionsPrompt } from "../../prompts/enhance-assertions/contractProviderAssertionsPrompt.js";
|
|
5
|
+
import { getIntegrationAssertionsPrompt } from "../../prompts/enhance-assertions/integrationAssertionsPrompt.js";
|
|
6
|
+
import { getUIAssertionsPrompt } from "../../prompts/enhance-assertions/uiAssertionsPrompt.js";
|
|
7
7
|
const TOOL_NAME = "skyramp_enhance_assertions";
|
|
8
|
-
const SCOPE_GENERATION = "Apply to every test function in the generated file.";
|
|
9
|
-
const SCOPE_MAINTENANCE = "Apply to **new test functions you are adding** and **existing functions that cover endpoints changed in the diff** only. Do NOT touch existing functions for endpoints unrelated to the diff.";
|
|
10
8
|
const TESTBOT_UI_CHECKS = `
|
|
11
9
|
### Additional Testbot-Specific Checks
|
|
12
10
|
- If no suitable selector exists in the generated file for an assertion you need to add, go back and call \`browser_assert\` on the live page to record it with a valid selector, then re-export and regenerate.
|
|
@@ -35,28 +33,28 @@ export function registerEnhanceAssertionsTool(server) {
|
|
|
35
33
|
inputSchema: enhanceAssertionsSchema,
|
|
36
34
|
}, async (params) => {
|
|
37
35
|
const { testFile, testType, enhanceType } = params;
|
|
36
|
+
const enhanceCtx = enhanceType;
|
|
38
37
|
let instructions;
|
|
39
38
|
if (testType === TestType.UI) {
|
|
40
|
-
instructions =
|
|
39
|
+
instructions = getUIAssertionsPrompt(testFile, enhanceCtx);
|
|
41
40
|
if (process.env.SKYRAMP_FEATURE_TESTBOT === "1") {
|
|
42
41
|
instructions += TESTBOT_UI_CHECKS;
|
|
43
42
|
}
|
|
44
43
|
}
|
|
45
44
|
else if (testType === TestType.CONTRACT) {
|
|
46
|
-
instructions =
|
|
45
|
+
instructions = getContractProviderAssertionsPrompt(testFile, enhanceCtx);
|
|
47
46
|
}
|
|
48
47
|
else if (testType === TestType.INTEGRATION) {
|
|
49
|
-
instructions =
|
|
48
|
+
instructions = getIntegrationAssertionsPrompt(testFile, enhanceCtx);
|
|
50
49
|
}
|
|
51
50
|
else {
|
|
52
51
|
throw new Error(`Unsupported testType for ${TOOL_NAME}: ${testType}`);
|
|
53
52
|
}
|
|
54
|
-
const scope = enhanceType === "maintenance" ? SCOPE_MAINTENANCE : SCOPE_GENERATION;
|
|
55
53
|
const result = {
|
|
56
54
|
content: [
|
|
57
55
|
{
|
|
58
56
|
type: "text",
|
|
59
|
-
text:
|
|
57
|
+
text: instructions,
|
|
60
58
|
},
|
|
61
59
|
],
|
|
62
60
|
isError: false,
|
|
@@ -54,7 +54,7 @@ describe("dockerImageExistsLocally", () => {
|
|
|
54
54
|
});
|
|
55
55
|
});
|
|
56
56
|
describe("pullDockerImage", () => {
|
|
57
|
-
const IMAGE = "skyramp/executor:v1.3.
|
|
57
|
+
const IMAGE = "skyramp/executor:v1.3.23";
|
|
58
58
|
beforeEach(() => jest.clearAllMocks());
|
|
59
59
|
describe("on amd64 host", () => {
|
|
60
60
|
const originalArch = process.arch;
|
package/build/utils/versions.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@skyramp/mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"main": "build/index.js",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": "./build/index.js",
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
"dependencies": {
|
|
56
56
|
"@modelcontextprotocol/sdk": "^1.24.3",
|
|
57
57
|
"@playwright/test": "^1.55.0",
|
|
58
|
-
"@skyramp/skyramp": "1.3.
|
|
58
|
+
"@skyramp/skyramp": "1.3.23",
|
|
59
59
|
"dockerode": "^5.0.0",
|
|
60
60
|
"fast-glob": "^3.3.3",
|
|
61
61
|
"js-yaml": "^4.1.1",
|