codeceptjs 3.7.0-beta.7 → 3.7.0-beta.9

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 (52) hide show
  1. package/lib/actor.js +1 -2
  2. package/lib/ai.js +130 -121
  3. package/lib/codecept.js +4 -4
  4. package/lib/command/check.js +4 -0
  5. package/lib/command/run-workers.js +1 -53
  6. package/lib/command/workers/runTests.js +25 -189
  7. package/lib/container.js +16 -0
  8. package/lib/els.js +87 -106
  9. package/lib/event.js +18 -17
  10. package/lib/helper/Playwright.js +7 -1
  11. package/lib/listener/exit.js +5 -8
  12. package/lib/listener/globalTimeout.js +26 -9
  13. package/lib/listener/result.js +12 -0
  14. package/lib/listener/steps.js +0 -6
  15. package/lib/listener/store.js +9 -1
  16. package/lib/mocha/asyncWrapper.js +12 -2
  17. package/lib/mocha/cli.js +65 -31
  18. package/lib/mocha/hooks.js +32 -3
  19. package/lib/mocha/suite.js +27 -1
  20. package/lib/mocha/test.js +91 -7
  21. package/lib/mocha/types.d.ts +5 -0
  22. package/lib/output.js +2 -1
  23. package/lib/plugin/analyze.js +348 -0
  24. package/lib/plugin/commentStep.js +5 -0
  25. package/lib/plugin/customReporter.js +52 -0
  26. package/lib/plugin/heal.js +2 -2
  27. package/lib/plugin/pageInfo.js +140 -0
  28. package/lib/plugin/retryTo.js +10 -2
  29. package/lib/plugin/screenshotOnFail.js +11 -16
  30. package/lib/plugin/stepByStepReport.js +5 -4
  31. package/lib/plugin/stepTimeout.js +1 -1
  32. package/lib/plugin/tryTo.js +9 -1
  33. package/lib/recorder.js +4 -4
  34. package/lib/rerun.js +43 -42
  35. package/lib/result.js +161 -0
  36. package/lib/step/base.js +52 -4
  37. package/lib/step/func.js +46 -0
  38. package/lib/step/helper.js +3 -0
  39. package/lib/step/meta.js +9 -1
  40. package/lib/step/record.js +5 -5
  41. package/lib/step/section.js +55 -0
  42. package/lib/step.js +6 -0
  43. package/lib/steps.js +28 -1
  44. package/lib/store.js +2 -0
  45. package/lib/{step/timeout.js → timeout.js} +24 -0
  46. package/lib/utils.js +35 -0
  47. package/lib/workers.js +28 -38
  48. package/package.json +7 -6
  49. package/typings/promiseBasedTypes.d.ts +104 -0
  50. package/typings/types.d.ts +104 -0
  51. package/lib/listener/artifacts.js +0 -19
  52. package/lib/plugin/debugErrors.js +0 -67
