codeceptjs 3.7.0-beta.3 → 3.7.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.
Files changed (45) hide show
  1. package/README.md +9 -10
  2. package/bin/codecept.js +7 -0
  3. package/lib/actor.js +47 -92
  4. package/lib/command/check.js +173 -0
  5. package/lib/command/definitions.js +2 -0
  6. package/lib/command/run-workers.js +1 -1
  7. package/lib/command/workers/runTests.js +112 -109
  8. package/lib/container.js +9 -0
  9. package/lib/effects.js +123 -0
  10. package/lib/heal.js +10 -0
  11. package/lib/helper/Appium.js +27 -16
  12. package/lib/helper/Playwright.js +15 -0
  13. package/lib/helper/Puppeteer.js +5 -0
  14. package/lib/helper/WebDriver.js +9 -1
  15. package/lib/listener/emptyRun.js +2 -5
  16. package/lib/listener/globalTimeout.js +41 -11
  17. package/lib/listener/steps.js +3 -0
  18. package/lib/mocha/cli.js +22 -5
  19. package/lib/mocha/featureConfig.js +13 -0
  20. package/lib/mocha/scenarioConfig.js +11 -0
  21. package/lib/mocha/test.js +15 -0
  22. package/lib/mocha/types.d.ts +6 -0
  23. package/lib/output.js +74 -73
  24. package/lib/pause.js +3 -7
  25. package/lib/plugin/heal.js +30 -0
  26. package/lib/plugin/stepTimeout.js +1 -1
  27. package/lib/recorder.js +1 -1
  28. package/lib/step/base.js +180 -0
  29. package/lib/step/config.js +50 -0
  30. package/lib/step/helper.js +47 -0
  31. package/lib/step/meta.js +91 -0
  32. package/lib/step/record.js +74 -0
  33. package/lib/step/retry.js +11 -0
  34. package/lib/step/section.js +25 -0
  35. package/lib/step/timeout.js +42 -0
  36. package/lib/step.js +15 -348
  37. package/lib/steps.js +23 -0
  38. package/lib/store.js +2 -0
  39. package/lib/utils.js +58 -0
  40. package/lib/within.js +2 -2
  41. package/lib/workers.js +2 -12
  42. package/package.json +7 -5
  43. package/typings/index.d.ts +5 -4
  44. package/typings/promiseBasedTypes.d.ts +1 -37
  45. package/typings/types.d.ts +39 -64
