@testspectra/skills 1.2.8 → 1.2.9

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.
@@ -24,12 +24,49 @@ per action/assertion in `.testspectra/reports/`.
24
24
  | `await el.clear()` | `Clear text in {el}` |
25
25
  | `await el.select(option)` | `Select option "{option}" in {el}` |
26
26
  | `await el.hover()` | `Hover over {el}` |
27
+ | `await el.focus()` | `Focus {el}` |
28
+ | `await el.blur()` | `Blur {el}` |
29
+ | `await el.click({ x, y })` | `Click on element "{el}" at (x, y)` |
27
30
  | `await source.dragDrop(dest)` | `Drag {source} and drop onto {dest}` |
28
31
  | `await el.scrollIntoView()` | `Scroll {el} into view` |
29
32
  | `await el.longPress(durationMs)` | `Long-press on {el} for {durationMs}ms` |
30
33
  | `await el.waitForElement(timeoutMs)` | `Wait for {el} to be displayed` |
34
+ | `await el.waitForElementRemoved(t?)` | `Wait for {el} to be removed` |
35
+ | `await el.waitForUndisplayed(o?)` | `Wait for {el} to be hidden` |
36
+ | `await el.waitForClickable(o?)` | `Wait for {el} to be clickable` |
37
+ | `await el.getBounds()` | `Get bounds of {el}` |
31
38
 
32
39
  `type()` accepts an options object, e.g. `{ clearFirst: true }` to clear the field before typing.
40
+ `click({ x, y })` clicks at coordinates relative to the element; `Spectra.clickAt(x, y)` /
41
+ `Spectra.browser.clickAt(x, y)` clicks at absolute viewport coordinates.
42
+ `getText()` returns authored text (`textContent`) — CSS `text-transform` never leaks into assertions.
43
+ `getBounds()` returns `{ x, y, top, right, bottom, left, width, height }` (web
44
+ `getBoundingClientRect`, Android node bounds).
45
+
46
+ ### 1b. DOM Traversal (cross-platform, no XPath)
47
+
48
+ | TestSpectra syntax | Meaning |
49
+ | :-------------------------------- | :--------------------------------------------------- |
50
+ | `el.parent()` | Parent element in the DOM / a11y tree |
51
+ | `await el.closest(selector)` | Closest matching ancestor, or `null` (like `closest`)|
52
+ | `el.children(selector?)` | Direct children as a collection (optional filter) |
53
+ | `el.siblings(selector?)` | Same-parent siblings (excluding self) as collection |
54
+ | `el.next(selector?)` | Next sibling, or first following sibling matching it |
55
+
56
+ ```typescript
57
+ await Spectra.get('ul').children('li').shouldHaveLength(3);
58
+ const card = await Spectra.get('#buy-button').closest('.product-card');
59
+ ```
60
+
61
+ Collection index survives traversal: `getAll('.x').nth(1).parent()` resolves the second
62
+ `.x`'s parent, not the first document match. No XPath here by design — XPath dialects differ
63
+ per platform, traversal keeps one API.
64
+
65
+ ### 1c. State Queries (return values, not assertions)
66
+
67
+ `await el.isClickable()` → `true` when visible + enabled + in viewport. Combines with
68
+ `isVisible()` / `isExisting()` / `isEnabled()` for explicit branching; prefer
69
+ `shouldBe*` matchers + `waitForClickable()` for plain waiting.
33
70
 
34
71
  ---
35
72
 
@@ -107,6 +144,8 @@ collection carry the scope forward too. No behavior difference on web vs. Androi
107
144
  | :------------- | :------------------------------------------------------------------------------------------------------------------------------- |
108
145
  | Navigation | `await Spectra.navigate("/login")` / `.back()` / `.forward()` / `.refresh()` |
109
146
  | Element query | `Spectra.get(selector)` / `Spectra.getAll(selector)` — chain `.get()`/`.getAll()` on the result to scope a lookup, see section 4 |
147
+ | Focus | `await Spectra.focus(target)` / `await Spectra.blur(target)` |
148
+ | Coordinate | `await Spectra.clickAt(x, y)` — absolute viewport click |
110
149
  | Window | `await Spectra.setViewport(1920, 1080)` |
111
150
  | Timing | `await Spectra.wait(1000)` |
112
151
  | Keyboard | `await Spectra.pressKey("Enter")` |
@@ -118,6 +157,7 @@ collection carry the scope forward too. No behavior difference on web vs. Androi
118
157
 
119
158
  | Syntax | Purpose |
120
159
  | :-------------------------------------------------- | :------------------------------ |
160
+ | `await Spectra.browser.clickAt(x, y)` | Click at viewport coordinates |
121
161
  | `await Spectra.browser.clearCookies()` | Clear all cookies |
122
162
  | `await Spectra.browser.clearLocalStorage()` | Clear localStorage |
123
163
  | `await Spectra.browser.shouldHaveUrl(url)` | Assert exact URL |
@@ -121,6 +121,32 @@ This is a Page Object concern, not a new selector prefix — `~`/`#`/xpath all s
121
121
  child selector, resolved within the parent's subtree instead of the whole document/screen. See the
