codeceptjs 4.0.0-beta.2 → 4.0.0-beta.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.
Files changed (209) hide show
  1. package/README.md +133 -120
  2. package/bin/codecept.js +107 -96
  3. package/bin/test-server.js +64 -0
  4. package/docs/webapi/clearCookie.mustache +1 -1
  5. package/docs/webapi/click.mustache +5 -1
  6. package/lib/actor.js +73 -103
  7. package/lib/ai.js +159 -188
  8. package/lib/assert/empty.js +22 -24
  9. package/lib/assert/equal.js +30 -37
  10. package/lib/assert/error.js +14 -14
  11. package/lib/assert/include.js +43 -48
  12. package/lib/assert/throws.js +11 -11
  13. package/lib/assert/truth.js +22 -22
  14. package/lib/assert.js +20 -18
  15. package/lib/codecept.js +262 -162
  16. package/lib/colorUtils.js +50 -52
  17. package/lib/command/check.js +206 -0
  18. package/lib/command/configMigrate.js +56 -51
  19. package/lib/command/definitions.js +96 -109
  20. package/lib/command/dryRun.js +77 -79
  21. package/lib/command/generate.js +234 -194
  22. package/lib/command/gherkin/init.js +42 -33
  23. package/lib/command/gherkin/snippets.js +76 -74
  24. package/lib/command/gherkin/steps.js +20 -17
  25. package/lib/command/info.js +74 -38
  26. package/lib/command/init.js +301 -290
  27. package/lib/command/interactive.js +41 -32
  28. package/lib/command/list.js +28 -27
  29. package/lib/command/run-multiple/chunk.js +51 -48
  30. package/lib/command/run-multiple/collection.js +5 -5
  31. package/lib/command/run-multiple/run.js +5 -1
  32. package/lib/command/run-multiple.js +97 -97
  33. package/lib/command/run-rerun.js +19 -25
  34. package/lib/command/run-workers.js +68 -92
  35. package/lib/command/run.js +39 -27
  36. package/lib/command/utils.js +80 -64
  37. package/lib/command/workers/runTests.js +388 -226
  38. package/lib/config.js +109 -50
  39. package/lib/container.js +765 -261
  40. package/lib/data/context.js +60 -61
  41. package/lib/data/dataScenarioConfig.js +47 -47
  42. package/lib/data/dataTableArgument.js +32 -32
  43. package/lib/data/table.js +22 -22
  44. package/lib/effects.js +307 -0
  45. package/lib/element/WebElement.js +327 -0
  46. package/lib/els.js +160 -0
  47. package/lib/event.js +173 -163
  48. package/lib/globals.js +141 -0
  49. package/lib/heal.js +89 -85
  50. package/lib/helper/AI.js +131 -41
  51. package/lib/helper/ApiDataFactory.js +107 -75
  52. package/lib/helper/Appium.js +542 -404
  53. package/lib/helper/FileSystem.js +100 -79
  54. package/lib/helper/GraphQL.js +44 -43
  55. package/lib/helper/GraphQLDataFactory.js +52 -52
  56. package/lib/helper/JSONResponse.js +126 -88
  57. package/lib/helper/Mochawesome.js +54 -29
  58. package/lib/helper/Playwright.js +2547 -1316
  59. package/lib/helper/Puppeteer.js +1578 -1181
  60. package/lib/helper/REST.js +209 -68
  61. package/lib/helper/WebDriver.js +1482 -1342
  62. package/lib/helper/errors/ConnectionRefused.js +6 -6
  63. package/lib/helper/errors/ElementAssertion.js +11 -16
  64. package/lib/helper/errors/ElementNotFound.js +5 -9
  65. package/lib/helper/errors/RemoteBrowserConnectionRefused.js +5 -5
  66. package/lib/helper/extras/Console.js +11 -11
  67. package/lib/helper/extras/PlaywrightLocator.js +110 -0
  68. package/lib/helper/extras/PlaywrightPropEngine.js +18 -18
  69. package/lib/helper/extras/PlaywrightReactVueLocator.js +17 -8
  70. package/lib/helper/extras/PlaywrightRestartOpts.js +25 -11
  71. package/lib/helper/extras/Popup.js +22 -22
  72. package/lib/helper/extras/React.js +27 -28
  73. package/lib/helper/network/actions.js +36 -42
  74. package/lib/helper/network/utils.js +78 -84
  75. package/lib/helper/scripts/blurElement.js +5 -5
  76. package/lib/helper/scripts/focusElement.js +5 -5
  77. package/lib/helper/scripts/highlightElement.js +8 -8
  78. package/lib/helper/scripts/isElementClickable.js +34 -34
  79. package/lib/helper.js +2 -3
  80. package/lib/history.js +23 -19
  81. package/lib/hooks.js +8 -8
  82. package/lib/html.js +94 -104
  83. package/lib/index.js +38 -27
  84. package/lib/listener/config.js +30 -23
  85. package/lib/listener/emptyRun.js +54 -0
  86. package/lib/listener/enhancedGlobalRetry.js +110 -0
  87. package/lib/listener/exit.js +16 -18
  88. package/lib/listener/globalRetry.js +70 -0
  89. package/lib/listener/globalTimeout.js +181 -0
  90. package/lib/listener/helpers.js +76 -51
  91. package/lib/listener/mocha.js +10 -11
  92. package/lib/listener/result.js +11 -0
  93. package/lib/listener/retryEnhancer.js +85 -0
  94. package/lib/listener/steps.js +71 -59
  95. package/lib/listener/store.js +20 -0
  96. package/lib/locator.js +214 -197
  97. package/lib/mocha/asyncWrapper.js +274 -0
  98. package/lib/mocha/bdd.js +167 -0
  99. package/lib/mocha/cli.js +341 -0
  100. package/lib/mocha/factory.js +163 -0
  101. package/lib/mocha/featureConfig.js +89 -0
  102. package/lib/mocha/gherkin.js +231 -0
  103. package/lib/mocha/hooks.js +121 -0
  104. package/lib/mocha/index.js +21 -0
  105. package/lib/mocha/inject.js +46 -0
  106. package/lib/{interfaces → mocha}/scenarioConfig.js +58 -34
  107. package/lib/mocha/suite.js +89 -0
  108. package/lib/mocha/test.js +184 -0
  109. package/lib/mocha/types.d.ts +42 -0
  110. package/lib/mocha/ui.js +242 -0
  111. package/lib/output.js +141 -71
  112. package/lib/parser.js +54 -44
  113. package/lib/pause.js +173 -145
  114. package/lib/plugin/analyze.js +403 -0
  115. package/lib/plugin/{autoLogin.js → auth.js} +178 -79
  116. package/lib/plugin/autoDelay.js +36 -40
  117. package/lib/plugin/coverage.js +131 -78
  118. package/lib/plugin/customLocator.js +22 -21
  119. package/lib/plugin/customReporter.js +53 -0
  120. package/lib/plugin/enhancedRetryFailedStep.js +99 -0
  121. package/lib/plugin/heal.js +101 -110
  122. package/lib/plugin/htmlReporter.js +3648 -0
  123. package/lib/plugin/pageInfo.js +140 -0
  124. package/lib/plugin/pauseOnFail.js +12 -11
  125. package/lib/plugin/retryFailedStep.js +82 -47
  126. package/lib/plugin/screenshotOnFail.js +111 -92
  127. package/lib/plugin/stepByStepReport.js +159 -101
  128. package/lib/plugin/stepTimeout.js +20 -25
  129. package/lib/plugin/subtitles.js +38 -38
  130. package/lib/recorder.js +193 -130
  131. package/lib/rerun.js +94 -49
  132. package/lib/result.js +238 -0
  133. package/lib/retryCoordinator.js +207 -0
  134. package/lib/secret.js +20 -18
  135. package/lib/session.js +95 -89
  136. package/lib/step/base.js +239 -0
  137. package/lib/step/comment.js +10 -0
  138. package/lib/step/config.js +50 -0
  139. package/lib/step/func.js +46 -0
  140. package/lib/step/helper.js +50 -0
  141. package/lib/step/meta.js +99 -0
  142. package/lib/step/record.js +74 -0
  143. package/lib/step/retry.js +11 -0
  144. package/lib/step/section.js +55 -0
  145. package/lib/step.js +18 -329
  146. package/lib/steps.js +54 -0
  147. package/lib/store.js +38 -7
  148. package/lib/template/heal.js +3 -12
  149. package/lib/template/prompts/generatePageObject.js +31 -0
  150. package/lib/template/prompts/healStep.js +13 -0
  151. package/lib/template/prompts/writeStep.js +9 -0
  152. package/lib/test-server.js +334 -0
  153. package/lib/timeout.js +60 -0
  154. package/lib/transform.js +8 -8
  155. package/lib/translation.js +34 -21
  156. package/lib/utils/loaderCheck.js +124 -0
  157. package/lib/utils/mask_data.js +47 -0
  158. package/lib/utils/typescript.js +237 -0
  159. package/lib/utils.js +411 -228
  160. package/lib/workerStorage.js +37 -34
  161. package/lib/workers.js +532 -296
  162. package/package.json +124 -95
  163. package/translations/de-DE.js +5 -3
  164. package/translations/fr-FR.js +5 -4
  165. package/translations/index.js +22 -12
  166. package/translations/it-IT.js +4 -3
  167. package/translations/ja-JP.js +4 -3
  168. package/translations/nl-NL.js +76 -0
  169. package/translations/pl-PL.js +4 -3
  170. package/translations/pt-BR.js +4 -3
  171. package/translations/ru-RU.js +4 -3
  172. package/translations/utils.js +10 -0
  173. package/translations/zh-CN.js +4 -3
  174. package/translations/zh-TW.js +4 -3
  175. package/typings/index.d.ts +546 -185
  176. package/typings/promiseBasedTypes.d.ts +150 -875
  177. package/typings/types.d.ts +547 -992
  178. package/lib/cli.js +0 -249
  179. package/lib/dirname.js +0 -5
  180. package/lib/helper/Expect.js +0 -425
  181. package/lib/helper/ExpectHelper.js +0 -399
  182. package/lib/helper/MockServer.js +0 -223
  183. package/lib/helper/Nightmare.js +0 -1411
  184. package/lib/helper/Protractor.js +0 -1835
  185. package/lib/helper/SoftExpectHelper.js +0 -381
  186. package/lib/helper/TestCafe.js +0 -1410
  187. package/lib/helper/clientscripts/nightmare.js +0 -213
  188. package/lib/helper/testcafe/testControllerHolder.js +0 -42
  189. package/lib/helper/testcafe/testcafe-utils.js +0 -63
  190. package/lib/interfaces/bdd.js +0 -98
  191. package/lib/interfaces/featureConfig.js +0 -69
  192. package/lib/interfaces/gherkin.js +0 -195
  193. package/lib/listener/artifacts.js +0 -19
  194. package/lib/listener/retry.js +0 -68
  195. package/lib/listener/timeout.js +0 -109
  196. package/lib/mochaFactory.js +0 -110
  197. package/lib/plugin/allure.js +0 -15
  198. package/lib/plugin/commentStep.js +0 -136
  199. package/lib/plugin/debugErrors.js +0 -67
  200. package/lib/plugin/eachElement.js +0 -127
  201. package/lib/plugin/fakerTransform.js +0 -49
  202. package/lib/plugin/retryTo.js +0 -121
  203. package/lib/plugin/selenoid.js +0 -371
  204. package/lib/plugin/standardActingHelpers.js +0 -9
  205. package/lib/plugin/tryTo.js +0 -105
  206. package/lib/plugin/wdio.js +0 -246
  207. package/lib/scenario.js +0 -222
  208. package/lib/ui.js +0 -238
  209. package/lib/within.js +0 -70
