@testspectra/matchers 1.0.69 → 1.1.0

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/tsconfig.json CHANGED
@@ -9,9 +9,9 @@
9
9
  "strict": true,
10
10
  "esModuleInterop": true,
11
11
  "skipLibCheck": true,
12
- "types": ["node", "@wdio/globals/types", "@wdio/mocha-framework"],
12
+ "types": ["node"],
13
13
  "forceConsistentCasingInFileNames": true
14
14
  },
15
15
  "include": ["src/**/*"],
16
- "exclude": ["node_modules", "dist"]
16
+ "exclude": ["node_modules", "dist", "src/**/__tests__/**"]
17
17
  }
package/src/matchers.ts DELETED
@@ -1,146 +0,0 @@
1
- import { SingleElementMatcher, MultiElementMatcher, ElementTarget } from "./types.js";
2
-
3
- /**
4
- * Resolves an ElementTarget (selector string, WebdriverIO.Element, or ChainablePromiseElement)
5
- * to an actionable WebdriverIO.Element.
6
- *
7
- * @param target - Selector string or element instance.
8
- * @returns Resolved WebdriverIO.Element instance.
9
- */
10
- export async function resolveElement(target: ElementTarget): Promise<WebdriverIO.Element> {
11
- if (typeof target === "string") {
12
- const el = await $(target);
13
- return el as unknown as WebdriverIO.Element;
14
- }
15
- const el = await (target as any);
16
- return el as unknown as WebdriverIO.Element;
17
- }
18
-
19
- /**
20
- * Executes a single element matcher against the WebdriverIO browser / element context.
21
- *
22
- * @param target - Optional element target (not required for browser-level assertions).
23
- * @param matcher - Single element matcher string.
24
- * @param args - Arguments passed to the matcher.
25
- */
26
- export async function executeSingleMatcher(
27
- target: ElementTarget | undefined,
28
- matcher: SingleElementMatcher,
29
- args: any[]
30
- ): Promise<void> {
31
- const isBrowserLevel =
32
- matcher === "have.url" ||
33
- matcher === "contain.url" ||
34
- matcher === "have.title" ||
35
- matcher === "contain.title";
36
-
37
- if (isBrowserLevel) {
38
- const expected = args[0];
39
- switch (matcher) {
40
- case "have.url":
41
- await expect(browser).toHaveUrl(expected);
42
- break;
43
- case "contain.url":
44
- await expect(browser).toHaveUrl(expect.stringContaining(expected));
45
- break;
46
- case "have.title":
47
- await expect(browser).toHaveTitle(expected);
48
- break;
49
- case "contain.title":
50
- await expect(browser).toHaveTitle(expect.stringContaining(expected));
51
- break;
52
- }
53
- return;
54
- }
55
-
56
- if (!target) {
57
- throw new Error(`Assertion matcher '${matcher}' requires an element target.`);
58
- }
59
-
60
- const el = await resolveElement(target);
61
-
62
- switch (matcher) {
63
- case "be.visible":
64
- await expect(el).toBeDisplayed();
65
- break;
66
- case "not.be.visible":
67
- await expect(el).not.toBeDisplayed();
68
- break;
69
- case "exist":
70
- await expect(el).toExist();
71
- break;
72
- case "not.exist":
73
- await expect(el).not.toExist();
74
- break;
75
- case "be.enabled":
76
- await expect(el).toBeEnabled();
77
- break;
78
- case "be.disabled":
79
- await expect(el).toBeDisabled();
80
- break;
81
- case "be.checked":
82
- case "be.selected":
83
- await expect(el).toBeSelected();
84
- break;
85
- case "not.be.checked":
86
- case "not.be.selected":
87
- await expect(el).not.toBeSelected();
88
- break;
89
- case "have.value":
90
- await expect(el).toHaveValue(args[0]);
91
- break;
92
- case "contain.value":
93
- await expect(el).toHaveValue(expect.stringContaining(args[0]));
94
- break;
95
- case "have.text":
96
- await expect(el).toHaveText(args[0]);
97
- break;
98
- case "contain.text":
99
- await expect(el).toHaveText(expect.stringContaining(args[0]));
100
- break;
101
- case "have.class":
102
- await expect(el).toHaveElementClass(args[0]);
103
- break;
104
- case "have.attr":
105
- if (args.length >= 2) {
106
- await expect(el).toHaveAttribute(args[0], args[1]);
107
- } else {
108
- await expect(el).toHaveAttribute(args[0]);
109
- }
110
- break;
111
- default:
112
- throw new Error(`Unsupported matcher: ${matcher}`);
113
- }
114
- }
115
-
116
- /**
117
- * Executes a multi-element collection matcher against WebdriverIO $$.
118
- *
119
- * @param selector - CSS/XPath selector for the collection.
120
- * @param matcher - Multi-element matcher string.
121
- * @param args - Expected values or count limits.
122
- */
123
- export async function executeMultiMatcher(
124
- selector: string,
125
- matcher: MultiElementMatcher,
126
- args: any[]
127
- ): Promise<void> {
128
- const elements = await $$(selector);
129
-
130
- switch (matcher) {
131
- case "have.length":
132
- await expect(elements).toBeElementsArrayOfSize(args[0]);
133
- break;
134
- case "have.length.greaterThan":
135
- await expect(elements).toBeElementsArrayOfSize({ gte: args[0] + 1 });
136
- break;
137
- case "be.empty":
138
- await expect(elements).toBeElementsArrayOfSize(0);
139
- break;
140
- case "exist":
141
- await expect(elements).toBeElementsArrayOfSize({ gte: 1 });
142
- break;
143
- default:
144
- throw new Error(`Unsupported multi-element matcher: ${matcher}`);
145
- }
146
- }
@@ -1,153 +0,0 @@
1
- import { MultiElementMatcher } from "../types.js";
2
- import { executeMultiMatcher } from "../matchers.js";
3
- import { SingleElementRunner } from "./single.js";
4
-
5
- /**
6
- * Representation of a queued assertion against an element collection.
7
- */
8
- type CollectionAssertion = {
9
- matcher: MultiElementMatcher;
10
- args: any[];
11
- };
12
-
13
- /**
14
- * Fluent assertion, filtering, and iteration runner for multi-element collections.
15
- *
16
- * Implements `PromiseLike<void>`, allowing assertions across matching elements,
17
- * array index access (`.eq()`, `.first()`, `.last()`), and sequential iteration (`.each()`).
18
- *
19
- * @example
20
- * ```ts
21
- * // Assert collection size
22
- * await Spectra.getAll(".cart-item").should("have.length", 3);
23
- *
24
- * // Target specific element from collection
25
- * await Spectra.getAll(".todo-item").first().click();
26
- * await Spectra.getAll(".todo-item").eq(2).should("have.text", "Buy Milk");
27
- *
28
- * // Iteration
29
- * await Spectra.getAll(".nav-link").each(async (link, i) => {
30
- * await link.should("be.visible");
31
- * });
32
- * ```
33
- */
34
- export class MultiElementRunner implements PromiseLike<void> {
35
- private _selector: string;
36
- private _assertions: CollectionAssertion[] = [];
37
-
38
- constructor(selector: string) {
39
- this._selector = selector;
40
- }
41
-
42
- // --- Collection Assertions ---
43
-
44
- /**
45
- * Queues an assertion matcher against the multi-element collection.
46
- *
47
- * @param matcher - Multi-element matcher string (e.g. `'have.length'`, `'be.empty'`).
48
- * @param args - Expected values or limits for the matcher.
49
- * @example
50
- * ```ts
51
- * await Spectra.getAll(".product-card").should("have.length.greaterThan", 0);
52
- * await Spectra.getAll(".error-badge").should("be.empty");
53
- * ```
54
- * @returns Current collection runner instance for chaining.
55
- */
56
- should(matcher: MultiElementMatcher, ...args: any[]): this {
57
- this._assertions.push({ matcher, args });
58
- return this;
59
- }
60
-
61
- // --- Item Navigation ---
62
-
63
- /**
64
- * Returns a `SingleElementRunner` targeting the element at the specified 0-based index.
65
- *
66
- * @param index - 0-based index of the element within the matched collection.
67
- * @example
68
- * ```ts
69
- * await Spectra.getAll("li.menu-item").eq(1).click();
70
- * ```
71
- * @returns `SingleElementRunner` for the indexed element.
72
- */
73
- eq(index: number): SingleElementRunner {
74
- const indexedSelector = `(${this._selector})[${index + 1}]`;
75
- return new SingleElementRunner(indexedSelector);
76
- }
77
-
78
- /**
79
- * Returns a `SingleElementRunner` targeting the first element in the collection (`index = 0`).
80
- *
81
- * @example
82
- * ```ts
83
- * await Spectra.getAll(".result-item").first().click();
84
- * ```
85
- * @returns `SingleElementRunner` for the first element.
86
- */
87
- first(): SingleElementRunner {
88
- return this.eq(0);
89
- }
90
-
91
- /**
92
- * Returns a `SingleElementRunner` targeting the last element in the collection.
93
- *
94
- * @example
95
- * ```ts
96
- * await Spectra.getAll(".step-indicator").last().should("have.class", "current");
97
- * ```
98
- * @returns `SingleElementRunner` for the last element.
99
- */
100
- last(): SingleElementRunner {
101
- const indexedSelector = `(${this._selector})[last()]`;
102
- return new SingleElementRunner(indexedSelector);
103
- }
104
-
105
- // --- Iteration Callback ---
106
-
107
- /**
108
- * Iterates asynchronously through every matched element in the collection sequentially.
109
- *
110
- * @param callback - Async function called for each element with `(runner, index)`.
111
- * @example
112
- * ```ts
113
- * await Spectra.getAll(".checkbox").each(async (item, index) => {
114
- * await item.should("be.visible");
115
- * });
116
- * ```
117
- */
118
- async each(
119
- callback: (el: SingleElementRunner, index: number) => Promise<void>
120
- ): Promise<void> {
121
- const elements = await $$(this._selector);
122
- const count = await (elements as any).length;
123
- for (let i = 0; i < count; i++) {
124
- const runner = this.eq(i);
125
- await callback(runner, i);
126
- }
127
- }
128
-
129
- // --- Execution when awaited ---
130
-
131
- /**
132
- * Standard Promise `then` implementation.
133
- * Executes all queued collection assertions sequentially when awaited.
134
- */
135
- async then<TResult1 = void, TResult2 = never>(
136
- onfulfilled?: ((value: void) => TResult1 | PromiseLike<TResult1>) | null,
137
- onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null
138
- ): Promise<TResult1 | TResult2> {
139
- try {
140
- for (const assert of this._assertions) {
141
- await executeMultiMatcher(this._selector, assert.matcher, assert.args);
142
- }
143
- const res = (onfulfilled ? await onfulfilled() : undefined) as TResult1;
144
- return res;
145
- } catch (err) {
146
- if (onrejected) {
147
- return onrejected(err);
148
- }
149
- throw err;
150
- }
151
- }
152
- }
153
-
@@ -1,301 +0,0 @@
1
- import { SingleElementMatcher, ElementTarget, TypeOptions } from "../types.js";
2
- import { executeSingleMatcher, resolveElement } from "../matchers.js";
3
-
4
- /**
5
- * Representation of a queued assertion against an element or page.
6
- */
7
- type AssertionItem = {
8
- target?: ElementTarget;
9
- matcher: SingleElementMatcher;
10
- args: any[];
11
- };
12
-
13
- /**
14
- * Fluent interaction and assertion runner for a single element or browser context.
15
- *
16
- * Implements `PromiseLike<void>`, allowing it to be awaited directly or chained with
17
- * multiple actions and `.should(...)` assertions.
18
- *
19
- * @example
20
- * ```ts
21
- * // Direct action + chained assertion
22
- * await Spectra.get("#submit-btn")
23
- * .click()
24
- * .should("not.be.visible");
25
- *
26
- * // Query + multiple assertions
27
- * await Spectra.get(LoginPage.usernameInput)
28
- * .should("be.visible")
29
- * .should("be.enabled")
30
- * .should("have.value", "admin");
31
- * ```
32
- */
33
- export class SingleElementRunner implements PromiseLike<void> {
34
- private _target: ElementTarget | undefined;
35
- private _action: (() => Promise<any>) | null = null;
36
- private _assertions: AssertionItem[] = [];
37
-
38
- constructor(target?: ElementTarget, action?: () => Promise<any>) {
39
- this._target = target;
40
- this._action = action || null;
41
- }
42
-
43
- // --- Chained Actions on current target ---
44
-
45
- /**
46
- * Clicks on the target element.
47
- *
48
- * @example
49
- * ```ts
50
- * await Spectra.get("#login-btn").click();
51
- * ```
52
- * @returns Current runner instance for method chaining.
53
- */
54
- click(): this {
55
- const target = this.requireTarget("click");
56
- this._action = async () => {
57
- const el = await resolveElement(target);
58
- await el.click();
59
- };
60
- return this;
61
- }
62
-
63
- /**
64
- * Double-clicks on the target element.
65
- *
66
- * @example
67
- * ```ts
68
- * await Spectra.get(".file-row").doubleClick();
69
- * ```
70
- * @returns Current runner instance for method chaining.
71
- */
72
- doubleClick(): this {
73
- const target = this.requireTarget("doubleClick");
74
- this._action = async () => {
75
- const el = await resolveElement(target);
76
- await el.doubleClick();
77
- };
78
- return this;
79
- }
80
-
81
- /**
82
- * Performs a long-press touch or hold gesture on the target element.
83
- *
84
- * @param durationMs - Duration in milliseconds to hold (default: 1000).
85
- * @example
86
- * ```ts
87
- * await Spectra.get("#context-menu-trigger").longPress(1500);
88
- * ```
89
- * @returns Current runner instance for method chaining.
90
- */
91
- longPress(durationMs = 1000): this {
92
- const target = this.requireTarget("longPress");
93
- this._action = async () => {
94
- const el = await resolveElement(target);
95
- if (typeof (el as any).touchAction === "function") {
96
- await (el as any).touchAction([
97
- { action: "longPress", ms: durationMs },
98
- { action: "release" },
99
- ]);
100
- } else {
101
- await el.click();
102
- }
103
- };
104
- return this;
105
- }
106
-
107
- /**
108
- * Types string value into the target input element.
109
- *
110
- * @param value - Text string to type into the element.
111
- * @param options - Optional typing options (e.g. `clearFirst: true`).
112
- * @example
113
- * ```ts
114
- * await Spectra.get("#search-input").type("iPhone 15", { clearFirst: true });
115
- * ```
116
- * @returns Current runner instance for method chaining.
117
- */
118
- type(value: string, options?: TypeOptions): this {
119
- const target = this.requireTarget("type");
120
- this._action = async () => {
121
- const el = await resolveElement(target);
122
- if (options?.clearFirst) {
123
- await el.clearValue();
124
- }
125
- await el.setValue(value);
126
- };
127
- return this;
128
- }
129
-
130
- /**
131
- * Clears the current value of the target input element.
132
- *
133
- * @example
134
- * ```ts
135
- * await Spectra.get("#search-input").clear();
136
- * ```
137
- * @returns Current runner instance for method chaining.
138
- */
139
- clear(): this {
140
- const target = this.requireTarget("clear");
141
- this._action = async () => {
142
- const el = await resolveElement(target);
143
- await el.clearValue();
144
- };
145
- return this;
146
- }
147
-
148
- /**
149
- * Selects an option from a `<select>` dropdown by its visible text.
150
- *
151
- * @param value - Visible text of the option to select.
152
- * @example
153
- * ```ts
154
- * await Spectra.get("#country-select").select("United States");
155
- * ```
156
- * @returns Current runner instance for method chaining.
157
- */
158
- select(value: string): this {
159
- const target = this.requireTarget("select");
160
- this._action = async () => {
161
- const el = await resolveElement(target);
162
- await el.selectByVisibleText(value);
163
- };
164
- return this;
165
- }
166
-
167
- /**
168
- * Moves the mouse cursor over the target element (hover).
169
- *
170
- * @example
171
- * ```ts
172
- * await Spectra.get("#dropdown-menu-trigger").hover();
173
- * ```
174
- * @returns Current runner instance for method chaining.
175
- */
176
- hover(): this {
177
- const target = this.requireTarget("hover");
178
- this._action = async () => {
179
- const el = await resolveElement(target);
180
- await el.moveTo();
181
- };
182
- return this;
183
- }
184
-
185
- /**
186
- * Waits for the target element to become displayed in the DOM.
187
- *
188
- * @param timeoutMs - Maximum duration in milliseconds to wait (default: 10000).
189
- * @example
190
- * ```ts
191
- * await Spectra.get("#modal-dialog").waitForElement(5000);
192
- * ```
193
- * @returns Current runner instance for method chaining.
194
- */
195
- waitForElement(timeoutMs = 10000): this {
196
- const target = this.requireTarget("waitForElement");
197
- this._action = async () => {
198
- const el = await resolveElement(target);
199
- await el.waitForDisplayed({ timeout: timeoutMs });
200
- };
201
- return this;
202
- }
203
-
204
- // --- Fluent Assertion Matchers ---
205
-
206
- /**
207
- * Queues an assertion matcher against the current target element or page.
208
- *
209
- * @param matcher - Single element matcher string (e.g. `'be.visible'`, `'have.text'`).
210
- * @param args - Additional parameters for the matcher (e.g. expected text or value).
211
- * @example
212
- * ```ts
213
- * await Spectra.get("#status-badge").should("be.visible");
214
- * await Spectra.get("#username").should("have.value", "alice");
215
- * await Spectra.get("#banner").should("have.class", "alert-success");
216
- * ```
217
- * @returns Current runner instance for method chaining.
218
- */
219
- should(matcher: SingleElementMatcher, ...args: any[]): this;
220
-
221
- /**
222
- * Queues an assertion matcher targeting a specific element.
223
- *
224
- * @param target - Custom element target for this specific assertion.
225
- * @param matcher - Single element matcher string.
226
- * @param args - Additional parameters for the matcher.
227
- * @example
228
- * ```ts
229
- * await Spectra.should(LoginPage.submitBtn, "be.enabled");
230
- * ```
231
- * @returns Current runner instance for method chaining.
232
- */
233
- should(target: ElementTarget, matcher: SingleElementMatcher, ...args: any[]): this;
234
-
235
- should(a: ElementTarget | SingleElementMatcher, b?: any, ...rest: any[]): this {
236
- const isFirstArgMatcher =
237
- typeof a === "string" &&
238
- (a.includes(".") ||
239
- a.includes("exist") ||
240
- a.includes("visible") ||
241
- a.includes("enabled") ||
242
- a.includes("disabled") ||
243
- a.includes("selected") ||
244
- a.includes("checked"));
245
-
246
- if (b !== undefined && !isFirstArgMatcher) {
247
- // Form: should(target, matcher, ...args)
248
- this._assertions.push({
249
- target: a as ElementTarget,
250
- matcher: b as SingleElementMatcher,
251
- args: rest,
252
- });
253
- } else {
254
- // Form: should(matcher, ...args) -> on current target
255
- this._assertions.push({
256
- target: this._target,
257
- matcher: a as SingleElementMatcher,
258
- args: b !== undefined ? [b, ...rest] : rest,
259
- });
260
- }
261
- return this;
262
- }
263
-
264
- // --- Execution when awaited ---
265
-
266
- /**
267
- * Standard Promise `then` implementation.
268
- * Executes queued action first (if any), followed sequentially by all queued assertions.
269
- */
270
- async then<TResult1 = void, TResult2 = never>(
271
- onfulfilled?: ((value: void) => TResult1 | PromiseLike<TResult1>) | null,
272
- onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null
273
- ): Promise<TResult1 | TResult2> {
274
- try {
275
- if (this._action) {
276
- await this._action();
277
- }
278
- for (const assert of this._assertions) {
279
- await executeSingleMatcher(assert.target, assert.matcher, assert.args);
280
- }
281
- const res = (onfulfilled ? await onfulfilled() : undefined) as TResult1;
282
- return res;
283
- } catch (err) {
284
- if (onrejected) {
285
- return onrejected(err);
286
- }
287
- throw err;
288
- }
289
- }
290
-
291
- /**
292
- * Helper that throws an error if an action is called without a defined target.
293
- */
294
- private requireTarget(actionName: string): ElementTarget {
295
- if (!this._target) {
296
- throw new Error(`Cannot execute '${actionName}' without specifying an element target.`);
297
- }
298
- return this._target;
299
- }
300
- }
301
-