@testspectra/matchers 1.1.14 → 1.2.1

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.
@@ -8,7 +8,7 @@
8
8
  * DSL layer contains zero platform branching.
9
9
  */
10
10
  import type { TestSpectraWorkerConfig } from './worker_config.js';
11
- import type { CDPNetworkEntry, MockInterceptHandle, MockRule, ScopedSelector, ScrollOptions, SpectraBrowserBridge, SwipeOptions } from './types.js';
11
+ import type { CDPNetworkEntry, ElementBounds, MockInterceptHandle, MockRule, ScopedSelector, ScrollOptions, SpectraBrowserBridge, SwipeOptions } from './types.js';
12
12
  /**
13
13
  * @deprecated Renamed to `TestSpectraWorkerConfig` (now shared by E2E and component testing).
14
14
  * Kept as an alias for backwards compatibility.
@@ -101,6 +101,7 @@ export interface PlatformDriverBridge extends SpectraBrowserBridge {
101
101
  select(selector: string | ScopedSelector, option: string, index?: number | null): Promise<void>;
102
102
  hover(selector: string | ScopedSelector, index?: number | null): Promise<void>;
103
103
  focus(selector: string | ScopedSelector, index?: number | null): Promise<void>;
104
+ blur(selector: string | ScopedSelector, index?: number | null): Promise<void>;
104
105
  dragDrop(sourceSelector: string | ScopedSelector, targetSelector: string, sourceIndex?: number | null): Promise<void>;
105
106
  longPress(selector: string | ScopedSelector, duration: number, index?: number | null): Promise<void>;
106
107
  scrollIntoView(selector: string | ScopedSelector, index?: number | null): Promise<void>;
@@ -119,6 +120,16 @@ export interface PlatformDriverBridge extends SpectraBrowserBridge {
119
120
  value: string;
120
121
  }>;
121
122
  count(selector: string | ScopedSelector): Promise<number>;
123
+ waitForElementRemoved(selector: string | ScopedSelector, timeoutMs?: number, index?: number | null): Promise<void>;
124
+ waitForUndisplayed(selector: string | ScopedSelector, opts?: {
125
+ timeout?: number;
126
+ }, index?: number | null): Promise<void>;
127
+ waitForClickable(selector: string | ScopedSelector, opts?: {
128
+ timeout?: number;
129
+ }, index?: number | null): Promise<boolean>;
130
+ getBounds(selector: string | ScopedSelector, index?: number | null): Promise<ElementBounds>;
131
+ closest(selector: string | ScopedSelector, targetSelector: string, index?: number | null): Promise<string | null>;
132
+ clickAt(x: number, y: number): Promise<void>;
122
133
  pressKey(key: string | number): Promise<void>;
123
134
  scroll(options?: ScrollOptions): Promise<void>;
124
135
  swipe(options: SwipeOptions): Promise<void>;
package/dist/types.d.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  * Canonical action keys supported across TestSpectra runner and database models.
3
3
  * @see backend/src/models/test_step.rs
4
4
  */
5
- export type ActionKey = 'navigate' | 'click' | 'type' | 'clear' | 'select' | 'scroll' | 'swipe' | 'wait' | 'waitForElement' | 'pressKey' | 'longPress' | 'doubleClick' | 'hover' | 'dragDrop' | 'upload' | 'back' | 'refresh';
5
+ export type ActionKey = 'navigate' | 'click' | 'type' | 'clear' | 'select' | 'scroll' | 'swipe' | 'wait' | 'waitForElement' | 'waitForElementRemoved' | 'waitForUndisplayed' | 'waitForClickable' | 'pressKey' | 'longPress' | 'doubleClick' | 'hover' | 'focus' | 'blur' | 'dragDrop' | 'upload' | 'back' | 'refresh';
6
6
  /**
7
7
  * Canonical assertion keys supported across TestSpectra runner and database models.
8
8
  * @see backend/src/models/test_step.rs
@@ -73,6 +73,22 @@ export interface ScrollOptions {
73
73
  /** Optional target element selector to scroll directly into view */
74
74
  selector?: ElementTarget;
75
75
  }
