@qavajs/cypress 2.7.0 → 2.8.2

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/CHANGELOG.md CHANGED
@@ -10,6 +10,19 @@ Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how
10
10
  :pencil: - chore
11
11
  :microscope: - experimental
12
12
 
13
+ ## [2.8.2]
14
+ - :rocket: added `clickable` condition (checks element is visible and not disabled)
15
+ - :beetle: fixed `validateAllOf` exiting after first successful match instead of validating all values
16
+ - :beetle: fixed circular reference in `I expect {value} {validation} at least one of:` step
17
+ - :beetle: fixed `execute` steps throwing generic JS error on invalid function string — now surfaces a descriptive message
18
+ - :pencil: added `engines` field to `package.json` (`node >= 18`)
19
+
20
+ ## [2.8.1]
21
+ - :beetle: fixed native selector logging to display `[function]` instead of function body
22
+
23
+ ## [2.8.0]
24
+ - :rocket: improved memory and page object logging
25
+
13
26
  ## [2.7.0]
14
27
  - :rocket: added logic to display resolved selector chain
15
28
 
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 @qavajs
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,5 +1,8 @@
1
1
  # @qavajs/cypress
2
- qavajs implementation for cypress runner
2
+ [![npm](https://img.shields.io/npm/v/@qavajs/cypress)](https://www.npmjs.com/package/@qavajs/cypress)
3
+ [![license](https://img.shields.io/npm/l/@qavajs/cypress)](LICENSE)
4
+
5
+ Gherkin/BDD step definitions for web testing with Cypress. Part of the [qavajs](https://github.com/qavajs) framework.
3
6
 
4
7
  ## Installation
5
8
 
@@ -8,49 +11,235 @@ npm install @qavajs/cypress @qavajs/cypress-runner-adapter @qavajs/memory
8
11
  ```
9
12
 
10
13
  ## Configuration
11
- cypress.config.js
14
+
15
+ **cypress.config.js**
12
16
  ```javascript
13
17
  const { defineConfig } = require('cypress');
14
18
  const cucumber = require('@qavajs/cypress-runner-adapter/adapter');
19
+
15
20
  module.exports = defineConfig({
16
21
  e2e: {
17
- specPattern: 'cypress/features/**/*.feature', //path to features
18
- supportFile: 'cypress/support/e2e.js', //path to main support file
22
+ specPattern: 'cypress/features/**/*.feature',
23
+ supportFile: 'cypress/support/e2e.js',
19
24
  setupNodeEvents(on, config) {
20
- on('file:preprocessor', cucumber)
25
+ on('file:preprocessor', cucumber);
21
26
  },
22
27
  },
23
28
  });
24
-
25
29
  ```
26
- support file defines steps and loads qavajs entities (memory and page object)
30
+
31
+ **cypress/support/e2e.js**
27
32
  ```typescript
28
33
  import defineQavajs from '@qavajs/cypress/defineQavajs';
29
34
  import '@qavajs/cypress';
30
35
 
31
- import PageObject from '../page_object/'; // path to qavajs page objects
32
- import Memory from '../memory'; // path to qavajs memory
36
+ import PageObject from '../page_object';
37
+ import Memory from '../memory';
33
38
 
34
39
  defineQavajs({
35
40
  pageObject: new PageObject(),
36
41
  memory: new Memory()
37
42
  });
43
+ ```
38
44
 
45
+ ## Page Objects
39
46
 
40
- ```
47
+ Page objects define element locators used in step definitions.
48
+
49
+ ```typescript
50
+ import { locator } from '@qavajs/cypress/po';
51
+
52
+ class Header {
53
+ Logo = locator('.logo');
54
+ Nav = locator('nav');
55
+ }
56
+
57
+ export default class App {
58
+ // Simple CSS/XPath selector
59
+ SearchInput = locator('#search');
60
+ SearchButton = locator('#searchBtn');
61
+ Results = locator('.result-item');
62
+
63
+ // Parameterized selector
64
+ ResultByIndex = locator.template(idx => `.result-item:nth-child(${idx})`);
65
+
66
+ // Custom Cypress query
67
+ ActiveTab = locator.native(({ cy }) => cy.get('.tab').filter('.active'));
41
68
 
42
- ## Development and testing
43
- Install dependencies
69
+ // Nested component
70
+ Header = locator('header').as(Header);
71
+ }
44
72
  ```
45
- npm install
73
+
74
+ Elements are referenced in steps by their property name (e.g. `'Search Input'` or `'Header > Logo'` for nested components).
75
+
76
+ ## Memory
77
+
78
+ The memory class stores values accessible in steps via the `$key` syntax.
79
+
80
+ ```typescript
81
+ export default class Memory {
82
+ baseUrl = 'https://example.com';
83
+ testUser = 'user@example.com';
84
+ // Functions are called on access
85
+ timestamp = () => Date.now();
86
+ uppercase = (str: string) => str.toUpperCase();
87
+ }
46
88
  ```
47
89
 
48
- Execute tests
90
+ Use `$key` in any step parameter to reference a memory value, `$key()` to call a function, and `$key.property` for nested access.
91
+
92
+ ---
93
+
94
+ ## Step Definitions
95
+
96
+ ### Navigation & Actions
97
+
98
+ | Step | Description |
99
+ |------|-------------|
100
+ | `I open '{value}' url` | Navigate to a URL |
101
+ | `I reload page` | Reload current page |
102
+ | `I go back` | Browser back |
103
+ | `I go forward` | Browser forward |
104
+ | `I click '{locator}'` | Click an element |
105
+ | `I force click '{locator}'` | Force-click (bypasses actionability checks) |
106
+ | `I double click '{locator}'` | Double-click |
107
+ | `I right click '{locator}'` | Right-click |
108
+ | `I type '{value}' to '{locator}'` | Type text into an element |
109
+ | `I type '{value}' into '{locator}'` | Alias for type |
110
+ | `I clear '{locator}'` | Clear an input |
111
+ | `I clear '{locator}' and type '{value}'` | Clear then type |
112
+ | `I press '{value}' key` | Press a keyboard key or combo (e.g. `Enter`, `Control+C`) |
113
+ | `I press '{value}' key {int} times` | Press key N times |
114
+ | `I hover over '{locator}'` | Hover over element |
115
+ | `I select '{value}' option from '{locator}' dropdown` | Select by text |
116
+ | `I select {int} option from '{locator}' dropdown` | Select by index |
117
+ | `I click '{value}' text in '{locator}' collection` | Click item in collection by text |
118
+ | `I upload '{value}' to '{locator}'` | Upload a file |
119
+ | `I scroll to '{locator}'` | Scroll element into view |
120
+ | `I scroll by '{value}'` | Scroll page by `x, y` offset |
121
+ | `I scroll by '{value}' in '{locator}'` | Scroll within element |
122
+
123
+ ### Alerts & Dialogs
124
+
125
+ | Step | Description |
126
+ |------|-------------|
127
+ | `I will accept alert` | Accept the next alert |
128
+ | `I will dismiss alert` | Dismiss the next alert |
129
+ | `I will type '{value}' to alert` | Type into the next prompt |
130
+
131
+ ### Validations
132
+
133
+ | Step | Description |
134
+ |------|-------------|
135
+ | `I expect '{locator}' {condition}` | Assert element condition (see Conditions) |
136
+ | `I expect text of '{locator}' {validation} '{value}'` | Assert element text |
137
+ | `I expect value of '{locator}' {validation} '{value}'` | Assert input value |
138
+ | `I expect '{attr}' attribute of '{locator}' {validation} '{value}'` | Assert attribute |
139
+ | `I expect '{prop}' property of '{locator}' {validation} '{value}'` | Assert property |
140
+ | `I expect '{expr}' custom property of '{locator}' {validation} '{value}'` | Assert via JS function (e.g. `$js(e => e.prop('value'))`) |
141
+ | `I expect '{prop}' css property of '{locator}' {validation} '{value}'` | Assert CSS property |
142
+ | `I expect current url {validation} '{value}'` | Assert current URL |
143
+ | `I expect page title {validation} '{value}'` | Assert page title |
144
+ | `I expect number of elements in '{locator}' collection {validation} '{value}'` | Assert collection count |
145
+ | `I expect every element in '{locator}' collection {condition}` | Assert all elements in collection |
146
+ | `I expect text of alert {validation} '{value}'` | Assert alert text |
147
+
148
+ **Conditions:** `to be visible`, `not to be visible`, `to be hidden`, `to be present`, `not to be present`, `to be enabled`, `to be disabled`, `to be in viewport`, `to be clickable`
149
+
150
+ **Validations:** `equal`, `strictly equal`, `deeply equal`, `contain`, `match`, `above`, `below`, `greater than`, `less than`, `have type`, `have property`, `have member`, `include members`, `case insensitive equal`, `satisfy`
151
+
152
+ ### Memory Steps
153
+
154
+ | Step | Description |
155
+ |------|-------------|
156
+ | `I save text of '{locator}' as '{key}'` | Save element text |
157
+ | `I save value of '{locator}' as '{key}'` | Save input value |
158
+ | `I save '{attr}' attribute of '{locator}' as '{key}'` | Save attribute value |
159
+ | `I save '{prop}' property of '{locator}' as '{key}'` | Save property value |
160
+ | `I save '{expr}' custom property of '{locator}' as '{key}'` | Save JS expression result |
161
+ | `I save text of every element of '{locator}' collection as '{key}'` | Save array of texts |
162
+ | `I save current url as '{key}'` | Save current URL |
163
+ | `I save page title as '{key}'` | Save page title |
164
+ | `I save bounding rect of '{locator}' as '{key}'` | Save element bounding rect |
165
+ | `I save '{value}' to memory as '{key}'` | Store a literal value |
166
+ | `I set '{key}' = '{value}'` | Alias for save to memory |
167
+ | `I save json to memory as '{key}':` + docstring | Store a JSON object |
168
+ | `I save key-value pairs to memory as '{key}':` + table | Store a map |
169
+ | `I save multiline string to memory as '{key}':` + docstring | Store a multiline string |
170
+
171
+ ### Mouse Interactions
172
+
173
+ | Step | Description |
174
+ |------|-------------|
175
+ | `I press {mouseButton} mouse button on '{value}'` | Press mouse button at `x, y` |
176
+ | `I release {mouseButton} mouse button on '{value}'` | Release mouse button at `x, y` |
177
+ | `I move mouse to '{value}'` | Move mouse to `x, y` |
178
+ | `I scroll mouse wheel by '{value}' on '{value}'` | Scroll wheel at `x, y` |
179
+
180
+ **Mouse buttons:** `left`, `right`, `middle`
181
+
182
+ ### JavaScript Execution
183
+
184
+ | Step | Description |
185
+ |------|-------------|
186
+ | `I execute '{script}' function` | Execute JS in page context |
187
+ | `I execute '{script}' function and save result as '{key}'` | Execute and save return value |
188
+ | `I execute '{script}' function on '{locator}'` | Execute JS with element as `arguments[0]` |
189
+ | `I execute '{script}' function on '{locator}' and save result as '{key}'` | Execute on element and save result |
190
+
191
+ ### Storage & Cookies
192
+
193
+ | Step | Description |
194
+ |------|-------------|
195
+ | `I set '{name}' cookie '{value}'` | Set a cookie |
196
+ | `I save value of '{name}' cookie as '{key}'` | Read a cookie |
197
+ | `I set '{key}' local storage value as '{value}'` | Set localStorage item |
198
+ | `I set '{key}' session storage value as '{value}'` | Set sessionStorage item |
199
+ | `I save value of '{key}' local storage as '{memKey}'` | Read localStorage item |
200
+ | `I save value of '{key}' session storage as '{memKey}'` | Read sessionStorage item |
201
+
202
+ ### HTTP Requests
203
+
204
+ | Step | Description |
205
+ |------|-------------|
206
+ | `I create '{method}' request '{key}'` | Create HTTP request object (GET, POST, etc.) |
207
+ | `I create GraphQL request '{key}'` | Create GraphQL request object |
208
+ | `I add '{url}' url to '{request}'` | Set request URL |
209
+ | `I add headers to '{request}':` + table | Add headers (key/value table) |
210
+ | `I add body to '{request}':` + docstring | Set request body (JSON) |
211
+ | `I add form data body to '{request}':` + table | Set multipart form data |
212
+ | `I add query to GraphQL '{request}':` + docstring | Set GraphQL query |
213
+ | `I add variables to GraphQL '{request}':` + docstring | Set GraphQL variables |
214
+ | `I send '{request}' request and save response as '{key}'` | Send request and save response |
215
+
216
+ ---
217
+
218
+ ## Example Feature
219
+
220
+ ```gherkin
221
+ Feature: Search
222
+
223
+ Scenario: User searches for a product
224
+ Given I open '$baseUrl' url
225
+ When I type 'laptop' to 'Search Input'
226
+ And I click 'Search Button'
227
+ Then I expect number of elements in 'Results' collection above '0'
228
+ And I save text of 'Results' as 'firstResult'
229
+ And I expect '$firstResult' to contain 'laptop'
49
230
  ```
231
+
232
+ ---
233
+
234
+ ## Development
235
+
236
+ ```bash
237
+ # Install dependencies
238
+ npm install
239
+
240
+ # Run tests
50
241
  npm run test
51
- ```
52
242
 
53
- Debug tests
54
- ```
243
+ # Open Cypress GUI
55
244
  npm run debug
56
245
  ```
package/lib/actions.js CHANGED
@@ -68,7 +68,7 @@ When('I clear {locator}', function (locator) {
68
68
 
69
69
  /**
70
70
  * Refresh current page
71
- * @example I r efresh page
71
+ * @example I refresh page
72
72
  */
73
73
  When('I refresh page', function () {
74
74
  cy.reload();
@@ -1,3 +1,13 @@
1
+ chai.Assertion.addMethod('beClickable', function () {
2
+ const subject = this._obj;
3
+ this.assert(
4
+ subject.is(':visible') && !subject.is(':disabled'),
5
+ 'expected #{this} to be clickable',
6
+ 'expected #{this} not to be clickable',
7
+ subject
8
+ );
9
+ });
10
+
1
11
  chai.Assertion.addMethod('inViewport', function (ER) {
2
12
  const subject = this._obj;
3
13
 
@@ -21,6 +31,7 @@ chai.Assertion.addMethod('inViewport', function (ER) {
21
31
 
22
32
  export const conditionValidations = {
23
33
  PRESENT: 'present',
34
+ CLICKABLE: 'clickable',
24
35
  VISIBLE: 'visible',
25
36
  INVISIBLE: 'invisible',
26
37
  ENABLED: 'enabled',
@@ -37,6 +48,7 @@ export const conditionWaitRegexp = new RegExp(`(${notClause}${toBeClause}${valid
37
48
 
38
49
  const expects = {
39
50
  [conditionValidations.PRESENT]: 'exist',
51
+ [conditionValidations.CLICKABLE]: 'beClickable',
40
52
  [conditionValidations.VISIBLE]: 'be.visible',
41
53
  [conditionValidations.INVISIBLE]: 'be.hidden',
42
54
  [conditionValidations.ENABLED]: 'be.enabled',
package/lib/execute.js CHANGED
@@ -1,5 +1,14 @@
1
1
  import { When } from '@qavajs/cypress-runner-adapter';
2
2
 
3
+ function toFunction(fnContent) {
4
+ if (typeof fnContent !== 'string') return fnContent;
5
+ try {
6
+ return new Function(`return ${fnContent}`);
7
+ } catch (e) {
8
+ throw new Error(`Failed to parse function expression: "${fnContent}"\n${e.message}`);
9
+ }
10
+ }
11
+
3
12
  /**
4
13
  * Execute client function
5
14
  * @param {string} functionKey - memory key of function
@@ -8,7 +17,7 @@ import { When } from '@qavajs/cypress-runner-adapter';
8
17
  */
9
18
  When('I execute {value} function', function (functionKey) {
10
19
  const fnContent = functionKey.value();
11
- const fn = typeof fnContent === 'string' ? new Function(`return ${fnContent}`): fnContent;
20
+ const fn = toFunction(fnContent);
12
21
  cy.window().then(win => {
13
22
  fn.apply(win);
14
23
  });
@@ -23,7 +32,7 @@ When('I execute {value} function', function (functionKey) {
23
32
  */
24
33
  When('I execute {value} function and save result as {value}', function (functionKey, memoryKey) {
25
34
  const fnContent = functionKey.value();
26
- const fn = typeof fnContent === 'string' ? new Function(`return ${fnContent}`): fnContent;
35
+ const fn = toFunction(fnContent);
27
36
  cy.window().then(win => {
28
37
  memoryKey.set(fn.apply(win));
29
38
  });
@@ -38,7 +47,7 @@ When('I execute {value} function and save result as {value}', function (function
38
47
  */
39
48
  When('I execute {value} function on {locator}', function (functionKey, locator) {
40
49
  const fnContent = functionKey.value();
41
- const fn = typeof fnContent === 'string' ? new Function(`return ${fnContent}`): fnContent;
50
+ const fn = toFunction(fnContent);
42
51
  cy.window().then(win => {
43
52
  locator.then((e) => {
44
53
  fn.apply(win, e);
@@ -57,7 +66,7 @@ When(
57
66
  'I execute {value} function on {locator} and save result as {value}',
58
67
  function (functionKey, locator, memoryKey) {
59
68
  const fnContent = functionKey.value();
60
- const fn = typeof fnContent === 'string' ? new Function(`return ${fnContent}`): fnContent;
69
+ const fn = toFunction(fnContent);
61
70
  cy.window().then(win => {
62
71
  locator.then((e) => {
63
72
  memoryKey.set(fn.apply(win, e));
@@ -86,7 +86,7 @@ export function element(path) {
86
86
  };
87
87
 
88
88
  const applyNative = (item) => {
89
- logChain += `.${method}([native])`;
89
+ logChain += `.native([function])`;
90
90
  current = item.selector({
91
91
  parent: current,
92
92
  cy: this.cy,
@@ -115,8 +115,17 @@ export function element(path) {
115
115
  method = item.selector ? 'find' : 'get';
116
116
  }
117
117
  Cypress.log({
118
- name: `${path} →`,
119
- message: logChain
118
+ displayName: `${path} →`,
119
+ message: logChain,
120
+ type: 'parent',
121
+ consoleProps: () => {
122
+ return {
123
+ Key: path,
124
+ Element: logChain,
125
+ }
126
+ },
127
+ alias: path,
128
+ aliasType: 'dom'
120
129
  });
121
130
  return current;
122
131
  });
package/lib/setup.js CHANGED
@@ -10,14 +10,14 @@ const logger = {
10
10
  Cypress.log({
11
11
  displayName: `${displayName} ${displayedDivider}`,
12
12
  message,
13
- type: 'parent',
13
+ alias: displayName,
14
14
  consoleProps: () => {
15
15
  return {
16
16
  Key: displayName,
17
17
  Value: message,
18
18
  }
19
19
  }
20
- })
20
+ });
21
21
  });
22
22
  }
23
23
  };
@@ -155,8 +155,7 @@ Then(
155
155
  cy.then(() => {
156
156
  const actualValue = actual.value();
157
157
  const expectedValues = dataTable2Array(this, expected);
158
- const validation = (AR, ER) => valueExpect(AR, ER, validation);
159
- validateAnyOf(actualValue, expectedValues, validation);
158
+ validateAnyOf(actualValue, expectedValues, valueExpect);
160
159
  });
161
160
  }
162
161
  );
@@ -222,7 +221,6 @@ function validateAllOf(AR, ERs, validation){
222
221
  for (const ER of ERs) {
223
222
  try {
224
223
  validation(AR, ER);
225
- return;
226
224
  } catch (err) {
227
225
  errorCollector.push(err);
228
226
  }
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "@qavajs/cypress",
3
- "version": "2.7.0",
3
+ "version": "2.8.2",
4
4
  "description": "qavajs for cypress runner",
5
5
  "main": "index.js",
6
6
  "scripts": {
7
7
  "debug": "cypress open --config-file test-e2e/cypress.config.js",
8
+ "debug:it": "MODE=it cypress open --config-file test-e2e/cypress.config.js",
8
9
  "test": "cypress run --config-file test-e2e/cypress.config.js",
9
10
  "test:it": "MODE=it cypress run --config-file test-e2e/cypress.config.js"
10
11
  },
@@ -27,9 +28,12 @@
27
28
  "homepage": "https://github.com/qavajs/cypress#readme",
28
29
  "author": "Alexandr Galichenko",
29
30
  "license": "MIT",
31
+ "engines": {
32
+ "node": ">=18"
33
+ },
30
34
  "devDependencies": {
31
- "@qavajs/cypress-runner-adapter": "^1.6.0",
32
- "@qavajs/memory": "^1.10.3",
33
- "cypress": "^15.10.0"
35
+ "@qavajs/cypress-runner-adapter": "^1.9.2",
36
+ "@qavajs/memory": "^1.11.0",
37
+ "cypress": "^15.14.1"
34
38
  }
35
39
  }