codeceptjs 4.0.0-beta.4 → 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.
Files changed (150) hide show
  1. package/README.md +134 -119
  2. package/bin/codecept.js +12 -2
  3. package/bin/test-server.js +53 -0
  4. package/docs/webapi/clearCookie.mustache +1 -1
  5. package/lib/actor.js +66 -102
  6. package/lib/ai.js +130 -121
  7. package/lib/assert/empty.js +3 -5
  8. package/lib/assert/equal.js +4 -7
  9. package/lib/assert/include.js +4 -6
  10. package/lib/assert/throws.js +2 -4
  11. package/lib/assert/truth.js +2 -2
  12. package/lib/codecept.js +139 -87
  13. package/lib/command/check.js +201 -0
  14. package/lib/command/configMigrate.js +2 -4
  15. package/lib/command/definitions.js +8 -26
  16. package/lib/command/generate.js +10 -14
  17. package/lib/command/gherkin/snippets.js +75 -73
  18. package/lib/command/gherkin/steps.js +1 -1
  19. package/lib/command/info.js +42 -8
  20. package/lib/command/init.js +13 -12
  21. package/lib/command/interactive.js +10 -2
  22. package/lib/command/list.js +1 -1
  23. package/lib/command/run-multiple/chunk.js +48 -45
  24. package/lib/command/run-multiple.js +12 -35
  25. package/lib/command/run-workers.js +21 -58
  26. package/lib/command/utils.js +5 -6
  27. package/lib/command/workers/runTests.js +262 -220
  28. package/lib/container.js +386 -238
  29. package/lib/data/context.js +10 -13
  30. package/lib/data/dataScenarioConfig.js +8 -8
  31. package/lib/data/dataTableArgument.js +6 -6
  32. package/lib/data/table.js +5 -11
  33. package/lib/effects.js +223 -0
  34. package/lib/element/WebElement.js +327 -0
  35. package/lib/els.js +158 -0
  36. package/lib/event.js +21 -17
  37. package/lib/heal.js +88 -80
  38. package/lib/helper/AI.js +2 -1
  39. package/lib/helper/ApiDataFactory.js +3 -6
  40. package/lib/helper/Appium.js +47 -51
  41. package/lib/helper/FileSystem.js +3 -3
  42. package/lib/helper/GraphQLDataFactory.js +3 -3
  43. package/lib/helper/JSONResponse.js +75 -37
  44. package/lib/helper/Mochawesome.js +31 -9
  45. package/lib/helper/Nightmare.js +35 -53
  46. package/lib/helper/Playwright.js +262 -267
  47. package/lib/helper/Protractor.js +54 -77
  48. package/lib/helper/Puppeteer.js +246 -260
  49. package/lib/helper/REST.js +5 -17
  50. package/lib/helper/TestCafe.js +21 -44
  51. package/lib/helper/WebDriver.js +151 -170
  52. package/lib/helper/extras/Popup.js +22 -22
  53. package/lib/helper/testcafe/testcafe-utils.js +26 -27
  54. package/lib/listener/emptyRun.js +55 -0
  55. package/lib/listener/exit.js +7 -10
  56. package/lib/listener/{retry.js → globalRetry.js} +5 -5
  57. package/lib/listener/globalTimeout.js +165 -0
  58. package/lib/listener/helpers.js +15 -15
  59. package/lib/listener/mocha.js +1 -1
  60. package/lib/listener/result.js +12 -0
  61. package/lib/listener/retryEnhancer.js +85 -0
  62. package/lib/listener/steps.js +32 -18
  63. package/lib/listener/store.js +20 -0
  64. package/lib/mocha/asyncWrapper.js +231 -0
  65. package/lib/{interfaces → mocha}/bdd.js +3 -3
  66. package/lib/mocha/cli.js +308 -0
  67. package/lib/mocha/factory.js +104 -0
  68. package/lib/{interfaces → mocha}/featureConfig.js +32 -12
  69. package/lib/{interfaces → mocha}/gherkin.js +26 -28
  70. package/lib/mocha/hooks.js +112 -0
  71. package/lib/mocha/index.js +12 -0
  72. package/lib/mocha/inject.js +29 -0
  73. package/lib/{interfaces → mocha}/scenarioConfig.js +31 -7
  74. package/lib/mocha/suite.js +82 -0
  75. package/lib/mocha/test.js +181 -0
  76. package/lib/mocha/types.d.ts +42 -0
  77. package/lib/mocha/ui.js +232 -0
  78. package/lib/output.js +82 -62
  79. package/lib/pause.js +160 -138
  80. package/lib/plugin/analyze.js +396 -0
  81. package/lib/plugin/auth.js +435 -0
  82. package/lib/plugin/autoDelay.js +8 -8
  83. package/lib/plugin/autoLogin.js +3 -338
  84. package/lib/plugin/commentStep.js +6 -1
  85. package/lib/plugin/coverage.js +10 -19
  86. package/lib/plugin/customLocator.js +3 -3
  87. package/lib/plugin/customReporter.js +52 -0
  88. package/lib/plugin/eachElement.js +1 -1
  89. package/lib/plugin/fakerTransform.js +1 -1
  90. package/lib/plugin/heal.js +36 -9
  91. package/lib/plugin/htmlReporter.js +1947 -0
  92. package/lib/plugin/pageInfo.js +140 -0
  93. package/lib/plugin/retryFailedStep.js +17 -18
  94. package/lib/plugin/retryTo.js +2 -113
  95. package/lib/plugin/screenshotOnFail.js +17 -58
  96. package/lib/plugin/selenoid.js +15 -35
  97. package/lib/plugin/standardActingHelpers.js +4 -1
  98. package/lib/plugin/stepByStepReport.js +56 -17
  99. package/lib/plugin/stepTimeout.js +5 -12
  100. package/lib/plugin/subtitles.js +4 -4
  101. package/lib/plugin/tryTo.js +3 -102
  102. package/lib/plugin/wdio.js +8 -10
  103. package/lib/recorder.js +155 -124
  104. package/lib/rerun.js +43 -42
  105. package/lib/result.js +161 -0
  106. package/lib/secret.js +1 -1
  107. package/lib/step/base.js +239 -0
  108. package/lib/step/comment.js +10 -0
  109. package/lib/step/config.js +50 -0
  110. package/lib/step/func.js +46 -0
  111. package/lib/step/helper.js +50 -0
  112. package/lib/step/meta.js +99 -0
  113. package/lib/step/record.js +74 -0
  114. package/lib/step/retry.js +11 -0
  115. package/lib/step/section.js +55 -0
  116. package/lib/step.js +21 -332
  117. package/lib/steps.js +50 -0
  118. package/lib/store.js +37 -5
  119. package/lib/template/heal.js +2 -11
  120. package/lib/test-server.js +323 -0
  121. package/lib/timeout.js +66 -0
  122. package/lib/utils.js +351 -218
  123. package/lib/within.js +75 -55
  124. package/lib/workerStorage.js +2 -1
  125. package/lib/workers.js +386 -276
  126. package/package.json +76 -70
  127. package/translations/de-DE.js +4 -3
  128. package/translations/fr-FR.js +4 -3
  129. package/translations/index.js +1 -0
  130. package/translations/it-IT.js +4 -3
  131. package/translations/ja-JP.js +4 -3
  132. package/translations/nl-NL.js +76 -0
  133. package/translations/pl-PL.js +4 -3
  134. package/translations/pt-BR.js +4 -3
  135. package/translations/ru-RU.js +4 -3
  136. package/translations/utils.js +9 -0
  137. package/translations/zh-CN.js +4 -3
  138. package/translations/zh-TW.js +4 -3
  139. package/typings/index.d.ts +188 -186
  140. package/typings/promiseBasedTypes.d.ts +18 -705
  141. package/typings/types.d.ts +301 -804
  142. package/lib/cli.js +0 -256
  143. package/lib/helper/ExpectHelper.js +0 -391
  144. package/lib/helper/SoftExpectHelper.js +0 -381
  145. package/lib/listener/artifacts.js +0 -19
  146. package/lib/listener/timeout.js +0 -109
  147. package/lib/mochaFactory.js +0 -113
  148. package/lib/plugin/debugErrors.js +0 -67
  149. package/lib/scenario.js +0 -224
  150. package/lib/ui.js +0 -236
