@testspectra/skills 1.2.41 → 1.2.43
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.
|
@@ -48,24 +48,32 @@ spectra inspect continue # run to the next breakpoint or the end
|
|
|
48
48
|
spectra inspect abort # fail the case here and stop the run (no hooks after it)
|
|
49
49
|
```
|
|
50
50
|
|
|
51
|
+
A breakpoint on an argument's line of a call written over several lines pauses at that call. One that never
|
|
52
|
+
paused (a blank or comment line, code that did not run) is named at the end of the run (`⚠ --break-at … never paused`).
|
|
53
|
+
|
|
51
54
|
Try a fix with `exec` on the paused page, then put it in the script. A pause nobody resumes continues
|
|
52
55
|
by itself after 15 minutes (`TESTSPECTRA_BREAK_TIMEOUT_MS`). Breakpoints run one worker and lift
|
|
53
56
|
the test timeout.
|
|
54
57
|
|
|
55
58
|
## 1. Signals and what they mean
|
|
56
59
|
|
|
57
|
-
| Signal
|
|
58
|
-
|
|
|
59
|
-
| `── failure context ──` (`[TESTSPECTRA_FAILURE_CONTEXT]` as JSON)
|
|
60
|
-
| `"resolved":"<button aria-label=…>"` on a click/type step
|
|
61
|
-
| `⚠ the page received N keydown events/s`
|
|
62
|
-
| `⚠ the page's main thread is busy` / `CDP command … timed out during step #N`
|
|
63
|
-
|
|
|
64
|
-
|
|
|
65
|
-
| `[
|
|
66
|
-
|
|
|
67
|
-
| `⚠
|
|
68
|
-
| `
|
|
60
|
+
| Signal | Meaning | Do |
|
|
61
|
+
| --------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
62
|
+
| `── failure context ──` (`[TESTSPECTRA_FAILURE_CONTEXT]` as JSON) | The page when the test failed: the `Spectra.step` it failed in (`in step:`), last step, URL, focused element, open dialogs, input rates, CDP calls, a screenshot | Open the screenshot; check the focused element and the dialogs against what the step expected |
|
|
63
|
+
| `"resolved":"<button aria-label=…>"` on a click/type step | The element the action actually hit | If it is not the one you meant, the lookup is wrong (a position, a repeated name) |
|
|
64
|
+
| `⚠ the page received N keydown events/s` | Input is repeating (a stuck key, a script re-dispatching) | Find the step named in the warning; the page is unusable after it |
|
|
65
|
+
| `⚠ the page's main thread is busy` / `CDP command … timed out during step #N` | The page did not answer a command in 10 s; the rest of the message says what hangs (the next three rows) | Look for an input storm or long tasks first, not for a longer timeout |
|
|
66
|
+
| `… The page still answers: this command alone waited too long` | Nothing hangs: the command waited on a promise (a request, an animation) | Look at what the step waits for |
|
|
67
|
+
| `… The browser still answers but the page does not: this tab hangs` | The app froze the tab: a loop, heavy synchronous work, a huge render, memory running out | Find the code the step triggered; the next test case gets a new tab and runs |
|
|
68
|
+
| `… The browser did not answer either …: it hangs` / `[TestSpectra] The browser stopped answering: started a new one.` | The browser hung or crashed; the runner started a new one and ran the test cases that had no page again, once | The test case that was running keeps its failure; one still failing with `No browser page to run the test case in … The test case did not run` never ran |
|
|
69
|
+
| `[TESTSPECTRA_INTERCEPT] '<pattern>' (METHOD) answered METHOD <url>` | Which mock answered which request | A URL you did not mean: narrow the pattern (a glob matches the whole URL) |
|
|
70
|
+
| `⚠ intercept '<pattern>' … answered no request` | The mock never matched | Check its URL and method |
|
|
71
|
+
| `[TESTSPECTRA_STATE_CARRIED] … sessionStorage: <keys>` | Storage the previous test case left (`isolation: 'suite'`) | A test that passes alone but fails in the suite: clear those keys in `beforeEach`, or set `webConfig.isolation: 'testcase'` |
|
|
72
|
+
| `⚠ another spectra run (pid …) is testing <url>` | Two runs share one server and reset each other's data | Stop one; results of both runs are unreliable |
|
|
73
|
+
| `⚠ another spectra run (pid …) writes its report to <folder>` | Two runs share one report folder: the one ending last overwrites the other's report, snapshots and screenshots | Give each run its own `-o <own-dir>/result.json` |
|
|
74
|
+
| `⚠ The machine has N MB of memory left; this run's processes hold M MB` | The machine is short of memory: pages slow down, commands time out | Do not blame the app for a timeout after it: run fewer workers (`-c`) or close other programs, then run again |
|
|
75
|
+
| `⚠ -c N starts more workers than the machine has room for now: K (…)` | The workers will compete for CPU or memory | Use `-c K` or fewer; the line names the cores and memory it counted |
|
|
76
|
+
| `Typed text did not reach … (focus was on …; the field is …)` | The keystrokes went elsewhere, or the field was disabled/inert | Wait for the state that enables the field |
|
|
69
77
|
|
|
70
78
|
`network-resources.json` holds every request with its HTTP method, request body and, when a mock
|
|
71
79
|
answered it, `interceptedBy`. The terminal no longer prints one line per request; set
|
|
@@ -100,6 +100,22 @@ intercept handle's, after a deliberate `delayMs`) — see the `network-intercept
|
|
|
100
100
|
await ReportPage.statusBadge.shouldHaveText('Ready', { timeoutMs: 5000 });
|
|
101
101
|
```
|
|
102
102
|
|
|
103
|
+
**Whose failure** — `err.name` tells the app's verdict from the machine's: every `should*` throws `AssertionError`; a hung tab or browser, a closed tab, a device or run timeout throw `CdpTimeoutError`, `BrowserHungError`, `CdpSocketClosedError`, `DriverTimeoutError`, `CaseAbortedError` or `TimeoutError`. To collect several differences from a spec and fail with all of them at the end, catch only `AssertionError` and rethrow the rest (never `.catch(() => {})` around an assertion: that passes the case):
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
const open: string[] = [];
|
|
107
|
+
const check = async (where: string, assertion: () => Promise<void>) => {
|
|
108
|
+
try {
|
|
109
|
+
await assertion();
|
|
110
|
+
} catch (err) {
|
|
111
|
+
if (!(err instanceof Error) || err.name !== 'AssertionError') throw err;
|
|
112
|
+
open.push(`${where}: ${err.message}`);
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
await check('amount', () => Page.amount.shouldHaveValue('10.50'));
|
|
116
|
+
if (open.length > 0) throw new Error(`${open.length} open gap(s):\n${open.join('\n')}`);
|
|
117
|
+
```
|
|
118
|
+
|
|
103
119
|
---
|
|
104
120
|
|
|
105
121
|
## 3. Collection Matchers (on `Spectra.getAll(selector)` getters)
|
|
@@ -141,54 +157,55 @@ collection carry the scope forward too. No behavior difference on web vs. Androi
|
|
|
141
157
|
|
|
142
158
|
## 5. Global `Spectra.*` Commands
|
|
143
159
|
|
|
144
|
-
| Category | Syntax
|
|
145
|
-
| :------------- |
|
|
146
|
-
| Navigation | `await Spectra.navigate("/login")` / `.back()` / `.forward()` / `.refresh()`
|
|
147
|
-
| Step group | `await Spectra.step('TC-12: Sort by name', async () => { … })` — nests the actions under the spec's step title; returns `fn`'s value
|
|
148
|
-
| Element query | `Spectra.get(selector)` / `Spectra.getAll(selector)` — chain `.get()`/`.getAll()` on the result to scope a lookup, see section 4
|
|
149
|
-
| Focus | `await Spectra.focus(target)` / `await Spectra.blur(target)`
|
|
150
|
-
| Coordinate | `await Spectra.clickAt(x, y)` — absolute viewport click
|
|
151
|
-
| Window | `await Spectra.setViewport(1920, 1080)`
|
|
152
|
-
| Timing | `await Spectra.wait(1000)`
|
|
153
|
-
| Keyboard | `await Spectra.pressKey("Enter")`
|
|
154
|
-
| Scroll | `await Spectra.scroll({ direction: "down", pixels: 400 })`
|
|
155
|
-
| Mobile gesture | `await Spectra.swipe({ direction: "left", distance: 300 })`
|
|
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
|
|
160
|
+
| Category | Syntax |
|
|
161
|
+
| :------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
162
|
+
| Navigation | `await Spectra.navigate("/login")` / `.back()` / `.forward()` / `.refresh()` |
|
|
163
|
+
| Step group | `await Spectra.step('TC-12: Sort by name', async () => { … })` — nests the actions under the spec's step title; returns `fn`'s value |
|
|
164
|
+
| Element query | `Spectra.get(selector)` / `Spectra.getAll(selector)` — chain `.get()`/`.getAll()` on the result to scope a lookup, see section 4 |
|
|
165
|
+
| Focus | `await Spectra.focus(target)` / `await Spectra.blur(target)` |
|
|
166
|
+
| Coordinate | `await Spectra.clickAt(x, y)` — absolute viewport click |
|
|
167
|
+
| Window | `await Spectra.setViewport(1920, 1080)` |
|
|
168
|
+
| Timing | `await Spectra.wait(1000)` |
|
|
169
|
+
| Keyboard | `await Spectra.pressKey("Enter")`, chords `"Shift+Tab"`, `"ControlOrMeta+a"` (select all: Command on macOS, Control elsewhere); type text with `el.type()` |
|
|
170
|
+
| Scroll | `await Spectra.scroll({ direction: "down", pixels: 400 })` |
|
|
171
|
+
| Mobile gesture | `await Spectra.swipe({ direction: "left", distance: 300 })` |
|
|
172
|
+
| Network mock | `const mock = await Spectra.intercept({ url, method, response })` — see the `network-interception` skill |
|
|
173
|
+
| Network spy | `const api = await Spectra.spy(pattern, method); ...; await api.waitForResponse()` — see the `network-interception` skill |
|
|
158
174
|
|
|
159
175
|
## 6. `Spectra.browser.*` — Browser/Context Level
|
|
160
176
|
|
|
161
|
-
| Syntax
|
|
162
|
-
|
|
|
163
|
-
| `await Spectra.browser.clickAt(x, y)`
|
|
164
|
-
| `await Spectra.browser.clearCookies()`
|
|
165
|
-
| `await Spectra.browser.clearLocalStorage()`
|
|
166
|
-
| `await Spectra.browser.shouldHaveUrl(url)`
|
|
167
|
-
| `await Spectra.browser.shouldContainUrl(substr)`
|
|
168
|
-
| `await Spectra.browser.shouldHaveTitle(title)`
|
|
169
|
-
| `await Spectra.browser.shouldContainTitle(substr)`
|
|
170
|
-
| `await Spectra.browser.shouldBeLoaded()`
|
|
171
|
-
| `await Spectra.browser.shouldHaveNoConsoleErrors()`
|
|
172
|
-
| `await Spectra.browser.openTab(url?)`
|
|
173
|
-
| `await Spectra.browser.openTab(url?, { isolated: true })`
|
|
174
|
-
| `await Spectra.browser.switchToTab(index)`
|
|
175
|
-
| `await Spectra.browser.closeTab()`
|
|
176
|
-
| `await Spectra.browser.openTab({ userAgent })`
|
|
177
|
-
| `await Spectra.browser.shouldHaveTabCount(n)`
|
|
178
|
-
| `await Spectra.browser.getClipboardText()`
|
|
179
|
-
| `await Spectra.browser.shouldHaveClipboardText(text)`
|
|
180
|
-
| `await Spectra.browser.getPageLoadCount(tab?)`
|
|
181
|
-
| `await Spectra.browser.shouldHavePageLoadCount(n, { tab? })`
|
|
182
|
-
| `await Spectra.browser.waitForDownload({ timeout? })`
|
|
183
|
-
| `await file.shouldHaveRows(rows, { header? })`
|
|
184
|
-
| `await file.shouldContainRow(row)`
|
|
185
|
-
| `await Spectra.browser.shouldHaveDownloaded(name \| /re/)`
|
|
186
|
-
| `await Spectra.browser.shouldNotHaveDownloaded({ within? })`
|
|
187
|
-
| `await Spectra.browser.shouldHaveDownloadCount(n)`
|
|
188
|
-
| `await Spectra.browser.installClock()`
|
|
189
|
-
| `await Spectra.browser.advanceClock(ms)`
|
|
190
|
-
| `await Spectra.browser.
|
|
191
|
-
| `await Spectra.browser.
|
|
177
|
+
| Syntax | Purpose |
|
|
178
|
+
| :----------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------ |
|
|
179
|
+
| `await Spectra.browser.clickAt(x, y)` | Click at viewport coordinates |
|
|
180
|
+
| `await Spectra.browser.clearCookies()` | Clear all cookies |
|
|
181
|
+
| `await Spectra.browser.clearLocalStorage()` | Clear localStorage |
|
|
182
|
+
| `await Spectra.browser.shouldHaveUrl(url)` | Assert exact URL |
|
|
183
|
+
| `await Spectra.browser.shouldContainUrl(substr)` | Assert URL contains substring |
|
|
184
|
+
| `await Spectra.browser.shouldHaveTitle(title)` | Assert exact document title |
|
|
185
|
+
| `await Spectra.browser.shouldContainTitle(substr)` | Assert title contains substring |
|
|
186
|
+
| `await Spectra.browser.shouldBeLoaded()` | Assert page fully loaded |
|
|
187
|
+
| `await Spectra.browser.shouldHaveNoConsoleErrors()` | Assert zero console errors |
|
|
188
|
+
| `await Spectra.browser.openTab(url?)` | Open a tab in the same session and act on it |
|
|
189
|
+
| `await Spectra.browser.openTab(url?, { isolated: true })` | Open a tab as a second browser: own cookies and storage |
|
|
190
|
+
| `await Spectra.browser.switchToTab(index)` | Act on tab `index` (0 = first; tabs the app opens follow in order) |
|
|
191
|
+
| `await Spectra.browser.closeTab()` | Close the active tab (any but the last), act on the one before |
|
|
192
|
+
| `await Spectra.browser.openTab({ userAgent })` | Open a tab whose user agent is `userAgent` (also with `isolated`) |
|
|
193
|
+
| `await Spectra.browser.shouldHaveTabCount(n)` | Assert `n` tabs open |
|
|
194
|
+
| `await Spectra.browser.getClipboardText()` | Read the page clipboard (origin granted `clipboardReadWrite`) |
|
|
195
|
+
| `await Spectra.browser.shouldHaveClipboardText(text)` | Assert the clipboard text, polling |
|
|
196
|
+
| `await Spectra.browser.getPageLoadCount(tab?)` | Loads of a tab since the test case began / the tab attached |
|
|
197
|
+
| `await Spectra.browser.shouldHavePageLoadCount(n, { tab? })` | Assert a tab's load count: only the refreshed tab grew |
|
|
198
|
+
| `await Spectra.browser.waitForDownload({ timeout? })` | Next file the page downloads: `{ fileName, path, size, text(), rows() }` |
|
|
199
|
+
| `await file.shouldHaveRows(rows, { header? })` | Assert the CSV / first XLSX sheet; `header: false`: `rows` omit the header row |
|
|
200
|
+
| `await file.shouldContainRow(row)` | Assert one row of the file equals `row` |
|
|
201
|
+
| `await Spectra.browser.shouldHaveDownloaded(name \| /re/)` | Assert a download with that name completed, polling |
|
|
202
|
+
| `await Spectra.browser.shouldNotHaveDownloaded({ within? })` | Assert no download this test case, nor in `within` ms (1000) |
|
|
203
|
+
| `await Spectra.browser.shouldHaveDownloadCount(n)` | Assert `n` downloads completed |
|
|
204
|
+
| `await Spectra.browser.installClock()` | Fake page clock, before the page loads |
|
|
205
|
+
| `await Spectra.browser.advanceClock(ms)` | Jump the clock; timers that fall due run |
|
|
206
|
+
| `await Spectra.browser.answerDialogs({ beforeunload: 'dismiss' })` | Answer the case's next dialogs: `'dismiss'` is Cancel / Stay on page, `confirm: 'accept'` is OK, `promptText` |
|
|
207
|
+
| `await Spectra.browser.shouldHaveOpenedDialog(msg?)` | Assert a JS dialog opened (each is answered as it opens) |
|
|
208
|
+
| `await Spectra.browser.shouldNotHaveOpenedDialog()` | Assert none opened, nor opens for 500ms |
|
|
192
209
|
|
|
193
210
|
Tabs, the clock, dialogs and downloads are web only, and reset when the next test case starts: one tab, no fake clock, no dialogs or downloads recorded. Install the clock before `Spectra.navigate`: timers the current document started earlier do not move.
|
|
194
211
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@testspectra/skills",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.43",
|
|
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",
|
|
@@ -48,24 +48,32 @@ spectra inspect continue # run to the next breakpoint or the end
|
|
|
48
48
|
spectra inspect abort # fail the case here and stop the run (no hooks after it)
|
|
49
49
|
```
|
|
50
50
|
|
|
51
|
+
A breakpoint on an argument's line of a call written over several lines pauses at that call. One that never
|
|
52
|
+
paused (a blank or comment line, code that did not run) is named at the end of the run (`⚠ --break-at … never paused`).
|
|
53
|
+
|
|
51
54
|
Try a fix with `exec` on the paused page, then put it in the script. A pause nobody resumes continues
|
|
52
55
|
by itself after 15 minutes (`TESTSPECTRA_BREAK_TIMEOUT_MS`). Breakpoints run one worker and lift
|
|
53
56
|
the test timeout.
|
|
54
57
|
|
|
55
58
|
## 1. Signals and what they mean
|
|
56
59
|
|
|
57
|
-
| Signal
|
|
58
|
-
|
|
|
59
|
-
| `── failure context ──` (`[TESTSPECTRA_FAILURE_CONTEXT]` as JSON)
|
|
60
|
-
| `"resolved":"<button aria-label=…>"` on a click/type step
|
|
61
|
-
| `⚠ the page received N keydown events/s`
|
|
62
|
-
| `⚠ the page's main thread is busy` / `CDP command … timed out during step #N`
|
|
63
|
-
|
|
|
64
|
-
|
|
|
65
|
-
| `[
|
|
66
|
-
|
|
|
67
|
-
| `⚠
|
|
68
|
-
| `
|
|
60
|
+
| Signal | Meaning | Do |
|
|
61
|
+
| --------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
62
|
+
| `── failure context ──` (`[TESTSPECTRA_FAILURE_CONTEXT]` as JSON) | The page when the test failed: the `Spectra.step` it failed in (`in step:`), last step, URL, focused element, open dialogs, input rates, CDP calls, a screenshot | Open the screenshot; check the focused element and the dialogs against what the step expected |
|
|
63
|
+
| `"resolved":"<button aria-label=…>"` on a click/type step | The element the action actually hit | If it is not the one you meant, the lookup is wrong (a position, a repeated name) |
|
|
64
|
+
| `⚠ the page received N keydown events/s` | Input is repeating (a stuck key, a script re-dispatching) | Find the step named in the warning; the page is unusable after it |
|
|
65
|
+
| `⚠ the page's main thread is busy` / `CDP command … timed out during step #N` | The page did not answer a command in 10 s; the rest of the message says what hangs (the next three rows) | Look for an input storm or long tasks first, not for a longer timeout |
|
|
66
|
+
| `… The page still answers: this command alone waited too long` | Nothing hangs: the command waited on a promise (a request, an animation) | Look at what the step waits for |
|
|
67
|
+
| `… The browser still answers but the page does not: this tab hangs` | The app froze the tab: a loop, heavy synchronous work, a huge render, memory running out | Find the code the step triggered; the next test case gets a new tab and runs |
|
|
68
|
+
| `… The browser did not answer either …: it hangs` / `[TestSpectra] The browser stopped answering: started a new one.` | The browser hung or crashed; the runner started a new one and ran the test cases that had no page again, once | The test case that was running keeps its failure; one still failing with `No browser page to run the test case in … The test case did not run` never ran |
|
|
69
|
+
| `[TESTSPECTRA_INTERCEPT] '<pattern>' (METHOD) answered METHOD <url>` | Which mock answered which request | A URL you did not mean: narrow the pattern (a glob matches the whole URL) |
|
|
70
|
+
| `⚠ intercept '<pattern>' … answered no request` | The mock never matched | Check its URL and method |
|
|
71
|
+
| `[TESTSPECTRA_STATE_CARRIED] … sessionStorage: <keys>` | Storage the previous test case left (`isolation: 'suite'`) | A test that passes alone but fails in the suite: clear those keys in `beforeEach`, or set `webConfig.isolation: 'testcase'` |
|
|
72
|
+
| `⚠ another spectra run (pid …) is testing <url>` | Two runs share one server and reset each other's data | Stop one; results of both runs are unreliable |
|
|
73
|
+
| `⚠ another spectra run (pid …) writes its report to <folder>` | Two runs share one report folder: the one ending last overwrites the other's report, snapshots and screenshots | Give each run its own `-o <own-dir>/result.json` |
|
|
74
|
+
| `⚠ The machine has N MB of memory left; this run's processes hold M MB` | The machine is short of memory: pages slow down, commands time out | Do not blame the app for a timeout after it: run fewer workers (`-c`) or close other programs, then run again |
|
|
75
|
+
| `⚠ -c N starts more workers than the machine has room for now: K (…)` | The workers will compete for CPU or memory | Use `-c K` or fewer; the line names the cores and memory it counted |
|
|
76
|
+
| `Typed text did not reach … (focus was on …; the field is …)` | The keystrokes went elsewhere, or the field was disabled/inert | Wait for the state that enables the field |
|
|
69
77
|
|
|
70
78
|
`network-resources.json` holds every request with its HTTP method, request body and, when a mock
|
|
71
79
|
answered it, `interceptedBy`. The terminal no longer prints one line per request; set
|
|
@@ -100,6 +100,22 @@ intercept handle's, after a deliberate `delayMs`) — see the `network-intercept
|
|
|
100
100
|
await ReportPage.statusBadge.shouldHaveText('Ready', { timeoutMs: 5000 });
|
|
101
101
|
```
|
|
102
102
|
|
|
103
|
+
**Whose failure** — `err.name` tells the app's verdict from the machine's: every `should*` throws `AssertionError`; a hung tab or browser, a closed tab, a device or run timeout throw `CdpTimeoutError`, `BrowserHungError`, `CdpSocketClosedError`, `DriverTimeoutError`, `CaseAbortedError` or `TimeoutError`. To collect several differences from a spec and fail with all of them at the end, catch only `AssertionError` and rethrow the rest (never `.catch(() => {})` around an assertion: that passes the case):
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
const open: string[] = [];
|
|
107
|
+
const check = async (where: string, assertion: () => Promise<void>) => {
|
|
108
|
+
try {
|
|
109
|
+
await assertion();
|
|
110
|
+
} catch (err) {
|
|
111
|
+
if (!(err instanceof Error) || err.name !== 'AssertionError') throw err;
|
|
112
|
+
open.push(`${where}: ${err.message}`);
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
await check('amount', () => Page.amount.shouldHaveValue('10.50'));
|
|
116
|
+
if (open.length > 0) throw new Error(`${open.length} open gap(s):\n${open.join('\n')}`);
|
|
117
|
+
```
|
|
118
|
+
|
|
103
119
|
---
|
|
104
120
|
|
|
105
121
|
## 3. Collection Matchers (on `Spectra.getAll(selector)` getters)
|
|
@@ -141,54 +157,55 @@ collection carry the scope forward too. No behavior difference on web vs. Androi
|
|
|
141
157
|
|
|
142
158
|
## 5. Global `Spectra.*` Commands
|
|
143
159
|
|
|
144
|
-
| Category | Syntax
|
|
145
|
-
| :------------- |
|
|
146
|
-
| Navigation | `await Spectra.navigate("/login")` / `.back()` / `.forward()` / `.refresh()`
|
|
147
|
-
| Step group | `await Spectra.step('TC-12: Sort by name', async () => { … })` — nests the actions under the spec's step title; returns `fn`'s value
|
|
148
|
-
| Element query | `Spectra.get(selector)` / `Spectra.getAll(selector)` — chain `.get()`/`.getAll()` on the result to scope a lookup, see section 4
|
|
149
|
-
| Focus | `await Spectra.focus(target)` / `await Spectra.blur(target)`
|
|
150
|
-
| Coordinate | `await Spectra.clickAt(x, y)` — absolute viewport click
|
|
151
|
-
| Window | `await Spectra.setViewport(1920, 1080)`
|
|
152
|
-
| Timing | `await Spectra.wait(1000)`
|
|
153
|
-
| Keyboard | `await Spectra.pressKey("Enter")`
|
|
154
|
-
| Scroll | `await Spectra.scroll({ direction: "down", pixels: 400 })`
|
|
155
|
-
| Mobile gesture | `await Spectra.swipe({ direction: "left", distance: 300 })`
|
|
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
|
|
160
|
+
| Category | Syntax |
|
|
161
|
+
| :------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
162
|
+
| Navigation | `await Spectra.navigate("/login")` / `.back()` / `.forward()` / `.refresh()` |
|
|
163
|
+
| Step group | `await Spectra.step('TC-12: Sort by name', async () => { … })` — nests the actions under the spec's step title; returns `fn`'s value |
|
|
164
|
+
| Element query | `Spectra.get(selector)` / `Spectra.getAll(selector)` — chain `.get()`/`.getAll()` on the result to scope a lookup, see section 4 |
|
|
165
|
+
| Focus | `await Spectra.focus(target)` / `await Spectra.blur(target)` |
|
|
166
|
+
| Coordinate | `await Spectra.clickAt(x, y)` — absolute viewport click |
|
|
167
|
+
| Window | `await Spectra.setViewport(1920, 1080)` |
|
|
168
|
+
| Timing | `await Spectra.wait(1000)` |
|
|
169
|
+
| Keyboard | `await Spectra.pressKey("Enter")`, chords `"Shift+Tab"`, `"ControlOrMeta+a"` (select all: Command on macOS, Control elsewhere); type text with `el.type()` |
|
|
170
|
+
| Scroll | `await Spectra.scroll({ direction: "down", pixels: 400 })` |
|
|
171
|
+
| Mobile gesture | `await Spectra.swipe({ direction: "left", distance: 300 })` |
|
|
172
|
+
| Network mock | `const mock = await Spectra.intercept({ url, method, response })` — see the `network-interception` skill |
|
|
173
|
+
| Network spy | `const api = await Spectra.spy(pattern, method); ...; await api.waitForResponse()` — see the `network-interception` skill |
|
|
158
174
|
|
|
159
175
|
## 6. `Spectra.browser.*` — Browser/Context Level
|
|
160
176
|
|
|
161
|
-
| Syntax
|
|
162
|
-
|
|
|
163
|
-
| `await Spectra.browser.clickAt(x, y)`
|
|
164
|
-
| `await Spectra.browser.clearCookies()`
|
|
165
|
-
| `await Spectra.browser.clearLocalStorage()`
|
|
166
|
-
| `await Spectra.browser.shouldHaveUrl(url)`
|
|
167
|
-
| `await Spectra.browser.shouldContainUrl(substr)`
|
|
168
|
-
| `await Spectra.browser.shouldHaveTitle(title)`
|
|
169
|
-
| `await Spectra.browser.shouldContainTitle(substr)`
|
|
170
|
-
| `await Spectra.browser.shouldBeLoaded()`
|
|
171
|
-
| `await Spectra.browser.shouldHaveNoConsoleErrors()`
|
|
172
|
-
| `await Spectra.browser.openTab(url?)`
|
|
173
|
-
| `await Spectra.browser.openTab(url?, { isolated: true })`
|
|
174
|
-
| `await Spectra.browser.switchToTab(index)`
|
|
175
|
-
| `await Spectra.browser.closeTab()`
|
|
176
|
-
| `await Spectra.browser.openTab({ userAgent })`
|
|
177
|
-
| `await Spectra.browser.shouldHaveTabCount(n)`
|
|
178
|
-
| `await Spectra.browser.getClipboardText()`
|
|
179
|
-
| `await Spectra.browser.shouldHaveClipboardText(text)`
|
|
180
|
-
| `await Spectra.browser.getPageLoadCount(tab?)`
|
|
181
|
-
| `await Spectra.browser.shouldHavePageLoadCount(n, { tab? })`
|
|
182
|
-
| `await Spectra.browser.waitForDownload({ timeout? })`
|
|
183
|
-
| `await file.shouldHaveRows(rows, { header? })`
|
|
184
|
-
| `await file.shouldContainRow(row)`
|
|
185
|
-
| `await Spectra.browser.shouldHaveDownloaded(name \| /re/)`
|
|
186
|
-
| `await Spectra.browser.shouldNotHaveDownloaded({ within? })`
|
|
187
|
-
| `await Spectra.browser.shouldHaveDownloadCount(n)`
|
|
188
|
-
| `await Spectra.browser.installClock()`
|
|
189
|
-
| `await Spectra.browser.advanceClock(ms)`
|
|
190
|
-
| `await Spectra.browser.
|
|
191
|
-
| `await Spectra.browser.
|
|
177
|
+
| Syntax | Purpose |
|
|
178
|
+
| :----------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------ |
|
|
179
|
+
| `await Spectra.browser.clickAt(x, y)` | Click at viewport coordinates |
|
|
180
|
+
| `await Spectra.browser.clearCookies()` | Clear all cookies |
|
|
181
|
+
| `await Spectra.browser.clearLocalStorage()` | Clear localStorage |
|
|
182
|
+
| `await Spectra.browser.shouldHaveUrl(url)` | Assert exact URL |
|
|
183
|
+
| `await Spectra.browser.shouldContainUrl(substr)` | Assert URL contains substring |
|
|
184
|
+
| `await Spectra.browser.shouldHaveTitle(title)` | Assert exact document title |
|
|
185
|
+
| `await Spectra.browser.shouldContainTitle(substr)` | Assert title contains substring |
|
|
186
|
+
| `await Spectra.browser.shouldBeLoaded()` | Assert page fully loaded |
|
|
187
|
+
| `await Spectra.browser.shouldHaveNoConsoleErrors()` | Assert zero console errors |
|
|
188
|
+
| `await Spectra.browser.openTab(url?)` | Open a tab in the same session and act on it |
|
|
189
|
+
| `await Spectra.browser.openTab(url?, { isolated: true })` | Open a tab as a second browser: own cookies and storage |
|
|
190
|
+
| `await Spectra.browser.switchToTab(index)` | Act on tab `index` (0 = first; tabs the app opens follow in order) |
|
|
191
|
+
| `await Spectra.browser.closeTab()` | Close the active tab (any but the last), act on the one before |
|
|
192
|
+
| `await Spectra.browser.openTab({ userAgent })` | Open a tab whose user agent is `userAgent` (also with `isolated`) |
|
|
193
|
+
| `await Spectra.browser.shouldHaveTabCount(n)` | Assert `n` tabs open |
|
|
194
|
+
| `await Spectra.browser.getClipboardText()` | Read the page clipboard (origin granted `clipboardReadWrite`) |
|
|
195
|
+
| `await Spectra.browser.shouldHaveClipboardText(text)` | Assert the clipboard text, polling |
|
|
196
|
+
| `await Spectra.browser.getPageLoadCount(tab?)` | Loads of a tab since the test case began / the tab attached |
|
|
197
|
+
| `await Spectra.browser.shouldHavePageLoadCount(n, { tab? })` | Assert a tab's load count: only the refreshed tab grew |
|
|
198
|
+
| `await Spectra.browser.waitForDownload({ timeout? })` | Next file the page downloads: `{ fileName, path, size, text(), rows() }` |
|
|
199
|
+
| `await file.shouldHaveRows(rows, { header? })` | Assert the CSV / first XLSX sheet; `header: false`: `rows` omit the header row |
|
|
200
|
+
| `await file.shouldContainRow(row)` | Assert one row of the file equals `row` |
|
|
201
|
+
| `await Spectra.browser.shouldHaveDownloaded(name \| /re/)` | Assert a download with that name completed, polling |
|
|
202
|
+
| `await Spectra.browser.shouldNotHaveDownloaded({ within? })` | Assert no download this test case, nor in `within` ms (1000) |
|
|
203
|
+
| `await Spectra.browser.shouldHaveDownloadCount(n)` | Assert `n` downloads completed |
|
|
204
|
+
| `await Spectra.browser.installClock()` | Fake page clock, before the page loads |
|
|
205
|
+
| `await Spectra.browser.advanceClock(ms)` | Jump the clock; timers that fall due run |
|
|
206
|
+
| `await Spectra.browser.answerDialogs({ beforeunload: 'dismiss' })` | Answer the case's next dialogs: `'dismiss'` is Cancel / Stay on page, `confirm: 'accept'` is OK, `promptText` |
|
|
207
|
+
| `await Spectra.browser.shouldHaveOpenedDialog(msg?)` | Assert a JS dialog opened (each is answered as it opens) |
|
|
208
|
+
| `await Spectra.browser.shouldNotHaveOpenedDialog()` | Assert none opened, nor opens for 500ms |
|
|
192
209
|
|
|
193
210
|
Tabs, the clock, dialogs and downloads are web only, and reset when the next test case starts: one tab, no fake clock, no dialogs or downloads recorded. Install the clock before `Spectra.navigate`: timers the current document started earlier do not move.
|
|
194
211
|
|