@@ -0,0 +1,91 @@
1
+ const Step = require('./base')
2
+ const event = require('../event')
3
+ const { humanizeString } = require('../utils')
4
+
5
+ class MetaStep extends Step {
6
+ constructor(actor, method) {
7
+ if (!method) method = ''
8
+ super(method)
9
+ this.actor = actor
10
+ }
11
+
12
+ /** @return {boolean} */
13
+ isBDD() {
14
+ if (this.actor && this.actor.match && this.actor.match(/^(Given|When|Then|And)/)) {
15
+ return true
16
+ }
17
+ return false
18
+ }
19
+
20
+ toCliStyled() {
21
+ return this.toString()
22
+ }
23
+
24
+ toString() {
25
+ const actorText = this.actor
26
+
27
+ if (this.isBDD()) {
28
+ return `${this.prefix}${actorText} ${this.name} "${this.humanizeArgs()}${this.suffix}"`
29
+ }
30
+
31
+ if (actorText === 'I') {
32
+ return `${this.prefix}${actorText} ${this.humanize()} ${this.humanizeArgs()}${this.suffix}`
33
+ }
34
+
35
+ return `On ${this.prefix}${actorText}: ${this.humanize()} ${this.humanizeArgs()}${this.suffix}`
36
+ }
37
+
38
+ humanize() {
39
+ return humanizeString(this.name)
40
+ }
41
+
42
+ setTrace() {}
43
+
44
+ setContext(context) {
45
+ this.context = context
46
+ }
47
+
48
+ /** @return {*} */
49
+ run(fn) {
50
+ this.status = 'queued'
51
+ this.setArguments(Array.from(arguments).slice(1))
52
+ let result
53
+
54
+ const registerStep = step => {
55
+ this.setMetaStep(null)
56
+ step.setMetaStep(this)
57
+ }
58
+ event.dispatcher.prependListener(event.step.before, registerStep)
59
+ // Handle async and sync methods.
60
+ if (fn.constructor.name === 'AsyncFunction') {
61
+ result = fn
62
+ .apply(this.context, this.args)
63
+ .then(result => {
64
+ return result
65
+ })
66
+ .catch(error => {
67
+ this.setStatus('failed')
68
+ throw error
69
+ })
70
+ .finally(() => {
71
+ this.endTime = Date.now()
72
+ event.dispatcher.removeListener(event.step.before, registerStep)
73
+ })
74
+ } else {
75
+ try {
76
+ this.startTime = Date.now()
77
+ result = fn.apply(this.context, this.args)
78
+ } catch (error) {
79
+ this.setStatus('failed')
80
+ throw error
81
+ } finally {
82
+ this.endTime = Date.now()
83
+ event.dispatcher.removeListener(event.step.before, registerStep)
84
+ }
85
+ }
86
+
87
+ return result
88
+ }
89
+ }
90
+
91
+ module.exports = MetaStep
@@ -0,0 +1,74 @@
1
+ const event = require('../event')
2
+ const recorder = require('../recorder')
3
+ const StepConfig = require('./config')
4
+ const { debug } = require('../output')
5
+ const store = require('../store')
6
+ const { TIMEOUT_ORDER } = require('./timeout')
7
+ const retryStep = require('./retry')
8
+ function recordStep(step, args) {
9
+ step.status = 'queued'
10
+
11
+ // apply step configuration
12
+ const lastArg = args[args.length - 1]
13
+ if (lastArg instanceof StepConfig) {
14
+ const stepConfig = args.pop()
15
+ const { opts, timeout, retry } = stepConfig.getConfig()
16
+
17
+ if (opts) {
18
+ debug(`Step ${step.name}: options applied ${JSON.stringify(opts)}`)
19
+ store.stepOptions = opts
20
+ step.opts = opts
21
+ }
22
+ if (timeout) {
23
+ debug(`Step ${step.name} timeout ${timeout}s`)
24
+ step.setTimeout(timeout * 1000, TIMEOUT_ORDER.codeLimitTime)
25
+ }
26
+ if (retry) retryStep(retry)
27
+ }
28
+
29
+ step.setArguments(args)
30
+ // run async before step hooks
31
+ event.emit(event.step.before, step)
32
+
33
+ const task = `${step.name}: ${step.humanizeArgs()}`
34
+ let val
35
+
36
+ // run step inside promise
37
+ recorder.add(
38
+ task,
39
+ () => {
40
+ if (!step.startTime) {
41
+ // step can be retries
42
+ event.emit(event.step.started, step)
43
+ step.startTime = Date.now()
44
+ }
45
+ return (val = step.run(...args))
46
+ },
47
+ false,
48
+ undefined,
49
+ step.timeout,
50
+ )
51
+
52
+ event.emit(event.step.after, step)
53
+
54
+ recorder.add('step passed', () => {
55
+ step.endTime = Date.now()
56
+ event.emit(event.step.passed, step, val)
57
+ event.emit(event.step.finished, step)
58
+ })
59
+
60
+ recorder.catchWithoutStop(err => {
61
+ step.status = 'failed'
62
+ step.endTime = Date.now()
63
+ event.emit(event.step.failed, step)
64
+ event.emit(event.step.finished, step)
65
+ throw err
66
+ })
67
+
68
+ recorder.add('return result', () => val)
69
+ // run async after step hooks
70
+
71
+ return recorder.promise()
72
+ }
73
+
74
+ module.exports = recordStep
@@ -0,0 +1,11 @@
1
+ const recorder = require('../recorder')
2
+ const event = require('../event')
3
+
4
+ function retryStep(opts) {
5
+ if (opts === undefined) opts = 1
6
+ recorder.retry(opts)
7
+ // remove retry once the step passed
8
+ recorder.add(() => event.dispatcher.once(event.step.finished, () => recorder.retries.pop()))
9
+ }
10
+
11
+ module.exports = retryStep
@@ -0,0 +1,25 @@
1
+ const event = require('../event')
2
+
3
+ class Section {
4
+ constructor(name = '') {
5
+ this.name = name
6
+
7
+ this.attachMetaStep = (step) => {
8
+ console.log('attach meta step', this.name)
9
+ }
10
+ }
11
+
12
+ start() {
13
+ event.dispatcher.on(event.step.started, this.attachMetaStep)
14
+ }
15
+
16
+ end() {
17
+ event.dispatcher.off(event.step.started, this.attachMetaStep)
18
+ }
19
+ }
20
+
21
+
22
+ function getRootMetaStep(step) {
23
+ if (step.metaStep) return getRootMetaStep(step.metaStep)
24
+ return step
25
+ }
@@ -0,0 +1,42 @@
1
+ const TIMEOUT_ORDER = {
2
+ /**
3
+ * timeouts set with order below zero only override timeouts of higher order if their value is smaller
4
+ */
5
+ testOrSuite: -5,
6
+ /**
7
+ * 0-9 - designated for override of timeouts set from code, 5 is used by stepTimeout plugin when stepTimeout.config.overrideStepLimits=true
8
+ */
9
+ stepTimeoutHard: 5,
10
+ /**
11
+ * 10-19 - designated for timeouts set from code, 15 is order of I.setTimeout(t) operation
12
+ */
13
+ codeLimitTime: 15,
14
+ /**
15
+ * 20-29 - designated for timeout settings which could be overriden in tests code, 25 is used by stepTimeout plugin when stepTimeout.config.overrideStepLimits=false
16
+ */
17
+ stepTimeoutSoft: 25,
18
+ }
19
+
20
+ function getCurrentTimeout(timeouts) {
21
+ let totalTimeout
22
+ // iterate over all timeouts starting from highest values of order
23
+ new Map([...timeouts.entries()].sort().reverse()).forEach((timeout, order) => {
24
+ if (
25
+ timeout !== undefined &&
26
+ // when orders >= 0 - timeout value overrides those set with higher order elements
27
+ (order >= 0 ||
28
+ // when `order < 0 && totalTimeout === undefined` - timeout is used when nothing is set by elements with higher order
29
+ totalTimeout === undefined ||
30
+ // when `order < 0` - timeout overrides higher values of timeout or 'no timeout' (totalTimeout === 0) set by elements with higher order
31
+ (timeout > 0 && (timeout < totalTimeout || totalTimeout === 0)))
32
+ ) {
33
+ totalTimeout = timeout
34
+ }
35
+ })
36
+ return totalTimeout
37
+ }
38
+
39
+ module.exports = {
40
+ TIMEOUT_ORDER,
41
+ getCurrentTimeout,
42
+ }
package/lib/step.js CHANGED
@@ -1,353 +1,20 @@
1
- // TODO: place MetaStep in other file, disable rule
2
-
3
- const color = require('chalk')
4
- const store = require('./store')
5
- const Secret = require('./secret')
6
- const event = require('./event')
7
- const { ucfirst } = require('./utils')
8
-
9
- const STACK_LINE = 4
10
-
1
+ // refactored step class, moved to helper
11
2
  /**
12
- * Each command in test executed through `I.` object is wrapped in Step.
13
- * Step allows logging executed commands and triggers hook before and after step execution.
14
- * @param {CodeceptJS.Helper} helper
15
- * @param {string} name
3
+ * Step is wrapper around a helper method.
4
+ * It is used to create a new step that is a combination of other steps.
16
5
  */