@@ -0,0 +1,181 @@
1
+ const Test = require('mocha/lib/test')
2
+ const Suite = require('mocha/lib/suite')
3
+ const { test: testWrapper } = require('./asyncWrapper')
4
+ const { enhanceMochaSuite, createSuite } = require('./suite')
5
+ const { genTestId, serializeError, clearString, relativeDir } = require('../utils')
6
+ const Step = require('../step/base')
7
+ /**
8
+ * Factory function to create enhanced tests
9
+ * @param {string} title - Test title
10
+ * @param {Function} fn - Test function
11
+ * @returns {CodeceptJS.Test & Mocha.Test} New enhanced test instance
12
+ */
13
+ function createTest(title, fn) {
14
+ const test = new Test(title, fn)
15
+ return enhanceMochaTest(test)
16
+ }
17
+
18
+ /**
19
+ * Enhances Mocha Test with CodeceptJS specific functionality using composition
20
+ * @param {CodeceptJS.Test & Mocha.Test} test - Test instance to enhance
21
+ * @returns {CodeceptJS.Test & Mocha.Test} Enhanced test instance
22
+ */
23
+ function enhanceMochaTest(test) {
24
+ // if no test, create a dummy one
25
+ if (!test) test = createTest('...', () => {})
26
+ // already enhanced
27
+ if (test.codeceptjs) return test
28
+
29
+ test.codeceptjs = true
30
+ // Add properties
31
+ test.tags = test.title.match(/(\@[a-zA-Z0-9-_]+)/g) || []
32
+ test.steps = []
33
+ test.config = {}
34
+ test.artifacts = []
35
+ test.inject = {}
36
+ test.opts = {}
37
+ test.meta = {}
38
+
39
+ test.notes = []
40
+ test.addNote = (type, note) => {
41
+ test.notes.push({ type, text: note })
42
+ }
43
+
44
+ // Add new methods
45
+ /**
46
+ * @param {Mocha.Suite} suite - The Mocha suite to add this test to
47
+ */
48
+ test.addToSuite = function (suite) {
49
+ enhanceMochaSuite(suite)
50
+ suite.addTest(testWrapper(this))
51
+ if (test.file && !suite.file) suite.file = test.file
52
+ test.tags = [...(test.tags || []), ...(suite.tags || [])]
53
+ test.fullTitle = () => `${suite.title}: ${test.title}`
54
+ test.uid = genTestId(test)
55
+ }
56
+
57
+ test.applyOptions = function (opts) {
58
+ if (!opts) opts = {}
59
+ test.opts = opts
60
+ test.meta = opts.meta || {}
61
+ test.totalTimeout = opts.timeout
62
+ if (opts.retries) this.retries(opts.retries)
63
+ }
64
+
65
+ test.simplify = function () {
66
+ return serializeTest(this)
67
+ }
68
+
69
+ return test
70
+ }
71
+
72
+ function deserializeTest(test) {
73
+ test = Object.assign(
74
+ createTest(test.title || '', () => {}),
75
+ test,
76
+ )
77
+ test.parent = Object.assign(new Suite(test.parent?.title || 'Suite'), test.parent)
78
+ enhanceMochaSuite(test.parent)
79
+ if (test.steps) test.steps = test.steps.map(step => Object.assign(new Step(step.title), step))
80
+
81
+ // Restore the custom fullTitle function to maintain consistency with original test
82
+ if (test.parent) {
83
+ test.fullTitle = () => `${test.parent.title}: ${test.title}`
84
+ }
85
+
86
+ return test
87
+ }
88
+
89
+ function serializeTest(test, error = null) {
90
+ // test = { ...test }
91
+
92
+ if (test.start && !test.duration) {
93
+ const end = +new Date()
94
+ test.duration = end - test.start
95
+ }
96
+
97
+ let err
98
+
99
+ if (test.err) {
100
+ err = serializeError(test.err)
101
+ test.state = 'failed'
102
+ } else if (error) {
103
+ err = serializeError(error)
104
+ test.state = 'failed'
105
+ }
106
+ const parent = {}
107
+ if (test.parent) {
108
+ parent.title = test.parent.title
109
+ }
110
+
111
+ if (test.opts) {
112
+ Object.keys(test.opts).forEach(k => {
113
+ if (typeof test.opts[k] === 'object') delete test.opts[k]
114
+ if (typeof test.opts[k] === 'function') delete test.opts[k]
115
+ })
116
+ }
117
+
118
+ let steps = undefined
119
+ if (Array.isArray(test.steps)) {
120
+ steps = test.steps.map(step => (step.simplify ? step.simplify() : step))
121
+ }
122
+
123
+ return {
124
+ opts: test.opts || {},
125
+ tags: test.tags || [],
126
+ uid: test.uid,
127
+ retries: test._retries,
128
+ title: test.title,
129
+ state: test.state,
130
+ notes: test.notes || [],
131
+ meta: test.meta || {},
132
+ artifacts: test.artifacts || {},
133
+ duration: test.duration || 0,
134
+ err,
135
+ parent,
136
+ steps,
137
+ }
138
+ }
139
+
140
+ function cloneTest(test) {
141
+ return deserializeTest(serializeTest(test))
142
+ }
143
+
144
+ /**
145
+ * Get a filename from the test object
146
+ * @param {CodeceptJS.Test} test
147
+ * @param {Object} options
148
+ * @param {string} options.suffix Add a suffix to the filename
149
+ * @param {boolean} options.unique Add a unique suffix to the file
150
+ *
151
+ * @returns {string} the filename
152
+ */
153
+ function testToFileName(test, { suffix = '', unique = false } = {}) {
154
+ let fileName = test.title
155
+
156
+ if (unique) fileName = `${fileName}_${test?.uid || Math.floor(new Date().getTime() / 1000)}`
157
+ if (suffix) fileName = `${fileName}_${suffix}`
158
+ // remove tags with empty string (disable for now)
159
+ // fileName = fileName.replace(/\@\w+/g, '')
160
+ fileName = fileName.slice(0, 100)
161
+ if (fileName.indexOf('{') !== -1) {
162
+ fileName = fileName.substr(0, fileName.indexOf('{') - 3).trim()
163
+ }
164
+ if (test.ctx && test.ctx.test && test.ctx.test.type === 'hook') fileName = clearString(`${test.title}_${test.ctx.test.title}`)
165
+ // TODO: add suite title to file name
166
+ // if (test.parent && test.parent.title) {
167
+ // fileName = `${clearString(test.parent.title)}_${fileName}`
168
+ // }
169
+ fileName = clearString(fileName).slice(0, 100)
170
+
171
+ return fileName
172
+ }
173
+
174
+ module.exports = {
175
+ createTest,
176
+ testToFileName,
177
+ enhanceMochaTest,
178
+ serializeTest,
179
+ deserializeTest,
180
+ cloneTest,
181
+ }
@@ -0,0 +1,42 @@
1
+ import { Test as MochaTest, Suite as MochaSuite } from 'mocha'
2
+
3
+ declare global {
4
+ namespace CodeceptJS {
5
+ interface Test extends MochaTest {
6
+ uid: string
7
+ title: string
8
+ tags: string[]
9
+ steps: string[]
10
+ meta: Record<string, any>
11
+ notes: Array<{
12
+ type: string
13
+ text: string
14
+ }>
15
+ state: string
16
+ err?: Error
17
+ config: Record<string, any>
18
+ artifacts: string[]
19
+ inject: Record<string, any>
20
+ opts: Record<string, any>
21
+ throws?: Error | string | RegExp | Function
22
+ totalTimeout?: number
23
+ relativeFile?: string
24
+ addToSuite(suite: Mocha.Suite): void
25
+ applyOptions(opts: Record<string, any>): void
26
+ simplify(): Record<string, any>
27
+ toFileName(): string
28
+ addNote(type: string, note: string): void
29
+ codeceptjs: boolean
30
+ }
31
+
32
+ interface Suite extends MochaSuite {
33
+ title: string
34
+ tags: string[]
35
+ opts: Record<string, any>
36
+ totalTimeout?: number
37
+ addTest(test: Test): void
38
+ applyOptions(opts: Record<string, any>): void
39
+ codeceptjs: boolean
40
+ }
41
+ }
42
+ }
@@ -0,0 +1,232 @@
1
+ const escapeRe = require('escape-string-regexp')
2
+ const { test, setup, teardown, suiteSetup, suiteTeardown, injected } = require('./asyncWrapper')
3
+ const ScenarioConfig = require('./scenarioConfig')
4
+ const FeatureConfig = require('./featureConfig')
5
+ const addDataContext = require('../data/context')
6
+ const { createTest } = require('./test')
7
+ const { createSuite } = require('./suite')
8
+ const { HookConfig, AfterSuiteHook, AfterHook, BeforeSuiteHook, BeforeHook } = require('./hooks')
9
+
10
+ const setContextTranslation = context => {
11
+ const container = require('../container')
12
+ const contexts = container.translation().value('contexts')
13
+
14
+ if (contexts) {
15
+ for (const key of Object.keys(contexts)) {
16
+ if (context[key]) {
17
+ context[contexts[key]] = context[key]
18
+ }
19
+ }
20
+ }
21
+ }
22
+
23
+ /**
24
+ * Codecept-style interface:
25
+ *
26
+ * Feature('login');
27
+ *
28
+ * Scenario('login as regular user', ({I}) {
29
+ * I.fillField();
30
+ * I.click();
31
+ * I.see('Hello, '+data.login);
32
+ * });
33
+ *
34
+ * @param {Mocha.Suite} suite Root suite.
35
+ * @ignore
36
+ */
37
+ module.exports = function (suite) {
38
+ const suites = [suite]
39
+ suite.timeout(0)
40
+ let afterAllHooks
41
+ let afterEachHooks
42
+ let afterAllHooksAreLoaded
43
+ let afterEachHooksAreLoaded
44
+
45
+ suite.on('pre-require', (context, file, mocha) => {
46
+ const common = require('mocha/lib/interfaces/common')(suites, context, mocha)
47
+
48
+ const addScenario = function (title, opts = {}, fn) {
49
+ const suite = suites[0]
50
+
51
+ if (typeof opts === 'function' && !fn) {
52
+ fn = opts
53
+ opts = {}
54
+ }
55
+ if (suite.pending) {
56
+ fn = null
57
+ }
58
+ const test = createTest(title, fn)
59
+ test.file = file
60
+ test.addToSuite(suite)
61
+ test.applyOptions(opts)
62
+
63
+ return new ScenarioConfig(test)
64
+ }
65
+
66
+ // create dispatcher
67
+
68
+ context.BeforeAll = common.before
69
+ context.AfterAll = common.after
70
+
71
+ context.run = mocha.options.delay && common.runWithSuite(suite)
72
+ /**
73
+ * Describe a "suite" with the given `title`
74
+ * and callback `fn` containing nested suites
75
+ * and/or tests.
76
+ * @global
77
+ * @param {string} title
78
+ * @param {Object<string, *>} [opts]
79
+ * @returns {FeatureConfig}
80
+ */
81
+
82
+ context.Feature = function (title, opts) {
83
+ if (suites.length > 1) {
84
+ suites.shift()
85
+ }
86
+
87
+ afterAllHooks = []
88
+ afterEachHooks = []
89
+ afterAllHooksAreLoaded = false
90
+ afterEachHooksAreLoaded = false
91
+
92
+ const suite = createSuite(suites[0], title)
93
+ suite.applyOptions(opts)
94
+
95
+ suite.file = file
96
+ suites.unshift(suite)
97
+ suite.beforeEach('codeceptjs.before', () => setup(suite))
98
+ afterEachHooks.push(['finalize codeceptjs', () => teardown(suite)])
99
+
100
+ suite.beforeAll('codeceptjs.beforeSuite', () => suiteSetup(suite))
101
+ afterAllHooks.push(['codeceptjs.afterSuite', () => suiteTeardown(suite)])
102
+
103
+ return new FeatureConfig(suite)
104
+ }
105
+
106
+ /**
107
+ * Exclusive test suite - runs only this feature.
108
+ * @global
109
+ * @kind constant
110
+ * @type {CodeceptJS.IFeature}
111
+ */
112
+ context.Feature.only = function (title, opts) {
113
+ const reString = `^${escapeRe(`${title}:`)}`
114
+ mocha.grep(new RegExp(reString))
115
+ process.env.FEATURE_ONLY = true
116
+ return context.Feature(title, opts)
117
+ }
118
+
119
+ /**
120
+ * Pending test suite.
121
+ * @global
122
+ * @kind constant
123
+ * @type {CodeceptJS.IFeature}
124
+ */
125
+ context.xFeature = context.Feature.skip = function (title, opts) {
126
+ const skipInfo = {
127
+ skipped: true,
128
+ message: 'Skipped due to "skip" on Feature.',
129
+ }
130
+ return context.Feature(title, { ...opts, skipInfo })
131
+ }
132
+
133
+ context.BeforeSuite = function (fn) {
134
+ suites[0].beforeAll('BeforeSuite', injected(fn, suites[0], 'beforeSuite'))
135
+ return new HookConfig(new BeforeSuiteHook({ suite: suites[0] }))
136
+ }
137
+
138
+ context.AfterSuite = function (fn) {
139
+ afterAllHooks.unshift(['AfterSuite', injected(fn, suites[0], 'afterSuite')])
140
+ return new HookConfig(new AfterSuiteHook({ suite: suites[0] }))
141
+ }
142
+
143
+ context.Background = context.Before = function (fn) {
144
+ suites[0].beforeEach('Before', injected(fn, suites[0], 'before'))
145
+ return new HookConfig(new BeforeHook({ suite: suites[0] }))
146
+ }
147
+
148
+ context.After = function (fn) {
149
+ afterEachHooks.unshift(['After', injected(fn, suites[0], 'after')])
150
+ return new HookConfig(new AfterHook({ suite: suites[0] }))
151
+ }
152
+
153
+ /**
154
+ * Describe a specification or test-case
155
+ * with the given `title` and callback `fn`
156
+ * acting as a thunk.
157
+ * @ignore
158
+ */
159
+ context.Scenario = addScenario
160
+ /**
161
+ * Exclusive test-case.
162
+ * @ignore
163
+ */
164
+ context.Scenario.only = function (title, opts, fn) {
165
+ const reString = `^${escapeRe(`${suites[0].title}: ${title}`.replace(/( \| {.+})?$/g, ''))}`
166
+ mocha.grep(new RegExp(reString))
167
+ process.env.SCENARIO_ONLY = true
168
+ return addScenario(title, opts, fn)
169
+ }
170
+
171
+ /**
172
+ * Pending test case.
173
+ * @global
174
+ * @kind constant
175
+ * @type {CodeceptJS.IScenario}
176
+ */
177
+ context.xScenario = context.Scenario.skip = function (title, opts = {}, fn) {
178
+ if (typeof opts === 'function' && !fn) {
179
+ opts = {}
180
+ }
181
+
182
+ return context.Scenario(title, opts)
183
+ }
184
+
185
+ /**
186
+ * Pending test case with message: 'Test not implemented!'.
187
+ * @global
188
+ * @kind constant
189
+ * @type {CodeceptJS.IScenario}
190
+ */
191
+ context.Scenario.todo = function (title, opts = {}, fn) {
192
+ if (typeof opts === 'function' && !fn) {
193
+ fn = opts
194
+ opts = {}
195
+ }
196
+
197
+ const skipInfo = {
198
+ message: 'Test not implemented!',
199
+ description: fn ? fn.toString() : '',
200
+ }
201
+
202
+ return context.Scenario(title, { ...opts, skipInfo })
203
+ }
204
+
205
+ /**
206
+ * For translation
207
+ */
208
+
209
+ setContextTranslation(context)
210
+
211
+ addDataContext(context)
212
+ })
213
+
214
+ suite.on('post-require', () => {
215
+ /**
216
+ * load hooks from arrays to suite to prevent reordering
217
+ */
218
+ if (!afterEachHooksAreLoaded && Array.isArray(afterEachHooks)) {
219
+ afterEachHooks.forEach(hook => {
220
+ suites[0].afterEach(hook[0], hook[1])
221
+ })
222
+ afterEachHooksAreLoaded = true
223
+ }
224
+
225
+ if (!afterAllHooksAreLoaded && Array.isArray(afterAllHooks)) {
226
+ afterAllHooks.forEach(hook => {
227
+ suites[0].afterAll(hook[0], hook[1])
228
+ })
229
+ afterAllHooksAreLoaded = true
230
+ }
231
+ })
232
+ }