76
+ /**
77
+ * Bounding rectangle of an element — mirrors `Element.getBoundingClientRect()` on web
78
+ * and the `bounds: [left, top, right, bottom]` field of an Android accessibility node.
79
+ */
80
+ export interface ElementBounds {
81
+ /** Horizontal position relative to viewport (alias of `left`). */
82
+ x: number;
83
+ /** Vertical position relative to viewport (alias of `top`). */
84
+ y: number;
85
+ top: number;
86
+ right: number;
87
+ bottom: number;
88
+ left: number;
89
+ width: number;
90
+ height: number;
91
+ }
76
92
  /**
77
93
  * Configuration options for touch swipe gestures (mobile & web).
78
94
  *
@@ -111,6 +127,10 @@ export interface ClickOptions {
111
127
  text?: string;
112
128
  /** Click type: standard single click or double click */
113
129
  clickType?: 'single' | 'double';
130
+ /** X coordinate for a coordinate-based click (relative to the element). */
131
+ x?: number;
132
+ /** Y coordinate for a coordinate-based click (relative to the element). */
133
+ y?: number;
114
134
  }
115
135
  /**
116
136
  * Configuration options for typing input into form elements.
@@ -741,6 +761,15 @@ export interface SingleElementProxy extends ElementReceiverAssertions<Promise<vo
741
761
  * ```
742
762
  */
743
763
  focus(): Promise<void>;
764
+ /**
765
+ * Removes focus from the target element (blurs it).
766
+ *
767
+ * @example
768
+ * ```ts
769
+ * await Spectra.get('#email-input').blur();
770
+ * ```
771
+ */
772
+ blur(): Promise<void>;
744
773
  /**
745
774
  * Moves the mouse cursor to the element (alias to `hover()`).
746
775
  */
@@ -786,6 +815,58 @@ export interface SingleElementProxy extends ElementReceiverAssertions<Promise<vo
786
815
  waitForDisplayed(opts?: {
787
816
  timeout?: number;
788
817
  }): Promise<boolean>;
818
+ /**
819
+ * Waits for the element to be removed from the DOM within the specified timeout.
820
+ * Opposite of `waitForElement()` — poll until the element no longer exists.
821
+ *
822
+ * @param timeoutMs Timeout in milliseconds (default: 5000ms).
823
+ */
824
+ waitForElementRemoved(timeoutMs?: number): Promise<void>;
825
+ /**
826
+ * Waits for the element to be hidden, detached, or not displayed within the specified timeout.
827
+ * Opposite of `waitForDisplayed()` — poll until the element is no longer visible.
828
+ *
829
+ * @param opts Optional timeout configuration object.
830
+ */
831
+ waitForUndisplayed(opts?: {
832
+ timeout?: number;
833
+ }): Promise<void>;
834
+ /**
835
+ * Waits until the element is enabled, visible, and clickable within the specified timeout.
836
+ *
837
+ * @param opts Optional timeout configuration object.
838
+ * @returns Promise resolving to true if the element became clickable, false on timeout.
839
+ */
840
+ waitForClickable(opts?: {
841
+ timeout?: number;
842
+ }): Promise<boolean>;
843
+ /**
844
+ * Returns the element's bounding rectangle (position + size relative to viewport).
845
+ *
846
+ * @example
847
+ * ```ts
848
+ * const bounds = await Spectra.get('#modal').getBounds();
849
+ * console.log(bounds.x, bounds.y, bounds.width, bounds.height);
850
+ * ```
851
+ */
852
+ getBounds(): Promise<ElementBounds>;
853
+ /**
854
+ * Returns the parent element of this element in the DOM tree.
855
+ *
856
+ * @example
857
+ * ```ts
858
+ * const parent = await Spectra.get('#child').parent();
859
+ * await parent.shouldBeVisible();
860
+ * ```
861
+ */
862
+ parent(): SingleElementProxy;
863
+ /**
864
+ * Finds the closest ancestor that matches the selector (mirrors `Element.closest()`).
865
+ *
866
+ * @param selector CSS selector to match against ancestors.
867
+ * @returns Promise resolving to the closest matching ancestor, or null if none found.
868
+ */
869
+ closest(selector: string): Promise<SingleElementProxy | null>;
789
870
  /**
790
871
  * Returns the inner text content of the element.
791
872
  *
@@ -991,6 +1072,17 @@ export interface SpectraBrowserBridge extends BrowserReceiverAssertions {
991
1072
  timeout?: number;
992
1073
  timeoutMsg?: string;
993
1074
  }): Promise<boolean>;
1075
+ /**
1076
+ * Clicks at absolute viewport coordinates (x, y).
1077
+ *
1078
+ * @param x Horizontal viewport coordinate.
1079
+ * @param y Vertical viewport coordinate.
1080
+ * @example
1081
+ * ```ts
1082
+ * await Spectra.browser.clickAt(100, 200);
1083
+ * ```
1084
+ */
1085
+ clickAt(x: number, y: number): Promise<void>;
994
1086
  /**
995
1087
  * Retrieves captured browser console log entries.
996
1088
  *
@@ -1153,6 +1245,53 @@ export interface CDPNetworkEntry {
1153
1245
  /** Start timestamp epoch. */
