@testspectra/skills 1.1.8-rc.2 → 1.1.8-rc.21
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/LICENSE.md +48 -48
- package/README.md +78 -78
- package/bin/spectra-skills.js +7 -7
- package/dist/index.js +29 -29
- package/dist/skills/fixtures-data/SKILL.md +109 -109
- package/dist/skills/lifecycle-hooks/SKILL.md +114 -114
- package/dist/skills/matchers-and-assertions/SKILL.md +156 -156
- package/dist/skills/network-interception/SKILL.md +115 -115
- package/dist/skills/page-objects-and-selectors/SKILL.md +132 -132
- package/dist/skills/shared-steps-and-actions/SKILL.md +184 -184
- package/dist/skills/spec-and-suite-authoring/SKILL.md +132 -132
- package/dist/skills/workspace-structure/SKILL.md +193 -193
- package/package.json +11 -12
- package/skills/fixtures-data/SKILL.md +109 -109
- package/skills/lifecycle-hooks/SKILL.md +114 -114
- package/skills/matchers-and-assertions/SKILL.md +156 -156
- package/skills/network-interception/SKILL.md +115 -115
- package/skills/page-objects-and-selectors/SKILL.md +132 -132
- package/skills/shared-steps-and-actions/SKILL.md +184 -184
- package/skills/spec-and-suite-authoring/SKILL.md +132 -132
- package/skills/workspace-structure/SKILL.md +193 -193
|
@@ -1,114 +1,114 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: lifecycle-hooks
|
|
3
|
-
description: TestSpectra lifecycle hooks — global-hooks/ (batch-run only) vs specs/<suite>/hooks/ (before, beforeEach, afterEach, after), their execution order in single-test vs global-batch runs, and the failure-cascade rules. Use whenever adding setup/teardown logic instead of duplicating it inside individual test cases.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Skill: Suite & Global Lifecycle Hooks
|
|
7
|
-
|
|
8
|
-
Use this skill whenever you need setup/teardown logic (auth, DB seeding, cookie clearing, failure
|
|
9
|
-
screenshots) that shouldn't be duplicated inside every `it()` body.
|
|
10
|
-
|
|
11
|
-
---
|
|
12
|
-
|
|
13
|
-
## 1. Two Hook Levels — Know the Difference
|
|
14
|
-
|
|
15
|
-
| Hook | Level | Scope | When it runs |
|
|
16
|
-
| :------------------------------- | :----- | :----------------- | :----------------------------------------------------------------------------------------------- |
|
|
17
|
-
| `global-hooks/before` | Global | Entire run session | **Once**, before all suites — **Global Batch Run / CI only**, never during single-test debugging |
|
|
18
|
-
| `global-hooks/after` | Global | Entire run session | **Once**, after all suites — Global Batch Run only |
|
|
19
|
-
| `specs/<suite>/hooks/before` | Suite | Test suite | Once before the first test case in the suite |
|
|
20
|
-
| `specs/<suite>/hooks/beforeEach` | Suite | Test case | Before every test case in the suite |
|
|
21
|
-
| `specs/<suite>/hooks/afterEach` | Suite | Test case | After every test case in the suite |
|
|
22
|
-
| `specs/<suite>/hooks/after` | Suite | Test suite | Once after the last test case in the suite |
|
|
23
|
-
|
|
24
|
-
**Global hooks are skipped when running/debugging a single test case** — put anything a single
|
|
25
|
-
test genuinely depends on (navigating to `/login`, clearing storage) in a **suite** hook instead,
|
|
26
|
-
not a global one, or the test will fail/behave differently when run in isolation.
|
|
27
|
-
|
|
28
|
-
---
|
|
29
|
-
|
|
30
|
-
## 2. Directory Layout
|
|
31
|
-
|
|
32
|
-
```
|
|
33
|
-
workspace-root/
|
|
34
|
-
├── global-hooks/
|
|
35
|
-
│ ├── before/
|
|
36
|
-
│ │ ├── web.hook.ts
|
|
37
|
-
│ │ └── mobile.hook.ts
|
|
38
|
-
│ └── after/
|
|
39
|
-
│ ├── web.hook.ts
|
|
40
|
-
│ └── mobile.hook.ts
|
|
41
|
-
└── specs/
|
|
42
|
-
└── authentication/
|
|
43
|
-
├── suite.md
|
|
44
|
-
├── hooks/
|
|
45
|
-
│ ├── before/web.hook.ts
|
|
46
|
-
│ ├── beforeEach/web.hook.ts # e.g. clear cookies, navigate to /login
|
|
47
|
-
│ ├── afterEach/web.hook.ts # e.g. failure screenshot, clear storage
|
|
48
|
-
│ └── after/web.hook.ts
|
|
49
|
-
├── TC-0001-valid-login/
|
|
50
|
-
└── TC-0002-invalid-password/
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
---
|
|
54
|
-
|
|
55
|
-
## 3. Execution Order
|
|
56
|
-
|
|
57
|
-
### Single Test Case Run (VS Code CodeLens `▶ Run on <Target>`)
|
|
58
|
-
|
|
59
|
-
```
|
|
60
|
-
suite/hooks/before()
|
|
61
|
-
└─► suite/hooks/beforeEach() ─► it('TC-0001') ─► suite/hooks/afterEach()
|
|
62
|
-
suite/hooks/after()
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
Global hooks do **not** run here.
|
|
66
|
-
|
|
67
|
-
### Global Batch Run (`▶ Global Run` / CI/CD)
|
|
68
|
-
|
|
69
|
-
```
|
|
70
|
-
global-hooks/before() (once, whole batch)
|
|
71
|
-
├─► Suite 1: before() → [beforeEach → TC → afterEach]* → after()
|
|
72
|
-
├─► Suite 2: before() → [beforeEach → TC → afterEach]* → after()
|
|
73
|
-
global-hooks/after() (once, whole batch)
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
---
|
|
77
|
-
|
|
78
|
-
## 4. Implementation: Anonymous Async Default Export
|
|
79
|
-
|
|
80
|
-
Same convention as steps/actions — one anonymous async function, default export, zero manual
|
|
81
|
-
imports:
|
|
82
|
-
|
|
83
|
-
```typescript
|
|
84
|
-
// specs/authentication/hooks/beforeEach/web.hook.ts
|
|
85
|
-
export default async function () {
|
|
86
|
-
await Spectra.navigate('/auth/login');
|
|
87
|
-
await Spectra.browser.clearLocalStorage();
|
|
88
|
-
await Spectra.browser.clearCookies();
|
|
89
|
-
}
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
```typescript
|
|
93
|
-
// global-hooks/before/web.hook.ts
|
|
94
|
-
export default async function () {
|
|
95
|
-
const response = await fetch(`${process.env.BASE_URL}/health`);
|
|
96
|
-
if (!response.ok) {
|
|
97
|
-
throw new Error(`Target environment is not healthy: ${response.statusText}`);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
---
|
|
103
|
-
|
|
104
|
-
## 5. Failure Cascade Rules
|
|
105
|
-
|
|
106
|
-
1. **`beforeEach` fails** → that test case is marked `failed` (Hook Failure), its `it()` body is
|
|
107
|
-
skipped, but `afterEach` still runs for cleanup.
|
|
108
|
-
2. **Suite `before` fails** → every test case in that suite is marked `skipped`/`error`; suite
|
|
109
|
-
`after` still runs to clean up partial resources.
|
|
110
|
-
3. **`global-hooks/before` fails** → the entire batch run aborts immediately; `global-hooks/after`
|
|
111
|
-
still runs to finalize cleanup.
|
|
112
|
-
|
|
113
|
-
Write hook bodies defensively with this cascade in mind — teardown logic in `after`/`afterEach`
|
|
114
|
-
must not assume `before`/`beforeEach` fully succeeded.
|
|
1
|
+
---
|
|
2
|
+
name: lifecycle-hooks
|
|
3
|
+
description: TestSpectra lifecycle hooks — global-hooks/ (batch-run only) vs specs/<suite>/hooks/ (before, beforeEach, afterEach, after), their execution order in single-test vs global-batch runs, and the failure-cascade rules. Use whenever adding setup/teardown logic instead of duplicating it inside individual test cases.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Skill: Suite & Global Lifecycle Hooks
|
|
7
|
+
|
|
8
|
+
Use this skill whenever you need setup/teardown logic (auth, DB seeding, cookie clearing, failure
|
|
9
|
+
screenshots) that shouldn't be duplicated inside every `it()` body.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## 1. Two Hook Levels — Know the Difference
|
|
14
|
+
|
|
15
|
+
| Hook | Level | Scope | When it runs |
|
|
16
|
+
| :------------------------------- | :----- | :----------------- | :----------------------------------------------------------------------------------------------- |
|
|
17
|
+
| `global-hooks/before` | Global | Entire run session | **Once**, before all suites — **Global Batch Run / CI only**, never during single-test debugging |
|
|
18
|
+
| `global-hooks/after` | Global | Entire run session | **Once**, after all suites — Global Batch Run only |
|
|
19
|
+
| `specs/<suite>/hooks/before` | Suite | Test suite | Once before the first test case in the suite |
|
|
20
|
+
| `specs/<suite>/hooks/beforeEach` | Suite | Test case | Before every test case in the suite |
|
|
21
|
+
| `specs/<suite>/hooks/afterEach` | Suite | Test case | After every test case in the suite |
|
|
22
|
+
| `specs/<suite>/hooks/after` | Suite | Test suite | Once after the last test case in the suite |
|
|
23
|
+
|
|
24
|
+
**Global hooks are skipped when running/debugging a single test case** — put anything a single
|
|
25
|
+
test genuinely depends on (navigating to `/login`, clearing storage) in a **suite** hook instead,
|
|
26
|
+
not a global one, or the test will fail/behave differently when run in isolation.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## 2. Directory Layout
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
workspace-root/
|
|
34
|
+
├── global-hooks/
|
|
35
|
+
│ ├── before/
|
|
36
|
+
│ │ ├── web.hook.ts
|
|
37
|
+
│ │ └── mobile.hook.ts
|
|
38
|
+
│ └── after/
|
|
39
|
+
│ ├── web.hook.ts
|
|
40
|
+
│ └── mobile.hook.ts
|
|
41
|
+
└── specs/
|
|
42
|
+
└── authentication/
|
|
43
|
+
├── suite.md
|
|
44
|
+
├── hooks/
|
|
45
|
+
│ ├── before/web.hook.ts
|
|
46
|
+
│ ├── beforeEach/web.hook.ts # e.g. clear cookies, navigate to /login
|
|
47
|
+
│ ├── afterEach/web.hook.ts # e.g. failure screenshot, clear storage
|
|
48
|
+
│ └── after/web.hook.ts
|
|
49
|
+
├── TC-0001-valid-login/
|
|
50
|
+
└── TC-0002-invalid-password/
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## 3. Execution Order
|
|
56
|
+
|
|
57
|
+
### Single Test Case Run (VS Code CodeLens `▶ Run on <Target>`)
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
suite/hooks/before()
|
|
61
|
+
└─► suite/hooks/beforeEach() ─► it('TC-0001') ─► suite/hooks/afterEach()
|
|
62
|
+
suite/hooks/after()
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Global hooks do **not** run here.
|
|
66
|
+
|
|
67
|
+
### Global Batch Run (`▶ Global Run` / CI/CD)
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
global-hooks/before() (once, whole batch)
|
|
71
|
+
├─► Suite 1: before() → [beforeEach → TC → afterEach]* → after()
|
|
72
|
+
├─► Suite 2: before() → [beforeEach → TC → afterEach]* → after()
|
|
73
|
+
global-hooks/after() (once, whole batch)
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## 4. Implementation: Anonymous Async Default Export
|
|
79
|
+
|
|
80
|
+
Same convention as steps/actions — one anonymous async function, default export, zero manual
|
|
81
|
+
imports:
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
// specs/authentication/hooks/beforeEach/web.hook.ts
|
|
85
|
+
export default async function () {
|
|
86
|
+
await Spectra.navigate('/auth/login');
|
|
87
|
+
await Spectra.browser.clearLocalStorage();
|
|
88
|
+
await Spectra.browser.clearCookies();
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
// global-hooks/before/web.hook.ts
|
|
94
|
+
export default async function () {
|
|
95
|
+
const response = await fetch(`${process.env.BASE_URL}/health`);
|
|
96
|
+
if (!response.ok) {
|
|
97
|
+
throw new Error(`Target environment is not healthy: ${response.statusText}`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## 5. Failure Cascade Rules
|
|
105
|
+
|
|
106
|
+
1. **`beforeEach` fails** → that test case is marked `failed` (Hook Failure), its `it()` body is
|
|
107
|
+
skipped, but `afterEach` still runs for cleanup.
|
|
108
|
+
2. **Suite `before` fails** → every test case in that suite is marked `skipped`/`error`; suite
|
|
109
|
+
`after` still runs to clean up partial resources.
|
|
110
|
+
3. **`global-hooks/before` fails** → the entire batch run aborts immediately; `global-hooks/after`
|
|
111
|
+
still runs to finalize cleanup.
|
|
112
|
+
|
|
113
|
+
Write hook bodies defensively with this cascade in mind — teardown logic in `after`/`afterEach`
|
|
114
|
+
must not assume `before`/`beforeEach` fully succeeded.
|
|
@@ -1,156 +1,156 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: matchers-and-assertions
|
|
3
|
-
description: Full reference of the TestSpectra element action/assertion API (shouldBeVisible, click, type...), collection matchers (shouldHaveLength...), and global Spectra.* / Spectra.browser.* commands. Use whenever writing or reviewing the body of an it() test script, step, or action.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Skill: Matchers, Actions & the Global `Spectra` API
|
|
7
|
-
|
|
8
|
-
Use this skill as the canonical vocabulary reference whenever you write the body of a test script
|
|
9
|
-
(`it()`), a shared step, or a custom action. TestSpectra never uses raw WebdriverIO/Jest matcher
|
|
10
|
-
names (`toBeDisplayed`, `setValue`, `$`) in authored code — always use the TestSpectra syntax below.
|
|
11
|
-
Every call is automatically traced (`trackCommand()`) and emitted as one natural-English log line
|
|
12
|
-
per action/assertion in `.testspectra/reports/`.
|
|
13
|
-
|
|
14
|
-
---
|
|
15
|
-
|
|
16
|
-
## 1. Element Action Commands (called on a Page Object getter)
|
|
17
|
-
|
|
18
|
-
| TestSpectra syntax | Semantic log output |
|
|
19
|
-
| :----------------------------------- | :-------------------------------------- |
|
|
20
|
-
| `await el.click()` | `Click on element "{el}"` |
|
|
21
|
-
| `await el.doubleClick()` | `Double-click on {el}` |
|
|
22
|
-
| `await el.rightClick()` | `Right-click on {el}` |
|
|
23
|
-
| `await el.type(text, options?)` | `Type "{text}" into element "{el}"` |
|
|
24
|
-
| `await el.clear()` | `Clear text in {el}` |
|
|
25
|
-
| `await el.select(option)` | `Select option "{option}" in {el}` |
|
|
26
|
-
| `await el.hover()` | `Hover over {el}` |
|
|
27
|
-
| `await source.dragDrop(dest)` | `Drag {source} and drop onto {dest}` |
|
|
28
|
-
| `await el.scrollIntoView()` | `Scroll {el} into view` |
|
|
29
|
-
| `await el.longPress(durationMs)` | `Long-press on {el} for {durationMs}ms` |
|
|
30
|
-
| `await el.waitForElement(timeoutMs)` | `Wait for {el} to be displayed` |
|
|
31
|
-
|
|
32
|
-
`type()` accepts an options object, e.g. `{ clearFirst: true }` to clear the field before typing.
|
|
33
|
-
|
|
34
|
-
---
|
|
35
|
-
|
|
36
|
-
## 2. Element Assertions (called on a Page Object getter)
|
|
37
|
-
|
|
38
|
-
| TestSpectra syntax | Semantic log output |
|
|
39
|
-
| :---------------------------------------- | :------------------------------------------------------- |
|
|
40
|
-
| `await el.shouldBeVisible()` | `Expect element "{el}" to be visible` |
|
|
41
|
-
| `await el.shouldNotBeVisible()` | `Expect element "{el}" not to be visible` |
|
|
42
|
-
| `await el.shouldExist()` | `Expect element "{el}" to exist in DOM` |
|
|
43
|
-
| `await el.shouldNotExist()` | `Expect element "{el}" not to exist in DOM` |
|
|
44
|
-
| `await el.shouldBeClickable()` | `Expect element "{el}" to be clickable` |
|
|
45
|
-
| `await el.shouldNotBeClickable()` | `Expect element "{el}" not to be clickable` |
|
|
46
|
-
| `await el.shouldBeEnabled()` | `Expect element "{el}" to be enabled` |
|
|
47
|
-
| `await el.shouldBeDisabled()` | `Expect element "{el}" to be disabled` |
|
|
48
|
-
| `await el.shouldBeChecked()` | `Expect element "{el}" to be checked` |
|
|
49
|
-
| `await el.shouldNotBeChecked()` | `Expect element "{el}" not to be checked` |
|
|
50
|
-
| `await el.shouldHaveText(text)` | `Expect element "{el}" to have text "{text}"` |
|
|
51
|
-
| `await el.shouldContainText(substr)` | `Expect element "{el}" to contain text "{substr}"` |
|
|
52
|
-
| `await el.shouldHaveValue(val)` | `Expect element "{el}" to have value "{val}"` |
|
|
53
|
-
| `await el.shouldHaveAttribute(attr, val)` | `Expect element "{el}" attribute "{attr}" to be "{val}"` |
|
|
54
|
-
| `await el.shouldBeFocused()` | `Expect element "{el}" to be focused` |
|
|
55
|
-
|
|
56
|
-
Every assertion above (and every collection matcher in section 3) accepts a trailing
|
|
57
|
-
`options?: { timeoutMs }` to override the adaptive fail-fast timeout for that one call, without
|
|
58
|
-
raising it for every other assertion in the test — use this when a specific assertion is known to
|
|
59
|
-
need longer, e.g. right after a deliberately delayed `Spectra.intercept(..., { delayMs })` mock:
|
|
60
|
-
|
|
61
|
-
```typescript
|
|
62
|
-
await ReportPage.statusBadge.shouldHaveText('Ready', { timeoutMs: 5000 });
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
---
|
|
66
|
-
|
|
67
|
-
## 3. Collection Matchers (on `Spectra.getAll(selector)` getters)
|
|
68
|
-
|
|
69
|
-
| TestSpectra syntax | Semantic log output |
|
|
70
|
-
| :------------------------------------------- | :------------------------------------------------------ |
|
|
71
|
-
| `await col.shouldHaveLength(n)` | `Expect collection "{selector}" count to equal {n}` |
|
|
72
|
-
| `await col.shouldNotHaveLength(n)` | `Expect collection "{selector}" count not to equal {n}` |
|
|
73
|
-
| `await col.shouldHaveLengthGreaterThan(min)` | `count to be greater than {min}` |
|
|
74
|
-
| `await col.shouldHaveLengthLessThan(max)` | `count to be less than {max}` |
|
|
75
|
-
| `await col.shouldBeEmpty()` | `Expect collection "{selector}" to be empty` |
|
|
76
|
-
| `await col.shouldNotBeEmpty()` | `Expect collection "{selector}" not to be empty` |
|
|
77
|
-
|
|
78
|
-
---
|
|
79
|
-
|
|
80
|
-
## 4. Scoped Element Lookups: `.get()` / `.getAll()`
|
|
81
|
-
|
|
82
|
-
Any resolved element also has `.get(childSelector)` / `.getAll(childSelector)`, chaining a lookup
|
|
83
|
-
scoped to that element's own subtree — Playwright's `locator.locator()` model. **Prefer this over
|
|
84
|
-
a hand-written compound selector** whenever disambiguating repeated markup that shares one
|
|
85
|
-
selector across rows (a list of cards, a table's rows):
|
|
86
|
-
|
|
87
|
-
```typescript
|
|
88
|
-
// WRONG: `.buy-btn` is the same selector on every card — this always resolves the first one.
|
|
89
|
-
await Spectra.get('.buy-btn').click();
|
|
90
|
-
|
|
91
|
-
// RIGHT: scoped to the specific card first.
|
|
92
|
-
const thirdCard = Spectra.getAll('.product-card').nth(2);
|
|
93
|
-
await thirdCard.get('.buy-btn').click();
|
|
94
|
-
await thirdCard.getAll('.badge').shouldHaveLength(2);
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
Works identically from a Page Object property, since it's already a resolved element:
|
|
98
|
-
`await SettingsPage.modal.get('#save-btn').click()`. `.first()`/`.last()`/`.nth(i)` on a scoped
|
|
99
|
-
collection carry the scope forward too. No behavior difference on web vs. Android — resolution
|
|
100
|
-
(real DOM query vs. bounds-containment) is handled transparently underneath.
|
|
101
|
-
|
|
102
|
-
---
|
|
103
|
-
|
|
104
|
-
## 5. Global `Spectra.*` Commands
|
|
105
|
-
|
|
106
|
-
| Category | Syntax |
|
|
107
|
-
| :------------- | :------------------------------------------------------------------------------------------------------- |
|
|
108
|
-
| Navigation | `await Spectra.navigate("/login")` / `.back()` / `.forward()` / `.refresh()` |
|
|
109
|
-
| Element query | `Spectra.get(selector)` / `Spectra.getAll(selector)` — chain `.get()`/`.getAll()` on the result to scope a lookup, see section 4 |
|
|
110
|
-
| Window | `await Spectra.setViewport(1920, 1080)` |
|
|
111
|
-
| Timing | `await Spectra.wait(1000)` |
|
|
112
|
-
| Keyboard | `await Spectra.pressKey("Enter")` |
|
|
113
|
-
| Scroll | `await Spectra.scroll({ direction: "down", pixels: 400 })` |
|
|
114
|
-
| Mobile gesture | `await Spectra.swipe({ direction: "left", distance: 300 })` |
|
|
115
|
-
| Network mock | `const mock = await Spectra.intercept({ url, method, response })` — see the `network-interception` skill |
|
|
116
|
-
|
|
117
|
-
## 6. `Spectra.browser.*` — Browser/Context Level
|
|
118
|
-
|
|
119
|
-
| Syntax | Purpose |
|
|
120
|
-
| :-------------------------------------------------- | :------------------------------ |
|
|
121
|
-
| `await Spectra.browser.clearCookies()` | Clear all cookies |
|
|
122
|
-
| `await Spectra.browser.clearLocalStorage()` | Clear localStorage |
|
|
123
|
-
| `await Spectra.browser.shouldHaveUrl(url)` | Assert exact URL |
|
|
124
|
-
| `await Spectra.browser.shouldContainUrl(substr)` | Assert URL contains substring |
|
|
125
|
-
| `await Spectra.browser.shouldHaveTitle(title)` | Assert exact document title |
|
|
126
|
-
| `await Spectra.browser.shouldContainTitle(substr)` | Assert title contains substring |
|
|
127
|
-
| `await Spectra.browser.shouldBeLoaded()` | Assert page fully loaded |
|
|
128
|
-
| `await Spectra.browser.shouldHaveNoConsoleErrors()` | Assert zero console errors |
|
|
129
|
-
|
|
130
|
-
---
|
|
131
|
-
|
|
132
|
-
## 7. Full Example
|
|
133
|
-
|
|
134
|
-
```typescript
|
|
135
|
-
it('should login as administrator and assert dashboard metrics', async () => {
|
|
136
|
-
await Spectra.navigate('/auth/login');
|
|
137
|
-
await Spectra.browser.clearCookies();
|
|
138
|
-
|
|
139
|
-
await LoginPage.emailInput.type(Fixture.users.admin.email, { clearFirst: true });
|
|
140
|
-
await LoginPage.passwordInput.type(Fixture.users.admin.password);
|
|
141
|
-
await LoginPage.submitButton.click();
|
|
142
|
-
|
|
143
|
-
await DashboardPage.header.shouldBeVisible();
|
|
144
|
-
await DashboardPage.welcomeMsg.shouldHaveText('Welcome back, Admin');
|
|
145
|
-
await DashboardPage.statCards.shouldHaveLength(4);
|
|
146
|
-
await LoginPage.loadingSpinner.shouldNotBeVisible();
|
|
147
|
-
|
|
148
|
-
await Spectra.browser.shouldHaveUrl('https://app.testspectra.dev/dashboard');
|
|
149
|
-
await Spectra.browser.shouldHaveNoConsoleErrors();
|
|
150
|
-
});
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
This is engine-agnostic: the exact same syntax dispatches to Chrome CDP on `web.test.ts` and to the
|
|
154
|
-
native Android TCP driver on `android.test.ts` — never branch authoring code per platform inside a
|
|
155
|
-
shared file; instead put platform-specific implementations in the correctly-suffixed file (see the
|
|
156
|
-
`workspace-structure` skill).
|
|
1
|
+
---
|
|
2
|
+
name: matchers-and-assertions
|
|
3
|
+
description: Full reference of the TestSpectra element action/assertion API (shouldBeVisible, click, type...), collection matchers (shouldHaveLength...), and global Spectra.* / Spectra.browser.* commands. Use whenever writing or reviewing the body of an it() test script, step, or action.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Skill: Matchers, Actions & the Global `Spectra` API
|
|
7
|
+
|
|
8
|
+
Use this skill as the canonical vocabulary reference whenever you write the body of a test script
|
|
9
|
+
(`it()`), a shared step, or a custom action. TestSpectra never uses raw WebdriverIO/Jest matcher
|
|
10
|
+
names (`toBeDisplayed`, `setValue`, `$`) in authored code — always use the TestSpectra syntax below.
|
|
11
|
+
Every call is automatically traced (`trackCommand()`) and emitted as one natural-English log line
|
|
12
|
+
per action/assertion in `.testspectra/reports/`.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## 1. Element Action Commands (called on a Page Object getter)
|
|
17
|
+
|
|
18
|
+
| TestSpectra syntax | Semantic log output |
|
|
19
|
+
| :----------------------------------- | :-------------------------------------- |
|
|
20
|
+
| `await el.click()` | `Click on element "{el}"` |
|
|
21
|
+
| `await el.doubleClick()` | `Double-click on {el}` |
|
|
22
|
+
| `await el.rightClick()` | `Right-click on {el}` |
|
|
23
|
+
| `await el.type(text, options?)` | `Type "{text}" into element "{el}"` |
|
|
24
|
+
| `await el.clear()` | `Clear text in {el}` |
|
|
25
|
+
| `await el.select(option)` | `Select option "{option}" in {el}` |
|
|
26
|
+
| `await el.hover()` | `Hover over {el}` |
|
|
27
|
+
| `await source.dragDrop(dest)` | `Drag {source} and drop onto {dest}` |
|
|
28
|
+
| `await el.scrollIntoView()` | `Scroll {el} into view` |
|
|
29
|
+
| `await el.longPress(durationMs)` | `Long-press on {el} for {durationMs}ms` |
|
|
30
|
+
| `await el.waitForElement(timeoutMs)` | `Wait for {el} to be displayed` |
|
|
31
|
+
|
|
32
|
+
`type()` accepts an options object, e.g. `{ clearFirst: true }` to clear the field before typing.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## 2. Element Assertions (called on a Page Object getter)
|
|
37
|
+
|
|
38
|
+
| TestSpectra syntax | Semantic log output |
|
|
39
|
+
| :---------------------------------------- | :------------------------------------------------------- |
|
|
40
|
+
| `await el.shouldBeVisible()` | `Expect element "{el}" to be visible` |
|
|
41
|
+
| `await el.shouldNotBeVisible()` | `Expect element "{el}" not to be visible` |
|
|
42
|
+
| `await el.shouldExist()` | `Expect element "{el}" to exist in DOM` |
|
|
43
|
+
| `await el.shouldNotExist()` | `Expect element "{el}" not to exist in DOM` |
|
|
44
|
+
| `await el.shouldBeClickable()` | `Expect element "{el}" to be clickable` |
|
|
45
|
+
| `await el.shouldNotBeClickable()` | `Expect element "{el}" not to be clickable` |
|
|
46
|
+
| `await el.shouldBeEnabled()` | `Expect element "{el}" to be enabled` |
|
|
47
|
+
| `await el.shouldBeDisabled()` | `Expect element "{el}" to be disabled` |
|
|
48
|
+
| `await el.shouldBeChecked()` | `Expect element "{el}" to be checked` |
|
|
49
|
+
| `await el.shouldNotBeChecked()` | `Expect element "{el}" not to be checked` |
|
|
50
|
+
| `await el.shouldHaveText(text)` | `Expect element "{el}" to have text "{text}"` |
|
|
51
|
+
| `await el.shouldContainText(substr)` | `Expect element "{el}" to contain text "{substr}"` |
|
|
52
|
+
| `await el.shouldHaveValue(val)` | `Expect element "{el}" to have value "{val}"` |
|
|
53
|
+
| `await el.shouldHaveAttribute(attr, val)` | `Expect element "{el}" attribute "{attr}" to be "{val}"` |
|
|
54
|
+
| `await el.shouldBeFocused()` | `Expect element "{el}" to be focused` |
|
|
55
|
+
|
|
56
|
+
Every assertion above (and every collection matcher in section 3) accepts a trailing
|
|
57
|
+
`options?: { timeoutMs }` to override the adaptive fail-fast timeout for that one call, without
|
|
58
|
+
raising it for every other assertion in the test — use this when a specific assertion is known to
|
|
59
|
+
need longer, e.g. right after a deliberately delayed `Spectra.intercept(..., { delayMs })` mock:
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
await ReportPage.statusBadge.shouldHaveText('Ready', { timeoutMs: 5000 });
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## 3. Collection Matchers (on `Spectra.getAll(selector)` getters)
|
|
68
|
+
|
|
69
|
+
| TestSpectra syntax | Semantic log output |
|
|
70
|
+
| :------------------------------------------- | :------------------------------------------------------ |
|
|
71
|
+
| `await col.shouldHaveLength(n)` | `Expect collection "{selector}" count to equal {n}` |
|
|
72
|
+
| `await col.shouldNotHaveLength(n)` | `Expect collection "{selector}" count not to equal {n}` |
|
|
73
|
+
| `await col.shouldHaveLengthGreaterThan(min)` | `count to be greater than {min}` |
|
|
74
|
+
| `await col.shouldHaveLengthLessThan(max)` | `count to be less than {max}` |
|
|
75
|
+
| `await col.shouldBeEmpty()` | `Expect collection "{selector}" to be empty` |
|
|
76
|
+
| `await col.shouldNotBeEmpty()` | `Expect collection "{selector}" not to be empty` |
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## 4. Scoped Element Lookups: `.get()` / `.getAll()`
|
|
81
|
+
|
|
82
|
+
Any resolved element also has `.get(childSelector)` / `.getAll(childSelector)`, chaining a lookup
|
|
83
|
+
scoped to that element's own subtree — Playwright's `locator.locator()` model. **Prefer this over
|
|
84
|
+
a hand-written compound selector** whenever disambiguating repeated markup that shares one
|
|
85
|
+
selector across rows (a list of cards, a table's rows):
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
// WRONG: `.buy-btn` is the same selector on every card — this always resolves the first one.
|
|
89
|
+
await Spectra.get('.buy-btn').click();
|
|
90
|
+
|
|
91
|
+
// RIGHT: scoped to the specific card first.
|
|
92
|
+
const thirdCard = Spectra.getAll('.product-card').nth(2);
|
|
93
|
+
await thirdCard.get('.buy-btn').click();
|
|
94
|
+
await thirdCard.getAll('.badge').shouldHaveLength(2);
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Works identically from a Page Object property, since it's already a resolved element:
|
|
98
|
+
`await SettingsPage.modal.get('#save-btn').click()`. `.first()`/`.last()`/`.nth(i)` on a scoped
|
|
99
|
+
collection carry the scope forward too. No behavior difference on web vs. Android — resolution
|
|
100
|
+
(real DOM query vs. bounds-containment) is handled transparently underneath.
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## 5. Global `Spectra.*` Commands
|
|
105
|
+
|
|
106
|
+
| Category | Syntax |
|
|
107
|
+
| :------------- | :------------------------------------------------------------------------------------------------------- |
|
|
108
|
+
| Navigation | `await Spectra.navigate("/login")` / `.back()` / `.forward()` / `.refresh()` |
|
|
109
|
+
| Element query | `Spectra.get(selector)` / `Spectra.getAll(selector)` — chain `.get()`/`.getAll()` on the result to scope a lookup, see section 4 |
|
|
110
|
+
| Window | `await Spectra.setViewport(1920, 1080)` |
|
|
111
|
+
| Timing | `await Spectra.wait(1000)` |
|
|
112
|
+
| Keyboard | `await Spectra.pressKey("Enter")` |
|
|
113
|
+
| Scroll | `await Spectra.scroll({ direction: "down", pixels: 400 })` |
|
|
114
|
+
| Mobile gesture | `await Spectra.swipe({ direction: "left", distance: 300 })` |
|
|
115
|
+
| Network mock | `const mock = await Spectra.intercept({ url, method, response })` — see the `network-interception` skill |
|
|
116
|
+
|
|
117
|
+
## 6. `Spectra.browser.*` — Browser/Context Level
|
|
118
|
+
|
|
119
|
+
| Syntax | Purpose |
|
|
120
|
+
| :-------------------------------------------------- | :------------------------------ |
|
|
121
|
+
| `await Spectra.browser.clearCookies()` | Clear all cookies |
|
|
122
|
+
| `await Spectra.browser.clearLocalStorage()` | Clear localStorage |
|
|
123
|
+
| `await Spectra.browser.shouldHaveUrl(url)` | Assert exact URL |
|
|
124
|
+
| `await Spectra.browser.shouldContainUrl(substr)` | Assert URL contains substring |
|
|
125
|
+
| `await Spectra.browser.shouldHaveTitle(title)` | Assert exact document title |
|
|
126
|
+
| `await Spectra.browser.shouldContainTitle(substr)` | Assert title contains substring |
|
|
127
|
+
| `await Spectra.browser.shouldBeLoaded()` | Assert page fully loaded |
|
|
128
|
+
| `await Spectra.browser.shouldHaveNoConsoleErrors()` | Assert zero console errors |
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## 7. Full Example
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
it('should login as administrator and assert dashboard metrics', async () => {
|
|
136
|
+
await Spectra.navigate('/auth/login');
|
|
137
|
+
await Spectra.browser.clearCookies();
|
|
138
|
+
|
|
139
|
+
await LoginPage.emailInput.type(Fixture.users.admin.email, { clearFirst: true });
|
|
140
|
+
await LoginPage.passwordInput.type(Fixture.users.admin.password);
|
|
141
|
+
await LoginPage.submitButton.click();
|
|
142
|
+
|
|
143
|
+
await DashboardPage.header.shouldBeVisible();
|
|
144
|
+
await DashboardPage.welcomeMsg.shouldHaveText('Welcome back, Admin');
|
|
145
|
+
await DashboardPage.statCards.shouldHaveLength(4);
|
|
146
|
+
await LoginPage.loadingSpinner.shouldNotBeVisible();
|
|
147
|
+
|
|
148
|
+
await Spectra.browser.shouldHaveUrl('https://app.testspectra.dev/dashboard');
|
|
149
|
+
await Spectra.browser.shouldHaveNoConsoleErrors();
|
|
150
|
+
});
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
This is engine-agnostic: the exact same syntax dispatches to Chrome CDP on `web.test.ts` and to the
|
|
154
|
+
native Android TCP driver on `android.test.ts` — never branch authoring code per platform inside a
|
|
155
|
+
shared file; instead put platform-specific implementations in the correctly-suffixed file (see the
|
|
156
|
+
`workspace-structure` skill).
|