package/lib/locator.js CHANGED
@@ -1,13 +1,11 @@
1
- import { sprintf } from 'sprintf-js';
1
+ import { sprintf } from 'sprintf-js'
2
+ import { xpathLocator } from './utils.js'
3
+ import { createRequire } from 'module'
2
4
 
3
- import csstoxpath from 'csstoxpath';
5
+ const require = createRequire(import.meta.url)
6
+ let cssToXPath
4
7
 
5
- import css_to_xpath from 'css-to-xpath';
6
- import { xpathLocator } from './utils.js';
7
-
8
- let cssToXPath;
9
-
10
- const locatorTypes = ['css', 'by', 'xpath', 'id', 'name', 'fuzzy', 'frame', 'shadow', 'pw'];
8
+ const locatorTypes = ['css', 'by', 'xpath', 'id', 'name', 'fuzzy', 'frame', 'shadow', 'pw', 'role']
11
9
  /** @class */
12
10
  class Locator {
13
11
  /**
@@ -15,162 +13,171 @@ class Locator {
15
13
  * @param {string} [defaultType]
16
14
  */
17
15
  constructor(locator, defaultType = '') {
18
- this.type = null;
19
- if (!locator) return;
16
+ this.type = null
17
+ if (!locator) return
20
18
 
21
- this.output = null;
19
+ this.output = null
22
20
 
23
21
  /**
24
22
  * @private
25
23
  * @type {boolean}
26
24
  */
27
- this.strict = false;
25
+ this.strict = false
28
26
 
29
27
  if (typeof locator === 'object') {
30
28
  if (locator.constructor.name === 'Locator') {
31
- Object.assign(this, locator);
32
- return;
29
+ Object.assign(this, locator)
30
+ return
33
31
  }
34
32
 
35
- this.locator = locator;
36
- this.type = Object.keys(locator)[0];
37
- this.value = locator[this.type];
38
- this.strict = true;
33
+ this.locator = locator
34
+ this.type = Object.keys(locator)[0]
35
+ this.value = locator[this.type]
36
+ this.strict = true
39
37
 
40
- Locator.filters.forEach(f => f(locator, this));
38
+ Locator.filters.forEach(f => f(locator, this))
41
39
 
42
- return;
40
+ return
43
41
  }
44
42
 
45
- this.type = defaultType || 'fuzzy';
46
- this.output = locator;
47
- this.value = locator;
43
+ this.type = defaultType || 'fuzzy'
44
+ this.output = locator
45
+ this.value = locator
48
46
 
49
47
  if (isCSS(locator)) {
50
- this.type = 'css';
48
+ this.type = 'css'
51
49
  }
52
50
  if (isXPath(locator)) {
53
- this.type = 'xpath';
51
+ this.type = 'xpath'
54
52
  }
55
53
  if (isShadow(locator)) {
56
- this.type = 'shadow';
54
+ this.type = 'shadow'
57
55
  }
58
56
  if (isPlaywrightLocator(locator)) {
59
- this.type = 'pw';
57
+ this.type = 'pw'
60
58
  }
61
59
 
62
- Locator.filters.forEach(f => f(locator, this));
60
+ Locator.filters.forEach(f => f(locator, this))
63
61
  }
64
62
 
65
63
  simplify() {
66
- if (this.isNull()) return null;
64
+ if (this.isNull()) return null
67
65
  switch (this.type) {
68
66
  case 'by':
69
67
  case 'xpath':
70
- return this.value;
68
+ return this.value
71
69
  case 'css':
72
- return this.value;
70
+ return this.value
73
71
  case 'id':
74
- return `#${this.value}`;
72
+ return `#${this.value}`
75
73
  case 'name':
76
- return `[name="${this.value}"]`;
74
+ return `[name="${this.value}"]`
77
75
  case 'fuzzy':
78
- return this.value;
76
+ return this.value
79
77
  case 'shadow':
80
- return { shadow: this.value };
78
+ return { shadow: this.value }
81
79
  case 'pw':
82
- return { pw: this.value };
80
+ return { pw: this.value }
81
+ case 'role':
82
+ return `[role="${this.value}"]`
83
83
  }
84
- return this.value;
84
+ return this.value
85
85
  }
86
86
 
87
87
  toStrict() {
88
- if (!this.type) return null;
89
- return { [this.type]: this.value };
88
+ if (!this.type) return null
89
+ return { [this.type]: this.value }
90
90
  }
91
91
 
92
92
  /**
93
93
  * @returns {string}
94
94
  */
95
95
  toString() {
96
- return this.output || `{${this.type}: ${this.value}}`;
96
+ return this.output || `{${this.type}: ${this.value}}`
97
97
  }
98
98
 
99
99
  /**
100
100
  * @returns {boolean}
101
101
  */
102
102
  isFuzzy() {
103
- return this.type === 'fuzzy';
103
+ return this.type === 'fuzzy'
104
104
  }
105
105
 
106
106
  /**
107
107
  * @returns {boolean}
108
108
  */
109
109
  isShadow() {
110
- return this.type === 'shadow';
110
+ return this.type === 'shadow'
111
111
  }
112
112
 
113
113
  /**
114
114
  * @returns {boolean}
115
115
  */
116
116
  isFrame() {
117
- return this.type === 'frame';
117
+ return this.type === 'frame'
118
118
  }
119
119
 
120
120
  /**
121
121
  * @returns {boolean}
122
122
  */
123
123
  isCSS() {
124
- return this.type === 'css';
124
+ return this.type === 'css'
125
125
  }
126
126
 
127
127
  /**
128
128
  * @returns {boolean}
129
129
  */
130
130
  isPlaywrightLocator() {
131
- return this.type === 'pw';
131
+ return this.type === 'pw'
132
+ }
133
+
134
+ /**
135
+ * @returns {boolean}
136
+ */
137
+ isRole() {
138
+ return this.type === 'role'
132
139
  }
133
140
 
134
141
  /**
135
142
  * @returns {boolean}
136
143
  */
137
144
  isNull() {
138
- return this.type === null;
145
+ return this.type === null
139
146
  }
140
147
 
141
148
  /**
142
149
  * @returns {boolean}
143
150
  */
144
151
  isXPath() {
145
- return this.type === 'xpath';
152
+ return this.type === 'xpath'
146
153
  }
147
154
 
148
155
  /**
149
156
  * @returns {boolean}
150
157
  */
151
158
  isCustom() {
152
- return !!this.type && !locatorTypes.includes(this.type);
159
+ return !!this.type && !locatorTypes.includes(this.type)
153
160
  }
154
161
 
155
162
  /**
156
163
  * @returns {boolean}
157
164
  */
158
165
  isStrict() {
159
- return this.strict;
166
+ return this.strict
160
167
  }
161
168
 
162
169
  /**
163
170
  * @returns {boolean}
164
171
  */
165
172
  isAccessibilityId() {
166
- return this.isFuzzy() && this.value[0] === '~';
173
+ return this.isFuzzy() && this.value[0] === '~'
167
174
  }
168
175
 
169
176
  /**
170
177
  * @returns {boolean}
171
178
  */
172
179
  isBasic() {
173
- return this.isCSS() || this.isXPath();
180
+ return this.isCSS() || this.isXPath()
174
181
  }
175
182
 
176
183
  /**
@@ -178,19 +185,19 @@ class Locator {
178
185
  * @returns {string}
179
186
  */
180
187
  toXPath(pseudoSelector = '') {
181
- const locator = `${this.value}${pseudoSelector}`;
182
- const limitation = [':nth-of-type', ':first-of-type', ':last-of-type', ':nth-last-child', ':nth-last-of-type', ':checked', ':disabled', ':enabled', ':required', ':lang', ':nth-child', ':has'];
188
+ const locator = `${this.value}${pseudoSelector}`
189
+ const limitation = [':nth-of-type', ':first-of-type', ':last-of-type', ':nth-last-child', ':nth-last-of-type', ':checked', ':disabled', ':enabled', ':required', ':lang', ':nth-child', ':has']
183
190
 
184
191
  if (limitation.some(item => locator.includes(item))) {
185
- cssToXPath = css_to_xpath;
192
+ cssToXPath = require('css-to-xpath')
186
193
  } else {
187
- cssToXPath = csstoxpath;
194
+ cssToXPath = require('csstoxpath')
188
195
  }
189
196
 
190
- if (this.isXPath()) return this.value;
191
- if (this.isCSS()) return cssToXPath(locator);
197
+ if (this.isXPath()) return this.value
198
+ if (this.isCSS()) return cssToXPath(locator)
192
199
 
193
- throw new Error('Can\'t be converted to XPath');
200
+ throw new Error("Can't be converted to XPath")
194
201
  }
195
202
 
196
203
  // DSL
@@ -199,11 +206,8 @@ class Locator {
199
206
  * @returns {Locator}
200
207
  */
201
208
  or(locator) {
202
- const xpath = xpathLocator.combine([
203
- this.toXPath(),
204
- (new Locator(locator, 'css')).toXPath(),
205
- ]);
206
- return new Locator({ xpath });
209
+ const xpath = xpathLocator.combine([this.toXPath(), new Locator(locator, 'css').toXPath()])
210
+ return new Locator({ xpath })
207
211
  }
208
212
 
209
213
  /**
@@ -211,8 +215,8 @@ class Locator {
211
215
  * @returns {Locator}
212
216
  */
213
217
  find(locator) {
214
- const xpath = sprintf('%s//%s', this.toXPath(), convertToSubSelector(locator));
215
- return new Locator({ xpath });
218
+ const xpath = sprintf('%s//%s', this.toXPath(), convertToSubSelector(locator))
219
+ return new Locator({ xpath })
216
220
  }
217
221
 
218
222
  /**
@@ -220,8 +224,8 @@ class Locator {
220
224
  * @returns {Locator}
221
225
  */
222
226
  withChild(locator) {
223
- const xpath = sprintf('%s[./child::%s]', this.toXPath(), convertToSubSelector(locator));
224
- return new Locator({ xpath });
227
+ const xpath = sprintf('%s[./child::%s]', this.toXPath(), convertToSubSelector(locator))
228
+ return new Locator({ xpath })
225
229
  }
226
230
 
227
231
  /**
@@ -229,8 +233,8 @@ class Locator {
229
233
  * @returns {Locator}
230
234
  */
231
235
  withDescendant(locator) {
232
- const xpath = sprintf('%s[./descendant::%s]', this.toXPath(), convertToSubSelector(locator));
233
- return new Locator({ xpath });
236
+ const xpath = sprintf('%s[./descendant::%s]', this.toXPath(), convertToSubSelector(locator))
237
+ return new Locator({ xpath })
234
238
  }
235
239
 
236
240
  /**
@@ -239,33 +243,33 @@ class Locator {
239
243
  */
240
244
  at(position) {
241
245
  if (position === 0) {
242
- throw new Error('0 is not valid element position. XPath expects first element to have index 1');
246
+ throw new Error('0 is not valid element position. XPath expects first element to have index 1')
243
247
  }
244
248
 
245
- let xpathPosition;
249
+ let xpathPosition
246
250
 
247
251
  if (position > 0) {
248
- xpathPosition = position.toString();
252
+ xpathPosition = position.toString()
249
253
  } else {
250
254
  // -1 points to the last element
251
- xpathPosition = `last()-${Math.abs(position + 1)}`;
255
+ xpathPosition = `last()-${Math.abs(position + 1)}`
252
256
  }
253
- const xpath = sprintf('(%s)[position()=%s]', this.toXPath(), xpathPosition);
254
- return new Locator({ xpath });
257
+ const xpath = sprintf('(%s)[position()=%s]', this.toXPath(), xpathPosition)
258
+ return new Locator({ xpath })
255
259
  }
256
260
 
257
261
  /**
258
262
  * @returns {Locator}
259
263
  */
260
264
  first() {
261
- return this.at(1);
265
+ return this.at(1)
262
266
  }
263
267
 
264
268
  /**
265
269
  * @returns {Locator}
266
270
  */
267
271
  last() {
268
- return this.at(-1);
272
+ return this.at(-1)
269
273
  }
270
274
 
271
275
  /**
@@ -274,9 +278,9 @@ class Locator {
274
278
  * @returns {Locator}
275
279
  */
276
280
  withText(text) {
277
- text = xpathLocator.literal(text);
278
- const xpath = sprintf('%s[%s]', this.toXPath(), `contains(., ${text})`);
279
- return new Locator({ xpath });
281
+ text = xpathLocator.literal(text)
282
+ const xpath = sprintf('%s[%s]', this.toXPath(), `contains(., ${text})`)
283
+ return new Locator({ xpath })
280
284
  }
281
285
 
282
286
  /**
@@ -285,9 +289,9 @@ class Locator {
285
289
  * @returns {Locator}
286
290
  */
287
291
  withTextEquals(text) {
288
- text = xpathLocator.literal(text);
289
- const xpath = sprintf('%s[%s]', this.toXPath(), `.= ${text}`);
290
- return new Locator({ xpath });
292
+ text = xpathLocator.literal(text)
293
+ const xpath = sprintf('%s[%s]', this.toXPath(), `.= ${text}`)
294
+ return new Locator({ xpath })
291
295
  }
292
296
 
293
297
  /**
@@ -295,55 +299,54 @@ class Locator {
295
299
  * @returns {Locator}
296
300
  */
297
301
  withAttr(attributes) {
298
- const operands = [];
302
+ const operands = []
299
303
  for (const attr of Object.keys(attributes)) {
300
- operands.push(`@${attr} = ${xpathLocator.literal(attributes[attr])}`);
304
+ operands.push(`@${attr} = ${xpathLocator.literal(attributes[attr])}`)
301
305
  }
302
- const xpath = sprintf('%s[%s]', this.toXPath(), operands.join(' and '));
303
- return new Locator({ xpath });
306
+ const xpath = sprintf('%s[%s]', this.toXPath(), operands.join(' and '))
307
+ return new Locator({ xpath })
304
308
  }
305
309
 
306
310
  /**
307
- * Adds condition: attribute value starts with text
308
- * (analog of XPATH: [starts-with(@attr,'startValue')] or CSS [attr^='startValue']
309
- * Example: I.click(locate('a').withAttrStartsWith('href', 'https://')));
310
- * Works with any attribute: class, href etc.
311
- * @param {string} attrName
312
- * @param {string} startsWith
313
- * @returns {Locator}
314
- */
311
+ * Adds condition: attribute value starts with text
312
+ * (analog of XPATH: [starts-with(@attr,'startValue')] or CSS [attr^='startValue']
313
+ * Example: I.click(locate('a').withAttrStartsWith('href', 'https://')));
314
+ * Works with any attribute: class, href etc.
315
+ * @param {string} attrName
316
+ * @param {string} startsWith
317
+ * @returns {Locator}
318
+ */
315
319
  withAttrStartsWith(attrName, startsWith) {
316
- const xpath = sprintf('%s[%s]', this.toXPath(), `starts-with(@${attrName}, "${startsWith}")`);
317
- return new Locator({ xpath });
320
+ const xpath = sprintf('%s[%s]', this.toXPath(), `starts-with(@${attrName}, "${startsWith}")`)
321
+ return new Locator({ xpath })
318
322
  }
319
323
 
320
324
  /**
321
- * Adds condition: attribute value ends with text
322
- * (analog of XPATH: [ends-with(@attr,'endValue')] or CSS [attr$='endValue']
323
- * Example: I.click(locate('a').withAttrEndsWith('href', '.com')));
324
- * Works with any attribute: class, href etc.
325
- * @param {string} attrName
326
- * @param {string} endsWith
327
- * @returns {Locator}
328
- */
325
+ * Adds condition: attribute value ends with text
326
+ * (analog of XPATH: [ends-with(@attr,'endValue')] or CSS [attr$='endValue']
327
+ * Example: I.click(locate('a').withAttrEndsWith('href', '.com')));
328
+ * Works with any attribute: class, href etc.
329
+ * @param {string} attrName
330
+ * @param {string} endsWith
331
+ * @returns {Locator}
332
+ */
329
333
  withAttrEndsWith(attrName, endsWith) {
330
- const xpath = sprintf('%s[%s]', this.toXPath(), `substring(@${attrName}, string-length(@${attrName}) - string-length("${endsWith}") + 1) = "${endsWith}"`,
331
- );
332
- return new Locator({ xpath });
334
+ const xpath = sprintf('%s[%s]', this.toXPath(), `substring(@${attrName}, string-length(@${attrName}) - string-length("${endsWith}") + 1) = "${endsWith}"`)
335
+ return new Locator({ xpath })
333
336
  }
334
337
 
335
338
  /**
336
- * Adds condition: attribute value contains text
337
- * (analog of XPATH: [contains(@attr,'partOfAttribute')] or CSS [attr*='partOfAttribute']
338
- * Example: I.click(locate('a').withAttrContains('href', 'google')));
339
- * Works with any attribute: class, href etc.
340
- * @param {string} attrName
341
- * @param {string} partOfAttrValue
342
- * @returns {Locator}
343
- */
339
+ * Adds condition: attribute value contains text
340
+ * (analog of XPATH: [contains(@attr,'partOfAttribute')] or CSS [attr*='partOfAttribute']
341
+ * Example: I.click(locate('a').withAttrContains('href', 'google')));
342
+ * Works with any attribute: class, href etc.
343
+ * @param {string} attrName
344
+ * @param {string} partOfAttrValue
345
+ * @returns {Locator}
346
+ */
344
347
  withAttrContains(attrName, partOfAttrValue) {
345
- const xpath = sprintf('%s[%s]', this.toXPath(), `contains(@${attrName}, "${partOfAttrValue}")`);
346
- return new Locator({ xpath });
348
+ const xpath = sprintf('%s[%s]', this.toXPath(), `contains(@${attrName}, "${partOfAttrValue}")`)
349
+ return new Locator({ xpath })
347
350
  }
348
351
 
349
352
  /**
@@ -351,8 +354,8 @@ class Locator {
351
354
  * @returns {Locator}
352
355
  */
353
356
  withClassAttr(text) {
354
- const xpath = sprintf('%s[%s]', this.toXPath(), `contains(@class, '${text}')`);
355
- return new Locator({ xpath });
357
+ const xpath = sprintf('%s[%s]', this.toXPath(), `contains(@class, '${text}')`)
358
+ return new Locator({ xpath })
356
359
  }
357
360
 
358
361
  /**
@@ -360,8 +363,8 @@ class Locator {
360
363
  * @returns {Locator}
361
364
  */
362
365
  as(output) {
363
- this.output = output;
364
- return this;
366
+ this.output = output
367
+ return this
365
368
  }
366
369
 
367
370
  /**
@@ -369,8 +372,8 @@ class Locator {
369
372
  * @returns {Locator}
370
373
  */
371
374
  inside(locator) {
372
- const xpath = sprintf('%s[ancestor::%s]', this.toXPath(), convertToSubSelector(locator));
373
- return new Locator({ xpath });
375
+ const xpath = sprintf('%s[ancestor::%s]', this.toXPath(), convertToSubSelector(locator))
376
+ return new Locator({ xpath })
374
377
  }
375
378
 
376
379
  /**
@@ -378,8 +381,8 @@ class Locator {
378
381
  * @returns {Locator}
379
382
  */
380
383
  after(locator) {
381
- const xpath = sprintf('%s[preceding-sibling::%s]', this.toXPath(), convertToSubSelector(locator));
382
- return new Locator({ xpath });
384
+ const xpath = sprintf('%s[preceding-sibling::%s]', this.toXPath(), convertToSubSelector(locator))
385
+ return new Locator({ xpath })
383
386
  }
384
387
 
385
388
  /**
@@ -387,90 +390,95 @@ class Locator {
387
390
  * @returns {Locator}
388
391
  */
389
392
  before(locator) {
390
- const xpath = sprintf('%s[following-sibling::%s]', this.toXPath(), convertToSubSelector(locator));
391
- return new Locator({ xpath });
393
+ const xpath = sprintf('%s[following-sibling::%s]', this.toXPath(), convertToSubSelector(locator))
394
+ return new Locator({ xpath })
392
395
  }
393
396
  }
394
397
 
395
398
  /**
396
- * @param {CodeceptJS.LocatorOrString} locator
399
+ * @param {CodeceptJS.LocatorOrString} [locator]
397
400
  * @returns {Locator}
398
401
  */
399
- Locator.build = (locator) => {
400
- if (!locator) return new Locator({ xpath: '//*' });
401
- return new Locator(locator, 'css');
402
- };
402
+ Locator.build = locator => {
403
+ if (!locator) return new Locator({ xpath: '//*' })
404
+ return new Locator(locator, 'css')
405
+ }
403
406
 
404
407
  /**
405
408
  * Filters to modify locators
406
409
  * @type {Array<function(CodeceptJS.LocatorOrString, Locator): void>}
407
410
  */
408
- Locator.filters = [];
411
+ Locator.filters = []
409
412
 
410
413
  /**
411
414
  * Appends new `Locator` filter to an `Locator.filters` array, and returns the new length of the array.
412
415
  * @param {function(CodeceptJS.LocatorOrString, Locator): void} fn
413
416
  * @returns {number}
414
417
  */
415
- Locator.addFilter = fn => Locator.filters.push(fn);
418
+ Locator.addFilter = fn => Locator.filters.push(fn)
416
419
 
417
420
  Locator.clickable = {
418
421
  /**
419
422
  * @param {string} literal
420
423
  * @returns {string}
421
424
  */
422
- narrow: literal => xpathLocator.combine([
423
- `.//a[normalize-space(.)=${literal}]`,
424
- `.//button[normalize-space(.)=${literal}]`,
425
- `.//a/img[normalize-space(@alt)=${literal}]/ancestor::a`,
426
- `.//input[./@type = 'submit' or ./@type = 'image' or ./@type = 'button'][normalize-space(@value)=${literal}]`,
427
- ]),
425
+ narrow: literal =>
426
+ xpathLocator.combine([
427
+ `.//a[normalize-space(.)=${literal}]`,
428
+ `.//button[normalize-space(.)=${literal}]`,
429
+ `.//a/img[normalize-space(@alt)=${literal}]/ancestor::a`,
430
+ `.//input[./@type = 'submit' or ./@type = 'image' or ./@type = 'button'][normalize-space(@value)=${literal}]`,
431
+ ]),
428
432
 
429
433
  /**
430
434
  * @param {string} literal
431
435
  * @returns {string}
432
436
  */
433
- wide: literal => xpathLocator.combine([
434
- `.//a[./@href][((contains(normalize-space(string(.)), ${literal})) or .//img[contains(./@alt, ${literal})])]`,
435
- `.//input[./@type = 'submit' or ./@type = 'image' or ./@type = 'button'][contains(./@value, ${literal})]`,
436
- `.//input[./@type = 'image'][contains(./@alt, ${literal})]`,
437
- `.//button[contains(normalize-space(string(.)), ${literal})]`,
438
- `.//label[contains(normalize-space(string(.)), ${literal})]`,
439
- `.//input[./@type = 'submit' or ./@type = 'image' or ./@type = 'button'][./@name = ${literal}]`,
440
- `.//button[./@name = ${literal}]`,
441
- `.//*[@aria-label = ${literal}]`,
442
- `.//*[@title = ${literal}]`,
443
- `.//*[@aria-labelledby = //*[@id][normalize-space(string(.)) = ${literal}]/@id ]`,
444
- ]),
437
+ wide: literal =>
438
+ xpathLocator.combine([
439
+ `.//a[./@href][((contains(normalize-space(string(.)), ${literal})) or .//img[contains(./@alt, ${literal})])]`,
440
+ `.//input[./@type = 'submit' or ./@type = 'image' or ./@type = 'button'][contains(./@value, ${literal})]`,
441
+ `.//input[./@type = 'image'][contains(./@alt, ${literal})]`,
442
+ `.//button[contains(normalize-space(string(.)), ${literal})]`,
443
+ `.//label[contains(normalize-space(string(.)), ${literal})]`,
444
+ `.//input[./@type = 'submit' or ./@type = 'image' or ./@type = 'button'][./@name = ${literal}]`,
445
+ `.//button[./@name = ${literal}]`,
446
+ `.//*[@aria-label = ${literal}]`,
447
+ `.//*[@title = ${literal}]`,
448
+ `.//*[@aria-labelledby = //*[@id][normalize-space(string(.)) = ${literal}]/@id ]`,
449
+ `.//*[@role='button'][normalize-space(.)=${literal}]`,
450
+ ]),
445
451
 
446
452
  /**
447
453
  * @param {string} literal
448
454
  * @returns {string}
449
455
  */
450
456
  self: literal => `./self::*[contains(normalize-space(string(.)), ${literal}) or contains(normalize-space(@value), ${literal})]`,
451
- };
457
+ }
452
458
 