1154
1246
  startTime: number;
1155
1247
  }
1248
+ /**
1249
+ * A user- or library-registered command callable on the global `Spectra` object.
1250
+ *
1251
+ * Commands are registered at runtime via `Spectra.registerCommand()` (typically from a plugin's
1252
+ * `setup()`), and their types are exposed to consumers through TypeScript module augmentation of
1253
+ * `SpectraStatic` — there is no type-generator integration for external plugins.
1254
+ */
1255
+ export type SpectraCommand = (...args: any[]) => any;
1256
+ /**
1257
+ * Context passed to a plugin's `setup()`. Plugins use `platform` to register only the commands
1258
+ * that are valid for the active target.
1259
+ */
1260
+ export interface SpectraPluginContext {
1261
+ /** Active execution platform: `'web'`, `'android'`, or `'ios'`. */
1262
+ platform: string;
1263
+ }
1264
+ /**
1265
+ * A TestSpectra plugin. A plugin is any external library that extends the `Spectra` DSL — its
1266
+ * `setup` receives the live `Spectra` object and registers commands (or performs other one-time
1267
+ * wiring) before test files are imported.
1268
+ *
1269
+ * Plugins are declared in `spectra.config.ts` under `executionConfig.plugins` as module specifiers
1270
+ * and loaded by both the native E2E worker and the component-testing runtime. Platform-specific
1271
+ * command **types** are shipped as per-stem subpath entries (`./web`, `./android`, `./ios`,
1272
+ * `./mobile`, `./common`) and wired into the generated ambient declarations by the type generator.
1273
+ *
1274
+ * @example
1275
+ * ```ts
1276
+ * import type { SpectraPlugin } from '@testspectra/matchers';
1277
+ *
1278
+ * const plugin: SpectraPlugin = {
1279
+ * name: 'my-plugin',
1280
+ * setup(spectra, { platform }) {
1281
+ * spectra.registerCommand('findButton', (selector: string) => spectra.get(selector));
1282
+ * if (platform === 'web') spectra.registerCommand('findByCss', (s: string) => spectra.get(s));
1283
+ * },
1284
+ * };
1285
+ *
1286
+ * export default plugin;
1287
+ * ```
1288
+ */
1289
+ export interface SpectraPlugin {
1290
+ /** Unique plugin identifier, used for diagnostics. */
1291
+ name: string;
1292
+ /** One-time wiring executed after `Spectra` is built and before test files are imported. */
1293
+ setup(spectra: SpectraStatic, context: SpectraPluginContext): void | Promise<void>;
1294
+ }
1156
1295
  /**
1157
1296
  * Main TestSpectra cross-platform automation and assertion interface contract.
1158
1297
  */
