@testspectra/matchers 1.1.8-rc.20 → 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/dist/reporter.js CHANGED
@@ -67,19 +67,7 @@ export function formatArgs(args) {
67
67
  */
68
68
  export async function trackCommand(category, commandText, fn) {
69
69
  const start = Date.now();
70
- const payloadObj = typeof commandText === 'object' ? { ...commandText } : { text: commandText };
71
- if (typeof globalThis !== 'undefined') {
72
- const g = globalThis;
73
- const currentTest = g.__CURRENT_TEST_TITLE__ || g.__CURRENT_TEST_ID__;
74
- if (currentTest && !payloadObj.test) {
75
- payloadObj.test = currentTest;
76
- }
77
- const workerId = g.__TESTSPECTRA_WORKER_ID__;
78
- if (workerId !== undefined && payloadObj.workerId === undefined) {
79
- payloadObj.workerId = workerId;
80
- }
81
- }
82
- const payload = JSON.stringify(payloadObj);
70
+ const payload = typeof commandText === 'object' ? JSON.stringify(commandText) : commandText;
83
71
  console.log(`[TESTSPECTRA_STEP_START] ${payload}`);
84
72
  try {
85
73
  const result = await fn();
@@ -84,22 +84,9 @@ export class MultiElementRunner {
84
84
  eq(index) {
85
85
  const targetSelector = `${this._selector}[${index}]`;
86
86
  const elPromise = (async () => {
87
- const getElements = typeof $$ !== 'undefined' ? $$ : globalThis.$$;
88
- if (typeof getElements === 'function') {
89
- const elements = await getElements(this._selector);
90
- if (typeof elements?.nth === 'function') {
91
- return elements.nth(index);
92
- }
93
- if (index === 0 && typeof elements?.first === 'function') {
94
- return elements.first();
95
- }
96
- if (elements && elements[index] !== undefined) {
97
- return elements[index];
98
- }
99
- }
100
- const getDollar = typeof $ !== 'undefined' ? $ : globalThis.$;
101
- if (typeof getDollar === 'function') {
102
- return getDollar(targetSelector);
87
+ if (typeof $$ !== 'undefined') {
88
+ const elements = await $$(this._selector);
89
+ return elements[index];
103
90
  }
104
91
  return { selector: targetSelector };
105
92
  })();
@@ -130,23 +117,10 @@ export class MultiElementRunner {
130
117
  last() {
131
118
  const targetSelector = `${this._selector}[last()]`;
132
119
  const elPromise = (async () => {
133
- const getElements = typeof $$ !== 'undefined' ? $$ : globalThis.$$;
134
- if (typeof getElements === 'function') {
135
- const elements = await getElements(this._selector);
136
- if (typeof elements?.last === 'function') {
137
- return elements.last();
138
- }
139
- if (typeof elements?.nth === 'function') {
140
- return elements.nth(-1);
141
- }
120
+ if (typeof $$ !== 'undefined') {
121
+ const elements = await $$(this._selector);
142
122
  const arr = Array.isArray(elements) ? elements : await elements;
143
- if (arr && arr.length > 0) {
144
- return arr[arr.length - 1];
145
- }
146
- }
147
- const getDollar = typeof $ !== 'undefined' ? $ : globalThis.$;
148
- if (typeof getDollar === 'function') {
149
- return getDollar(targetSelector);
123
+ return arr[arr.length - 1];
150
124
  }
151
125
  return { selector: targetSelector };
152
126
  })();
@@ -90,11 +90,11 @@ export declare class SingleElementRunner implements PromiseLike<void>, ElementRe
90
90
  */
91
91
  select(value: string): this;
92
92
  /**
93
- * Hovers over the target element.
93
+ * Moves the mouse cursor over the target element (hover).
94
94
  *
95
95
  * @example
96
96
  * ```ts
97
- * await Spectra.get("#dropdown-trigger").hover();
97
+ * await Spectra.get("#dropdown-menu-trigger").hover();
98
98
  * ```
99
99
  * @returns Current runner instance for method chaining.
100
100
  */
@@ -120,17 +120,9 @@ export class SingleElementRunner {
120
120
  this._action = async () => {
121
121
  const el = await resolveElement(target);
122
122
  if (options?.clearFirst) {
123
- if (typeof el.clearValue === 'function')
124
- await el.clearValue();
125
- else if (typeof el.clear === 'function')
126
- await el.clear();
123
+ await el.clearValue();
127
124
  }
128
- if (typeof el.setValue === 'function')
129
- await el.setValue(value);
130
- else if (typeof el.fill === 'function')
131
- await el.fill(value);
132
- else if (typeof el.type === 'function')
133
- await el.type(value);
125
+ await el.setValue(value);
134
126
  };
135
127
  return this;
136
128
  }
@@ -148,10 +140,7 @@ export class SingleElementRunner {
148
140
  this._actionPayload = { type: 'action', key: 'clear', target: getRawTarget(target), args: [] };
149
141
  this._action = async () => {
150
142
  const el = await resolveElement(target);
151
- if (typeof el.clearValue === 'function')
152
- await el.clearValue();
153
- else if (typeof el.clear === 'function')
154
- await el.clear();
143
+ await el.clearValue();
155
144
  };
156
145
  return this;
157
146
  }
@@ -170,19 +159,16 @@ export class SingleElementRunner {
170
159
  this._actionPayload = { type: 'action', key: 'selectOption', target: getRawTarget(target), args: [value] };
171
160
  this._action = async () => {
172
161
  const el = await resolveElement(target);
173
- if (typeof el.selectByVisibleText === 'function')
174
- await el.selectByVisibleText(value);
175
- else if (typeof el.selectOption === 'function')
176
- await el.selectOption({ label: value });
162
+ await el.selectByVisibleText(value);
177
163
  };
178
164
  return this;
179
165
  }
180
166
  /**
181
- * Hovers over the target element.
167
+ * Moves the mouse cursor over the target element (hover).
182
168
  *
183
169
  * @example
184
170
  * ```ts
185
- * await Spectra.get("#dropdown-trigger").hover();
171
+ * await Spectra.get("#dropdown-menu-trigger").hover();
186
172
  * ```
187
173
  * @returns Current runner instance for method chaining.
188
174
  */
@@ -191,10 +177,7 @@ export class SingleElementRunner {
191
177
  this._actionPayload = { type: 'action', key: 'hover', target: getRawTarget(target), args: [] };
192
178
  this._action = async () => {
193
179
  const el = await resolveElement(target);
194
- if (typeof el.moveTo === 'function')
195
- await el.moveTo();
196
- else if (typeof el.hover === 'function')
197
- await el.hover();
180
+ await el.moveTo();
198
181
  };
199
182
  return this;
200
183
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@testspectra/matchers",
3
- "version": "1.1.8-rc.20",
3
+ "version": "1.1.8-rc.21",
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",
@@ -1 +0,0 @@
1
- export {};
@@ -1,498 +0,0 @@
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
- });