453
459
  Locator.field = {
454
460
  /**
455
461
  * @param {string} literal
456
462
  * @returns {string}
457
463
  */
458
- labelEquals: literal => xpathLocator.combine([
459
- `.//*[self::input | self::textarea | self::select][not(./@type = 'submit' or ./@type = 'image' or ./@type = 'hidden')][((./@name = ${literal}) or ./@id = //label[@for][normalize-space(string(.)) = ${literal}]/@for or ./@placeholder = ${literal})]`,
460
- `.//label[normalize-space(string(.)) = ${literal}]//.//*[self::input | self::textarea | self::select][not(./@type = 'submit' or ./@type = 'image' or ./@type = 'hidden')]`,
461
- ]),
464
+ labelEquals: literal =>
465
+ xpathLocator.combine([
466
+ `.//*[self::input | self::textarea | self::select][not(./@type = 'submit' or ./@type = 'image' or ./@type = 'hidden')][((./@name = ${literal}) or ./@id = //label[@for][normalize-space(string(.)) = ${literal}]/@for or ./@placeholder = ${literal})]`,
467
+ `.//label[normalize-space(string(.)) = ${literal}]//.//*[self::input | self::textarea | self::select][not(./@type = 'submit' or ./@type = 'image' or ./@type = 'hidden')]`,
468
+ ]),
462
469
 
463
470
  /**
464
471
  * @param {string} literal
465
472
  * @returns {string}
466
473
  */
467
- labelContains: literal => xpathLocator.combine([
468
- `.//*[self::input | self::textarea | self::select][not(./@type = 'submit' or ./@type = 'image' or ./@type = 'hidden')][(((./@name = ${literal}) or ./@id = //label[@for][contains(normalize-space(string(.)), ${literal})]/@for) or ./@placeholder = ${literal})]`,
469
- `.//label[contains(normalize-space(string(.)), ${literal})]//.//*[self::input | self::textarea | self::select][not(./@type = 'submit' or ./@type = 'image' or ./@type = 'hidden')]`,
470
- `.//*[@aria-label = ${literal}]`,
471
- `.//*[@title = ${literal}]`,
472
- `.//*[@aria-labelledby = //*[@id][normalize-space(string(.)) = ${literal}]/@id ]`,
473
- ]),
474
+ labelContains: literal =>
475
+ xpathLocator.combine([
476
+ `.//*[self::input | self::textarea | self::select][not(./@type = 'submit' or ./@type = 'image' or ./@type = 'hidden')][(((./@name = ${literal}) or ./@id = //label[@for][contains(normalize-space(string(.)), ${literal})]/@for) or ./@placeholder = ${literal})]`,
477
+ `.//label[contains(normalize-space(string(.)), ${literal})]//.//*[self::input | self::textarea | self::select][not(./@type = 'submit' or ./@type = 'image' or ./@type = 'hidden')]`,
478
+ `.//*[@aria-label = ${literal}]`,
479
+ `.//*[@title = ${literal}]`,
480
+ `.//*[@aria-labelledby = //*[@id][normalize-space(string(.)) = ${literal}]/@id ]`,
481
+ ]),
474
482
 
475
483
  /**
476
484
  * @param {string} literal
@@ -482,51 +490,52 @@ Locator.field = {
482
490
  * @param {string} literal
483
491
  * @returns {string}
484
492
  */
485
- byText: literal => xpathLocator.combine([
486
- `.//*[self::input | self::textarea | self::select][not(./@type = 'submit' or ./@type = 'image' or ./@type = 'hidden')][(((./@name = ${literal}) or ./@id = //label[@for][contains(normalize-space(string(.)), ${literal})]/@for) or ./@placeholder = ${literal})]`,
487
- `.//label[contains(normalize-space(string(.)), ${literal})]//.//*[self::input | self::textarea | self::select][not(./@type = 'submit' or ./@type = 'image' or ./@type = 'hidden')]`,
488
- ]),
489
-
490
- };
493
+ byText: literal =>
494
+ xpathLocator.combine([
495
+ `.//*[self::input | self::textarea | self::select][not(./@type = 'submit' or ./@type = 'image' or ./@type = 'hidden')][(((./@name = ${literal}) or ./@id = //label[@for][contains(normalize-space(string(.)), ${literal})]/@for) or ./@placeholder = ${literal})]`,
496
+ `.//label[contains(normalize-space(string(.)), ${literal})]//.//*[self::input | self::textarea | self::select][not(./@type = 'submit' or ./@type = 'image' or ./@type = 'hidden')]`,
497
+ ]),
498
+ }
491
499
 