@@ -1316,6 +1455,16 @@ export interface SpectraStatic {
1316
1455
  * ```
1317
1456
  */
1318
1457
  focus(target: ElementTarget): Promise<void>;
1458
+ /**
1459
+ * Removes focus from the target element (blurs it).
1460
+ *
1461
+ * @param target Target element selector or proxy.
1462
+ * @example
1463
+ * ```ts
1464
+ * await Spectra.blur('#email-input');
1465
+ * ```
1466
+ */
1467
+ blur(target: ElementTarget): Promise<void>;
1319
1468
  /**
1320
1469
  * Drags a source element and drops it onto a destination element target.
1321
1470
  *
@@ -1459,4 +1608,32 @@ export interface SpectraStatic {
1459
1608
  * ```
1460
1609
  */
1461
1610
  clearMocks(): void;
1611
+ /**
1612
+ * Registers a custom command on the `Spectra` object, making it callable as
1613
+ * `Spectra.<name>(...)`. Intended for plugins to extend the DSL.
1614
+ *
1615
+ * Throws if `name` is not a valid identifier, collides with a built-in `Spectra` method, or is
1616
+ * already registered — built-ins can never be monkey-patched.
1617
+ *
1618
+ * @param name Command name (must be a valid JavaScript identifier).
1619
+ * @param command Implementation to attach.
1620
+ * @example
1621
+ * ```ts
1622
+ * Spectra.registerCommand('findButton', (selector: string) => Spectra.get(selector));
1623
+ * ```
1624
+ */
1625
+ registerCommand(name: string, command: SpectraCommand): void;
1626
+ /**
1627
+ * Registers multiple custom commands at once. Each entry is validated exactly like
1628
+ * `registerCommand`.
1629
+ *
1630
+ * @example
1631
+ * ```ts
1632
+ * Spectra.registerCommands({
1633
+ * findButton: (selector: string) => Spectra.get(selector),
1634
+ * findInput: (selector: string) => Spectra.get(selector),
1635
+ * });
1636
+ * ```
1637
+ */
1638
+ registerCommands(commands: Record<string, SpectraCommand>): void;
1462
1639
  }
@@ -94,4 +94,9 @@ export interface TestSpectraWorkerConfig {
94
94
  }[];
95
95
  /** Mirrors `spectra.config.ts`'s `executionConfig.environmentVariables`. */
96
96
  environmentVariables?: Record<string, string>;
97
+ /**
98
+ * Mirrors `spectra.config.ts`'s `executionConfig.plugins` — module specifiers (absolute paths,
99
+ * after CLI resolution) of TestSpectra plugins to install before test files are imported.
100
+ */
101
+ plugins?: string[];
97
102
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@testspectra/matchers",
3
- "version": "1.1.14",
3
+ "version": "1.2.1",
4
4
  "description": "Canonical TypeScript contracts and ambient type definitions for TestSpectra",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
package/src/contract.ts CHANGED
@@ -11,6 +11,7 @@
11
11
  import type { TestSpectraWorkerConfig } from './worker_config.js';