122
122
  `matchers-and-assertions` skill for the full `.get()`/`.getAll()` chaining behavior.
123
123
 
124
+ ### Traversal instead of compound selectors
125
+
126
+ For moving up/sideways in the tree, prefer traversal over xpath axes (xpath dialects differ per
127
+ platform — DOM attrs on web vs `@text`/`@resource-id` on Android — so traversal is the one
128
+ cross-platform API):
129
+
130
+ ```typescript
131
+ class ProductCard {
132
+ buyButton() {
133
+ return Spectra.get('#buy-button');
134
+ }
135
+ card() {
136
+ return this.buyButton().parent(); // or await .closest('.product-card')
137
+ }
138
+ siblings() {
139
+ return Spectra.get('.step-1').next('.step-2');
140
+ }
141
+ }
142
+ ```
143
+
144
+ - `parent()` / `await closest(sel)` / `children(sel?)` / `siblings(sel?)` / `next(sel?)` work on
145
+ web (DOM) and Android (a11y-tree links) identically.
146
+ - Index survives traversal: `cards.nth(1).parent()` resolves the second card's parent, never the
147
+ first document match. Unfiltered `parent()` chains stay document-wide — scope with
148
+ `closest(selector)` or a filter when several subtrees match.
149
+
124
150
  ---
125
151
 
126
152
  ## 4. Never Use Raw `$` / `$$` / `browser` / `driver`
@@ -152,6 +152,7 @@ export default defineConfig({
152
152
  normalResponseTime: '1000',
153
153
  monitoredDomains: [{ domain: 'api.example.com', enabled: true }],
154
154
  environmentVariables: { API_MODE: 'sandbox' },
155
+ plugins: ['@myorg/spectra-button-plugin'],
155
156
  },
156
157
  });
157
158
  ```
@@ -173,6 +174,9 @@ Numeric-looking fields (timeouts, ports, session counts) accept either a `number
173
174
  `.testspectra/types/env.d.ts`, autocompleted, zero-import — **prefer this over `process.env`**,
174
175
  which is untyped `string | undefined`) and a plain `process.env.KEY` string for code that must
175
176
  consume the untyped standard API.
177
+ - **`plugins`**: module specifiers (package name or project-relative path) extending `Spectra`
178
+ with custom commands via `Spectra.registerCommand()`; installs are idempotent per specifier.
179
+ Run `spectra sync-types` to regenerate per-platform ambient declarations.
176
180
 
177
181
  ---
178
182
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@testspectra/skills",
3
- "version": "1.2.8",
3
+ "version": "1.2.9",
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",
@@ -24,12 +24,49 @@ per action/assertion in `.testspectra/reports/`.
24
24
  | `await el.clear()` | `Clear text in {el}` |
25
25
  | `await el.select(option)` | `Select option "{option}" in {el}` |
26
26
  | `await el.hover()` | `Hover over {el}` |
27
+ | `await el.focus()` | `Focus {el}` |
28
+ | `await el.blur()` | `Blur {el}` |
29
+ | `await el.click({ x, y })` | `Click on element "{el}" at (x, y)` |
27
30
  | `await source.dragDrop(dest)` | `Drag {source} and drop onto {dest}` |
28
31
  | `await el.scrollIntoView()` | `Scroll {el} into view` |
29
32
  | `await el.longPress(durationMs)` | `Long-press on {el} for {durationMs}ms` |
30
33
  | `await el.waitForElement(timeoutMs)` | `Wait for {el} to be displayed` |
34
+ | `await el.waitForElementRemoved(t?)` | `Wait for {el} to be removed` |
35
+ | `await el.waitForUndisplayed(o?)` | `Wait for {el} to be hidden` |
36
+ | `await el.waitForClickable(o?)` | `Wait for {el} to be clickable` |
37
+ | `await el.getBounds()` | `Get bounds of {el}` |
31
38
 
32
39
  `type()` accepts an options object, e.g. `{ clearFirst: true }` to clear the field before typing.
40
+ `click({ x, y })` clicks at coordinates relative to the element; `Spectra.clickAt(x, y)` /
41
+ `Spectra.browser.clickAt(x, y)` clicks at absolute viewport coordinates.
42
+ `getText()` returns authored text (`textContent`) — CSS `text-transform` never leaks into assertions.
43
+ `getBounds()` returns `{ x, y, top, right, bottom, left, width, height }` (web
44
+ `getBoundingClientRect`, Android node bounds).
45
+
46
+ ### 1b. DOM Traversal (cross-platform, no XPath)
47
+
48
+ | TestSpectra syntax | Meaning |
49
+ | :-------------------------------- | :--------------------------------------------------- |
50
+ | `el.parent()` | Parent element in the DOM / a11y tree |
51
+ | `await el.closest(selector)` | Closest matching ancestor, or `null` (like `closest`)|
52
+ | `el.children(selector?)` | Direct children as a collection (optional filter) |
53
+ | `el.siblings(selector?)` | Same-parent siblings (excluding self) as collection |
54
+ | `el.next(selector?)` | Next sibling, or first following sibling matching it |
55
+
56
+ ```typescript
57
+ await Spectra.get('ul').children('li').shouldHaveLength(3);
58
+ const card = await Spectra.get('#buy-button').closest('.product-card');
59
+ ```
60
+
61
+ Collection index survives traversal: `getAll('.x').nth(1).parent()` resolves the second
62
+ `.x`'s parent, not the first document match. No XPath here by design — XPath dialects differ
63
+ per platform, traversal keeps one API.
64
+
65
+ ### 1c. State Queries (return values, not assertions)
66
+
67
+ `await el.isClickable()` → `true` when visible + enabled + in viewport. Combines with
68
+ `isVisible()` / `isExisting()` / `isEnabled()` for explicit branching; prefer
69
+ `shouldBe*` matchers + `waitForClickable()` for plain waiting.
33
70
 
