@testspectra/matchers 1.1.11-rc.5 → 1.1.13

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.
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,498 @@
1
+ import { describe, it, expect, vi } from 'vitest';
2
+ // Helper polling simulation resembling worker.ts pollCondition
3
+ async function pollCondition(fn, timeoutMs = 100, intervalMs = 10) {
4
+ const start = Date.now();
5
+ while (Date.now() - start < timeoutMs) {
6
+ try {
7
+ if (await fn())
8
+ return true;
9
+ }
10
+ catch { }
11
+ await new Promise((r) => setTimeout(r, intervalMs));
12
+ }
13
+ try {
14
+ return await fn();
15
+ }
16
+ catch {
17
+ return false;
18
+ }
19
+ }
20
+ // Factory to create a test SingleElementProxy backed by simulated DOM element state
21
+ function createTestElementProxy(selector, state) {
22
+ const proxy = {
23
+ selector,
24
+ async isDisplayed() {
25
+ return state.displayed ?? true;
26
+ },
27
+ async isExisting() {
28
+ return state.existing ?? true;
29
+ },
30
+ async isEnabled() {
31
+ return state.enabled ?? true;
32
+ },
33
+ async isSelected() {
34
+ return state.selected ?? false;
35
+ },
36
+ async isFocused() {
37
+ return state.focused ?? false;
38
+ },
39
+ async getText() {
40
+ return state.text ?? '';
41
+ },
42
+ async getValue() {
43
+ return state.value ?? '';
44
+ },
45
+ async getAttribute(name) {
46
+ return state.attributes?.[name] ?? null;
47
+ },
48
+ async getCSSProperty(property) {
49
+ return { property, value: state.css?.[property] ?? '', parsed: {} };
50
+ },
51
+ async click() { },
52
+ async doubleClick() { },
53
+ async rightClick() { },
54
+ async hover() { },
55
+ async focus() { },
56
+ async clear() { },
57
+ async type(_text) { },
58
+ async select(_option) { },
59
+ async selectByAttribute(_attribute, _value) { },
60
+ async selectByValue(_value) { },
61
+ async selectByIndex(_index) { },
62
+ async selectByVisibleText(_text) { },
63
+ async dragDrop(_destination) { },
64
+ async scrollIntoView() { },
65
+ async swipe(_options) { },
66
+ async longPress(_options) { },
67
+ async waitForElement(_timeoutMs) { },
68
+ async waitForExist(_timeoutMs) { },
69
+ async waitForDisplayed(_timeoutMs) { },
70
+ async waitForEnabled(_timeoutMs) { },
71
+ // Matcher implementations identical to worker.ts logic
72
+ async shouldBeVisible() {
73
+ const ok = await pollCondition(async () => await this.isDisplayed());
74
+ if (!ok)
75
+ throw new Error(`Expected element "${selector}" to be visible`);
76
+ },
77
+ async shouldNotBeVisible() {
78
+ const ok = await pollCondition(async () => !(await this.isDisplayed()));
79
+ if (!ok)
80
+ throw new Error(`Expected element "${selector}" not to be visible`);
81
+ },
82
+ async shouldExist() {
83
+ const ok = await pollCondition(async () => await this.isExisting());
84
+ if (!ok)
85
+ throw new Error(`Expected element "${selector}" to exist in DOM`);
86
+ },
87
+ async shouldNotExist() {
88
+ const ok = await pollCondition(async () => !(await this.isExisting()));
89
+ if (!ok)
90
+ throw new Error(`Expected element "${selector}" not to exist in DOM`);
91
+ },
92
+ async shouldBeEnabled() {
93
+ const ok = await pollCondition(async () => await this.isEnabled());
94
+ if (!ok)
95
+ throw new Error(`Expected element "${selector}" to be enabled`);
96
+ },
97
+ async shouldBeDisabled() {
98
+ const ok = await pollCondition(async () => !(await this.isEnabled()));
99
+ if (!ok)
100
+ throw new Error(`Expected element "${selector}" to be disabled`);
101
+ },
102
+ async shouldBeChecked() {
103
+ const ok = await pollCondition(async () => await this.isSelected());
104
+ if (!ok)
105
+ throw new Error(`Expected element "${selector}" to be checked`);
106
+ },
107
+ async shouldNotBeChecked() {
108
+ const ok = await pollCondition(async () => !(await this.isSelected()));
109
+ if (!ok)
110
+ throw new Error(`Expected element "${selector}" not to be checked`);
111
+ },
112
+ async shouldBeFocused() {
113
+ const ok = await pollCondition(async () => await this.isFocused());
114
+ if (!ok)
115
+ throw new Error(`Expected element "${selector}" to be focused`);
116
+ },
117
+ async shouldNotBeFocused() {
118
+ const ok = await pollCondition(async () => !(await this.isFocused()));
119
+ if (!ok)
120
+ throw new Error(`Expected element "${selector}" not to be focused`);
121
+ },
122
+ async shouldHaveText(expected) {
123
+ let actual = '';
124
+ const ok = await pollCondition(async () => {
125
+ actual = await this.getText();
126
+ if (expected instanceof RegExp)
127
+ return expected.test(actual);
128
+ return actual === String(expected) || actual.trim() === String(expected).trim();
129
+ });
130
+ if (!ok) {
131
+ if (expected instanceof RegExp)
132
+ throw new Error(`Expected element "${selector}" text to match ${expected}, but got "${actual}"`);
133
+ throw new Error(`Expected element "${selector}" to have text "${expected}", but got "${actual}"`);
134
+ }
135
+ },
136
+ async shouldNotHaveText(expected) {
137
+ let actual = '';
138
+ const ok = await pollCondition(async () => {
139
+ actual = await this.getText();
140
+ if (expected instanceof RegExp)
141
+ return !expected.test(actual);
142
+ return actual !== String(expected) && actual.trim() !== String(expected).trim();
143
+ });
144
+ if (!ok) {
145
+ if (expected instanceof RegExp)
146
+ throw new Error(`Expected element "${selector}" text not to match ${expected}`);
147
+ throw new Error(`Expected element "${selector}" not to have text "${expected}"`);
148
+ }
149
+ },
150
+ async shouldContainText(substring) {
151
+ let actual = '';
152
+ const ok = await pollCondition(async () => {
153
+ actual = await this.getText();
154
+ return actual.includes(String(substring));
155
+ });
156
+ if (!ok)
157
+ throw new Error(`Expected element "${selector}" to contain text "${substring}", but got "${actual}"`);
158
+ },
159
+ async shouldNotContainText(substring) {
160
+ let actual = '';
161
+ const ok = await pollCondition(async () => {
162
+ actual = await this.getText();
163
+ return !actual.includes(String(substring));
164
+ });
165
+ if (!ok)
166
+ throw new Error(`Expected element "${selector}" not to contain text "${substring}"`);
167
+ },
168
+ async shouldHaveValue(value) {
169
+ let actual = '';
170
+ const ok = await pollCondition(async () => {
171
+ actual = await this.getValue();
172
+ return actual === String(value);
173
+ });
174
+ if (!ok)
175
+ throw new Error(`Expected element "${selector}" to have value "${value}", but got "${actual}"`);
176
+ },
177
+ async shouldNotHaveValue(value) {
178
+ const ok = await pollCondition(async () => {
179
+ const actual = await this.getValue();
180
+ return actual !== String(value);
181
+ });
182
+ if (!ok)
183
+ throw new Error(`Expected element "${selector}" not to have value "${value}"`);
184
+ },
185
+ async shouldContainValue(value) {
186
+ let actual = '';
187
+ const ok = await pollCondition(async () => {
188
+ actual = await this.getValue();
189
+ return actual.includes(String(value));
190
+ });
191
+ if (!ok)
192
+ throw new Error(`Expected element "${selector}" to contain value "${value}", but got "${actual}"`);
193
+ },
194
+ async shouldNotContainValue(value) {
195
+ const ok = await pollCondition(async () => {
196
+ const actual = await this.getValue();
197
+ return !actual.includes(String(value));
198
+ });
199
+ if (!ok)
200
+ throw new Error(`Expected element "${selector}" not to contain value "${value}"`);
201
+ },
202
+ async shouldHaveClass(className) {
203
+ const ok = await pollCondition(async () => state.classes?.includes(className) ?? false);
204
+ if (!ok)
205
+ throw new Error(`Expected element "${selector}" to have class "${className}"`);
206
+ },
207
+ async shouldNotHaveClass(className) {
208
+ const ok = await pollCondition(async () => !(state.classes?.includes(className) ?? false));
209
+ if (!ok)
210
+ throw new Error(`Expected element "${selector}" not to have class "${className}"`);
211
+ },
212
+ async shouldHaveAttribute(name, value) {
213
+ let actual = null;
214
+ const ok = await pollCondition(async () => {
215
+ actual = await this.getAttribute(name);
216
+ if (value !== undefined)
217
+ return actual === String(value);
218
+ return actual !== null;
219
+ });
220
+ if (!ok) {
221
+ if (value !== undefined)
222
+ throw new Error(`Expected element "${selector}" to have attribute "${name}" = "${value}", but got "${actual}"`);
223
+ throw new Error(`Expected element "${selector}" to have attribute "${name}"`);
224
+ }
225
+ },
226
+ async shouldNotHaveAttribute(name) {
227
+ const ok = await pollCondition(async () => {
228
+ const actual = await this.getAttribute(name);
229
+ return actual === null;
230
+ });
231
+ if (!ok)
232
+ throw new Error(`Expected element "${selector}" not to have attribute "${name}"`);
233
+ },
234
+ async shouldHaveCss(property, value) {
235
+ let actualCss = '';
236
+ const ok = await pollCondition(async () => {
237
+ const css = await this.getCSSProperty(property);
238
+ actualCss = css.value;
239
+ return actualCss === value;
240
+ });
241
+ if (!ok)
242
+ throw new Error(`Expected element "${selector}" to have CSS "${property}" = "${value}", but got "${actualCss}"`);
243
+ },
244
+ async shouldNotHaveCss(property, value) {
245
+ const ok = await pollCondition(async () => {
246
+ const css = await this.getCSSProperty(property);
247
+ return css.value !== value;
248
+ });
249
+ if (!ok)
250
+ throw new Error(`Expected element "${selector}" not to have CSS "${property}" = "${value}"`);
251
+ },
252
+ };
253
+ return proxy;
254
+ }
255
+ // Factory to create a test CollectionProxy backed by simulated count
256
+ function createTestCollectionProxy(selector, itemsCount) {
257
+ return {
258
+ selector,
259
+ async count() {
260
+ return itemsCount;
261
+ },
262
+ get length() {
263
+ return this.count();
264
+ },
265
+ first() {
266
+ return createTestElementProxy(`${selector}:first-child`, {});
267
+ },
268
+ last() {
269
+ return createTestElementProxy(`${selector}:last-child`, {});
270
+ },
271
+ nth(index) {
272
+ return createTestElementProxy(`${selector}:nth-child(${index})`, {});
273
+ },
274
+ async shouldHaveLength(expected) {
275
+ let actual = 0;
276
+ const ok = await pollCondition(async () => {
277
+ actual = await this.count();
278
+ return actual === expected;
279
+ });
280
+ if (!ok)
281
+ throw new Error(`Expected collection "${selector}" count to equal ${expected}, but got ${actual}`);
282
+ },
283
+ async shouldNotHaveLength(expected) {
284
+ const ok = await pollCondition(async () => {
285
+ const actual = await this.count();
286
+ return actual !== expected;
287
+ });
288
+ if (!ok)
289
+ throw new Error(`Expected collection "${selector}" count not to equal ${expected}`);
290
+ },
291
+ async shouldHaveLengthGreaterThan(expected) {
292
+ let actual = 0;
293
+ const ok = await pollCondition(async () => {
294
+ actual = await this.count();
295
+ return actual > expected;
296
+ });
297
+ if (!ok)
298
+ throw new Error(`Expected collection "${selector}" count to be greater than ${expected}, but got ${actual}`);
299
+ },
300
+ async shouldHaveLengthLessThan(expected) {
301
+ let actual = 0;
302
+ const ok = await pollCondition(async () => {
303
+ actual = await this.count();
304
+ return actual < expected;
305
+ });
306
+ if (!ok)
307
+ throw new Error(`Expected collection "${selector}" count to be less than ${expected}, but got ${actual}`);
308
+ },
309
+ async shouldBeEmpty() {
310
+ let actual = 0;
311
+ const ok = await pollCondition(async () => {
312
+ actual = await this.count();
313
+ return actual === 0;
314
+ });
315
+ if (!ok)
316
+ throw new Error(`Expected collection "${selector}" to be empty, but got ${actual}`);
317
+ },
318
+ async shouldNotBeEmpty() {
319
+ const ok = await pollCondition(async () => {
320
+ const actual = await this.count();
321
+ return actual > 0;
322
+ });
323
+ if (!ok)
324
+ throw new Error(`Expected collection "${selector}" not to be empty`);
325
+ },
326
+ };
327
+ }
328
+ describe('Worker Matchers & Static Actions Verification Suite', () => {
329
+ describe('Single Element Visibility & Existence Matchers', () => {
330
+ it('passes shouldBeVisible when element is displayed, fails and throws when not displayed', async () => {
331
+ const visibleEl = createTestElementProxy('#btn-visible', { displayed: true });
332
+ await expect(visibleEl.shouldBeVisible()).resolves.toBeUndefined();
333
+ const hiddenEl = createTestElementProxy('#btn-hidden', { displayed: false });
334
+ await expect(hiddenEl.shouldBeVisible()).rejects.toThrow('Expected element "#btn-hidden" to be visible');
335
+ });
336
+ it('passes shouldNotBeVisible when element is hidden, fails and throws when displayed', async () => {
337
+ const hiddenEl = createTestElementProxy('#btn-hidden', { displayed: false });
338
+ await expect(hiddenEl.shouldNotBeVisible()).resolves.toBeUndefined();
339
+ const visibleEl = createTestElementProxy('#btn-visible', { displayed: true });
340
+ await expect(visibleEl.shouldNotBeVisible()).rejects.toThrow('Expected element "#btn-visible" not to be visible');
341
+ });
342
+ it('passes shouldExist when element is in DOM, fails and throws when missing', async () => {
343
+ const domEl = createTestElementProxy('#card', { existing: true });
344
+ await expect(domEl.shouldExist()).resolves.toBeUndefined();
345
+ const missingEl = createTestElementProxy('#missing-card', { existing: false });
346
+ await expect(missingEl.shouldExist()).rejects.toThrow('Expected element "#missing-card" to exist in DOM');
347
+ });
348
+ it('passes shouldNotExist when element is missing, fails and throws when element exists', async () => {
349
+ const missingEl = createTestElementProxy('#missing-card', { existing: false });
350
+ await expect(missingEl.shouldNotExist()).resolves.toBeUndefined();
351
+ const domEl = createTestElementProxy('#card', { existing: true });
352
+ await expect(domEl.shouldNotExist()).rejects.toThrow('Expected element "#card" not to exist in DOM');
353
+ });
354
+ });
355
+ describe('Single Element State Matchers (Enabled, Checked, Focused)', () => {
356
+ it('verifies shouldBeEnabled and shouldBeDisabled strictly', async () => {
357
+ const enabledEl = createTestElementProxy('#submit-btn', { enabled: true });
358
+ await expect(enabledEl.shouldBeEnabled()).resolves.toBeUndefined();
359
+ await expect(enabledEl.shouldBeDisabled()).rejects.toThrow('Expected element "#submit-btn" to be disabled');
360
+ const disabledEl = createTestElementProxy('#disabled-btn', { enabled: false });
361
+ await expect(disabledEl.shouldBeDisabled()).resolves.toBeUndefined();
362
+ await expect(disabledEl.shouldBeEnabled()).rejects.toThrow('Expected element "#disabled-btn" to be enabled');
363
+ });
364
+ it('verifies shouldBeChecked and shouldNotBeChecked strictly', async () => {
365
+ const checkedCheckbox = createTestElementProxy('#agree', { selected: true });
366
+ await expect(checkedCheckbox.shouldBeChecked()).resolves.toBeUndefined();
367
+ await expect(checkedCheckbox.shouldNotBeChecked()).rejects.toThrow('Expected element "#agree" not to be checked');
368
+ const uncheckedCheckbox = createTestElementProxy('#disagree', { selected: false });
369
+ await expect(uncheckedCheckbox.shouldNotBeChecked()).resolves.toBeUndefined();
370
+ await expect(uncheckedCheckbox.shouldBeChecked()).rejects.toThrow('Expected element "#disagree" to be checked');
371
+ });
372
+ it('verifies shouldBeFocused and shouldNotBeFocused strictly', async () => {
373
+ const focusedInput = createTestElementProxy('#email-input', { focused: true });
374
+ await expect(focusedInput.shouldBeFocused()).resolves.toBeUndefined();
375
+ await expect(focusedInput.shouldNotBeFocused()).rejects.toThrow('Expected element "#email-input" not to be focused');
376
+ const unfocusedInput = createTestElementProxy('#password-input', { focused: false });
377
+ await expect(unfocusedInput.shouldNotBeFocused()).resolves.toBeUndefined();
378
+ await expect(unfocusedInput.shouldBeFocused()).rejects.toThrow('Expected element "#password-input" to be focused');
379
+ });
380
+ });
381
+ describe('Content Matchers (Text, Value, Classes, Attributes, CSS)', () => {
382
+ it('strictly checks text equality and fails on mismatch', async () => {
383
+ const textEl = createTestElementProxy('#heading', { text: 'Welcome to TestSpectra' });
384
+ await expect(textEl.shouldHaveText('Welcome to TestSpectra')).resolves.toBeUndefined();
385
+ await expect(textEl.shouldHaveText(/Welcome to \w+/)).resolves.toBeUndefined();
386
+ await expect(textEl.shouldContainText('TestSpectra')).resolves.toBeUndefined();
387
+ // Negative assertions
388
+ await expect(textEl.shouldNotHaveText('Goodbye')).resolves.toBeUndefined();
389
+ await expect(textEl.shouldNotContainText('Cypress')).resolves.toBeUndefined();
390
+ // Failing conditions MUST throw and not falsely pass
391
+ await expect(textEl.shouldHaveText('Wrong Text')).rejects.toThrow('Expected element "#heading" to have text "Wrong Text", but got "Welcome to TestSpectra"');
392
+ await expect(textEl.shouldContainText('Selenium')).rejects.toThrow('Expected element "#heading" to contain text "Selenium", but got "Welcome to TestSpectra"');
393
+ await expect(textEl.shouldNotHaveText('Welcome to TestSpectra')).rejects.toThrow('Expected element "#heading" not to have text "Welcome to TestSpectra"');
394
+ await expect(textEl.shouldNotContainText('TestSpectra')).rejects.toThrow('Expected element "#heading" not to contain text "TestSpectra"');
395
+ });
396
+ it('strictly checks input values and fails on mismatch', async () => {
397
+ const inputEl = createTestElementProxy('#username', { value: 'admin_testspectra' });
398
+ await expect(inputEl.shouldHaveValue('admin_testspectra')).resolves.toBeUndefined();
399
+ await expect(inputEl.shouldContainValue('admin_')).resolves.toBeUndefined();
400
+ await expect(inputEl.shouldNotHaveValue('guest')).resolves.toBeUndefined();
401
+ await expect(inputEl.shouldNotContainValue('root')).resolves.toBeUndefined();
402
+ await expect(inputEl.shouldHaveValue('wrong_user')).rejects.toThrow('Expected element "#username" to have value "wrong_user", but got "admin_testspectra"');
403
+ await expect(inputEl.shouldNotHaveValue('admin_testspectra')).rejects.toThrow('Expected element "#username" not to have value "admin_testspectra"');
404
+ });
405
+ it('strictly checks classes and attributes', async () => {
406
+ const badge = createTestElementProxy('#status-badge', {
407
+ classes: ['badge', 'badge-success', 'active'],
408
+ attributes: { id: 'status-badge', 'data-role': 'indicator', disabled: 'false' },
409
+ css: { color: 'rgb(0, 128, 0)' },
410
+ });
411
+ await expect(badge.shouldHaveClass('badge-success')).resolves.toBeUndefined();
412
+ await expect(badge.shouldNotHaveClass('badge-danger')).resolves.toBeUndefined();
413
+ await expect(badge.shouldHaveAttribute('data-role', 'indicator')).resolves.toBeUndefined();
414
+ await expect(badge.shouldNotHaveAttribute('data-unknown')).resolves.toBeUndefined();
415
+ await expect(badge.shouldHaveCss('color', 'rgb(0, 128, 0)')).resolves.toBeUndefined();
416
+ // Fails when class/attribute is missing or incorrect
417
+ await expect(badge.shouldHaveClass('badge-danger')).rejects.toThrow('Expected element "#status-badge" to have class "badge-danger"');
418
+ await expect(badge.shouldNotHaveClass('badge-success')).rejects.toThrow('Expected element "#status-badge" not to have class "badge-success"');
419
+ await expect(badge.shouldHaveAttribute('data-role', 'wrong-val')).rejects.toThrow('Expected element "#status-badge" to have attribute "data-role" = "wrong-val", but got "indicator"');
420
+ await expect(badge.shouldNotHaveAttribute('data-role')).rejects.toThrow('Expected element "#status-badge" not to have attribute "data-role"');
421
+ });
422
+ });
423
+ describe('Collection Matchers ($$ / Spectra.getAll)', () => {
424
+ it('strictly checks collection lengths and emptiness', async () => {
425
+ const items = createTestCollectionProxy('.post-item', 5);
426
+ await expect(items.shouldHaveLength(5)).resolves.toBeUndefined();
427
+ await expect(items.shouldHaveLengthGreaterThan(3)).resolves.toBeUndefined();
428
+ await expect(items.shouldHaveLengthLessThan(10)).resolves.toBeUndefined();
429
+ await expect(items.shouldNotBeEmpty()).resolves.toBeUndefined();
430
+ // Failing conditions MUST throw
431
+ await expect(items.shouldHaveLength(0)).rejects.toThrow('Expected collection ".post-item" count to equal 0, but got 5');
432
+ await expect(items.shouldHaveLengthGreaterThan(10)).rejects.toThrow('Expected collection ".post-item" count to be greater than 10, but got 5');
433
+ await expect(items.shouldHaveLengthLessThan(3)).rejects.toThrow('Expected collection ".post-item" count to be less than 3, but got 5');
434
+ await expect(items.shouldBeEmpty()).rejects.toThrow('Expected collection ".post-item" to be empty, but got 5');
435
+ const emptyItems = createTestCollectionProxy('.empty-list', 0);
436
+ await expect(emptyItems.shouldBeEmpty()).resolves.toBeUndefined();
437
+ await expect(emptyItems.shouldNotBeEmpty()).rejects.toThrow('Expected collection ".empty-list" not to be empty');
438
+ });
439
+ });
440
+ describe('Spectra Static Automation API (Single Global Object)', () => {
441
+ it('correctly maps Spectra static methods to underlying actions without error', async () => {
442
+ const elementMock = createTestElementProxy('#test-btn', { text: 'Click Me', value: 'hello' });
443
+ const clickSpy = vi.spyOn(elementMock, 'click');
444
+ const typeSpy = vi.spyOn(elementMock, 'type');
445
+ const clearSpy = vi.spyOn(elementMock, 'clear');
446
+ const mockSpectra = {
447
+ get: (target) => (typeof target === 'string' ? elementMock : target),
448
+ getAll: (selector) => createTestCollectionProxy(selector, 2),
449
+ navigate: async (_url) => { },
450
+ back: async () => { },
451
+ forward: async () => { },
452
+ refresh: async () => { },
453
+ setViewport: async (_w, _h) => { },
454
+ wait: async (ms) => {
455
+ await new Promise((r) => setTimeout(r, ms));
456
+ },
457
+ click: async (target) => {
458
+ const el = typeof target === 'string' ? elementMock : target;
459
+ await el.click();
460
+ },
461
+ doubleClick: async () => { },
462
+ rightClick: async () => { },
463
+ type: async (target, text) => {
464
+ const el = typeof target === 'string' ? elementMock : target;
465
+ await el.type(text);
466
+ },
467
+ clear: async (target) => {
468
+ const el = typeof target === 'string' ? elementMock : target;
469
+ await el.clear();
470
+ },
471
+ select: async () => { },
472
+ hover: async () => { },
473
+ dragDrop: async () => { },
474
+ scrollIntoView: async () => { },
475
+ scroll: async () => { },
476
+ swipe: async () => { },
477
+ longPress: async () => { },
478
+ pressKey: async () => { },
479
+ waitForElement: async () => { },
480
+ intercept: async () => ({ respondWith: async () => { }, callCount: () => 0 }),
481
+ clearMocks: () => { },
482
+ browser: {},
483
+ };
484
+ // Test actions call underlying proxy methods
485
+ await mockSpectra.click('#test-btn');
486
+ expect(clickSpy).toHaveBeenCalled();
487
+ await mockSpectra.type('#test-btn', 'Test input');
488
+ expect(typeSpy).toHaveBeenCalledWith('Test input');
489
+ await mockSpectra.clear('#test-btn');
490
+ expect(clearSpy).toHaveBeenCalled();
491
+ // Test wait execution timing
492
+ const start = Date.now();
493
+ await mockSpectra.wait(20);
494
+ const elapsed = Date.now() - start;
495
+ expect(elapsed).toBeGreaterThanOrEqual(15);
496
+ });
497
+ });
498
+ });
@@ -7,48 +7,13 @@
7
7
  * (`WebCdpDriverBridge`, `AndroidTcpDriverBridge`) implements this interface so that the