17
- class Step {
18
- static get TIMEOUT_ORDER() {
19
- return {
20
- /**
21
- * timeouts set with order below zero only override timeouts of higher order if their value is smaller
22
- */
23
- testOrSuite: -5,
24
- /**
25
- * 0-9 - designated for override of timeouts set from code, 5 is used by stepTimeout plugin when stepTimeout.config.overrideStepLimits=true
26
- */
27
- stepTimeoutHard: 5,
28
- /**
29
- * 10-19 - designated for timeouts set from code, 15 is order of I.setTimeout(t) operation
30
- */
31
- codeLimitTime: 15,
32
- /**
33
- * 20-29 - designated for timeout settings which could be overriden in tests code, 25 is used by stepTimeout plugin when stepTimeout.config.overrideStepLimits=false
34
- */
35
- stepTimeoutSoft: 25,
36
- }
37
- }
38
-
39
- constructor(helper, name) {
40
- /** @member {string} */
41
- this.actor = 'I' // I = actor
42
- /** @member {CodeceptJS.Helper} */
43
- this.helper = helper // corresponding helper
44
- /** @member {string} */
45
- this.name = name // name of a step console
46
- /** @member {string} */
47
- this.helperMethod = name // helper method
48
- /** @member {string} */
49
- this.status = 'pending'
50
- /**
51
- * @member {string} suffix
52
- * @memberof CodeceptJS.Step#
53
- */
54
- /** @member {string} */
55
- this.prefix = this.suffix = ''
56
- /** @member {string} */
57
- this.comment = ''
58
- /** @member {Array<*>} */
59
- this.args = []
60
- /** @member {MetaStep} */
61
- this.metaStep = undefined
62
- /** @member {string} */
63
- this.stack = ''
64
-
65
- const timeouts = new Map()
66
- /**
67
- * @method
68
- * @returns {number|undefined}
69
- */
70
- this.getTimeout = function () {
71
- let totalTimeout
72
- // iterate over all timeouts starting from highest values of order
73
- new Map([...timeouts.entries()].sort().reverse()).forEach((timeout, order) => {
74
- if (
75
- timeout !== undefined &&
76
- // when orders >= 0 - timeout value overrides those set with higher order elements
77
- (order >= 0 ||
78
- // when `order < 0 && totalTimeout === undefined` - timeout is used when nothing is set by elements with higher order
79
- totalTimeout === undefined ||
80
- // when `order < 0` - timeout overrides higher values of timeout or 'no timeout' (totalTimeout === 0) set by elements with higher order
81
- (timeout > 0 && (timeout < totalTimeout || totalTimeout === 0)))
82
- ) {
83
- totalTimeout = timeout
84
- }
85
- })
86
- return totalTimeout
87
- }
88
- /**
89
- * @method
90
- * @param {number} timeout - timeout in milliseconds or 0 if no timeout
91
- * @param {number} order - order defines the priority of timeout, timeouts set with lower order override those set with higher order.
92
- * When order below 0 value of timeout only override if new value is lower
93
- */
94
- this.setTimeout = function (timeout, order) {
95
- timeouts.set(order, timeout)
96
- }
97
-
98
- this.setTrace()
99
- }
100
-
101
- /** @function */
102
- setTrace() {
103
- Error.captureStackTrace(this)
104
- }
105
-
106
- /** @param {Array<*>} args */
107
- setArguments(args) {
108
- this.args = args
109
- }
110
-
111
- /**
112
- * @param {...any} args
113
- * @return {*}
114
- */
115
- run() {
116
- this.args = Array.prototype.slice.call(arguments)
117
- if (store.dryRun) {
118
- this.setStatus('success')
119
- return Promise.resolve(new Proxy({}, dryRunResolver()))
120
- }
121
- let result
122
- try {
123
- if (this.helperMethod !== 'say') {
124
- result = this.helper[this.helperMethod].apply(this.helper, this.args)
125
- }
126
- this.setStatus('success')
127
- } catch (err) {
128
- this.setStatus('failed')
129
- throw err
130
- }
131
- return result
132
- }
133
-
134
- setActor(actor) {
135
- this.actor = actor || ''
136
- }
137
-
138
- /** @param {string} status */
139
- setStatus(status) {
140
- this.status = status
141
- if (this.metaStep) {
142
- this.metaStep.setStatus(status)
143
- }
144
- }
145
-
146
- /** @return {string} */
147
- humanize() {
148
- return humanizeString(this.name)
149
- }
150
-
151
- /** @return {string} */
152
- humanizeArgs() {
153
- return this.args
154
- .map(arg => {
155
- if (!arg) {
156
- return ''
157
- }
158
- if (typeof arg === 'string') {
159
- return `"${arg}"`
160
- }
161
- if (Array.isArray(arg)) {
162
- try {
163
- const res = JSON.stringify(arg)
164
- return res
165
- } catch (err) {
166
- return `[${arg.toString()}]`
167
- }
168
- } else if (typeof arg === 'function') {
169
- return arg.toString()
170
- } else if (typeof arg === 'undefined') {
171
- return `${arg}`
172
- } else if (arg instanceof Secret) {
173
- return arg.getMasked()
174
- } else if (arg.toString && arg.toString() !== '[object Object]') {
175
- return arg.toString()
176
- } else if (typeof arg === 'object') {
177
- const returnedArg = {}
178
- for (const [key, value] of Object.entries(arg)) {
179
- returnedArg[key] = value
180
- if (value instanceof Secret) returnedArg[key] = value.getMasked()
181
- }
182
- return JSON.stringify(returnedArg)
183
- }
184
- return arg
185
- })
186
- .join(', ')
187
- }
188
-
189
- /** @return {string} */
190
- line() {
191
- const lines = this.stack.split('\n')
192
- if (lines[STACK_LINE]) {
193
- return lines[STACK_LINE].trim()
194
- .replace(global.codecept_dir || '', '.')
195
- .trim()
196
- }
197
- return ''
198
- }
199
-
200
- /** @return {string} */
201
- toString() {
202
- return ucfirst(`${this.prefix}${this.actor} ${this.humanize()} ${this.humanizeArgs()}${this.suffix}`).trim()
203
- }
204
-
205
- /** @return {string} */
206
- toCliStyled() {
207
- return `${this.prefix}${this.actor} ${color.italic(this.humanize())} ${color.yellow(this.humanizeArgs())}${this.suffix}`
208
- }
209
-
210
- /** @return {string} */
211
- toCode() {
212
- return `${this.prefix}${this.actor}.${this.name}(${this.humanizeArgs()})${this.suffix}`
213
- }
214
-
215
- isMetaStep() {
216
- return this.constructor.name === 'MetaStep'
217
- }
218
-
219
- /** @return {boolean} */
220
- hasBDDAncestor() {
221
- let hasBDD = false
222
- let processingStep
223
- processingStep = this
224
-
225
- while (processingStep.metaStep) {
226
- if (processingStep.metaStep.actor.match(/^(Given|When|Then|And)/)) {
227
- hasBDD = true
228
- break
229
- } else {
230
- processingStep = processingStep.metaStep
231
- }
232
- }
233
- return hasBDD
234
- }
235
- }
6
+ const BaseStep = require('./step/base')
7
+ const StepConfig = require('./step/config')
8
+ const Step = require('./step/helper')
236
9
 