34
71
  ---
35
72
 
@@ -107,6 +144,8 @@ collection carry the scope forward too. No behavior difference on web vs. Androi
107
144
  | :------------- | :------------------------------------------------------------------------------------------------------------------------------- |
108
145
  | Navigation | `await Spectra.navigate("/login")` / `.back()` / `.forward()` / `.refresh()` |
109
146
  | Element query | `Spectra.get(selector)` / `Spectra.getAll(selector)` — chain `.get()`/`.getAll()` on the result to scope a lookup, see section 4 |
147
+ | Focus | `await Spectra.focus(target)` / `await Spectra.blur(target)` |
148
+ | Coordinate | `await Spectra.clickAt(x, y)` — absolute viewport click |
110
149
  | Window | `await Spectra.setViewport(1920, 1080)` |
111
150
  | Timing | `await Spectra.wait(1000)` |
112
151
  | Keyboard | `await Spectra.pressKey("Enter")` |
@@ -118,6 +157,7 @@ collection carry the scope forward too. No behavior difference on web vs. Androi
118
157
 
119
158
  | Syntax | Purpose |
120
159
  | :-------------------------------------------------- | :------------------------------ |
160
+ | `await Spectra.browser.clickAt(x, y)` | Click at viewport coordinates |
121
161
  | `await Spectra.browser.clearCookies()` | Clear all cookies |
122
162
  | `await Spectra.browser.clearLocalStorage()` | Clear localStorage |
123
163
  | `await Spectra.browser.shouldHaveUrl(url)` | Assert exact URL |
@@ -121,6 +121,32 @@ This is a Page Object concern, not a new selector prefix — `~`/`#`/xpath all s
121
121
  child selector, resolved within the parent's subtree instead of the whole document/screen. See the
122
122
  `matchers-and-assertions` skill for the full `.get()`/`.getAll()` chaining behavior.
123
123
 
124
+ ### Traversal instead of compound selectors
125
+
126
+ For moving up/sideways in the tree, prefer traversal over xpath axes (xpath dialects differ per
127
+ platform — DOM attrs on web vs `@text`/`@resource-id` on Android — so traversal is the one
128
+ cross-platform API):
129
+
130
+ ```typescript
131
+ class ProductCard {
132
+ buyButton() {
133
+ return Spectra.get('#buy-button');
134
+ }
135
+ card() {
136
+ return this.buyButton().parent(); // or await .closest('.product-card')
137
+ }
138
+ siblings() {
139
+ return Spectra.get('.step-1').next('.step-2');
140
+ }
141
+ }
142
+ ```
143
+
144
+ - `parent()` / `await closest(sel)` / `children(sel?)` / `siblings(sel?)` / `next(sel?)` work on
145
+ web (DOM) and Android (a11y-tree links) identically.
146
+ - Index survives traversal: `cards.nth(1).parent()` resolves the second card's parent, never the
147
+ first document match. Unfiltered `parent()` chains stay document-wide — scope with
148
+ `closest(selector)` or a filter when several subtrees match.
149
+
124
150
  ---
125
151
 
126
152
  ## 4. Never Use Raw `$` / `$$` / `browser` / `driver`
@@ -152,6 +152,7 @@ export default defineConfig({
152
152
  normalResponseTime: '1000',
153
153
  monitoredDomains: [{ domain: 'api.example.com', enabled: true }],
154
154
  environmentVariables: { API_MODE: 'sandbox' },
155
+ plugins: ['@myorg/spectra-button-plugin'],
155
156
  },
156
157
  });
157
158
  ```
@@ -173,6 +174,9 @@ Numeric-looking fields (timeouts, ports, session counts) accept either a `number
173
174
  `.testspectra/types/env.d.ts`, autocompleted, zero-import — **prefer this over `process.env`**,
174
175
  which is untyped `string | undefined`) and a plain `process.env.KEY` string for code that must
175
176
  consume the untyped standard API.
177
+ - **`plugins`**: module specifiers (package name or project-relative path) extending `Spectra`
178
+ with custom commands via `Spectra.registerCommand()`; installs are idempotent per specifier.
179
+ Run `spectra sync-types` to regenerate per-platform ambient declarations.
176
180
 
177
181
  ---
178
182