8
8
  * DSL layer contains zero platform branching.
9
9
  */
10
+ import type { TestSpectraWorkerConfig } from './worker_config.js';
10
11
  import type { CDPNetworkEntry, MockInterceptHandle, MockRule, ScopedSelector, ScrollOptions, SpectraBrowserBridge, SwipeOptions } from './types.js';
11
12
  /**
12
- * Runtime configuration injected by the Rust orchestrator via `globalThis.__TESTSPECTRA_CONFIG__`.
13
+ * @deprecated Renamed to `TestSpectraWorkerConfig` (now shared by E2E and component testing).
14
+ * Kept as an alias for backwards compatibility.
13
15
  */
14
- export interface RuntimeWorkerConfig {
15
- workerId?: number;
16
- rootDir: string;
17
- testFilePath?: string;
18
- testFiles?: Array<{
19
- id: string;
20
- title: string;
21
- filePath: string;
22
- suite: string;
23
- }>;
24
- activePlatform: string;
25
- configuredBaseUrl: string;
26
- cdpWsUrl: string;
27
- driverServerUrl: string;
28
- manifest?: Record<string, any>;
29
- /** Target app package (Android). Paired with androidSerial/adbPath for on-demand adb actions. */
30
- appPackage?: string;
31
- /** Target adb device serial for this worker (Android only). */
32
- androidSerial?: string;
33
- /** Resolved `adb` binary path (Android only), paired with androidSerial above. */
34
- adbPath?: string;
35
- /** Host port this worker's own MobileMockProxyServer should listen on (Android only) — derived
36
- * per-device the same way androidSerial/the driver-server port already are, so each device in
37
- * a multi-device run gets its own proxy instead of every worker racing for one shared port. */
38
- mockProxyPort?: number;
39
- implicitWaitMs?: number;
40
- timeoutMs?: number;
41
- stepDelayMs?: number;
42
- /** Mirrors `spectra.config.ts`'s `executionConfig.networkMonitoringEnabled` (default: true). */
43
- networkMonitoringEnabled?: boolean;
44
- /** Mirrors `spectra.config.ts`'s `executionConfig.monitoredDomains`. Empty/absent = capture all. */
45
- monitoredDomains?: {
46
- domain: string;
47
- enabled: boolean;
48
- }[];
49
- /** Mirrors `spectra.config.ts`'s `executionConfig.environmentVariables`. */
50
- environmentVariables?: Record<string, string>;
51
- }
16
+ export type RuntimeWorkerConfig = TestSpectraWorkerConfig;
52
17
  /**
53
18
  * Cross-cutting shared runtime state injected into the assembled worker script.
54
19
  *
@@ -139,6 +104,7 @@ export interface PlatformDriverBridge extends SpectraBrowserBridge {
139
104
  dragDrop(sourceSelector: string | ScopedSelector, targetSelector: string, sourceIndex?: number | null): Promise<void>;
140
105
  longPress(selector: string | ScopedSelector, duration: number, index?: number | null): Promise<void>;
141
106
  scrollIntoView(selector: string | ScopedSelector, index?: number | null): Promise<void>;
107
+ upload(selector: string | ScopedSelector, files: unknown, index?: number | null): Promise<void>;
142
108
  getText(selector: string | ScopedSelector, index?: number | null): Promise<string>;
143
109
  getValue(selector: string | ScopedSelector, index?: number | null): Promise<string>;
144
110
  isDisplayed(selector: string | ScopedSelector, index?: number | null): Promise<boolean>;
@@ -168,6 +134,3 @@ export interface PlatformDriverBridge extends SpectraBrowserBridge {
168
134
  consoleErrors: string[];
169
135
  recordedNetwork: CDPNetworkEntry[];
170
136
  }
171
- declare global {
172
- var __TS_RUNTIME__: RuntimeContext;
173
- }
package/dist/index.d.ts CHANGED
@@ -5,5 +5,6 @@
5
5
  * Canonical TypeScript contracts, action/assertion types, and ambient type definitions for TestSpectra.
6
6
  */