12
12
  import type {
13
13
  CDPNetworkEntry,
14
+ ElementBounds,
14
15
  MockInterceptHandle,
15
16
  MockRule,
16
17
  ScopedSelector,
@@ -151,6 +152,7 @@ export interface PlatformDriverBridge extends SpectraBrowserBridge {
151
152
  select(selector: string | ScopedSelector, option: string, index?: number | null): Promise<void>;
152
153
  hover(selector: string | ScopedSelector, index?: number | null): Promise<void>;
153
154
  focus(selector: string | ScopedSelector, index?: number | null): Promise<void>;
155
+ blur(selector: string | ScopedSelector, index?: number | null): Promise<void>;
154
156
  dragDrop(sourceSelector: string | ScopedSelector, targetSelector: string, sourceIndex?: number | null): Promise<void>;
155
157
  longPress(selector: string | ScopedSelector, duration: number, index?: number | null): Promise<void>;
156
158
  scrollIntoView(selector: string | ScopedSelector, index?: number | null): Promise<void>;
@@ -170,6 +172,21 @@ export interface PlatformDriverBridge extends SpectraBrowserBridge {
170
172
  getCSSProperty(selector: string | ScopedSelector, name: string, index?: number | null): Promise<{ value: string }>;
171
173
  count(selector: string | ScopedSelector): Promise<number>;
172
174
 
175
+ waitForElementRemoved(selector: string | ScopedSelector, timeoutMs?: number, index?: number | null): Promise<void>;
176
+ waitForUndisplayed(
177
+ selector: string | ScopedSelector,
178
+ opts?: { timeout?: number },
179
+ index?: number | null,
180
+ ): Promise<void>;
181
+ waitForClickable(
182
+ selector: string | ScopedSelector,
183
+ opts?: { timeout?: number },
184
+ index?: number | null,
185
+ ): Promise<boolean>;
186
+ getBounds(selector: string | ScopedSelector, index?: number | null): Promise<ElementBounds>;
187
+ closest(selector: string | ScopedSelector, targetSelector: string, index?: number | null): Promise<string | null>;
188
+ clickAt(x: number, y: number): Promise<void>;
189
+
173
190
  // --- Gestures & Keys ---
174
191
  pressKey(key: string | number): Promise<void>;
175
192
  scroll(options?: ScrollOptions): Promise<void>;
package/src/types.ts CHANGED
@@ -12,10 +12,15 @@ export type ActionKey =
12
12
  | 'swipe'
13
13
  | 'wait'
14
14
  | 'waitForElement'
15
+ | 'waitForElementRemoved'
16
+ | 'waitForUndisplayed'
17
+ | 'waitForClickable'
15
18
  | 'pressKey'
16
19
  | 'longPress'
17
20
  | 'doubleClick'
18
21
  | 'hover'
22
+ | 'focus'
23
+ | 'blur'
19
24
  | 'dragDrop'
20
25
  | 'upload'
21
26
  | 'back'
@@ -145,6 +150,23 @@ export interface ScrollOptions {
145
150
  selector?: ElementTarget;
146
151
  }
147
152
 
153
+ /**
154
+ * Bounding rectangle of an element — mirrors `Element.getBoundingClientRect()` on web
155
+ * and the `bounds: [left, top, right, bottom]` field of an Android accessibility node.
156
+ */
157
+ export interface ElementBounds {
158
+ /** Horizontal position relative to viewport (alias of `left`). */
159
+ x: number;
160
+ /** Vertical position relative to viewport (alias of `top`). */
161
+ y: number;
162
+ top: number;
163
+ right: number;
164
+ bottom: number;
165
+ left: number;
166
+ width: number;
167
+ height: number;
168
+ }
169
+
148
170
  /**
149
171
  * Configuration options for touch swipe gestures (mobile & web).
150
172
  *
@@ -185,6 +207,10 @@ export interface ClickOptions {
185
207
  text?: string;
186
208
  /** Click type: standard single click or double click */
187
209
  clickType?: 'single' | 'double';
210
+ /** X coordinate for a coordinate-based click (relative to the element). */
211
+ x?: number;
212
+ /** Y coordinate for a coordinate-based click (relative to the element). */
213
+ y?: number;
188
214
  }
189
215
 
190
216
  /**
@@ -918,6 +944,16 @@ export interface SingleElementProxy extends ElementReceiverAssertions<Promise<vo
918
944
  */
919
945
  focus(): Promise<void>;
920
946
 
947
+ /**
948
+ * Removes focus from the target element (blurs it).
949
+ *
950
+ * @example
951
+ * ```ts
952
+ * await Spectra.get('#email-input').blur();
953
+ * ```
954
+ */
955
+ blur(): Promise<void>;
956
+
921
957
  /**
922
958
  * Moves the mouse cursor to the element (alias to `hover()`).
923
959
  */
@@ -967,6 +1003,60 @@ export interface SingleElementProxy extends ElementReceiverAssertions<Promise<vo
967
1003
  */
968
1004
  waitForDisplayed(opts?: { timeout?: number }): Promise<boolean>;
969
1005
 
1006
+ /**
1007
+ * Waits for the element to be removed from the DOM within the specified timeout.
1008
+ * Opposite of `waitForElement()` — poll until the element no longer exists.
1009
+ *
1010
+ * @param timeoutMs Timeout in milliseconds (default: 5000ms).
1011
+ */
1012
+ waitForElementRemoved(timeoutMs?: number): Promise<void>;
1013
+
1014
+ /**
1015
+ * Waits for the element to be hidden, detached, or not displayed within the specified timeout.
1016
+ * Opposite of `waitForDisplayed()` — poll until the element is no longer visible.
1017
+ *
1018
+ * @param opts Optional timeout configuration object.
1019
+ */
1020
+ waitForUndisplayed(opts?: { timeout?: number }): Promise<void>;
1021
+
1022
+ /**
1023
+ * Waits until the element is enabled, visible, and clickable within the specified timeout.
1024
+ *
1025
+ * @param opts Optional timeout configuration object.
1026
+ * @returns Promise resolving to true if the element became clickable, false on timeout.
1027
+ */
1028
+ waitForClickable(opts?: { timeout?: number }): Promise<boolean>;
1029
+
1030
+ /**
1031
+ * Returns the element's bounding rectangle (position + size relative to viewport).
1032
+ *
1033
+ * @example
1034
+ * ```ts
1035
+ * const bounds = await Spectra.get('#modal').getBounds();
1036
+ * console.log(bounds.x, bounds.y, bounds.width, bounds.height);
1037
+ * ```
1038
+ */
1039
+ getBounds(): Promise<ElementBounds>;
1040
+
1041
+ /**
1042
+ * Returns the parent element of this element in the DOM tree.
1043
+ *
1044
+ * @example
1045
+ * ```ts
1046
+ * const parent = await Spectra.get('#child').parent();
1047
+ * await parent.shouldBeVisible();
1048
+ * ```
1049
+ */
1050
+ parent(): SingleElementProxy;
1051
+
1052
+ /**
1053
+ * Finds the closest ancestor that matches the selector (mirrors `Element.closest()`).
1054
+ *
1055
+ * @param selector CSS selector to match against ancestors.
1056
+ * @returns Promise resolving to the closest matching ancestor, or null if none found.
1057
+ */
1058
+ closest(selector: string): Promise<SingleElementProxy | null>;
1059
+
970
1060
  /**
971
1061
  * Returns the inner text content of the element.
972
1062
  *
@@ -1192,6 +1282,18 @@ export interface SpectraBrowserBridge extends BrowserReceiverAssertions {
1192
1282
  */
1193
1283
  waitUntil(fn: () => Promise<boolean> | boolean, opts?: { timeout?: number; timeoutMsg?: string }): Promise<boolean>;
1194
1284
 
1285
+ /**
1286
+ * Clicks at absolute viewport coordinates (x, y).
1287
+ *
1288
+ * @param x Horizontal viewport coordinate.
1289
+ * @param y Vertical viewport coordinate.
1290
+ * @example
1291
+ * ```ts
1292
+ * await Spectra.browser.clickAt(100, 200);
1293
+ * ```
1294
+ */
1295
+ clickAt(x: number, y: number): Promise<void>;
1296
+
1195
1297
  /**
1196
1298
  * Retrieves captured browser console log entries.
1197
1299
  *
@@ -1352,6 +1454,56 @@ export interface CDPNetworkEntry {
1352
1454
  startTime: number;
1353
1455
  }
1354
1456
 
1457
+ /**
1458
+ * A user- or library-registered command callable on the global `Spectra` object.
1459
+ *
1460
+ * Commands are registered at runtime via `Spectra.registerCommand()` (typically from a plugin's
1461
+ * `setup()`), and their types are exposed to consumers through TypeScript module augmentation of
1462
+ * `SpectraStatic` — there is no type-generator integration for external plugins.
1463
+ */
1464
+ export type SpectraCommand = (...args: any[]) => any;
1465
+
1466
+ /**
1467
+ * Context passed to a plugin's `setup()`. Plugins use `platform` to register only the commands
1468
+ * that are valid for the active target.
1469
+ */
1470
+ export interface SpectraPluginContext {
1471
+ /** Active execution platform: `'web'`, `'android'`, or `'ios'`. */
1472
+ platform: string;
1473
+ }
1474
+
1475
+ /**
1476
+ * A TestSpectra plugin. A plugin is any external library that extends the `Spectra` DSL — its
1477
+ * `setup` receives the live `Spectra` object and registers commands (or performs other one-time
1478
+ * wiring) before test files are imported.
1479
+ *
1480
+ * Plugins are declared in `spectra.config.ts` under `executionConfig.plugins` as module specifiers
1481
+ * and loaded by both the native E2E worker and the component-testing runtime. Platform-specific
1482
+ * command **types** are shipped as per-stem subpath entries (`./web`, `./android`, `./ios`,
1483
+ * `./mobile`, `./common`) and wired into the generated ambient declarations by the type generator.
1484
+ *
1485
+ * @example
1486
+ * ```ts
1487
+ * import type { SpectraPlugin } from '@testspectra/matchers';
1488
+ *
1489
+ * const plugin: SpectraPlugin = {
1490
+ * name: 'my-plugin',
1491
+ * setup(spectra, { platform }) {
1492
+ * spectra.registerCommand('findButton', (selector: string) => spectra.get(selector));
1493
+ * if (platform === 'web') spectra.registerCommand('findByCss', (s: string) => spectra.get(s));
1494
+ * },
1495
+ * };
1496
+ *
1497
+ * export default plugin;
1498
+ * ```
1499
+ */
1500
+ export interface SpectraPlugin {
1501
+ /** Unique plugin identifier, used for diagnostics. */
1502
+ name: string;
1503
+ /** One-time wiring executed after `Spectra` is built and before test files are imported. */
1504
+ setup(spectra: SpectraStatic, context: SpectraPluginContext): void | Promise<void>;
1505
+ }
1506
+
1355
1507
  /**
1356
1508
  * Main TestSpectra cross-platform automation and assertion interface contract.
1357
1509
  */
@@ -1530,6 +1682,17 @@ export interface SpectraStatic {
1530
1682
  */
1531
1683
  focus(target: ElementTarget): Promise<void>;
1532
1684
 
1685
+ /**
1686
+ * Removes focus from the target element (blurs it).
1687
+ *
1688
+ * @param target Target element selector or proxy.
1689
+ * @example
1690
+ * ```ts
1691
+ * await Spectra.blur('#email-input');
1692
+ * ```
1693
+ */
1694
+ blur(target: ElementTarget): Promise<void>;
1695
+
1533
1696
  /**
1534
1697
  * Drags a source element and drops it onto a destination element target.
1535
1698
  *
@@ -1682,4 +1845,34 @@ export interface SpectraStatic {
1682
1845
  * ```
1683
1846
  */
1684
1847
  clearMocks(): void;
1848
+
1849
+ /**
1850
+ * Registers a custom command on the `Spectra` object, making it callable as
1851
+ * `Spectra.<name>(...)`. Intended for plugins to extend the DSL.
1852
+ *
1853
+ * Throws if `name` is not a valid identifier, collides with a built-in `Spectra` method, or is
1854
+ * already registered — built-ins can never be monkey-patched.
1855
+ *
1856
+ * @param name Command name (must be a valid JavaScript identifier).
1857
+ * @param command Implementation to attach.
1858
+ * @example
1859
+ * ```ts
1860
+ * Spectra.registerCommand('findButton', (selector: string) => Spectra.get(selector));
1861
+ * ```
1862
+ */
1863
+ registerCommand(name: string, command: SpectraCommand): void;
1864
+
1865
+ /**
1866
+ * Registers multiple custom commands at once. Each entry is validated exactly like
1867
+ * `registerCommand`.
1868
+ *
1869
+ * @example
1870
+ * ```ts
1871
+ * Spectra.registerCommands({
1872
+ * findButton: (selector: string) => Spectra.get(selector),
1873
+ * findInput: (selector: string) => Spectra.get(selector),
1874
+ * });
1875
+ * ```
1876
+ */
1877
+ registerCommands(commands: Record<string, SpectraCommand>): void;
1685
1878
  }
@@ -99,4 +99,9 @@ export interface TestSpectraWorkerConfig {
99
99
  monitoredDomains?: { domain: string; enabled: boolean }[];
100
100
  /** Mirrors `spectra.config.ts`'s `executionConfig.environmentVariables`. */
101
101
  environmentVariables?: Record<string, string>;
102
+ /**
103
+ * Mirrors `spectra.config.ts`'s `executionConfig.plugins` — module specifiers (absolute paths,
104
+ * after CLI resolution) of TestSpectra plugins to install before test files are imported.
105
+ */
106
+ plugins?: string[];
102
107
  }
Binary file
Binary file