codeceptjs 3.7.0-beta.4 → 3.7.0-beta.6

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 (47) 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 +218 -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 +15 -3
  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/retryTo.js +18 -126
  27. package/lib/plugin/stepTimeout.js +1 -1
  28. package/lib/plugin/tryTo.js +13 -111
  29. package/lib/recorder.js +1 -1
  30. package/lib/step/base.js +180 -0
  31. package/lib/step/config.js +50 -0
  32. package/lib/step/helper.js +47 -0
  33. package/lib/step/meta.js +91 -0
  34. package/lib/step/record.js +74 -0
  35. package/lib/step/retry.js +11 -0
  36. package/lib/step/timeout.js +42 -0
  37. package/lib/step.js +15 -348
  38. package/lib/steps.js +23 -0
  39. package/lib/store.js +2 -0
  40. package/lib/utils.js +58 -0
  41. package/lib/within.js +2 -2
  42. package/lib/workers.js +2 -12
  43. package/package.json +9 -7
  44. package/typings/index.d.ts +5 -4
  45. package/typings/promiseBasedTypes.d.ts +520 -6
  46. package/typings/types.d.ts +562 -44
  47. package/lib/step/section.js +0 -25
@@ -0,0 +1,180 @@
1
+ const color = require('chalk')
2
+ const Secret = require('../secret')
3
+ const { getCurrentTimeout } = require('./timeout')
4
+ const { ucfirst, humanizeString } = require('../utils')
5
+
6
+ const STACK_LINE = 4
7
+
8
+ /**
9
+ * Each command in test executed through `I.` object is wrapped in Step.
10
+ * Step allows logging executed commands and triggers hook before and after step execution.
11
+ * @param {string} name
12
+ */
13
+ class Step {
14
+ constructor(name) {
15
+ /** @member {string} */
16
+ this.name = name
17
+ /** @member {Map<number, number>} */
18
+ this.timeouts = new Map()
19
+
20
+ /** @member {Array<*>} */
21
+ this.args = []
22
+
23
+ /** @member {Record<string,any>} */
24
+ this.opts = {}
25
+ /** @member {string} */
26
+ this.actor = 'I' // I = actor
27
+ /** @member {string} */
28
+ this.helperMethod = name // helper method
29
+ /** @member {string} */
30
+ this.status = 'pending'
31
+ /** @member {string} */
32
+ this.prefix = this.suffix = ''
33
+ /** @member {string} */
34
+ this.comment = ''
35
+ /** @member {any} */
36
+ this.metaStep = undefined
37
+ /** @member {string} */
38
+ this.stack = ''
39
+
40
+ this.setTrace()
41
+ }
42
+
43
+ setMetaStep(metaStep) {
44
+ this.metaStep = metaStep
45
+ }
46
+
47
+ run() {
48
+ throw new Error('Not implemented')
49
+ }
50
+
51
+ /**
52
+ * @returns {number|undefined}
53
+ */
54
+ get timeout() {
55
+ return getCurrentTimeout(this.timeouts)
56
+ }
57
+
58
+ /**
59
+ * @param {number} timeout - timeout in milliseconds or 0 if no timeout
60
+ * @param {number} order - order defines the priority of timeout, timeouts set with lower order override those set with higher order.
61
+ * When order below 0 value of timeout only override if new value is lower
62
+ */
63
+ setTimeout(timeout, order) {
64
+ this.timeouts.set(order, timeout)
65
+ }
66
+
67
+ /** @function */
68
+ setTrace() {
69
+ Error.captureStackTrace(this)
70
+ }
71
+
72
+ /** @param {Array<*>} args */
73
+ setArguments(args) {
74
+ this.args = args
75
+ }
76
+
77
+ setActor(actor) {
78
+ this.actor = actor || ''
79
+ }
80
+
81
+ /** @param {string} status */
82
+ setStatus(status) {
83
+ this.status = status
84
+ if (this.metaStep) {
85
+ this.metaStep.setStatus(status)
86
+ }
87
+ }
88
+
89
+ /** @return {string} */
90
+ humanize() {
91
+ return humanizeString(this.name)
92
+ }
93
+
94
+ /** @return {string} */
95
+ humanizeArgs() {
96
+ return this.args
97
+ .map(arg => {
98
+ if (!arg) {
99
+ return ''
100
+ }
101
+ if (typeof arg === 'string') {
102
+ return `"${arg}"`
103
+ }
104
+ if (Array.isArray(arg)) {
105
+ try {
106
+ const res = JSON.stringify(arg)
107
+ return res
108
+ } catch (err) {
109
+ return `[${arg.toString()}]`
110
+ }
111
+ } else if (typeof arg === 'function') {
112
+ return arg.toString()
113
+ } else if (typeof arg === 'undefined') {
114
+ return `${arg}`
115
+ } else if (arg instanceof Secret) {
116
+ return arg.getMasked()
117
+ } else if (arg.toString && arg.toString() !== '[object Object]') {
118
+ return arg.toString()
119
+ } else if (typeof arg === 'object') {
120
+ const returnedArg = {}
121
+ for (const [key, value] of Object.entries(arg)) {
122
+ returnedArg[key] = value
123
+ if (value instanceof Secret) returnedArg[key] = value.getMasked()
124
+ }
125
+ return JSON.stringify(returnedArg)
126
+ }
127
+ return arg
128
+ })
129
+ .join(', ')
130
+ }
131
+
132
+ /** @return {string} */
133
+ line() {
134
+ const lines = this.stack.split('\n')
135
+ if (lines[STACK_LINE]) {
136
+ return lines[STACK_LINE].trim()
137
+ .replace(global.codecept_dir || '', '.')
138
+ .trim()
139
+ }
140
+ return ''
141
+ }
142
+
143
+ /** @return {string} */
144
+ toString() {
145
+ return ucfirst(`${this.prefix}${this.actor} ${this.humanize()} ${this.humanizeArgs()}${this.suffix}`).trim()
146
+ }
147
+
148
+ /** @return {string} */
149
+ toCliStyled() {
150
+ return `${this.prefix}${this.actor} ${color.italic(this.humanize())} ${color.yellow(this.humanizeArgs())}${this.suffix}`
151
+ }
152
+
153
+ /** @return {string} */
154
+ toCode() {
155
+ return `${this.prefix}${this.actor}.${this.name}(${this.humanizeArgs()})${this.suffix}`
156
+ }
157
+
158
+ isMetaStep() {
159
+ return this.constructor.name === 'MetaStep'
160
+ }
161
+
162
+ /** @return {boolean} */
163
+ hasBDDAncestor() {
164
+ let hasBDD = false
165
+ let processingStep
166
+ processingStep = this
167
+
168
+ while (processingStep.metaStep) {
169
+ if (processingStep.metaStep.actor.match(/^(Given|When|Then|And)/)) {
170
+ hasBDD = true
171
+ break
172
+ } else {
173
+ processingStep = processingStep.metaStep
174
+ }
175
+ }
176
+ return hasBDD
177
+ }
178
+ }
179
+
180
+ module.exports = Step
@@ -0,0 +1,50 @@
1
+ /**
2
+ * StepConfig is a configuration object for a step.
3
+ * It is used to create a new step that is a combination of other steps.
4
+ */
5
+ class StepConfig {
6
+ constructor(opts = {}) {
7
+ /** @member {{ opts: Record<string, any>, timeout: number|undefined, retry: number|undefined }} */
8
+ this.config = {
9
+ opts,
10
+ timeout: undefined,
11
+ retry: undefined,
12
+ }
13
+ }
14
+
15
+ /**
16
+ * Set the options for the step.
17
+ * @param {object} opts - The options for the step.
18
+ * @returns {StepConfig} - The step configuration object.
19
+ */
20
+ opts(opts) {
21
+ this.config.opts = opts
22
+ return this
23
+ }
24
+
25
+ /**
26
+ * Set the timeout for the step.
27
+ * @param {number} timeout - The timeout for the step.
28
+ * @returns {StepConfig} - The step configuration object.
29
+ */
30
+ timeout(timeout) {
31
+ this.config.timeout = timeout
32
+ return this
33
+ }
34
+
35
+ /**
36
+ * Set the retry for the step.
37
+ * @param {number} retry - The retry for the step.
38
+ * @returns {StepConfig} - The step configuration object.
39
+ */
40
+ retry(retry) {
41
+ this.config.retry = retry
42
+ return this
43
+ }
44
+
45
+ getConfig() {
46
+ return this.config
47
+ }
48
+ }
49
+
50
+ module.exports = StepConfig
@@ -0,0 +1,47 @@
1
+ const Step = require('./base')
2
+ const store = require('../store')
3
+
4
+ class HelperStep extends Step {
5
+ constructor(helper, name) {
6
+ super(name)
7
+ /** @member {CodeceptJS.Helper} helper corresponding helper */
8
+ this.helper = helper
9
+ /** @member {string} helperMethod name of method to be executed */
10
+ this.helperMethod = name
11
+ }
12
+
13
+ /**
14
+ * @param {...any} args
15
+ * @return {*}
16
+ */
17
+ run() {
18
+ this.args = Array.prototype.slice.call(arguments)
19
+
20
+ if (store.dryRun) {
21
+ this.setStatus('success')
22
+ return Promise.resolve(new Proxy({}, dryRunResolver()))
23
+ }
24
+ let result
25
+ try {
26
+ if (this.helperMethod !== 'say') {
27
+ result = this.helper[this.helperMethod].apply(this.helper, this.args)
28
+ }
29
+ this.setStatus('success')
30
+ } catch (err) {
31
+ this.setStatus('failed')
32
+ throw err
33
+ }
34
+ return result
35
+ }
36
+ }
37
+
38
+ module.exports = HelperStep
39
+
40
+ function dryRunResolver() {
41
+ return {
42
+ get(target, prop) {
43
+ if (prop === 'toString') return () => '<VALUE>'
44
+ return new Proxy({}, dryRunResolver())
45
+ },
46
+ }
47
+ }
@@ -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,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
+ }