@testspectra/skills 1.2.29 → 1.2.31
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/README.md +1 -1
- package/dist/skills/matchers-and-assertions/SKILL.md +3 -1
- package/dist/skills/network-interception/SKILL.md +47 -3
- package/dist/skills/workspace-structure/SKILL.md +8 -0
- package/package.json +1 -1
- package/skills/matchers-and-assertions/SKILL.md +3 -1
- package/skills/network-interception/SKILL.md +47 -3
- package/skills/workspace-structure/SKILL.md +8 -0
package/README.md
CHANGED
|
@@ -60,7 +60,7 @@ overrides the saved selection for a single invocation.
|
|
|
60
60
|
| `shared-steps-and-actions` | `Step.*` business workflows vs `support/actions/*` technical utilities |
|
|
61
61
|
| `lifecycle-hooks` | `global-hooks/` vs `specs/<suite>/hooks/`, execution order, failure cascade |
|
|
62
62
|
| `fixtures-data` | `fixtures/*.json` and the ambient `Fixture.*` namespace |
|
|
63
|
-
| `network-interception` | `Spectra.intercept()` mocking
|
|
63
|
+
| `network-interception` | `Spectra.intercept()` mocking and failure simulation; `Spectra.spy()` to wait for a real API |
|
|
64
64
|
|
|
65
65
|
Each skill is grounded in `docs/v2/` and the reference workspace at `cli/templates/default`, so an
|
|
66
66
|
AI coding agent scaffolding TestSpectra scripts produces code that already passes `spectra lint`.
|
|
@@ -93,7 +93,8 @@ per platform, traversal keeps one API.
|
|
|
93
93
|
Every assertion above (and every collection matcher in section 3) accepts a trailing
|
|
94
94
|
`options?: { timeoutMs }` to override the adaptive fail-fast timeout for that one call, without
|
|
95
95
|
raising it for every other assertion in the test — use this when a specific assertion is known to
|
|
96
|
-
need longer,
|
|
96
|
+
need longer. To wait for an API, don't raise a timeout: `await spy.waitForResponse()` (or the
|
|
97
|
+
intercept handle's, after a deliberate `delayMs`) — see the `network-interception` skill.
|
|
97
98
|
|
|
98
99
|
```typescript
|
|
99
100
|
await ReportPage.statusBadge.shouldHaveText('Ready', { timeoutMs: 5000 });
|
|
@@ -153,6 +154,7 @@ collection carry the scope forward too. No behavior difference on web vs. Androi
|
|
|
153
154
|
| Scroll | `await Spectra.scroll({ direction: "down", pixels: 400 })` |
|
|
154
155
|
| Mobile gesture | `await Spectra.swipe({ direction: "left", distance: 300 })` |
|
|
155
156
|
| Network mock | `const mock = await Spectra.intercept({ url, method, response })` — see the `network-interception` skill |
|
|
157
|
+
| Network spy | `const api = await Spectra.spy(pattern, method); ...; await api.waitForResponse()` — see the `network-interception` skill |
|
|
156
158
|
|
|
157
159
|
## 6. `Spectra.browser.*` — Browser/Context Level
|
|
158
160
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: network-interception
|
|
3
|
-
description: The Spectra.intercept()
|
|
3
|
+
description: The Spectra.intercept() and Spectra.spy() APIs in TestSpectra — InterceptRule/MockResponse/MockHandle shapes, respondOnce vs respondWith sequential polling, aborting requests, waiting for a real API with spy().waitForResponse(), and waitForCall/shouldHaveCallCount/shouldHaveStatus/shouldHaveRequestBody assertions. Use whenever a test needs to wait for, mock, stub, or assert on an API call.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Skill: Network Interception (`Spectra.intercept`)
|
|
@@ -31,7 +31,10 @@ interface MockHandle {
|
|
|
31
31
|
respondOnce(response: MockResponse): this; // one-time FIFO-queued response
|
|
32
32
|
abort(errorCode?: 'Failed' | 'Aborted' | 'TimedOut' | 'ConnectionReset'): this;
|
|
33
33
|
waitForCall(options?: { timeout?: number; count?: number }): Promise<InterceptedRequest>;
|
|
34
|
+
waitForResponse(options?: { timeout?: number; count?: number }): Promise<CDPNetworkEntry>; // the answer delivered, after delayMs
|
|
34
35
|
shouldHaveCallCount(count: number, options?: { timeout?: number }): Promise<void>; // polls; 0 proves no request went out
|
|
36
|
+
shouldHaveStatus(status: number, options?: { count?: number; timeout?: number }): Promise<void>; // default: the last answer
|
|
37
|
+
shouldHaveRequestBody(expected: string | object, options?: { timeout?: number }): Promise<void>; // object: JSON subset; string: substring
|
|
35
38
|
readonly callCount: number;
|
|
36
39
|
readonly calls: InterceptedRequest[];
|
|
37
40
|
}
|
|
@@ -52,7 +55,11 @@ await Spectra.intercept({
|
|
|
52
55
|
url: '**/api/v1/brands/*',
|
|
53
56
|
method: 'PATCH',
|
|
54
57
|
response: async (request) => {
|
|
55
|
-
const reply = await fetch(request.url, {
|
|
58
|
+
const reply = await fetch(request.url, {
|
|
59
|
+
method: 'PATCH',
|
|
60
|
+
headers: { 'content-type': 'application/json' },
|
|
61
|
+
body: request.postData,
|
|
62
|
+
});
|
|
56
63
|
return { status: reply.status, body: await reply.text(), delayMs: 3000 };
|
|
57
64
|
},
|
|
58
65
|
});
|
|
@@ -123,7 +130,42 @@ it('should handle offline mode gracefully when sync fails', async () => {
|
|
|
123
130
|
|
|
124
131
|
---
|
|
125
132
|
|
|
126
|
-
## 5.
|
|
133
|
+
## 5. Pattern: Waiting for an API (`Spectra.spy`)
|
|
134
|
+
|
|
135
|
+
Wait for an API with a spy, never with a guessed duration (`Spectra.sleep`). The spy leaves the
|
|
136
|
+
request alone: the real server answers. Make it before the step that sends the request.
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
const verify = await Spectra.spy('**/v2/iam/auth/otp/verify', 'POST'); // method default: 'ALL'
|
|
140
|
+
await OtpPage.boxes.setValue('000000');
|
|
141
|
+
await verify.waitForResponse(); // default timeout: the implicit wait
|
|
142
|
+
await verify.shouldHaveStatus(422);
|
|
143
|
+
await verify.shouldHaveRequestBody({ code: '000000' });
|
|
144
|
+
await OtpPage.error.shouldHaveText('Invalid code');
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
The spy handle has the same `waitForResponse` / `shouldHaveCallCount` / `shouldHaveStatus` /
|
|
148
|
+
`shouldHaveRequestBody` / `callCount` / `calls` as above. Only requests answered after the spy is
|
|
149
|
+
made count. `requestBody` is cut to 500 characters: expect a part of a long body as a string.
|
|
150
|
+
|
|
151
|
+
Intercept with `delayMs` only when the step reads the page while the request is in flight; then
|
|
152
|
+
`waitForResponse()` before checking the result:
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
const save = await Spectra.intercept({
|
|
156
|
+
url: '**/api/v1/brands',
|
|
157
|
+
method: 'POST',
|
|
158
|
+
response: { status: 201, body: {}, delayMs: 3000 },
|
|
159
|
+
});
|
|
160
|
+
await BrandPage.saveButton.click();
|
|
161
|
+
await BrandPage.saveButton.shouldBeDisabled(); // read mid-request
|
|
162
|
+
await save.waitForResponse();
|
|
163
|
+
await BrandPage.toast.shouldHaveText('Brand created');
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## 6. Conventions
|
|
127
169
|
|
|
128
170
|
- Prefer glob URL patterns (`'**/api/v1/...'`) over exact origin strings so mocks survive
|
|
129
171
|
environment/base-URL changes between local, staging, and CI.
|
|
@@ -131,6 +173,8 @@ it('should handle offline mode gracefully when sync fails', async () => {
|
|
|
131
173
|
- Only mock what the test is actually about — don't blanket-intercept every domain; the linter and
|
|
132
174
|
reviewers expect `monitoredDomains` in `spectra.config.ts` (see the `workspace-structure` skill)
|
|
133
175
|
to reflect real traffic, and over-mocking hides real integration regressions.
|
|
176
|
+
- Wait for an API with `Spectra.spy(...).waitForResponse()`, not a guessed duration. Intercept +
|
|
177
|
+
`delayMs` only when the step reads the page mid-request, then `waitForResponse()` before checking the result.
|
|
134
178
|
- Always assert on the mock (`waitForCall` / `shouldHaveCallCount(n)`) when the test's purpose is to
|
|
135
179
|
verify the request was made — don't just assert on UI state and silently hope the mock fired. A
|
|
136
180
|
blocked submit: `shouldHaveCallCount(0)` after the `should*` on the state the app settles in.
|
|
@@ -136,6 +136,14 @@ actual for platform "android": add android.action.ts (or the common one) in …`
|
|
|
136
136
|
is never loaded.
|
|
137
137
|
- An entity that is the same everywhere has no contract: one `common.*` file.
|
|
138
138
|
|
|
139
|
+
**Split at the smallest unit.** When a page object, step or flow is the same on every platform but
|
|
140
|
+
one step, keep it one `common.*` file and put only that step behind a contract. Name the contract
|
|
141
|
+
for what the step achieves, not how one platform does it (`passBotCheck`, not `solveRecaptcha`);
|
|
142
|
+
a platform where the step does not exist gets an actual that does nothing. Never copy a whole page
|
|
143
|
+
object or script per platform for one differing step. A script stays `web.test.ts` (or
|
|
144
|
+
`mobile.test.ts`) only when its subject is a mechanism that one platform has (a captcha's caption,
|
|
145
|
+
a browser tab), not when it merely passes through one.
|
|
146
|
+
|
|
139
147
|
---
|
|
140
148
|
|
|
141
149
|
## 5. `spectra.config.ts`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@testspectra/skills",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.31",
|
|
4
4
|
"description": "Installer & manager for modular AI agent skills that standardize TestSpectra script authoring (specs, page objects, steps, actions, hooks, fixtures, matchers)",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agents",
|
|
@@ -93,7 +93,8 @@ per platform, traversal keeps one API.
|
|
|
93
93
|
Every assertion above (and every collection matcher in section 3) accepts a trailing
|
|
94
94
|
`options?: { timeoutMs }` to override the adaptive fail-fast timeout for that one call, without
|
|
95
95
|
raising it for every other assertion in the test — use this when a specific assertion is known to
|
|
96
|
-
need longer,
|
|
96
|
+
need longer. To wait for an API, don't raise a timeout: `await spy.waitForResponse()` (or the
|
|
97
|
+
intercept handle's, after a deliberate `delayMs`) — see the `network-interception` skill.
|
|
97
98
|
|
|
98
99
|
```typescript
|
|
99
100
|
await ReportPage.statusBadge.shouldHaveText('Ready', { timeoutMs: 5000 });
|
|
@@ -153,6 +154,7 @@ collection carry the scope forward too. No behavior difference on web vs. Androi
|
|
|
153
154
|
| Scroll | `await Spectra.scroll({ direction: "down", pixels: 400 })` |
|
|
154
155
|
| Mobile gesture | `await Spectra.swipe({ direction: "left", distance: 300 })` |
|
|
155
156
|
| Network mock | `const mock = await Spectra.intercept({ url, method, response })` — see the `network-interception` skill |
|
|
157
|
+
| Network spy | `const api = await Spectra.spy(pattern, method); ...; await api.waitForResponse()` — see the `network-interception` skill |
|
|
156
158
|
|
|
157
159
|
## 6. `Spectra.browser.*` — Browser/Context Level
|
|
158
160
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: network-interception
|
|
3
|
-
description: The Spectra.intercept()
|
|
3
|
+
description: The Spectra.intercept() and Spectra.spy() APIs in TestSpectra — InterceptRule/MockResponse/MockHandle shapes, respondOnce vs respondWith sequential polling, aborting requests, waiting for a real API with spy().waitForResponse(), and waitForCall/shouldHaveCallCount/shouldHaveStatus/shouldHaveRequestBody assertions. Use whenever a test needs to wait for, mock, stub, or assert on an API call.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Skill: Network Interception (`Spectra.intercept`)
|
|
@@ -31,7 +31,10 @@ interface MockHandle {
|
|
|
31
31
|
respondOnce(response: MockResponse): this; // one-time FIFO-queued response
|
|
32
32
|
abort(errorCode?: 'Failed' | 'Aborted' | 'TimedOut' | 'ConnectionReset'): this;
|
|
33
33
|
waitForCall(options?: { timeout?: number; count?: number }): Promise<InterceptedRequest>;
|
|
34
|
+
waitForResponse(options?: { timeout?: number; count?: number }): Promise<CDPNetworkEntry>; // the answer delivered, after delayMs
|
|
34
35
|
shouldHaveCallCount(count: number, options?: { timeout?: number }): Promise<void>; // polls; 0 proves no request went out
|
|
36
|
+
shouldHaveStatus(status: number, options?: { count?: number; timeout?: number }): Promise<void>; // default: the last answer
|
|
37
|
+
shouldHaveRequestBody(expected: string | object, options?: { timeout?: number }): Promise<void>; // object: JSON subset; string: substring
|
|
35
38
|
readonly callCount: number;
|
|
36
39
|
readonly calls: InterceptedRequest[];
|
|
37
40
|
}
|
|
@@ -52,7 +55,11 @@ await Spectra.intercept({
|
|
|
52
55
|
url: '**/api/v1/brands/*',
|
|
53
56
|
method: 'PATCH',
|
|
54
57
|
response: async (request) => {
|
|
55
|
-
const reply = await fetch(request.url, {
|
|
58
|
+
const reply = await fetch(request.url, {
|
|
59
|
+
method: 'PATCH',
|
|
60
|
+
headers: { 'content-type': 'application/json' },
|
|
61
|
+
body: request.postData,
|
|
62
|
+
});
|
|
56
63
|
return { status: reply.status, body: await reply.text(), delayMs: 3000 };
|
|
57
64
|
},
|
|
58
65
|
});
|
|
@@ -123,7 +130,42 @@ it('should handle offline mode gracefully when sync fails', async () => {
|
|
|
123
130
|
|
|
124
131
|
---
|
|
125
132
|
|
|
126
|
-
## 5.
|
|
133
|
+
## 5. Pattern: Waiting for an API (`Spectra.spy`)
|
|
134
|
+
|
|
135
|
+
Wait for an API with a spy, never with a guessed duration (`Spectra.sleep`). The spy leaves the
|
|
136
|
+
request alone: the real server answers. Make it before the step that sends the request.
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
const verify = await Spectra.spy('**/v2/iam/auth/otp/verify', 'POST'); // method default: 'ALL'
|
|
140
|
+
await OtpPage.boxes.setValue('000000');
|
|
141
|
+
await verify.waitForResponse(); // default timeout: the implicit wait
|
|
142
|
+
await verify.shouldHaveStatus(422);
|
|
143
|
+
await verify.shouldHaveRequestBody({ code: '000000' });
|
|
144
|
+
await OtpPage.error.shouldHaveText('Invalid code');
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
The spy handle has the same `waitForResponse` / `shouldHaveCallCount` / `shouldHaveStatus` /
|
|
148
|
+
`shouldHaveRequestBody` / `callCount` / `calls` as above. Only requests answered after the spy is
|
|
149
|
+
made count. `requestBody` is cut to 500 characters: expect a part of a long body as a string.
|
|
150
|
+
|
|
151
|
+
Intercept with `delayMs` only when the step reads the page while the request is in flight; then
|
|
152
|
+
`waitForResponse()` before checking the result:
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
const save = await Spectra.intercept({
|
|
156
|
+
url: '**/api/v1/brands',
|
|
157
|
+
method: 'POST',
|
|
158
|
+
response: { status: 201, body: {}, delayMs: 3000 },
|
|
159
|
+
});
|
|
160
|
+
await BrandPage.saveButton.click();
|
|
161
|
+
await BrandPage.saveButton.shouldBeDisabled(); // read mid-request
|
|
162
|
+
await save.waitForResponse();
|
|
163
|
+
await BrandPage.toast.shouldHaveText('Brand created');
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## 6. Conventions
|
|
127
169
|
|
|
128
170
|
- Prefer glob URL patterns (`'**/api/v1/...'`) over exact origin strings so mocks survive
|
|
129
171
|
environment/base-URL changes between local, staging, and CI.
|
|
@@ -131,6 +173,8 @@ it('should handle offline mode gracefully when sync fails', async () => {
|
|
|
131
173
|
- Only mock what the test is actually about — don't blanket-intercept every domain; the linter and
|
|
132
174
|
reviewers expect `monitoredDomains` in `spectra.config.ts` (see the `workspace-structure` skill)
|
|
133
175
|
to reflect real traffic, and over-mocking hides real integration regressions.
|
|
176
|
+
- Wait for an API with `Spectra.spy(...).waitForResponse()`, not a guessed duration. Intercept +
|
|
177
|
+
`delayMs` only when the step reads the page mid-request, then `waitForResponse()` before checking the result.
|
|
134
178
|
- Always assert on the mock (`waitForCall` / `shouldHaveCallCount(n)`) when the test's purpose is to
|
|
135
179
|
verify the request was made — don't just assert on UI state and silently hope the mock fired. A
|
|
136
180
|
blocked submit: `shouldHaveCallCount(0)` after the `should*` on the state the app settles in.
|
|
@@ -136,6 +136,14 @@ actual for platform "android": add android.action.ts (or the common one) in …`
|
|
|
136
136
|
is never loaded.
|
|
137
137
|
- An entity that is the same everywhere has no contract: one `common.*` file.
|
|
138
138
|
|
|
139
|
+
**Split at the smallest unit.** When a page object, step or flow is the same on every platform but
|
|
140
|
+
one step, keep it one `common.*` file and put only that step behind a contract. Name the contract
|
|
141
|
+
for what the step achieves, not how one platform does it (`passBotCheck`, not `solveRecaptcha`);
|
|
142
|
+
a platform where the step does not exist gets an actual that does nothing. Never copy a whole page
|
|
143
|
+
object or script per platform for one differing step. A script stays `web.test.ts` (or
|
|
144
|
+
`mobile.test.ts`) only when its subject is a mechanism that one platform has (a captcha's caption,
|
|
145
|
+
a browser tab), not when it merely passes through one.
|
|
146
|
+
|
|
139
147
|
---
|
|
140
148
|
|
|
141
149
|
## 5. `spectra.config.ts`
|