237
- /** @extends Step */
238
- class MetaStep extends Step {
239
- constructor(obj, method) {
240
- if (!method) method = ''
241
- super(null, method)
242
- this.actor = obj
243
- }
244
-
245
- /** @return {boolean} */
246
- isBDD() {
247
- if (this.actor && this.actor.match && this.actor.match(/^(Given|When|Then|And)/)) {
248
- return true
249
- }
250
- return false
251
- }
252
-
253
- toCliStyled() {
254
- return this.toString()
255
- }
256
-
257
- toString() {
258
- const actorText = this.actor
259
-
260
- if (this.isBDD()) {
261
- return `${this.prefix}${actorText} ${this.name} "${this.humanizeArgs()}${this.suffix}"`
262
- }
263
-
264
- if (actorText === 'I') {
265
- return `${this.prefix}${actorText} ${this.humanize()} ${this.humanizeArgs()}${this.suffix}`
266
- }
267
-
268
- return `On ${this.prefix}${actorText}: ${this.humanize()} ${this.humanizeArgs()}${this.suffix}`
269
- }
270
-
271
- humanize() {
272
- return humanizeString(this.name)
273
- }
274
-
275
- setTrace() {}
276
-
277
- setContext(context) {
278
- this.context = context
279
- }
280
-
281
- /** @return {*} */
282
- run(fn) {
283
- this.status = 'queued'
284
- this.setArguments(Array.from(arguments).slice(1))
285
- let result
286
-
287
- const registerStep = step => {
288
- this.metaStep = null
289
- step.metaStep = this
290
- }
291
- event.dispatcher.prependListener(event.step.before, registerStep)
292
- // Handle async and sync methods.
293
- if (fn.constructor.name === 'AsyncFunction') {
294
- result = fn
295
- .apply(this.context, this.args)
296
- .then(result => {
297
- return result
298
- })
299
- .catch(error => {
300
- this.setStatus('failed')
301
- throw error
302
- })
303
- .finally(() => {
304
- this.endTime = Date.now()
305
- event.dispatcher.removeListener(event.step.before, registerStep)
306
- })
307
- } else {
308
- try {
309
- this.startTime = Date.now()
310
- result = fn.apply(this.context, this.args)
311
- } catch (error) {
312
- this.setStatus('failed')
313
- throw error
314
- } finally {
315
- this.endTime = Date.now()
316
- event.dispatcher.removeListener(event.step.before, registerStep)
317
- }
318
- }
319
-
320
- return result
321
- }
322
- }
323
-
324
- Step.TIMEOUTS = {}
325
-
326
- /** @type {Class<MetaStep>} */
327
- Step.MetaStep = MetaStep
10
+ /**
11
+ * MetaStep is a step that is used to wrap other steps.
12
+ * It is used to create a new step that is a combination of other steps.
13
+ * It is used to create a new step that is a combination of other steps.
14
+ */
15
+ const MetaStep = require('./step/meta')
328
16
 
