codeceptjs 4.0.0-beta.3 → 4.0.0-beta.5
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/README.md +134 -119
- package/bin/codecept.js +12 -2
- package/bin/test-server.js +53 -0
- package/docs/webapi/clearCookie.mustache +1 -1
- package/lib/actor.js +66 -102
- package/lib/ai.js +130 -121
- package/lib/assert/empty.js +3 -5
- package/lib/assert/equal.js +4 -7
- package/lib/assert/include.js +4 -6
- package/lib/assert/throws.js +2 -4
- package/lib/assert/truth.js +2 -2
- package/lib/codecept.js +141 -86
- package/lib/command/check.js +201 -0
- package/lib/command/configMigrate.js +2 -4
- package/lib/command/definitions.js +8 -26
- package/lib/command/dryRun.js +30 -35
- package/lib/command/generate.js +10 -14
- package/lib/command/gherkin/snippets.js +75 -73
- package/lib/command/gherkin/steps.js +1 -1
- package/lib/command/info.js +42 -8
- package/lib/command/init.js +13 -12
- package/lib/command/interactive.js +10 -2
- package/lib/command/list.js +1 -1
- package/lib/command/run-multiple/chunk.js +48 -45
- package/lib/command/run-multiple.js +12 -35
- package/lib/command/run-workers.js +21 -58
- package/lib/command/utils.js +5 -6
- package/lib/command/workers/runTests.js +263 -222
- package/lib/container.js +386 -238
- package/lib/data/context.js +10 -13
- package/lib/data/dataScenarioConfig.js +8 -8
- package/lib/data/dataTableArgument.js +6 -6
- package/lib/data/table.js +5 -11
- package/lib/effects.js +223 -0
- package/lib/element/WebElement.js +327 -0
- package/lib/els.js +158 -0
- package/lib/event.js +21 -17
- package/lib/heal.js +88 -80
- package/lib/helper/AI.js +2 -1
- package/lib/helper/ApiDataFactory.js +4 -7
- package/lib/helper/Appium.js +50 -57
- package/lib/helper/FileSystem.js +3 -3
- package/lib/helper/GraphQLDataFactory.js +4 -4
- package/lib/helper/JSONResponse.js +75 -37
- package/lib/helper/Mochawesome.js +31 -9
- package/lib/helper/Nightmare.js +37 -58
- package/lib/helper/Playwright.js +267 -272
- package/lib/helper/Protractor.js +56 -87
- package/lib/helper/Puppeteer.js +247 -264
- package/lib/helper/REST.js +29 -17
- package/lib/helper/TestCafe.js +22 -47
- package/lib/helper/WebDriver.js +157 -368
- package/lib/helper/extras/PlaywrightPropEngine.js +2 -2
- package/lib/helper/extras/Popup.js +22 -22
- package/lib/helper/network/utils.js +1 -1
- package/lib/helper/testcafe/testcafe-utils.js +27 -28
- package/lib/listener/emptyRun.js +55 -0
- package/lib/listener/exit.js +7 -10
- package/lib/listener/{retry.js → globalRetry.js} +5 -5
- package/lib/listener/globalTimeout.js +165 -0
- package/lib/listener/helpers.js +15 -15
- package/lib/listener/mocha.js +1 -1
- package/lib/listener/result.js +12 -0
- package/lib/listener/retryEnhancer.js +85 -0
- package/lib/listener/steps.js +32 -18
- package/lib/listener/store.js +20 -0
- package/lib/locator.js +1 -1
- package/lib/mocha/asyncWrapper.js +231 -0
- package/lib/{interfaces → mocha}/bdd.js +3 -3
- package/lib/mocha/cli.js +308 -0
- package/lib/mocha/factory.js +104 -0
- package/lib/{interfaces → mocha}/featureConfig.js +32 -12
- package/lib/{interfaces → mocha}/gherkin.js +26 -28
- package/lib/mocha/hooks.js +112 -0
- package/lib/mocha/index.js +12 -0
- package/lib/mocha/inject.js +29 -0
- package/lib/{interfaces → mocha}/scenarioConfig.js +31 -7
- package/lib/mocha/suite.js +82 -0
- package/lib/mocha/test.js +181 -0
- package/lib/mocha/types.d.ts +42 -0
- package/lib/mocha/ui.js +232 -0
- package/lib/output.js +93 -65
- package/lib/pause.js +160 -138
- package/lib/plugin/analyze.js +396 -0
- package/lib/plugin/auth.js +435 -0
- package/lib/plugin/autoDelay.js +8 -8
- package/lib/plugin/autoLogin.js +3 -338
- package/lib/plugin/commentStep.js +6 -1
- package/lib/plugin/coverage.js +10 -22
- package/lib/plugin/customLocator.js +3 -3
- package/lib/plugin/customReporter.js +52 -0
- package/lib/plugin/eachElement.js +1 -1
- package/lib/plugin/fakerTransform.js +1 -1
- package/lib/plugin/heal.js +36 -9
- package/lib/plugin/htmlReporter.js +1947 -0
- package/lib/plugin/pageInfo.js +140 -0
- package/lib/plugin/retryFailedStep.js +17 -18
- package/lib/plugin/retryTo.js +2 -113
- package/lib/plugin/screenshotOnFail.js +17 -58
- package/lib/plugin/selenoid.js +15 -35
- package/lib/plugin/standardActingHelpers.js +4 -1
- package/lib/plugin/stepByStepReport.js +56 -17
- package/lib/plugin/stepTimeout.js +5 -12
- package/lib/plugin/subtitles.js +4 -4
- package/lib/plugin/tryTo.js +3 -102
- package/lib/plugin/wdio.js +8 -10
- package/lib/recorder.js +155 -124
- package/lib/rerun.js +43 -42
- package/lib/result.js +161 -0
- package/lib/secret.js +1 -2
- package/lib/step/base.js +239 -0
- package/lib/step/comment.js +10 -0
- package/lib/step/config.js +50 -0
- package/lib/step/func.js +46 -0
- package/lib/step/helper.js +50 -0
- package/lib/step/meta.js +99 -0
- package/lib/step/record.js +74 -0
- package/lib/step/retry.js +11 -0
- package/lib/step/section.js +55 -0
- package/lib/step.js +21 -332
- package/lib/steps.js +50 -0
- package/lib/store.js +37 -5
- package/lib/template/heal.js +2 -11
- package/lib/test-server.js +323 -0
- package/lib/timeout.js +66 -0
- package/lib/utils.js +351 -218
- package/lib/within.js +75 -55
- package/lib/workerStorage.js +2 -1
- package/lib/workers.js +386 -277
- package/package.json +81 -75
- package/translations/de-DE.js +5 -3
- package/translations/fr-FR.js +5 -4
- package/translations/index.js +1 -0
- package/translations/it-IT.js +4 -3
- package/translations/ja-JP.js +4 -3
- package/translations/nl-NL.js +76 -0
- package/translations/pl-PL.js +4 -3
- package/translations/pt-BR.js +4 -3
- package/translations/ru-RU.js +4 -3
- package/translations/utils.js +9 -0
- package/translations/zh-CN.js +4 -3
- package/translations/zh-TW.js +4 -3
- package/typings/index.d.ts +197 -187
- package/typings/promiseBasedTypes.d.ts +53 -903
- package/typings/types.d.ts +372 -1042
- package/lib/cli.js +0 -257
- package/lib/helper/ExpectHelper.js +0 -391
- package/lib/helper/MockServer.js +0 -221
- package/lib/helper/SoftExpectHelper.js +0 -381
- package/lib/listener/artifacts.js +0 -19
- package/lib/listener/timeout.js +0 -109
- package/lib/mochaFactory.js +0 -113
- package/lib/plugin/debugErrors.js +0 -67
- package/lib/scenario.js +0 -224
- package/lib/ui.js +0 -236
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
const assert = require('assert')
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Unified WebElement class that wraps native element instances from different helpers
|
|
5
|
+
* and provides a consistent API across all supported helpers (Playwright, WebDriver, Puppeteer).
|
|
6
|
+
*/
|
|
7
|
+
class WebElement {
|
|
8
|
+
constructor(element, helper) {
|
|
9
|
+
this.element = element
|
|
10
|
+
this.helper = helper
|
|
11
|
+
this.helperType = this._detectHelperType(helper)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
_detectHelperType(helper) {
|
|
15
|
+
if (!helper) return 'unknown'
|
|
16
|
+
|
|
17
|
+
const className = helper.constructor.name
|
|
18
|
+
if (className === 'Playwright') return 'playwright'
|
|
19
|
+
if (className === 'WebDriver') return 'webdriver'
|
|
20
|
+
if (className === 'Puppeteer') return 'puppeteer'
|
|
21
|
+
|
|
22
|
+
return 'unknown'
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Get the native element instance
|
|
27
|
+
* @returns {ElementHandle|WebElement|ElementHandle} Native element
|
|
28
|
+
*/
|
|
29
|
+
getNativeElement() {
|
|
30
|
+
return this.element
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Get the helper instance
|
|
35
|
+
* @returns {Helper} Helper instance
|
|
36
|
+
*/
|
|
37
|
+
getHelper() {
|
|
38
|
+
return this.helper
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Get text content of the element
|
|
43
|
+
* @returns {Promise<string>} Element text content
|
|
44
|
+
*/
|
|
45
|
+
async getText() {
|
|
46
|
+
switch (this.helperType) {
|
|
47
|
+
case 'playwright':
|
|
48
|
+
return this.element.textContent()
|
|
49
|
+
case 'webdriver':
|
|
50
|
+
return this.element.getText()
|
|
51
|
+
case 'puppeteer':
|
|
52
|
+
return this.element.evaluate(el => el.textContent)
|
|
53
|
+
default:
|
|
54
|
+
throw new Error(`Unsupported helper type: ${this.helperType}`)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Get attribute value of the element
|
|
60
|
+
* @param {string} name Attribute name
|
|
61
|
+
* @returns {Promise<string|null>} Attribute value
|
|
62
|
+
*/
|
|
63
|
+
async getAttribute(name) {
|
|
64
|
+
switch (this.helperType) {
|
|
65
|
+
case 'playwright':
|
|
66
|
+
return this.element.getAttribute(name)
|
|
67
|
+
case 'webdriver':
|
|
68
|
+
return this.element.getAttribute(name)
|
|
69
|
+
case 'puppeteer':
|
|
70
|
+
return this.element.evaluate((el, attrName) => el.getAttribute(attrName), name)
|
|
71
|
+
default:
|
|
72
|
+
throw new Error(`Unsupported helper type: ${this.helperType}`)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Get property value of the element
|
|
78
|
+
* @param {string} name Property name
|
|
79
|
+
* @returns {Promise<any>} Property value
|
|
80
|
+
*/
|
|
81
|
+
async getProperty(name) {
|
|
82
|
+
switch (this.helperType) {
|
|
83
|
+
case 'playwright':
|
|
84
|
+
return this.element.evaluate((el, propName) => el[propName], name)
|
|
85
|
+
case 'webdriver':
|
|
86
|
+
return this.element.getProperty(name)
|
|
87
|
+
case 'puppeteer':
|
|
88
|
+
return this.element.evaluate((el, propName) => el[propName], name)
|
|
89
|
+
default:
|
|
90
|
+
throw new Error(`Unsupported helper type: ${this.helperType}`)
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Get innerHTML of the element
|
|
96
|
+
* @returns {Promise<string>} Element innerHTML
|
|
97
|
+
*/
|
|
98
|
+
async getInnerHTML() {
|
|
99
|
+
switch (this.helperType) {
|
|
100
|
+
case 'playwright':
|
|
101
|
+
return this.element.innerHTML()
|
|
102
|
+
case 'webdriver':
|
|
103
|
+
return this.element.getProperty('innerHTML')
|
|
104
|
+
case 'puppeteer':
|
|
105
|
+
return this.element.evaluate(el => el.innerHTML)
|
|
106
|
+
default:
|
|
107
|
+
throw new Error(`Unsupported helper type: ${this.helperType}`)
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Get value of the element (for input elements)
|
|
113
|
+
* @returns {Promise<string>} Element value
|
|
114
|
+
*/
|
|
115
|
+
async getValue() {
|
|
116
|
+
switch (this.helperType) {
|
|
117
|
+
case 'playwright':
|
|
118
|
+
return this.element.inputValue()
|
|
119
|
+
case 'webdriver':
|
|
120
|
+
return this.element.getValue()
|
|
121
|
+
case 'puppeteer':
|
|
122
|
+
return this.element.evaluate(el => el.value)
|
|
123
|
+
default:
|
|
124
|
+
throw new Error(`Unsupported helper type: ${this.helperType}`)
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Check if element is visible
|
|
130
|
+
* @returns {Promise<boolean>} True if element is visible
|
|
131
|
+
*/
|
|
132
|
+
async isVisible() {
|
|
133
|
+
switch (this.helperType) {
|
|
134
|
+
case 'playwright':
|
|
135
|
+
return this.element.isVisible()
|
|
136
|
+
case 'webdriver':
|
|
137
|
+
return this.element.isDisplayed()
|
|
138
|
+
case 'puppeteer':
|
|
139
|
+
return this.element.evaluate(el => {
|
|
140
|
+
const style = window.getComputedStyle(el)
|
|
141
|
+
return style.display !== 'none' && style.visibility !== 'hidden' && style.opacity !== '0'
|
|
142
|
+
})
|
|
143
|
+
default:
|
|
144
|
+
throw new Error(`Unsupported helper type: ${this.helperType}`)
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Check if element is enabled
|
|
150
|
+
* @returns {Promise<boolean>} True if element is enabled
|
|
151
|
+
*/
|
|
152
|
+
async isEnabled() {
|
|
153
|
+
switch (this.helperType) {
|
|
154
|
+
case 'playwright':
|
|
155
|
+
return this.element.isEnabled()
|
|
156
|
+
case 'webdriver':
|
|
157
|
+
return this.element.isEnabled()
|
|
158
|
+
case 'puppeteer':
|
|
159
|
+
return this.element.evaluate(el => !el.disabled)
|
|
160
|
+
default:
|
|
161
|
+
throw new Error(`Unsupported helper type: ${this.helperType}`)
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Check if element exists in DOM
|
|
167
|
+
* @returns {Promise<boolean>} True if element exists
|
|
168
|
+
*/
|
|
169
|
+
async exists() {
|
|
170
|
+
try {
|
|
171
|
+
switch (this.helperType) {
|
|
172
|
+
case 'playwright':
|
|
173
|
+
// For Playwright, if we have the element, it exists
|
|
174
|
+
return await this.element.evaluate(el => !!el)
|
|
175
|
+
case 'webdriver':
|
|
176
|
+
// For WebDriver, if we have the element, it exists
|
|
177
|
+
return true
|
|
178
|
+
case 'puppeteer':
|
|
179
|
+
// For Puppeteer, if we have the element, it exists
|
|
180
|
+
return await this.element.evaluate(el => !!el)
|
|
181
|
+
default:
|
|
182
|
+
throw new Error(`Unsupported helper type: ${this.helperType}`)
|
|
183
|
+
}
|
|
184
|
+
} catch (e) {
|
|
185
|
+
return false
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Get bounding box of the element
|
|
191
|
+
* @returns {Promise<Object>} Bounding box with x, y, width, height properties
|
|
192
|
+
*/
|
|
193
|
+
async getBoundingBox() {
|
|
194
|
+
switch (this.helperType) {
|
|
195
|
+
case 'playwright':
|
|
196
|
+
return this.element.boundingBox()
|
|
197
|
+
case 'webdriver':
|
|
198
|
+
const rect = await this.element.getRect()
|
|
199
|
+
return {
|
|
200
|
+
x: rect.x,
|
|
201
|
+
y: rect.y,
|
|
202
|
+
width: rect.width,
|
|
203
|
+
height: rect.height,
|
|
204
|
+
}
|
|
205
|
+
case 'puppeteer':
|
|
206
|
+
return this.element.boundingBox()
|
|
207
|
+
default:
|
|
208
|
+
throw new Error(`Unsupported helper type: ${this.helperType}`)
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Click the element
|
|
214
|
+
* @param {Object} options Click options
|
|
215
|
+
* @returns {Promise<void>}
|
|
216
|
+
*/
|
|
217
|
+
async click(options = {}) {
|
|
218
|
+
switch (this.helperType) {
|
|
219
|
+
case 'playwright':
|
|
220
|
+
return this.element.click(options)
|
|
221
|
+
case 'webdriver':
|
|
222
|
+
return this.element.click()
|
|
223
|
+
case 'puppeteer':
|
|
224
|
+
return this.element.click(options)
|
|
225
|
+
default:
|
|
226
|
+
throw new Error(`Unsupported helper type: ${this.helperType}`)
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Type text into the element
|
|
232
|
+
* @param {string} text Text to type
|
|
233
|
+
* @param {Object} options Type options
|
|
234
|
+
* @returns {Promise<void>}
|
|
235
|
+
*/
|
|
236
|
+
async type(text, options = {}) {
|
|
237
|
+
switch (this.helperType) {
|
|
238
|
+
case 'playwright':
|
|
239
|
+
return this.element.type(text, options)
|
|
240
|
+
case 'webdriver':
|
|
241
|
+
return this.element.setValue(text)
|
|
242
|
+
case 'puppeteer':
|
|
243
|
+
return this.element.type(text, options)
|
|
244
|
+
default:
|
|
245
|
+
throw new Error(`Unsupported helper type: ${this.helperType}`)
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Find first child element matching the locator
|
|
251
|
+
* @param {string|Object} locator Element locator
|
|
252
|
+
* @returns {Promise<WebElement|null>} WebElement instance or null if not found
|
|
253
|
+
*/
|
|
254
|
+
async $(locator) {
|
|
255
|
+
let childElement
|
|
256
|
+
|
|
257
|
+
switch (this.helperType) {
|
|
258
|
+
case 'playwright':
|
|
259
|
+
childElement = await this.element.$(this._normalizeLocator(locator))
|
|
260
|
+
break
|
|
261
|
+
case 'webdriver':
|
|
262
|
+
try {
|
|
263
|
+
childElement = await this.element.$(this._normalizeLocator(locator))
|
|
264
|
+
} catch (e) {
|
|
265
|
+
return null
|
|
266
|
+
}
|
|
267
|
+
break
|
|
268
|
+
case 'puppeteer':
|
|
269
|
+
childElement = await this.element.$(this._normalizeLocator(locator))
|
|
270
|
+
break
|
|
271
|
+
default:
|
|
272
|
+
throw new Error(`Unsupported helper type: ${this.helperType}`)
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return childElement ? new WebElement(childElement, this.helper) : null
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Find all child elements matching the locator
|
|
280
|
+
* @param {string|Object} locator Element locator
|
|
281
|
+
* @returns {Promise<WebElement[]>} Array of WebElement instances
|
|
282
|
+
*/
|
|
283
|
+
async $$(locator) {
|
|
284
|
+
let childElements
|
|
285
|
+
|
|
286
|
+
switch (this.helperType) {
|
|
287
|
+
case 'playwright':
|
|
288
|
+
childElements = await this.element.$$(this._normalizeLocator(locator))
|
|
289
|
+
break
|
|
290
|
+
case 'webdriver':
|
|
291
|
+
childElements = await this.element.$$(this._normalizeLocator(locator))
|
|
292
|
+
break
|
|
293
|
+
case 'puppeteer':
|
|
294
|
+
childElements = await this.element.$$(this._normalizeLocator(locator))
|
|
295
|
+
break
|
|
296
|
+
default:
|
|
297
|
+
throw new Error(`Unsupported helper type: ${this.helperType}`)
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
return childElements.map(el => new WebElement(el, this.helper))
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Normalize locator for element search
|
|
305
|
+
* @param {string|Object} locator Locator to normalize
|
|
306
|
+
* @returns {string} Normalized CSS selector
|
|
307
|
+
* @private
|
|
308
|
+
*/
|
|
309
|
+
_normalizeLocator(locator) {
|
|
310
|
+
if (typeof locator === 'string') {
|
|
311
|
+
return locator
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (typeof locator === 'object') {
|
|
315
|
+
// Handle CodeceptJS locator objects
|
|
316
|
+
if (locator.css) return locator.css
|
|
317
|
+
if (locator.xpath) return locator.xpath
|
|
318
|
+
if (locator.id) return `#${locator.id}`
|
|
319
|
+
if (locator.name) return `[name="${locator.name}"]`
|
|
320
|
+
if (locator.className) return `.${locator.className}`
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
return locator.toString()
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
module.exports = WebElement
|
package/lib/els.js
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
const output = require('./output')
|
|
2
|
+
const store = require('./store')
|
|
3
|
+
const container = require('./container')
|
|
4
|
+
const StepConfig = require('./step/config')
|
|
5
|
+
const recordStep = require('./step/record')
|
|
6
|
+
const FuncStep = require('./step/func')
|
|
7
|
+
const { truth } = require('./assert/truth')
|
|
8
|
+
const { isAsyncFunction, humanizeFunction } = require('./utils')
|
|
9
|
+
|
|
10
|
+
function element(purpose, locator, fn) {
|
|
11
|
+
let stepConfig
|
|
12
|
+
if (arguments[arguments.length - 1] instanceof StepConfig) {
|
|
13
|
+
stepConfig = arguments[arguments.length - 1]
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (!fn || fn === stepConfig) {
|
|
17
|
+
fn = locator
|
|
18
|
+
locator = purpose
|
|
19
|
+
purpose = 'first element'
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const step = prepareStep(purpose, locator, fn)
|
|
23
|
+
if (!step) return
|
|
24
|
+
|
|
25
|
+
return executeStep(
|
|
26
|
+
step,
|
|
27
|
+
async () => {
|
|
28
|
+
const els = await step.helper._locate(locator)
|
|
29
|
+
output.debug(`Found ${els.length} elements, using first element`)
|
|
30
|
+
|
|
31
|
+
return fn(els[0])
|
|
32
|
+
},
|
|
33
|
+
stepConfig,
|
|
34
|
+
)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function eachElement(purpose, locator, fn) {
|
|
38
|
+
if (!fn) {
|
|
39
|
+
fn = locator
|
|
40
|
+
locator = purpose
|
|
41
|
+
purpose = 'for each element'
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const step = prepareStep(purpose, locator, fn)
|
|
45
|
+
if (!step) return
|
|
46
|
+
|
|
47
|
+
return executeStep(step, async () => {
|
|
48
|
+
const els = await step.helper._locate(locator)
|
|
49
|
+
output.debug(`Found ${els.length} elements for each elements to iterate`)
|
|
50
|
+
|
|
51
|
+
const errs = []
|
|
52
|
+
let i = 0
|
|
53
|
+
for (const el of els) {
|
|
54
|
+
try {
|
|
55
|
+
await fn(el, i)
|
|
56
|
+
} catch (err) {
|
|
57
|
+
output.error(`eachElement: failed operation on element #${i} ${el}`)
|
|
58
|
+
errs.push(err)
|
|
59
|
+
}
|
|
60
|
+
i++
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (errs.length) {
|
|
64
|
+
throw errs[0]
|
|
65
|
+
}
|
|
66
|
+
})
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function expectElement(locator, fn) {
|
|
70
|
+
const step = prepareStep('expect element to be', locator, fn)
|
|
71
|
+
if (!step) return
|
|
72
|
+
|
|
73
|
+
return executeStep(step, async () => {
|
|
74
|
+
const els = await step.helper._locate(locator)
|
|
75
|
+
output.debug(`Found ${els.length} elements, first will be used for assertion`)
|
|
76
|
+
|
|
77
|
+
const result = await fn(els[0])
|
|
78
|
+
const assertion = truth(`element (${locator})`, fn.toString())
|
|
79
|
+
assertion.assert(result)
|
|
80
|
+
})
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function expectAnyElement(locator, fn) {
|
|
84
|
+
const step = prepareStep('expect any element to be', locator, fn)
|
|
85
|
+
if (!step) return
|
|
86
|
+
|
|
87
|
+
return executeStep(step, async () => {
|
|
88
|
+
const els = await step.helper._locate(locator)
|
|
89
|
+
output.debug(`Found ${els.length} elements, at least one should pass the assertion`)
|
|
90
|
+
|
|
91
|
+
const assertion = truth(`any element of (${locator})`, fn.toString())
|
|
92
|
+
|
|
93
|
+
let found = false
|
|
94
|
+
for (const el of els) {
|
|
95
|
+
const result = await fn(el)
|
|
96
|
+
if (result) {
|
|
97
|
+
found = true
|
|
98
|
+
break
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (!found) throw assertion.getException()
|
|
102
|
+
})
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function expectAllElements(locator, fn) {
|
|
106
|
+
const step = prepareStep('expect all elements', locator, fn)
|
|
107
|
+
if (!step) return
|
|
108
|
+
|
|
109
|
+
return executeStep(step, async () => {
|
|
110
|
+
const els = await step.helper._locate(locator)
|
|
111
|
+
output.debug(`Found ${els.length} elements, all should pass the assertion`)
|
|
112
|
+
|
|
113
|
+
let i = 1
|
|
114
|
+
for (const el of els) {
|
|
115
|
+
output.debug(`checking element #${i}: ${el}`)
|
|
116
|
+
const result = await fn(el)
|
|
117
|
+
const assertion = truth(`element #${i} of (${locator})`, humanizeFunction(fn))
|
|
118
|
+
assertion.assert(result)
|
|
119
|
+
i++
|
|
120
|
+
}
|
|
121
|
+
})
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
module.exports = {
|
|
125
|
+
element,
|
|
126
|
+
eachElement,
|
|
127
|
+
expectElement,
|
|
128
|
+
expectAnyElement,
|
|
129
|
+
expectAllElements,
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function prepareStep(purpose, locator, fn) {
|
|
133
|
+
if (store.dryRun) return
|
|
134
|
+
const helpers = Object.values(container.helpers())
|
|
135
|
+
|
|
136
|
+
const helper = helpers.filter(h => !!h._locate)[0]
|
|
137
|
+
|
|
138
|
+
if (!helper) {
|
|
139
|
+
throw new Error('No helper enabled with _locate method with returns a list of elements.')
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (!isAsyncFunction(fn)) {
|
|
143
|
+
throw new Error('Async function should be passed into each element')
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const isAssertion = purpose.startsWith('expect')
|
|
147
|
+
|
|
148
|
+
const step = new FuncStep(`${purpose} within "${locator}" ${isAssertion ? 'to be' : 'to'}`)
|
|
149
|
+
step.setHelper(helper)
|
|
150
|
+
step.setArguments([humanizeFunction(fn)]) // user defined function is a passed argument
|
|
151
|
+
|
|
152
|
+
return step
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
async function executeStep(step, action, stepConfig = {}) {
|
|
156
|
+
step.setCallable(action)
|
|
157
|
+
return recordStep(step, [stepConfig])
|
|
158
|
+
}
|
package/lib/event.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
const debug = require('debug')('codeceptjs:event')
|
|
2
|
-
const events = require('events')
|
|
3
|
-
const { error } = require('./output')
|
|
1
|
+
const debug = require('debug')('codeceptjs:event')
|
|
2
|
+
const events = require('events')
|
|
3
|
+
const { error } = require('./output')
|
|
4
4
|
|
|
5
|
-
const dispatcher = new events.EventEmitter()
|
|
5
|
+
const dispatcher = new events.EventEmitter()
|
|
6
6
|
|
|
7
|
-
dispatcher.setMaxListeners(50)
|
|
7
|
+
dispatcher.setMaxListeners(50)
|
|
8
8
|
/**
|
|
9
9
|
* @namespace
|
|
10
10
|
* @alias event
|
|
@@ -54,12 +54,16 @@ module.exports = {
|
|
|
54
54
|
* @inner
|
|
55
55
|
* @property {'hook.start'} started
|
|
56
56
|
* @property {'hook.passed'} passed
|
|
57
|
+
* @property {'hook.failed'} failed
|
|
58
|
+
* @property {'hook.finished'} finished
|
|
57
59
|
*/
|
|
58
60
|
hook: {
|
|
59
61
|
started: 'hook.start',
|
|
60
62
|
passed: 'hook.passed',
|
|
61
63
|
failed: 'hook.failed',
|
|
64
|
+
finished: 'hook.finished',
|
|
62
65
|
},
|
|
66
|
+
|
|
63
67
|
/**
|
|
64
68
|
* @type {object}
|
|
65
69
|
* @constant
|
|
@@ -140,33 +144,33 @@ module.exports = {
|
|
|
140
144
|
* @param {*} [param]
|
|
141
145
|
*/
|
|
142
146
|
emit(event, param) {
|
|
143
|
-
let msg = `Emitted | ${event}
|
|
147
|
+
let msg = `Emitted | ${event}`
|
|
144
148
|
if (param && param.toString()) {
|
|
145
|
-
msg += ` (${param.toString()})
|
|
149
|
+
msg += ` (${param.toString()})`
|
|
146
150
|
}
|
|
147
|
-
debug(msg)
|
|
151
|
+
debug(msg)
|
|
148
152
|
try {
|
|
149
|
-
this.dispatcher.emit.apply(this.dispatcher, arguments)
|
|
153
|
+
this.dispatcher.emit.apply(this.dispatcher, arguments)
|
|
150
154
|
} catch (err) {
|
|
151
|
-
error(`Error processing ${event} event:`)
|
|
152
|
-
error(err.stack)
|
|
155
|
+
error(`Error processing ${event} event:`)
|
|
156
|
+
error(err.stack)
|
|
153
157
|
}
|
|
154
158
|
},
|
|
155
159
|
|
|
156
160
|
/** for testing only! */
|
|
157
161
|
cleanDispatcher: () => {
|
|
158
|
-
let event
|
|
162
|
+
let event
|
|
159
163
|
for (event in this.test) {
|
|
160
|
-
this.dispatcher.removeAllListeners(this.test[event])
|
|
164
|
+
this.dispatcher.removeAllListeners(this.test[event])
|
|
161
165
|
}
|
|
162
166
|
for (event in this.suite) {
|
|
163
|
-
this.dispatcher.removeAllListeners(this.test[event])
|
|
167
|
+
this.dispatcher.removeAllListeners(this.test[event])
|
|
164
168
|
}
|
|
165
169
|
for (event in this.step) {
|
|
166
|
-
this.dispatcher.removeAllListeners(this.test[event])
|
|
170
|
+
this.dispatcher.removeAllListeners(this.test[event])
|
|
167
171
|
}
|
|
168
172
|
for (event in this.all) {
|
|
169
|
-
this.dispatcher.removeAllListeners(this.test[event])
|
|
173
|
+
this.dispatcher.removeAllListeners(this.test[event])
|
|
170
174
|
}
|
|
171
175
|
},
|
|
172
|
-
}
|
|
176
|
+
}
|