@@ -1,4 +1,6 @@
1
- module.exports = function () {
1
+ const { tryTo } = require('../effects')
2
+
3
+ module.exports = function (config) {
2
4
  console.log(`
3
5
  Deprecated Warning: 'tryTo' has been moved to the effects module.
4
6
  You should update your tests to use it as follows:
@@ -14,4 +16,10 @@ await tryTo(() => {
14
16
 
15
17
  For more details, refer to the documentation.
16
18
  `)
19
+
20
+ if (config.registerGlobal) {
21
+ global.tryTo = tryTo
22
+ }
23
+
24
+ return tryTo
17
25
  }
package/lib/recorder.js CHANGED
@@ -3,7 +3,7 @@ const promiseRetry = require('promise-retry')
3
3
  const chalk = require('chalk')
4
4
  const { printObjectProperties } = require('./utils')
5
5
  const { log } = require('./output')
6
-
6
+ const { TimeoutError } = require('./timeout')
7
7
  const MAX_TASKS = 100
8
8
 
9
9
  let promise
@@ -191,13 +191,13 @@ module.exports = {
191
191
  .slice(-1)
192
192
  .pop()
193
193
  // no retries or unnamed tasks
194
+ debug(`${currentQueue()} Running | ${taskName} | Timeout: ${timeout || 'None'}`)
195
+
194
196
  if (!retryOpts || !taskName || !retry) {
195
197
  const [promise, timer] = getTimeoutPromise(timeout, taskName)
196
198
  return Promise.race([promise, Promise.resolve(res).then(fn)]).finally(() => clearTimeout(timer))
197
199
  }
198
200
 
199
- debug(`${currentQueue()} Running | ${taskName}`)
200
-
201
201
  const retryRules = this.retries.slice().reverse()
202
202
  return promiseRetry(Object.assign(defaultRetryOptions, retryOpts), (retry, number) => {
203
203
  if (number > 1) log(`${currentQueue()}Retrying... Attempt #${number}`)
@@ -386,7 +386,7 @@ function getTimeoutPromise(timeoutMs, taskName) {
386
386
  return [
387
387
  new Promise((done, reject) => {
388
388
  timer = setTimeout(() => {
389
- reject(new Error(`Action ${taskName} was interrupted on step timeout ${timeoutMs}ms`))
389
+ reject(new TimeoutError(`Action ${taskName} was interrupted on timeout ${timeoutMs}ms`))
390
390
  }, timeoutMs || 2e9)
391
391
  }),
392
392
  timer,
package/lib/rerun.js CHANGED
@@ -1,81 +1,82 @@
1
- const fsPath = require('path');
2
- const container = require('./container');
3
- const event = require('./event');
4
- const BaseCodecept = require('./codecept');
5
- const output = require('./output');
1
+ const fsPath = require('path')
2
+ const container = require('./container')
3
+ const event = require('./event')
4
+ const BaseCodecept = require('./codecept')
5
+ const output = require('./output')
6
6
 
7
7
  class CodeceptRerunner extends BaseCodecept {
8
8
  runOnce(test) {
9
9
  return new Promise((resolve, reject) => {
10
10
  // @ts-ignore
11
- container.createMocha();
12
- const mocha = container.mocha();
13
- this.testFiles.forEach((file) => {
14
- delete require.cache[file];
15
- });
16
- mocha.files = this.testFiles;
11
+ container.createMocha()
12
+ const mocha = container.mocha()
13
+ this.testFiles.forEach(file => {
14
+ delete require.cache[file]
15
+ })
16
+ mocha.files = this.testFiles
17
17
  if (test) {
18
18
  if (!fsPath.isAbsolute(test)) {
19
- test = fsPath.join(global.codecept_dir, test);
19
+ test = fsPath.join(global.codecept_dir, test)
20
20
  }
21
- mocha.files = mocha.files.filter(t => fsPath.basename(t, '.js') === test || t === test);
21
+ mocha.files = mocha.files.filter(t => fsPath.basename(t, '.js') === test || t === test)
22
22
  }
23
23
  try {
24
- mocha.run((failures) => {
24
+ mocha.run(failures => {
25
25
  if (failures === 0) {
26
- resolve();
26
+ resolve()
27
27
  } else {
28
- reject(new Error(`${failures} tests fail`));
28
+ reject(new Error(`${failures} tests fail`))
29
29
  }
30
- });
30
+ })
31
31
  } catch (e) {
32
- reject(e);
32
+ reject(e)
33
33
  }
34
- });
34
+ })
35
35
  }
36
36
 
37
37
  async runTests(test) {
38
- const configRerun = this.config.rerun || {};
39
- const minSuccess = configRerun.minSuccess || 1;
40
- const maxReruns = configRerun.maxReruns || 1;
38
+ const configRerun = this.config.rerun || {}
39
+ const minSuccess = configRerun.minSuccess || 1
40
+ const maxReruns = configRerun.maxReruns || 1
41
41
  if (minSuccess > maxReruns) {
42
- process.exitCode = 1;
43
- throw new Error(`run-rerun Configuration Error: minSuccess must be less than maxReruns. Current values: minSuccess=${minSuccess} maxReruns=${maxReruns}`);
42
+ process.exitCode = 1
43
+ throw new Error(`run-rerun Configuration Error: minSuccess must be less than maxReruns. Current values: minSuccess=${minSuccess} maxReruns=${maxReruns}`)
44
44
  }
45
45
  if (maxReruns === 1) {
46
- await this.runOnce(test);
47
- return;
46
+ await this.runOnce(test)
47
+ return
48
48
  }
49
- let successCounter = 0;
50
- let rerunsCounter = 0;
49
+ let successCounter = 0
50
+ let rerunsCounter = 0
51
51
  while (rerunsCounter < maxReruns && successCounter < minSuccess) {
52
- rerunsCounter++;
52
+ container.result().reset() // reset result
53
+ rerunsCounter++
53
54
  try {
54
- await this.runOnce(test);
55
- successCounter++;
56
- output.success(`\nProcess run ${rerunsCounter} of max ${maxReruns}, success runs ${successCounter}/${minSuccess}\n`);
55
+ await this.runOnce(test)
56
+ successCounter++
57
+ output.success(`\nProcess run ${rerunsCounter} of max ${maxReruns}, success runs ${successCounter}/${minSuccess}\n`)
57
58
  } catch (e) {
58
- output.error(`\nFail run ${rerunsCounter} of max ${maxReruns}, success runs ${successCounter}/${minSuccess} \n`);
59
- console.error(e);
59
+ output.error(`\nFail run ${rerunsCounter} of max ${maxReruns}, success runs ${successCounter}/${minSuccess} \n`)
60
+ console.error(e)
60
61
  }
61
62
  }
62
63
  if (successCounter < minSuccess) {
63
- throw new Error(`Flaky tests detected! ${successCounter} success runs achieved instead of ${minSuccess} success runs expected`);
64
+ throw new Error(`Flaky tests detected! ${successCounter} success runs achieved instead of ${minSuccess} success runs expected`)
64
65
  }
65
66
  }
66
67
 
67
68
  async run(test) {
68
- event.emit(event.all.before, this);
69
+ event.emit(event.all.before, this)
69
70
  try {
70
- await this.runTests(test);
71
+ await this.runTests(test)
71
72
  } catch (e) {
72
- output.error(e.stack);
73
- throw e;
73
+ output.error(e.stack)
74
+ throw e
74
75
  } finally {
75
- event.emit(event.all.result, this);
76
- event.emit(event.all.after, this);
76
+ event.emit(event.all.result, this)
77
+ event.emit(event.all.after, this)
77
78
  }
78
79
  }
79
80
  }
80
81
 
81
- module.exports = CodeceptRerunner;
82
+ module.exports = CodeceptRerunner
package/lib/result.js ADDED
@@ -0,0 +1,161 @@
1
+ const fs = require('fs')
2
+ const path = require('path')
3
+ const { serializeTest } = require('./mocha/test')
4
+
5
+ /**
6
+ * Result of the test run
7
+ *
8
+ * @typedef {Object} Stats
9
+ * @property {number} passes
10
+ * @property {number} failures
11
+ * @property {number} tests
12
+ * @property {number} pending
13
+ * @property {number} failedHooks
14
+ * @property {Date} start
15
+ * @property {Date} end
16
+ * @property {number} duration
17
+ */
18
+ class Result {
19
+ /**
20
+ * Create Result of the test run
21
+ */
22
+ constructor() {
23
+ this._startTime = new Date()
24
+ this._endTime = null
25
+
26
+ this.reset()
27
+ this.start()
28
+ }
29
+
30
+ reset() {
31
+ this._stats = {
32
+ passes: 0,
33
+ failures: 0,
34
+ tests: 0,
35
+ pending: 0,
36
+ failedHooks: 0,
37
+ start: null,
38
+ end: null,
39
+ duration: 0,
40
+ }
41
+
42
+ /** @type {CodeceptJS.Test[]} */
43
+ this._tests = []
44
+
45
+ /** @type {String[]} */
46
+ this._failures = []
47
+ }
48
+
49
+ start() {
50
+ this._startTime = new Date()
51
+ }
52
+
53
+ finish() {
54
+ this._endTime = new Date()
55
+ }
56
+
57
+ get hasFailed() {
58
+ return this._stats.failures > 0
59
+ }
60
+
61
+ get tests() {
62
+ return this._tests
63
+ }
64
+
65
+ get failures() {
66
+ return this._failures.filter(f => f && (!Array.isArray(f) || f.length > 0))
67
+ }
68
+
69
+ get stats() {
70
+ return this._stats
71
+ }
72
+
73
+ get startTime() {
74
+ return this._startTime
75
+ }
76
+
77
+ /**
78
+ * Add test to result
79
+ *
80
+ * @param {CodeceptJS.Test} test
81
+ */
82
+ addTest(test) {
83
+ const existingTestIndex = this._tests.findIndex(t => !!t.uid && t.uid === test.uid)
84
+ if (existingTestIndex >= 0) {
85
+ this._tests[existingTestIndex] = test
86
+ return
87
+ }
88
+
89
+ this._tests.push(test)
90
+ }
91
+
92
+ /**
93
+ * Add failures to result
94
+ *
95
+ * @param {String[]} newFailures
96
+ */
97
+ addFailures(newFailures) {
98
+ this._failures.push(...newFailures)
99
+ }
100
+
101
+ get hasFailures() {
102
+ return this.stats.failures > 0
103
+ }
104
+
105
+ get duration() {
106
+ return this._endTime ? +this._endTime - +this._startTime : 0
107
+ }
108
+
109
+ get failedTests() {
110
+ return this._tests.filter(test => test.state === 'failed')
111
+ }
112
+
113
+ get passedTests() {
114
+ return this._tests.filter(test => test.state === 'passed')
115
+ }
116
+
117
+ get skippedTests() {
118
+ return this._tests.filter(test => test.state === 'skipped' || test.state === 'pending')
119
+ }
120
+
121
+ simplify() {
122
+ return {
123
+ hasFailed: this.hasFailed,
124
+ stats: this.stats,
125
+ duration: this.duration,
126
+ tests: this._tests.map(test => serializeTest(test)),
127
+ failures: this._failures,
128
+ }
129
+ }
130
+
131
+ /**
132
+ * Save result to json file
133
+ *
134
+ * @param {string} fileName
135
+ */
136
+ save(fileName) {
137
+ if (!fileName) fileName = 'result.json'
138
+ fs.writeFileSync(path.join(global.output_dir, fileName), JSON.stringify(this.simplify(), null, 2))
139
+ }
140
+
141
+ /**
142
+ * Add stats to result
143
+ *
144
+ * @param {object} newStats
145
+ */
146
+ addStats(newStats = {}) {
147
+ this._stats.passes += newStats.passes || 0
148
+ this._stats.failures += newStats.failures || 0
149
+ this._stats.tests += newStats.tests || 0
150
+ this._stats.pending += newStats.pending || 0
151
+ this._stats.failedHooks += newStats.failedHooks || 0
152
+
153
+ // do not override start time
154
+ this._stats.start = this._stats.start || newStats.start
155
+
156
+ this._stats.end = newStats.end || this._stats.end
157
+ this._stats.duration = newStats.duration
158
+ }
159
+ }
160
+
161
+ module.exports = Result
package/lib/step/base.js CHANGED
@@ -1,9 +1,9 @@
1
1
  const color = require('chalk')
2
2
  const Secret = require('../secret')
3
- const { getCurrentTimeout } = require('./timeout')
4
- const { ucfirst, humanizeString } = require('../utils')
3
+ const { getCurrentTimeout } = require('../timeout')
4
+ const { ucfirst, humanizeString, serializeError } = require('../utils')
5
5
 
6
- const STACK_LINE = 4
6
+ const STACK_LINE = 5
7
7
 
8
8
  /**
9
9
  * Each command in test executed through `I.` object is wrapped in Step.
@@ -37,6 +37,9 @@ class Step {
37
37
  /** @member {string} */
38
38
  this.stack = ''
39
39
 
40
+ this.startTime = 0
41
+ this.endTime = 0
42
+
40
43
  this.setTrace()
41
44
  }
42
45
 
@@ -159,6 +162,51 @@ class Step {
159
162
  return this.constructor.name === 'MetaStep'
160
163
  }
161
164
 
165
+ get duration() {
166
+ if (!this.startTime || !this.endTime) return 0
167
+ return this.endTime - this.startTime
168
+ }
169
+
170
+ simplify() {
171
+ const step = this
172
+
173
+ const parent = {}
174
+ if (step.metaStep) {
175
+ parent.title = step.metaStep.actor
176
+ }
177
+
178
+ if (step.opts) {
179
+ Object.keys(step.opts).forEach(k => {
180
+ if (typeof step.opts[k] === 'object') delete step.opts[k]
181
+ if (typeof step.opts[k] === 'function') delete step.opts[k]
182
+ })
183
+ }
184
+
185
+ const args = []
186
+ if (step.args) {
187
+ for (const arg of step.args) {
188
+ // check if arg is a JOI object
189
+ if (arg && typeof arg === 'function') {
190
+ args.push(arg.name)
191
+ } else if (typeof arg == 'string') {
192
+ args.push(arg)
193
+ } else {
194
+ args.push(JSON.stringify(arg).slice(0, 300))
195
+ }
196
+ }
197
+ }
198
+
199
+ return {
200
+ opts: step.opts || {},
201
+ title: step.name,
202
+ args: args,
203
+ status: step.status,
204
+ startTime: step.startTime,
205
+ endTime: step.endTime,
206
+ parent,
207
+ }
208
+ }
209
+
162
210
  /** @return {boolean} */
163
211
  hasBDDAncestor() {
164
212
  let hasBDD = false
@@ -166,7 +214,7 @@ class Step {
166
214
  processingStep = this
167
215
 
168
216
  while (processingStep.metaStep) {
169
- if (processingStep.metaStep.actor.match(/^(Given|When|Then|And)/)) {
217
+ if (processingStep.metaStep.actor?.match(/^(Given|When|Then|And)/)) {
170
218
  hasBDD = true
171
219
  break
172
220
  } else {
@@ -0,0 +1,46 @@
1
+ const BaseStep = require('./base')
2
+ const store = require('../store')
3
+
4
+ /**
5
+ * Function executed as a step
6
+ */
7
+ class FuncStep extends BaseStep {
8
+ // this is actual function that should be executed within step
9
+ setCallable(fn) {
10
+ this.fn = fn
11
+ }
12
+
13
+ // helper is optional, if we need to allow step to access helper methods
14
+ setHelper(helper) {
15
+ this.helper = helper
16
+ }
17
+
18
+ run() {
19
+ if (!this.fn) throw new Error('Function is not set')
20
+
21
+ // we wrap that function to track time and status
22
+ // and disable it in dry run mode
23
+ this.args = Array.prototype.slice.call(arguments)
24
+ this.startTime = +Date.now()
25
+
26
+ if (store.dryRun) {
27
+ this.setStatus('success')
28
+ // should we add Proxy and dry run resolver here?
29
+ return Promise.resolve(true)
30
+ }
31
+
32
+ let result
33
+ try {
34
+ result = this.fn.apply(this.helper, this.args)
35
+ this.setStatus('success')
36
+ this.endTime = +Date.now()
37
+ } catch (err) {
38
+ this.endTime = +Date.now()
39
+ this.setStatus('failed')
40
+ throw err
41
+ }
42
+ return result
43
+ }
44
+ }
45
+
46
+ module.exports = FuncStep
@@ -16,6 +16,7 @@ class HelperStep extends Step {
16
16
  */
17
17
  run() {
18
18
  this.args = Array.prototype.slice.call(arguments)
19
+ this.startTime = +Date.now()
19
20
 
20
21
  if (store.dryRun) {
21
22
  this.setStatus('success')
@@ -27,7 +28,9 @@ class HelperStep extends Step {
27
28
  result = this.helper[this.helperMethod].apply(this.helper, this.args)
28
29
  }
29
30
  this.setStatus('success')
31
+ this.endTime = +Date.now()
30
32
  } catch (err) {
33
+ this.endTime = +Date.now()
31
34
  this.setStatus('failed')
32
35
  throw err
33
36
  }
package/lib/step/meta.js CHANGED
@@ -6,6 +6,10 @@ class MetaStep extends Step {
6
6
  constructor(actor, method) {
7
7
  if (!method) method = ''
8
8
  super(method)
9
+
10
+ /** @member {boolean} collsapsed hide children steps from output */
11
+ this.collapsed = false
12
+
9
13
  this.actor = actor
10
14
  }
11
15
 
@@ -32,7 +36,11 @@ class MetaStep extends Step {
32
36
  return `${this.prefix}${actorText} ${this.humanize()} ${this.humanizeArgs()}${this.suffix}`
33
37
  }
34
38
 
35
- return `On ${this.prefix}${actorText}: ${this.humanize()} ${this.humanizeArgs()}${this.suffix}`
39
+ if (!this.actor) {
40
+ return `${this.name} ${this.humanizeArgs()}${this.suffix}`.trim()
41
+ }
42
+
43
+ return `On ${this.prefix}${actorText}: ${this.humanize()} ${this.humanizeArgs()}${this.suffix}`.trim()
36
44
  }
37
45
 
38
46
  humanize() {
@@ -3,7 +3,7 @@ const recorder = require('../recorder')
3
3
  const StepConfig = require('./config')
4
4
  const { debug } = require('../output')
5
5
  const store = require('../store')
6
- const { TIMEOUT_ORDER } = require('./timeout')
6
+ const { TIMEOUT_ORDER } = require('../timeout')
7
7
  const retryStep = require('./retry')
8
8
  function recordStep(step, args) {
9
9
  step.status = 'queued'
@@ -40,7 +40,7 @@ function recordStep(step, args) {
40
40
  if (!step.startTime) {
41
41
  // step can be retries
42
42
  event.emit(event.step.started, step)
43
- step.startTime = Date.now()
43
+ step.startTime = +Date.now()
44
44
  }
45
45
  return (val = step.run(...args))
46
46
  },
@@ -52,15 +52,15 @@ function recordStep(step, args) {
52
52
  event.emit(event.step.after, step)
53
53
 
54
54
  recorder.add('step passed', () => {
55
- step.endTime = Date.now()
55
+ step.endTime = +Date.now()
56
56
  event.emit(event.step.passed, step, val)
57
57
  event.emit(event.step.finished, step)
58
58
  })
59
59
 
60
60
  recorder.catchWithoutStop(err => {
61
61
  step.status = 'failed'
62
- step.endTime = Date.now()
63
- event.emit(event.step.failed, step)
62
+ step.endTime = +Date.now()
63
+ event.emit(event.step.failed, step, err)
64
64
  event.emit(event.step.finished, step)
65
65
  throw err
66
66
  })
@@ -0,0 +1,55 @@
1
+ const MetaStep = require('./meta')
2
+ const event = require('../event')
3
+
4
+ let currentSection
5
+
6
+ class Section {
7
+ constructor(name = '') {
8
+ this.name = name
9
+
10
+ this.metaStep = new MetaStep(null, name)
11
+
12
+ this.attachMetaStep = step => {
13
+ if (currentSection !== this) return
14
+ if (!step) return
15
+ const metaStep = getRootMetaStep(step)
16
+
17
+ if (metaStep !== this.metaStep) {
18
+ metaStep.metaStep = this.metaStep
19
+ }
20
+ }
21
+ }
22
+
23
+ hidden() {
24
+ this.metaStep.collapsed = true
25
+ return this
26
+ }
27
+
28
+ start() {
29
+ if (currentSection) currentSection.end()
30
+ currentSection = this
31
+ event.dispatcher.prependListener(event.step.before, this.attachMetaStep)
32
+ event.dispatcher.once(event.test.finished, () => this.end())
33
+ return this
34
+ }
35
+
36
+ end() {
37
+ currentSection = null
38
+ event.dispatcher.off(event.step.started, this.attachMetaStep)
39
+ return this
40
+ }
41
+
42
+ /**
43
+ * @returns {Section}
44
+ */
45
+ static current() {
46
+ return currentSection
47
+ }
48
+ }
49
+
50
+ function getRootMetaStep(step) {
51
+ if (step.metaStep) return getRootMetaStep(step.metaStep)
52
+ return step
53
+ }
54
+
55
+ module.exports = Section
package/lib/step.js CHANGED
@@ -14,7 +14,13 @@ const Step = require('./step/helper')
14
14
  */
15
15
  const MetaStep = require('./step/meta')
16
16
 
17
+ /**
18
+ * Step used to execute a single function
19
+ */
20
+ const FuncStep = require('./step/func')
21
+
17
22
  module.exports = Step
18
23
  module.exports.MetaStep = MetaStep
19
24
  module.exports.BaseStep = BaseStep
20
25
  module.exports.StepConfig = StepConfig
26
+ module.exports.FuncStep = FuncStep
package/lib/steps.js CHANGED
@@ -1,5 +1,5 @@
1
1
  const StepConfig = require('./step/config')
2
-
2
+ const Section = require('./step/section')
3
3
  function stepOpts(opts = {}) {
4
4
  return new StepConfig(opts)
5
5
  }
@@ -12,12 +12,39 @@ function stepRetry(retry) {
12
12
  return new StepConfig().retry(retry)
13
13
  }
14
14
 
15
+ function section(name) {
16
+ if (!name) return endSection()
17
+ return new Section(name).start()
18
+ }
19
+
20
+ function endSection() {
21
+ return Section.current().end()
22
+ }
23
+
15
24
  // Section function to be added here
16
25
 
17
26
  const step = {
27
+ // steps.opts syntax
18
28
  opts: stepOpts,
19
29
  timeout: stepTimeout,
20
30
  retry: stepRetry,
31
+
32
+ // one-function syntax
33
+ stepTimeout,
34
+ stepRetry,
35
+ stepOpts,
36
+
37
+ // sections
38
+ section,
39
+ endSection,
40
+
41
+ Section: section,
42
+ EndSection: endSection,
43
+
44
+ // shortcuts
45
+ Given: () => section('Given'),
46
+ When: () => section('When'),
47
+ Then: () => section('Then'),
21
48
  }
22
49
 
23
50
  module.exports = step
package/lib/store.js CHANGED
@@ -15,6 +15,8 @@ const store = {
15
15
  currentTest: null,
16
16
  /** @type {any} */
17
17
  currentStep: null,
18
+ /** @type {CodeceptJS.Suite | null} */
19
+ currentSuite: null,
18
20
  }
19
21
 
20
22
  module.exports = store