@skyramp/mcp 0.1.0 → 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/prompts/test-recommendation/test-recommendation-prompt.js +146 -27
- package/build/prompts/test-recommendation/test-recommendation-prompt.test.js +202 -5
- package/build/prompts/testbot/testbot-prompts.js +10 -9
- package/build/services/TestDiscoveryService.js +417 -58
- package/build/services/TestDiscoveryService.test.js +361 -0
- package/build/tools/code-refactor/enhanceAssertionsTool.js +8 -10
- package/build/tools/test-management/actionsTool.js +4 -1
- package/build/tools/test-management/analyzeChangesTool.js +76 -9
- package/build/tools/test-management/analyzeTestHealthTool.js +6 -2
- package/build/types/RepositoryAnalysis.js +1 -0
- package/build/types/TestAnalysis.js +6 -1
- package/build/utils/docker.test.js +1 -1
- package/build/utils/routeParsers.js +7 -0
- package/build/utils/routeParsers.test.js +29 -1
- package/build/utils/versions.js +1 -1
- package/package.json +2 -2
|
@@ -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
|
+
}
|