@cepharum/contextual-gherkin 3.0.0-beta.5 → 3.0.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/index.d.ts +33 -2
- package/index.js +2 -0
- package/lib/api.js +6 -4
- package/lib/step-helper.js +579 -0
- package/package.json +2 -2
- package/steps/global-action.js +19 -169
- package/steps/global-find.js +8 -104
- package/steps/local-action.js +1 -42
- package/steps/local-find.js +8 -117
- package/steps/local-state.js +7 -110
package/index.d.ts
CHANGED
|
@@ -751,7 +751,7 @@ declare module "@cepharum/contextual-gherkin" {
|
|
|
751
751
|
* @param value value to expect in a matching element's attribute
|
|
752
752
|
* @returns another context with all those elements of current one which have a matching value in named attribute
|
|
753
753
|
*/
|
|
754
|
-
withAttribute( name: string, value: string|RegExp ): Context;
|
|
754
|
+
withAttribute( name: string, value: string|RegExp|boolean ): Context;
|
|
755
755
|
|
|
756
756
|
/**
|
|
757
757
|
* Limits set of matching elements to those with a named DOM property
|
|
@@ -768,7 +768,7 @@ declare module "@cepharum/contextual-gherkin" {
|
|
|
768
768
|
* @param value value to expect in a matching element's DOM property, use callback invoked with found value to return truthy on a match
|
|
769
769
|
* @returns another context with all those elements of current one which have a matching value in named DOM property
|
|
770
770
|
*/
|
|
771
|
-
withProperty( name: string, value: string|RegExp|SelectorFn ): Context;
|
|
771
|
+
withProperty( name: string, value: string|RegExp|boolean|SelectorFn ): Context;
|
|
772
772
|
|
|
773
773
|
/**
|
|
774
774
|
* Limits set of matching elements to those with a named DOM property
|
|
@@ -1000,4 +1000,35 @@ declare module "@cepharum/contextual-gherkin" {
|
|
|
1000
1000
|
type PlaywrightBrowserContextOptions = BrowserContextOptions;
|
|
1001
1001
|
type PlaywrightBrowser = Browser;
|
|
1002
1002
|
type PlaywrightLocator = Locator;
|
|
1003
|
+
|
|
1004
|
+
type Action = "click" | "hover" | "enter";
|
|
1005
|
+
|
|
1006
|
+
async function globalFind( cardinal: NumberAndWord ): Promise<void>;
|
|
1007
|
+
async function globalFindByAttribute( cardinal: NumberAndWord, name: string, value: string|RegExp ): Promise<void>;
|
|
1008
|
+
async function globalFindByProperty( cardinal: NumberAndWord, name: string, value: string|RegExp ): Promise<void>;
|
|
1009
|
+
async function globalFindByClass( cardinal: NumberAndWord, className: string, negated: boolean ): Promise<void>;
|
|
1010
|
+
async function globalFindByLabel( cardinal: NumberAndWord, label: string, partially: boolean ): Promise<void>;
|
|
1011
|
+
async function globalFindByTextContent( cardinal: NumberAndWord, text: string, partially: boolean ): Promise<void>;
|
|
1012
|
+
|
|
1013
|
+
async function globalActionByAttribute( cardinal: NumberAndWord, name: string, value: string|RegExp, action: Action, input?: string ): Promise<void>;
|
|
1014
|
+
async function globalActionByProperty( cardinal: NumberAndWord, name: string, value: string|RegExp, action: Action, input?: string ): Promise<void>;
|
|
1015
|
+
async function globalActionByClass( cardinal: NumberAndWord, className: string, negated?: boolean, action: Action, input?: string ): Promise<void>;
|
|
1016
|
+
async function globalActionByLabel( cardinal: NumberAndWord, label: string, partially: boolean, action: Action, input?: string ): Promise<void>;
|
|
1017
|
+
async function globalActionByTextContent( cardinal: NumberAndWord, text: string, partially: boolean, action: Action, input?: string ): Promise<void>;
|
|
1018
|
+
|
|
1019
|
+
async function localFind( phrase: ContextualWord, cardinal: NumberAndWord ): Promise<void>;
|
|
1020
|
+
async function localFindByAttribute( phrase: ContextualWord, cardinal: NumberAndWord, name: string, value: string|RegExp ): Promise<void>;
|
|
1021
|
+
async function localFindByProperty( phrase: ContextualWord, cardinal: NumberAndWord, name: string, value: string|RegExp ): Promise<void>;
|
|
1022
|
+
async function localFindByClass( phrase: ContextualWord, cardinal: NumberAndWord, className: string, negated: boolean ): Promise<void>;
|
|
1023
|
+
async function localFindByLabel( phrase: ContextualWord, cardinal: NumberAndWord, label: string, partially: boolean ): Promise<void>;
|
|
1024
|
+
async function localFindByTextContent( phrase: ContextualWord, cardinal: NumberAndWord, text: string, partially: boolean ): Promise<void>;
|
|
1025
|
+
|
|
1026
|
+
async function localAction( phrase: ContextualWord, action: Action, input?: string ): Promise<void>;
|
|
1027
|
+
|
|
1028
|
+
async function localStateByAttribute( phrase: ContextualWord, all: boolean, name: string, value: string|RegExp ): Promise<void>;
|
|
1029
|
+
async function localStateByProperty( phrase: ContextualWord, all: boolean, name: string, value: string|RegExp ): Promise<void>;
|
|
1030
|
+
async function localStateByClass( phrase: ContextualWord, all: boolean, className: string, negated: boolean ): Promise<void>;
|
|
1031
|
+
async function localStateByLabel( phrase: ContextualWord, label: string, partially: boolean ): Promise<void>;
|
|
1032
|
+
async function localStateByTextContent( phrase: ContextualWord, text: string, partially: boolean ): Promise<void>;
|
|
1033
|
+
async function localStateExists( phrase: ContextualWord, mustExist: boolean ): Promise<void>;
|
|
1003
1034
|
}
|
package/index.js
CHANGED
|
@@ -13,6 +13,8 @@ export * from "./lib/adapter/playwright.js";
|
|
|
13
13
|
export * from "./lib/context/abstract.js";
|
|
14
14
|
export * from "./lib/context/playwright.js";
|
|
15
15
|
|
|
16
|
+
export * from "./lib/step-helper.js";
|
|
17
|
+
|
|
16
18
|
/**
|
|
17
19
|
* Configures test controller to support phrases of contextual Gherkin.
|
|
18
20
|
*
|
package/lib/api.js
CHANGED
|
@@ -157,11 +157,12 @@ export class Api {
|
|
|
157
157
|
* view in case you need a view with a dedicated session.
|
|
158
158
|
*
|
|
159
159
|
* @param {string} name name of view to provide
|
|
160
|
+
* @param {boolean} sharedSession if true, the optionally created view shares a session with current one, otherwise it runs in a different session
|
|
160
161
|
*/
|
|
161
|
-
async getViewByName( name = "default" ) {
|
|
162
|
+
async getViewByName( name = "default", sharedSession = true ) {
|
|
162
163
|
const view = this.#views.get( name );
|
|
163
164
|
|
|
164
|
-
return view == null ? await this.createView( name ) : view;
|
|
165
|
+
return view == null ? await this.createView( name, sharedSession ) : view;
|
|
165
166
|
}
|
|
166
167
|
|
|
167
168
|
/**
|
|
@@ -173,12 +174,13 @@ export class Api {
|
|
|
173
174
|
* view in case you need a view with a dedicated session.
|
|
174
175
|
*
|
|
175
176
|
* @param {string} name name of view to provide
|
|
177
|
+
* @param {boolean} sharedSession if true, the optionally created view shares a session with current one, otherwise it runs in a different session
|
|
176
178
|
*/
|
|
177
|
-
async selectViewByName( name = "default" ) {
|
|
179
|
+
async selectViewByName( name = "default", sharedSession = true ) {
|
|
178
180
|
const view = this.#views.get( name );
|
|
179
181
|
|
|
180
182
|
if ( view == null ) {
|
|
181
|
-
return await this.createView( name,
|
|
183
|
+
return await this.createView( name, sharedSession, true );
|
|
182
184
|
}
|
|
183
185
|
|
|
184
186
|
this.#currentViewName = name;
|
|
@@ -0,0 +1,579 @@
|
|
|
1
|
+
import { Api } from "./api.js";
|
|
2
|
+
import { stringMessageOperator as sOp } from "../lib/helper.js";
|
|
3
|
+
|
|
4
|
+
/* eslint-disable spaced-comment */
|
|
5
|
+
|
|
6
|
+
//#region common helpers
|
|
7
|
+
|
|
8
|
+
export const actAndTrack = async( cardinal, matches, action, input ) => {
|
|
9
|
+
const api = Api.access();
|
|
10
|
+
const target = matches.find( "$" + action, false );
|
|
11
|
+
|
|
12
|
+
await api.expect( target.locator, `none or multiple matching $${action} target for selected ${cardinal.word} to ${input == null ? action : `enter '${input}' into`}` )
|
|
13
|
+
.toHaveCount( 1 );
|
|
14
|
+
|
|
15
|
+
// detach matches first as interacting with them might change them in a way
|
|
16
|
+
// so that the original query isn't finding them anymore (e.g. changing
|
|
17
|
+
// label of a button on click)
|
|
18
|
+
const detached = await matches.detach( cardinal.cardinality );
|
|
19
|
+
|
|
20
|
+
if ( input == null ) {
|
|
21
|
+
await target.actions[action]();
|
|
22
|
+
} else {
|
|
23
|
+
await target.actions.fill( input );
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
await detached.track( cardinal.word, cardinal.cardinality );
|
|
27
|
+
|
|
28
|
+
const wait = Number( process.env.ACTION_WAIT ) || 100;
|
|
29
|
+
if ( wait > 0 ) {
|
|
30
|
+
await api.wait( wait );
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
//#endregion
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
//#region global querying helpers
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Searches for type of elements with a certain label.
|
|
41
|
+
*
|
|
42
|
+
* @param {NumberAndWord} cardinal provides cardinal number and word
|
|
43
|
+
* @returns {Promise<void>} promises step passed
|
|
44
|
+
*/
|
|
45
|
+
export async function globalFind( cardinal ) {
|
|
46
|
+
const api = Api.access();
|
|
47
|
+
|
|
48
|
+
await api
|
|
49
|
+
.find( cardinal.word )
|
|
50
|
+
.checkCardinalWord( cardinal );
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Searches for element(s) matching a named attribute by value.
|
|
55
|
+
*
|
|
56
|
+
* @param {NumberAndWord} cardinal describes what to search
|
|
57
|
+
* @param {string|RegExp} name name of attribute to match
|
|
58
|
+
* @param {string|RegExp|boolean} value value of named attribute to match, boolean value to explicitly test for named attribute being truthy or falsy
|
|
59
|
+
* @return {Promise<void>} promises step passed
|
|
60
|
+
*/
|
|
61
|
+
export async function globalFindByAttribute( cardinal, name, value ) {
|
|
62
|
+
const api = Api.access();
|
|
63
|
+
|
|
64
|
+
await api
|
|
65
|
+
.find( cardinal.word )
|
|
66
|
+
.withAttribute( name, value )
|
|
67
|
+
.checkCardinalWord( cardinal, true, `expected %amount with ${name} ${sOp( value )} ${value}` );
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Searches for element(s) matching a named DOM property by value.
|
|
72
|
+
*
|
|
73
|
+
* @param {NumberAndWord} cardinal describes what to search
|
|
74
|
+
* @param {string} name name of DOM property to match
|
|
75
|
+
* @param {string|boolean} value value of named DOM property to match, or boolean value to explicitly test if property is truthy or falsy
|
|
76
|
+
* @return {Promise<void>} promises step passed
|
|
77
|
+
*/
|
|
78
|
+
export async function globalFindByProperty( cardinal, name, value ) {
|
|
79
|
+
const api = Api.access();
|
|
80
|
+
|
|
81
|
+
let text;
|
|
82
|
+
|
|
83
|
+
if ( typeof value === "boolean" ) {
|
|
84
|
+
text = `${value ? "not " : ""}${name}`;
|
|
85
|
+
} else {
|
|
86
|
+
text = `with ${name} ${sOp( value )} ${value}`;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
await api
|
|
90
|
+
.find( cardinal.word )
|
|
91
|
+
.withProperty( name, value )
|
|
92
|
+
.checkCardinalWord( cardinal, true, `expected %amount ${text}` );
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Searches for element(s) with a named class set.
|
|
97
|
+
*
|
|
98
|
+
* @param {NumberAndWord} cardinal describes what to search
|
|
99
|
+
* @param {string} className name of class to test
|
|
100
|
+
* @param {boolean} negated inverts the assertion by means of requiring one or all elements of context not to have selected class
|
|
101
|
+
* @return {Promise<void>} promises step passed
|
|
102
|
+
*/
|
|
103
|
+
export async function globalFindByClass( cardinal, className, negated ) {
|
|
104
|
+
const api = Api.access();
|
|
105
|
+
const matches = api
|
|
106
|
+
.find( cardinal.word )
|
|
107
|
+
.withListProperty( "classList", className, negated );
|
|
108
|
+
|
|
109
|
+
await matches.checkCardinalWord( cardinal, true, `expected %amount ${negated ? "not " : ""}marked as "${className}"` );
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Searches for type of elements with a certain label.
|
|
114
|
+
*
|
|
115
|
+
* @param {NumberAndWord} cardinal provides cardinal number and word
|
|
116
|
+
* @param {string} label provides required label of matching element(s)
|
|
117
|
+
* @param {boolean} partially if true, provided text does not have to match whole content of matching subs
|
|
118
|
+
* @returns {Promise<void>} promises step passed
|
|
119
|
+
*/
|
|
120
|
+
export async function globalFindByLabel( cardinal, label, partially ) {
|
|
121
|
+
const api = Api.access();
|
|
122
|
+
|
|
123
|
+
await api
|
|
124
|
+
.find( cardinal.word )
|
|
125
|
+
.filterBySubsWithText( "$label", "label", label, partially )
|
|
126
|
+
.checkCardinalWord( cardinal, true, `expected %amount ${partially ? "partially " : ""}labelled with "${label}"` );
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Searches for type of elements with a certain label.
|
|
131
|
+
*
|
|
132
|
+
* @param {NumberAndWord} cardinal provides cardinal number and word
|
|
133
|
+
* @param {string} text provides text to be found in eventually matching element(s)
|
|
134
|
+
* @param {boolean} partially if true, provided text does not have to match whole content of matching subs
|
|
135
|
+
* @returns {Promise<void>} promises step passed
|
|
136
|
+
*/
|
|
137
|
+
export async function globalFindByTextContent( cardinal, text, partially ) {
|
|
138
|
+
const api = Api.access();
|
|
139
|
+
|
|
140
|
+
await api
|
|
141
|
+
.find( cardinal.word )
|
|
142
|
+
.filterBySubsWithText( "$text", false, text, partially )
|
|
143
|
+
.checkCardinalWord( cardinal, true, `expected %amount ${partially ? "partially " : ""}reading "${text}"` );
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
//#endregion
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
//#region global action helpers
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Searches for element matching a named attribute by value and performs
|
|
153
|
+
* selected action on it.
|
|
154
|
+
*
|
|
155
|
+
* @param {NumberAndWord} cardinal describes what to search
|
|
156
|
+
* @param {string|RegExp} name name of attribute to match
|
|
157
|
+
* @param {string|RegExp} value value of named attribute to match
|
|
158
|
+
* @param {'click'|'hover'|'enter'} action action to perform on found element
|
|
159
|
+
* @param {string} [input] text to enter into selected field (instead of clicking or hovering)
|
|
160
|
+
* @return {Promise<void>} promises step passed
|
|
161
|
+
*/
|
|
162
|
+
export async function globalActionByAttribute( cardinal, name, value, action, input ) {
|
|
163
|
+
const api = Api.access();
|
|
164
|
+
|
|
165
|
+
if ( cardinal.cardinality !== "singular" ) {
|
|
166
|
+
await api.expect( false, `selector is not addressing a single ${cardinal.word} to ${input == null ? action : `enter '${input}' into`}` )
|
|
167
|
+
.toEqual( true );
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const matches = api.find( cardinal.word ).withAttribute( name, value );
|
|
171
|
+
|
|
172
|
+
await api.expect( matches.locator, `no single matching ${cardinal.word} with attribute ${name} == "${value}" to ${input == null ? action : `enter '${input}' into`}` )
|
|
173
|
+
.toHaveCount( 1 );
|
|
174
|
+
|
|
175
|
+
await actAndTrack( cardinal, matches, action, input );
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Searches for element matching a named attribute by value and performs
|
|
180
|
+
* selected action on it.
|
|
181
|
+
*
|
|
182
|
+
* @param {NumberAndWord} cardinal describes what to search
|
|
183
|
+
* @param {string|RegExp} className class required for match
|
|
184
|
+
* @param {boolean} negated inverts the assertion by means of requiring one or all elements of context not to have selected class
|
|
185
|
+
* @param {'click'|'hover'|'enter'} action action to perform on found element
|
|
186
|
+
* @param {string} [input] text to enter into selected field (instead of clicking or hovering)
|
|
187
|
+
* @return {Promise<void>} promises step passed
|
|
188
|
+
*/
|
|
189
|
+
export async function globalActionByClass( cardinal, className, negated, action, input ) {
|
|
190
|
+
const api = Api.access();
|
|
191
|
+
|
|
192
|
+
if ( cardinal.cardinality !== "singular" ) {
|
|
193
|
+
await api.expect( false, `selector is not addressing a single ${cardinal.word} ${negated ? "not " : ""}marked as ${className} to ${input == null ? action : `enter '${input}' into`}` )
|
|
194
|
+
.toEqual( true );
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const matches = api
|
|
198
|
+
.find( cardinal.word )
|
|
199
|
+
.withListProperty( "classList", className, negated );
|
|
200
|
+
|
|
201
|
+
await api.expect( matches.locator, `no single matching ${cardinal.word} ${negated ? "not " : ""}marked as ${className} to ${input == null ? action : `enter '${input}' into`}` )
|
|
202
|
+
.toHaveCount( 1 );
|
|
203
|
+
|
|
204
|
+
await actAndTrack( cardinal, matches, action, input );
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Searches for element matching a named DOM property by value and performs
|
|
209
|
+
* selected action on it.
|
|
210
|
+
*
|
|
211
|
+
* @param {NumberAndWord} cardinal describes what to search
|
|
212
|
+
* @param {string|RegExp} name name of attribute to match
|
|
213
|
+
* @param {string|RegExp} value value of named attribute to match
|
|
214
|
+
* @param {'click'|'hover'|'enter'} action action to perform on found element
|
|
215
|
+
* @param {string} [input] text to enter into selected field (instead of clicking or hovering)
|
|
216
|
+
* @return {Promise<void>} promises step passed
|
|
217
|
+
*/
|
|
218
|
+
export async function globalActionByProperty( cardinal, name, value, action, input ) {
|
|
219
|
+
const api = Api.access();
|
|
220
|
+
|
|
221
|
+
if ( cardinal.cardinality !== "singular" ) {
|
|
222
|
+
await api.expect( false, `selector is not addressing a single ${cardinal.word} with property ${name} == "${value}" to ${input == null ? action : `enter '${input}' into`}` )
|
|
223
|
+
.toEqual( true );
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const matches = api.find( cardinal.word ).withProperty( name, value );
|
|
227
|
+
|
|
228
|
+
await api.expect( matches.locator, `no single matching ${cardinal.word} with property ${name} == "${value}" to ${input == null ? action : `enter '${input}' into`}` )
|
|
229
|
+
.toHaveCount( 1 );
|
|
230
|
+
|
|
231
|
+
await actAndTrack( cardinal, matches, action, input );
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Searches for type of element with a certain label and performs selected
|
|
236
|
+
* action on it.
|
|
237
|
+
*
|
|
238
|
+
* @param {NumberAndWord} cardinal provides cardinal number and word
|
|
239
|
+
* @param {string} label provides required label of matching element(s)
|
|
240
|
+
* @param {boolean} partially if true, provided text does not have to match whole content of matching subs
|
|
241
|
+
* @param {'click'|'hover'|'enter'} action action to perform on found element
|
|
242
|
+
* @param {string} [input] text to enter into selected field (instead of clicking or hovering)
|
|
243
|
+
* @returns {Promise<void>} promises step passed
|
|
244
|
+
*/
|
|
245
|
+
export async function globalActionByLabel( cardinal, label, partially, action, input ) {
|
|
246
|
+
const api = Api.access();
|
|
247
|
+
|
|
248
|
+
if ( cardinal.cardinality !== "singular" ) {
|
|
249
|
+
await api.expect( false, `selector is not addressing a single ${cardinal.word} with ${partially ? "partial " : ""}label "${label}" to ${input == null ? action : `enter '${input}' into`}` )
|
|
250
|
+
.toEqual( true );
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const matches = api.find( cardinal.word ).filterBySubsWithText( "$label", "label", label, partially );
|
|
254
|
+
|
|
255
|
+
await api.expect( matches.locator, `no single matching ${cardinal.word} with ${partially ? "partial " : ""}label "${label}" to ${input == null ? action : `enter '${input}' into`}` )
|
|
256
|
+
.toHaveCount( 1 );
|
|
257
|
+
|
|
258
|
+
await actAndTrack( cardinal, matches, action, input );
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Searches for type of element with given text content and performs selected
|
|
263
|
+
* action on it.
|
|
264
|
+
*
|
|
265
|
+
* @param {NumberAndWord} cardinal provides cardinal number and word
|
|
266
|
+
* @param {string} text provides text to be found in eventually matching element(s)
|
|
267
|
+
* @param {boolean} partially if true, provided text does not have to match whole content of matching subs
|
|
268
|
+
* @param {'click'|'hover'|'enter'} action action to perform on found element
|
|
269
|
+
* @param {string} [input] text to enter into selected field (instead of clicking or hovering)
|
|
270
|
+
* @returns {Promise<void>} promises step passed
|
|
271
|
+
*/
|
|
272
|
+
export async function globalActionByTextContent( cardinal, text, partially, action, input ) {
|
|
273
|
+
const api = Api.access();
|
|
274
|
+
|
|
275
|
+
if ( cardinal.cardinality !== "singular" ) {
|
|
276
|
+
await api.expect( false, `selector is not addressing a single ${cardinal.word} ${partially ? "partially " : ""}reading "${text}" to ${input == null ? action : `enter '${input}' into`}` )
|
|
277
|
+
.toEqual( true );
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const matches = api.find( cardinal.word ).filterBySubsWithText( "$text", false, text, partially );
|
|
281
|
+
|
|
282
|
+
await api.expect( matches.locator, `no single matching ${cardinal.word} ${partially ? "partially " : ""}reading "${text}" to ${input == null ? action : `enter '${input}' into`}` )
|
|
283
|
+
.toHaveCount( 1 );
|
|
284
|
+
|
|
285
|
+
await actAndTrack( cardinal, matches, action, input );
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
//#endregion
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
//#region local querying helpers
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Finds elements in context of containing elements.
|
|
295
|
+
*
|
|
296
|
+
* @param {ContextualWord} phrase describes context of search
|
|
297
|
+
* @param {NumberAndWord} cardinal describes what to search
|
|
298
|
+
* @return {Promise<void>} promises step passed
|
|
299
|
+
*/
|
|
300
|
+
export async function localFind( phrase, cardinal ) {
|
|
301
|
+
const api = Api.access();
|
|
302
|
+
const context = api.getContextFor( phrase );
|
|
303
|
+
|
|
304
|
+
await context
|
|
305
|
+
.find( cardinal.word )
|
|
306
|
+
.checkCardinalWord( cardinal );
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Finds elements in context of containing elements matching a named attribute
|
|
311
|
+
* by value.
|
|
312
|
+
*
|
|
313
|
+
* @param {ContextualWord} phrase describes context of search
|
|
314
|
+
* @param {NumberAndWord} cardinal describes what to search
|
|
315
|
+
* @param {string|RegExp} name name of attribute to match
|
|
316
|
+
* @param {string|RegExp} value value of named attribute to match
|
|
317
|
+
* @return {Promise<void>} promises step passed
|
|
318
|
+
*/
|
|
319
|
+
export async function localFindByAttribute( phrase, cardinal, name, value ) {
|
|
320
|
+
const api = Api.access();
|
|
321
|
+
const context = api.getContextFor( phrase );
|
|
322
|
+
|
|
323
|
+
await context
|
|
324
|
+
.find( cardinal.word )
|
|
325
|
+
.withAttribute( name, value )
|
|
326
|
+
.checkCardinalWord( cardinal, true, `expected %amount with ${name} ${sOp( value )} ${value}` );
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Finds elements in context of containing elements matching a named DOM
|
|
331
|
+
* property by value.
|
|
332
|
+
*
|
|
333
|
+
* @param {ContextualWord} phrase describes context of search
|
|
334
|
+
* @param {NumberAndWord} cardinal describes what to search
|
|
335
|
+
* @param {string} name name of DOM property to match
|
|
336
|
+
* @param {string|boolean} value value of named DOM property to match, or boolean to explicitly test named property for being truthy or falsy
|
|
337
|
+
* @return {Promise<void>} promises step passed
|
|
338
|
+
*/
|
|
339
|
+
export async function localFindByProperty( phrase, cardinal, name, value ) {
|
|
340
|
+
const api = Api.access();
|
|
341
|
+
const context = api.getContextFor( phrase );
|
|
342
|
+
let text;
|
|
343
|
+
|
|
344
|
+
if ( typeof value === "boolean" ) {
|
|
345
|
+
text = `${value ? "" : "not "}${name}`;
|
|
346
|
+
} else {
|
|
347
|
+
text = `with ${name} ${value instanceof RegExp ? "matching" : "equals"} ${value}`;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
await context
|
|
351
|
+
.find( cardinal.word )
|
|
352
|
+
.withProperty( name, value )
|
|
353
|
+
.checkCardinalWord( cardinal, true, `expected %amount ${text}` );
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Finds elements in context of containing elements with named class.
|
|
358
|
+
*
|
|
359
|
+
* @param {ContextualWord} phrase describes context of search
|
|
360
|
+
* @param {NumberAndWord} cardinal describes what to search
|
|
361
|
+
* @param {string} className name of class to check
|
|
362
|
+
* @param {boolean} negated inverts the assertion by means of requiring one or all elements of context not to have selected class
|
|
363
|
+
* @return {Promise<void>} promises step passed
|
|
364
|
+
*/
|
|
365
|
+
export async function localFindByClass( phrase, cardinal, className, negated ) {
|
|
366
|
+
const api = Api.access();
|
|
367
|
+
const context = api.getContextFor( phrase );
|
|
368
|
+
const matches = context.find( cardinal.word );
|
|
369
|
+
|
|
370
|
+
await matches
|
|
371
|
+
.withListProperty( "classList", className, negated )
|
|
372
|
+
.checkCardinalWord( cardinal, true, `expected %amount ${negated ? "not " : ""}marked as "${className}"` );
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Finds elements in context of containing elements with a given label.
|
|
377
|
+
*
|
|
378
|
+
* @param {ContextualWord} phrase describes context of search
|
|
379
|
+
* @param {NumberAndWord} cardinal describes what to search
|
|
380
|
+
* @param {string} label expected label of item(s) to find
|
|
381
|
+
* @param {boolean} partially if true, provided text does not have to match whole content of matching subs
|
|
382
|
+
* @return {Promise<void>} promises step passed
|
|
383
|
+
*/
|
|
384
|
+
export async function localFindByLabel( phrase, cardinal, label, partially ) {
|
|
385
|
+
const api = Api.access();
|
|
386
|
+
const context = api.getContextFor( phrase );
|
|
387
|
+
|
|
388
|
+
await context
|
|
389
|
+
.find( cardinal.word )
|
|
390
|
+
.filterBySubsWithText( "$label", "label", label, partially )
|
|
391
|
+
.checkCardinalWord( cardinal, true, `expected %amount ${partially ? "partially " : ""}labelled with "${label}"` );
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Finds elements in context of containing elements containing given text.
|
|
396
|
+
*
|
|
397
|
+
* @param {ContextualWord} phrase describes context of search
|
|
398
|
+
* @param {NumberAndWord} cardinal describes what to search
|
|
399
|
+
* @param {string} text provides expected text content of item(s) to find
|
|
400
|
+
* @param {boolean} partially if true, provided text does not have to match whole content of matching subs
|
|
401
|
+
* @return {Promise<void>} promises step passed
|
|
402
|
+
*/
|
|
403
|
+
export async function localFindByTextContent( phrase, cardinal, text, partially ) {
|
|
404
|
+
const api = Api.access();
|
|
405
|
+
const context = api.getContextFor( phrase );
|
|
406
|
+
|
|
407
|
+
await context
|
|
408
|
+
.find( cardinal.word )
|
|
409
|
+
.filterBySubsWithText( "$text", false, text, partially )
|
|
410
|
+
.checkCardinalWord( cardinal, true, `expected %amount ${partially ? "partially " : ""}reading "${text}"` );
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
//#endregion
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
//#region local action helpers
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Searches for type of element in current context and performs selected action
|
|
420
|
+
* on it.
|
|
421
|
+
*
|
|
422
|
+
* @param {ContextualWord} phrase description of element to be clicked.
|
|
423
|
+
* @param {'click'|'hover'|'enter'} action action to perform on found element
|
|
424
|
+
* @param {string} [input] text to enter into selected field (instead of clicking or hovering)
|
|
425
|
+
* @returns {Promise<void>} promises element clicked
|
|
426
|
+
*/
|
|
427
|
+
export async function localAction( phrase, action, input ) {
|
|
428
|
+
const api = Api.access();
|
|
429
|
+
const context = api.getContextFor( phrase );
|
|
430
|
+
|
|
431
|
+
await api.expect( context.locator, `no single matching ${phrase.word} to ${input == null ? action : `enter '${input}' into`}` )
|
|
432
|
+
.toHaveCount( 1 );
|
|
433
|
+
|
|
434
|
+
const target = context.find( "$" + action, false );
|
|
435
|
+
|
|
436
|
+
await api.expect( target.locator, `no single matching $${action} target for selected ${phrase.word} to ${input == null ? action : `enter '${input}' into`}` )
|
|
437
|
+
.toHaveCount( 1 );
|
|
438
|
+
|
|
439
|
+
// detach matches first as interacting with them might change them in a way
|
|
440
|
+
// so that the original query isn't finding them anymore (e.g. changing
|
|
441
|
+
// label of a button on click)
|
|
442
|
+
const detached = await context.detach( phrase.targetCardinality );
|
|
443
|
+
|
|
444
|
+
if ( input == null ) {
|
|
445
|
+
await target.actions[action]();
|
|
446
|
+
} else {
|
|
447
|
+
await target.actions.fill( input );
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
if ( phrase.isRefining ) {
|
|
451
|
+
await detached.track( phrase.word, phrase.targetCardinality );
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
const wait = Number( process.env.ACTION_WAIT ) || 100;
|
|
455
|
+
if ( wait > 0 ) {
|
|
456
|
+
await api.wait( wait );
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
//#endregion
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
//#region local assertion helpers
|
|
464
|
+
|
|
465
|
+
/**
|
|
466
|
+
* Checks if some or all elements of an addressed match have a named attribute
|
|
467
|
+
* matching some expected value.
|
|
468
|
+
*
|
|
469
|
+
* @param {ContextualWord} phrase description of previous match to check
|
|
470
|
+
* @param {boolean} all if true, all matching elements have to match named attribute's expected value
|
|
471
|
+
* @param {string} name name of attribute to check with either matching element
|
|
472
|
+
* @param {string} value expected value of named attribute per matching element
|
|
473
|
+
* @returns {Promise<void>} promises check passed
|
|
474
|
+
*/
|
|
475
|
+
export async function localStateByAttribute( phrase, all, name, value ) {
|
|
476
|
+
const api = Api.access();
|
|
477
|
+
const context = api.getContextFor( phrase );
|
|
478
|
+
const filtered = context.withAttribute( name, value );
|
|
479
|
+
|
|
480
|
+
await context.checkFilteredInContext( filtered, phrase, all );
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
/**
|
|
484
|
+
* Checks if all elements of an addressed match have a named property matching
|
|
485
|
+
* some expected value.
|
|
486
|
+
*
|
|
487
|
+
* @param {ContextualWord} phrase description of previous match to check
|
|
488
|
+
* @param {boolean} all if true, all matching elements have to match named attribute's expected value
|
|
489
|
+
* @param {string} name name of DOM property to check on either matching element
|
|
490
|
+
* @param {string|RegExp|boolean} value expected value of named property per matching element
|
|
491
|
+
* @returns {Promise<void>} promises check passed
|
|
492
|
+
*/
|
|
493
|
+
export async function localStateByProperty( phrase, all, name, value ) {
|
|
494
|
+
const api = Api.access();
|
|
495
|
+
const context = api.getContextFor( phrase );
|
|
496
|
+
const filtered = context.withProperty( name, value );
|
|
497
|
+
|
|
498
|
+
await context.checkFilteredInContext( filtered, phrase, all );
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
/**
|
|
502
|
+
* Asserts if selected element(s) has/have given class or not.
|
|
503
|
+
*
|
|
504
|
+
* @param {ContextualWord} phrase describes context of search
|
|
505
|
+
* @param {boolean} all if true, all matching elements have to match named attribute's expected value
|
|
506
|
+
* @param {string} className name of class to check
|
|
507
|
+
* @param {boolean} negated inverts the assertion by means of requiring one or all elements of context not to have selected class
|
|
508
|
+
* @return {Promise<void>} promises step passed
|
|
509
|
+
*/
|
|
510
|
+
export async function localStateByClass( phrase, all, className, negated ) {
|
|
511
|
+
const api = Api.access();
|
|
512
|
+
const context = api.getContextFor( phrase );
|
|
513
|
+
const filtered = context.withListProperty( "classList", className, negated );
|
|
514
|
+
|
|
515
|
+
await context.checkFilteredInContext( filtered, phrase, all );
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
/**
|
|
519
|
+
* Checks if an addressed match of a previously tracked search contains a given
|
|
520
|
+
* text.
|
|
521
|
+
*
|
|
522
|
+
* @param {ContextualWord} phrase description of previous match to check
|
|
523
|
+
* @param {string} text expected text found in elements of selected match
|
|
524
|
+
* @param {boolean} partially if true, provided text does not have to match whole content of matching subs
|
|
525
|
+
* @returns {Promise<void>} promises check passed
|
|
526
|
+
*/
|
|
527
|
+
export async function localStateByLabel( phrase, text, partially ) {
|
|
528
|
+
const api = Api.access();
|
|
529
|
+
const context = api.getContextFor( phrase );
|
|
530
|
+
const cardinal = phrase.asCardinalWord( await context.matchCount() );
|
|
531
|
+
|
|
532
|
+
await context
|
|
533
|
+
.filterBySubsWithText( "$label", "label", text, partially )
|
|
534
|
+
.checkCardinalWord( cardinal, phrase.isRefining, `expected %amount labelled with "${text}"` );
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
/**
|
|
538
|
+
* Checks if an addressed match of a previously tracked search contains a given
|
|
539
|
+
* text.
|
|
540
|
+
*
|
|
541
|
+
* @param {ContextualWord} phrase description of previous match to check
|
|
542
|
+
* @param {string} text expected text found in elements of selected match
|
|
543
|
+
* @param {boolean} partially if true searched elements may partially contain provided text to be considered a match
|
|
544
|
+
* @returns {Promise<void>} promises check passed
|
|
545
|
+
*/
|
|
546
|
+
export async function localStateByTextContent( phrase, text, partially ) {
|
|
547
|
+
const api = Api.access();
|
|
548
|
+
const context = api.getContextFor( phrase );
|
|
549
|
+
const cardinal = phrase.asCardinalWord( await context.matchCount() );
|
|
550
|
+
|
|
551
|
+
await context
|
|
552
|
+
.filterBySubsWithText( "$text", false, text, partially )
|
|
553
|
+
.checkCardinalWord( cardinal, phrase.isRefining, `expected %amount ${partially ? "partially " : ""}reading "${text}"` );
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
/**
|
|
557
|
+
* Checks if some contextual elements exist or are missing explicitly.
|
|
558
|
+
*
|
|
559
|
+
* @param {ContextualWord} phrase description of previous match to check
|
|
560
|
+
* @param {boolean} mustExist if true, described contextual element has to exist to pass, otherwise it has to be missing
|
|
561
|
+
* @returns {Promise<void>} promises check passed
|
|
562
|
+
*/
|
|
563
|
+
export async function localStateExists( phrase, mustExist ) {
|
|
564
|
+
const api = Api.access();
|
|
565
|
+
const context = api.getContextFor( phrase );
|
|
566
|
+
|
|
567
|
+
if ( mustExist ) {
|
|
568
|
+
// FIXME might pass unintentionally if just one of several elements to be tested exists
|
|
569
|
+
await api.expect( context.locator ).not.toHaveCount( 0 );
|
|
570
|
+
|
|
571
|
+
if ( phrase.isRefining ) {
|
|
572
|
+
await context.track( phrase.word, phrase.targetCardinality );
|
|
573
|
+
}
|
|
574
|
+
} else {
|
|
575
|
+
await api.expect( context.locator ).toHaveCount( 0 );
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
//#endregion
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cepharum/contextual-gherkin",
|
|
3
|
-
"version": "3.0.0
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "flexible step definitions for Gherkin",
|
|
5
5
|
"author": "cepharum GmbH <thomas.urban@cepharum.de>",
|
|
6
6
|
"homepage": "https://cepharum-foss.gitlab.io/contextual-gherkin/",
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"semver": "^7.7.4"
|
|
19
19
|
},
|
|
20
20
|
"peerDependencies": {
|
|
21
|
-
"@cucumber/cucumber": "^12.
|
|
21
|
+
"@cucumber/cucumber": "^12.7.0",
|
|
22
22
|
"@cucumber/cucumber-expressions": "^19.0.0",
|
|
23
23
|
"@playwright/test": "^1.58.2"
|
|
24
24
|
}
|