329
17
  module.exports = Step
330
-
331
- function dryRunResolver() {
332
- return {
333
- get(target, prop) {
334
- if (prop === 'toString') return () => '<VALUE>'
335
- return new Proxy({}, dryRunResolver())
336
- },
337
- }
338
- }
339
-
340
- function humanizeString(string) {
341
- // split strings by words, then make them all lowercase
342
- const _result = string
343
- .replace(/([a-z](?=[A-Z]))/g, '$1 ')
344
- .split(' ')
345
- .map(word => word.toLowerCase())
346
-
347
- _result[0] = _result[0] === 'i' ? capitalizeFLetter(_result[0]) : _result[0]
348
- return _result.join(' ').trim()
349
- }
350
-
351
- function capitalizeFLetter(string) {
352
- return string[0].toUpperCase() + string.slice(1)
353
- }
18
+ module.exports.MetaStep = MetaStep
19
+ module.exports.BaseStep = BaseStep
20
+ module.exports.StepConfig = StepConfig
package/lib/steps.js ADDED
@@ -0,0 +1,23 @@
1
+ const StepConfig = require('./step/config')
2
+
3
+ function stepOpts(opts = {}) {
4
+ return new StepConfig(opts)
5
+ }
6
+
7
+ function stepTimeout(timeout) {
8
+ return new StepConfig().timeout(timeout)
9
+ }
10
+
11
+ function stepRetry(retry) {
12
+ return new StepConfig().retry(retry)
13
+ }
14
+
15
+ // Section function to be added here
16
+
17
+ const step = {
18
+ opts: stepOpts,
19
+ timeout: stepTimeout,
20
+ retry: stepRetry,
21
+ }
22
+
23
+ module.exports = step
package/lib/store.js CHANGED
@@ -13,6 +13,8 @@ const store = {
13
13
  onPause: false,
14
14
  /** @type {CodeceptJS.Test | null} */
15
15
  currentTest: null,
16
+ /** @type {any} */
17
+ currentStep: null,
16
18
  }
17
19
 
18
20
  module.exports = store