7
7
  export * from './types.js';
8
+ export * from './worker_config.js';
8
9
  export * from './contract.js';
9
10
  export * from './proto.js';
package/dist/index.js CHANGED
@@ -5,5 +5,6 @@
5
5
  * Canonical TypeScript contracts, action/assertion types, and ambient type definitions for TestSpectra.
6
6
  */
7
7
  export * from './types.js';
8
+ export * from './worker_config.js';
8
9
  export * from './contract.js';
9
10
  export * from './proto.js';
@@ -6,9 +6,16 @@ import { SingleElementMatcher, MultiElementMatcher, ElementTarget } from './type
6
6
  * @param target - Selector string or element instance.
7
7
  * @returns Resolved WebdriverIO.Element instance.
8
8
  */
9
- export declare function resolveElement(target: ElementTarget): Promise<WebdriverIO.Element>;
10
9
  /**
11
- * Executes a single element matcher against the WebdriverIO browser / element context.
10
+ * Resolves an ElementTarget (selector string, WebdriverIO.Element, Playwright Locator, or ChainablePromiseElement)
11
+ * to an actionable element instance.
12
+ *
13
+ * @param target - Selector string or element instance.
14
+ * @returns Resolved element or locator instance.
15
+ */
16
+ export declare function resolveElement(target: ElementTarget): Promise<any>;
17
+ /**
18
+ * Executes a single element matcher against the active runner context (Playwright or WebdriverIO).
12
19
  *
13
20
  * @param target - Optional element target (not required for browser-level assertions).
14
21
  * @param matcher - Single element matcher string.
@@ -16,7 +23,7 @@ export declare function resolveElement(target: ElementTarget): Promise<Webdriver
16
23
  */
17
24
  export declare function executeSingleMatcher(target: ElementTarget | undefined, matcher: SingleElementMatcher, args: any[]): Promise<void>;
18
25
  /**
19
- * Executes a multi-element collection matcher against WebdriverIO $$.
26
+ * Executes a multi-element collection matcher against $$.
20
27
  *
21
28
  * @param selector - CSS/XPath selector for the collection.
22
29
  * @param matcher - Multi-element matcher string.