492
500
  Locator.checkable = {
493
501
  /**
494
502
  * @param {string} literal
495
503
  * @returns {string}
496
504
  */
497
- byText: literal => xpathLocator.combine([
498
- `.//input[@type = 'checkbox' or @type = 'radio'][(@id = //label[@for][contains(normalize-space(string(.)), ${literal})]/@for) or @placeholder = ${literal}]`,
499
- `.//label[contains(normalize-space(string(.)), ${literal})]//input[@type = 'radio' or @type = 'checkbox']`,
500
- ]),
505
+ byText: literal =>
506
+ xpathLocator.combine([
507
+ `.//input[@type = 'checkbox' or @type = 'radio'][(@id = //label[@for][contains(normalize-space(string(.)), ${literal})]/@for) or @placeholder = ${literal}]`,
508
+ `.//label[contains(normalize-space(string(.)), ${literal})]//input[@type = 'radio' or @type = 'checkbox']`,
509
+ ]),
501
510
 
502
511
  /**
503
512
  * @param {string} literal
504
513
  * @returns {string}
505
514
  */
506
515
  byName: literal => `.//input[@type = 'checkbox' or @type = 'radio'][@name = ${literal}]`,
507
- };
516
+ }
508
517
 
509
518
  Locator.select = {
510
519
  /**
511
520
  * @param {string} opt
512
521
  * @returns {string}
513
522
  */
514
- byVisibleText: (opt) => {
515
- const normalized = `[normalize-space(.) = ${opt.trim()}]`;
516
- return `./option${normalized}|./optgroup/option${normalized}`;
523
+ byVisibleText: opt => {
524
+ const normalized = `[normalize-space(.) = ${opt.trim()}]`
525
+ return `./option${normalized}|./optgroup/option${normalized}`
517
526
  },
518
527
 
519
528
  /**
520
529
  * @param {string} opt
521
530
  * @returns {string}
522
531
  */
523
- byValue: (opt) => {
524
- const normalized = `[normalize-space(@value) = ${opt.trim()}]`;
525
- return `./option${normalized}|./optgroup/option${normalized}`;
532
+ byValue: opt => {
533
+ const normalized = `[normalize-space(@value) = ${opt.trim()}]`
534
+ return `./option${normalized}|./optgroup/option${normalized}`
526
535
  },
527
- };
536
+ }
528
537
 
529
- export default Locator;
538
+ export default Locator
530
539
 
531
540
  /**
532
541
  * @private
@@ -536,7 +545,7 @@ export default Locator;
536
545
  * @returns {boolean}
537
546
  */
538
547
  function isCSS(locator) {
539
- return locator[0] === '#' || locator[0] === '.' || locator[0] === '[';
548
+ return locator[0] === '#' || locator[0] === '.' || locator[0] === '['
540
549
  }
541
550
 
542
551
  /**
@@ -547,8 +556,8 @@ function isCSS(locator) {
547
556
  * @returns {boolean}
548
557
  */
549
558
  function isXPath(locator) {
550
- const trimmed = locator.replace(/^\(+/, '').substr(0, 2);
551
- return trimmed === '//' || trimmed === './';
559
+ const trimmed = locator.replace(/^\(+/, '').substr(0, 2)
560
+ return trimmed === '//' || trimmed === './'
552
561
  }
553
562
 
554
563
  /**
@@ -565,8 +574,8 @@ function isXPath(locator) {
565
574
  * @returns {boolean}
566
575
  */
567
576
  function isShadow(locator) {
568
- const hasShadowProperty = (locator.shadow !== undefined) && (Object.keys(locator).length === 1);
569
- return hasShadowProperty;
577
+ const hasShadowProperty = locator.shadow !== undefined && Object.keys(locator).length === 1
578
+ return hasShadowProperty
570
579
  }
571
580
 
572
581
  /**
@@ -576,7 +585,7 @@ function isShadow(locator) {
576
585
  * @returns {boolean}
577
586
  */
578
587
  function isXPathStartingWithRoundBrackets(xpath) {
579
- return isXPath(xpath) && xpath[0] === '(';
588
+ return isXPath(xpath) && xpath[0] === '('
580
589
  }
581
590
 
582
591
  /**
@@ -586,8 +595,7 @@ function isXPathStartingWithRoundBrackets(xpath) {
586
595
  * @returns {string}
587
596
  */
588
597
  function removePrefix(xpath) {
589
- return xpath
590
- .replace(/^(\.|\/)+/, '');
598
+ return xpath.replace(/^(\.|\/)+/, '')
591
599
  }
592
600
 
593
601
  /**
@@ -597,7 +605,17 @@ function removePrefix(xpath) {
597
605
  * @returns {boolean}
598
606
  */
599
607
  function isPlaywrightLocator(locator) {
600
- return locator.includes('_react') || locator.includes('_vue');
608
+ return locator.includes('_react') || locator.includes('_vue')
609
+ }
610
+
611
+ /**
612
+ * @private
613
+ * check if the locator is a role locator
614
+ * @param {{role: string}} locator
615
+ * @returns {boolean}
616
+ */
617
+ function isRoleLocator(locator) {
618
+ return locator.role !== undefined && typeof locator.role === 'string' && Object.keys(locator).length >= 1
601
619
  }
602
620
 
603
621
  /**
@@ -606,10 +624,9 @@ function isPlaywrightLocator(locator) {
606
624
  * @returns {string}
607
625
  */
608
626
  function convertToSubSelector(locator) {
609
- const xpath = (new Locator(locator, 'css')).toXPath();
627
+ const xpath = new Locator(locator, 'css').toXPath()
610
628
  if (isXPathStartingWithRoundBrackets(xpath)) {
611
- throw new Error('XPath with round brackets is not possible here! '
612
- + 'May be a nested locator with at() last() or first() causes this error.');
629
+ throw new Error('XPath with round brackets is not possible here! ' + 'May be a nested locator with at() last() or first() causes this error.')
613
630
  }
614
- return removePrefix(xpath);
631
+ return removePrefix(xpath)
615
632
  }