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.
Files changed (155) 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 +141 -86
  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/dryRun.js +30 -35
  17. package/lib/command/generate.js +10 -14
  18. package/lib/command/gherkin/snippets.js +75 -73
  19. package/lib/command/gherkin/steps.js +1 -1
  20. package/lib/command/info.js +42 -8
  21. package/lib/command/init.js +13 -12
  22. package/lib/command/interactive.js +10 -2
  23. package/lib/command/list.js +1 -1
  24. package/lib/command/run-multiple/chunk.js +48 -45
  25. package/lib/command/run-multiple.js +12 -35
  26. package/lib/command/run-workers.js +21 -58
  27. package/lib/command/utils.js +5 -6
  28. package/lib/command/workers/runTests.js +263 -222
  29. package/lib/container.js +386 -238
  30. package/lib/data/context.js +10 -13
  31. package/lib/data/dataScenarioConfig.js +8 -8
  32. package/lib/data/dataTableArgument.js +6 -6
  33. package/lib/data/table.js +5 -11
  34. package/lib/effects.js +223 -0
  35. package/lib/element/WebElement.js +327 -0
  36. package/lib/els.js +158 -0
  37. package/lib/event.js +21 -17
  38. package/lib/heal.js +88 -80
  39. package/lib/helper/AI.js +2 -1
  40. package/lib/helper/ApiDataFactory.js +4 -7
  41. package/lib/helper/Appium.js +50 -57
  42. package/lib/helper/FileSystem.js +3 -3
  43. package/lib/helper/GraphQLDataFactory.js +4 -4
  44. package/lib/helper/JSONResponse.js +75 -37
  45. package/lib/helper/Mochawesome.js +31 -9
  46. package/lib/helper/Nightmare.js +37 -58
  47. package/lib/helper/Playwright.js +267 -272
  48. package/lib/helper/Protractor.js +56 -87
  49. package/lib/helper/Puppeteer.js +247 -264
  50. package/lib/helper/REST.js +29 -17
  51. package/lib/helper/TestCafe.js +22 -47
  52. package/lib/helper/WebDriver.js +157 -368
  53. package/lib/helper/extras/PlaywrightPropEngine.js +2 -2
  54. package/lib/helper/extras/Popup.js +22 -22
  55. package/lib/helper/network/utils.js +1 -1
  56. package/lib/helper/testcafe/testcafe-utils.js +27 -28
  57. package/lib/listener/emptyRun.js +55 -0
  58. package/lib/listener/exit.js +7 -10
  59. package/lib/listener/{retry.js → globalRetry.js} +5 -5
  60. package/lib/listener/globalTimeout.js +165 -0
  61. package/lib/listener/helpers.js +15 -15
  62. package/lib/listener/mocha.js +1 -1
  63. package/lib/listener/result.js +12 -0
  64. package/lib/listener/retryEnhancer.js +85 -0
  65. package/lib/listener/steps.js +32 -18
  66. package/lib/listener/store.js +20 -0
  67. package/lib/locator.js +1 -1
  68. package/lib/mocha/asyncWrapper.js +231 -0
  69. package/lib/{interfaces → mocha}/bdd.js +3 -3
  70. package/lib/mocha/cli.js +308 -0
  71. package/lib/mocha/factory.js +104 -0
  72. package/lib/{interfaces → mocha}/featureConfig.js +32 -12
  73. package/lib/{interfaces → mocha}/gherkin.js +26 -28
  74. package/lib/mocha/hooks.js +112 -0
  75. package/lib/mocha/index.js +12 -0
  76. package/lib/mocha/inject.js +29 -0
  77. package/lib/{interfaces → mocha}/scenarioConfig.js +31 -7
  78. package/lib/mocha/suite.js +82 -0
  79. package/lib/mocha/test.js +181 -0
  80. package/lib/mocha/types.d.ts +42 -0
  81. package/lib/mocha/ui.js +232 -0
  82. package/lib/output.js +93 -65
  83. package/lib/pause.js +160 -138
  84. package/lib/plugin/analyze.js +396 -0
  85. package/lib/plugin/auth.js +435 -0
  86. package/lib/plugin/autoDelay.js +8 -8
  87. package/lib/plugin/autoLogin.js +3 -338
  88. package/lib/plugin/commentStep.js +6 -1
  89. package/lib/plugin/coverage.js +10 -22
  90. package/lib/plugin/customLocator.js +3 -3
  91. package/lib/plugin/customReporter.js +52 -0
  92. package/lib/plugin/eachElement.js +1 -1
  93. package/lib/plugin/fakerTransform.js +1 -1
  94. package/lib/plugin/heal.js +36 -9
  95. package/lib/plugin/htmlReporter.js +1947 -0
  96. package/lib/plugin/pageInfo.js +140 -0
  97. package/lib/plugin/retryFailedStep.js +17 -18
  98. package/lib/plugin/retryTo.js +2 -113
  99. package/lib/plugin/screenshotOnFail.js +17 -58
  100. package/lib/plugin/selenoid.js +15 -35
  101. package/lib/plugin/standardActingHelpers.js +4 -1
  102. package/lib/plugin/stepByStepReport.js +56 -17
  103. package/lib/plugin/stepTimeout.js +5 -12
  104. package/lib/plugin/subtitles.js +4 -4
  105. package/lib/plugin/tryTo.js +3 -102
  106. package/lib/plugin/wdio.js +8 -10
  107. package/lib/recorder.js +155 -124
  108. package/lib/rerun.js +43 -42
  109. package/lib/result.js +161 -0
  110. package/lib/secret.js +1 -2
  111. package/lib/step/base.js +239 -0
  112. package/lib/step/comment.js +10 -0
  113. package/lib/step/config.js +50 -0
  114. package/lib/step/func.js +46 -0
  115. package/lib/step/helper.js +50 -0
  116. package/lib/step/meta.js +99 -0
  117. package/lib/step/record.js +74 -0
  118. package/lib/step/retry.js +11 -0
  119. package/lib/step/section.js +55 -0
  120. package/lib/step.js +21 -332
  121. package/lib/steps.js +50 -0
  122. package/lib/store.js +37 -5
  123. package/lib/template/heal.js +2 -11
  124. package/lib/test-server.js +323 -0
  125. package/lib/timeout.js +66 -0
  126. package/lib/utils.js +351 -218
  127. package/lib/within.js +75 -55
  128. package/lib/workerStorage.js +2 -1
  129. package/lib/workers.js +386 -277
  130. package/package.json +81 -75
  131. package/translations/de-DE.js +5 -3
  132. package/translations/fr-FR.js +5 -4
  133. package/translations/index.js +1 -0
  134. package/translations/it-IT.js +4 -3
  135. package/translations/ja-JP.js +4 -3
  136. package/translations/nl-NL.js +76 -0
  137. package/translations/pl-PL.js +4 -3
  138. package/translations/pt-BR.js +4 -3
  139. package/translations/ru-RU.js +4 -3
  140. package/translations/utils.js +9 -0
  141. package/translations/zh-CN.js +4 -3
  142. package/translations/zh-TW.js +4 -3
  143. package/typings/index.d.ts +197 -187
  144. package/typings/promiseBasedTypes.d.ts +53 -903
  145. package/typings/types.d.ts +372 -1042
  146. package/lib/cli.js +0 -257
  147. package/lib/helper/ExpectHelper.js +0 -391
  148. package/lib/helper/MockServer.js +0 -221
  149. package/lib/helper/SoftExpectHelper.js +0 -381
  150. package/lib/listener/artifacts.js +0 -19
  151. package/lib/listener/timeout.js +0 -109
  152. package/lib/mochaFactory.js +0 -113
  153. package/lib/plugin/debugErrors.js +0 -67
  154. package/lib/scenario.js +0 -224
  155. package/lib/ui.js +0 -236
@@ -0,0 +1,323 @@
1
+ const express = require('express')
2
+ const fs = require('fs')
3
+ const path = require('path')
4
+
5
+ /**
6
+ * Internal API test server to replace json-server dependency
7
+ * Provides REST API endpoints for testing CodeceptJS helpers
8
+ */
9
+ class TestServer {
10
+ constructor(config = {}) {
11
+ this.app = express()
12
+ this.server = null
13
+ this.port = config.port || 8010
14
+ this.host = config.host || 'localhost'
15
+ this.dbFile = config.dbFile || path.join(__dirname, '../test/data/rest/db.json')
16
+ this.lastModified = null
17
+ this.data = this.loadData()
18
+
19
+ this.setupMiddleware()
20
+ this.setupRoutes()
21
+ this.setupFileWatcher()
22
+ }
23
+
24
+ loadData() {
25
+ try {
26
+ const content = fs.readFileSync(this.dbFile, 'utf8')
27
+ const data = JSON.parse(content)
28
+ // Update lastModified time when loading data
29
+ if (fs.existsSync(this.dbFile)) {
30
+ this.lastModified = fs.statSync(this.dbFile).mtime
31
+ }
32
+ console.log('[Data Load] Loaded data from file:', JSON.stringify(data))
33
+ return data
34
+ } catch (err) {
35
+ console.warn(`[Data Load] Could not load data file ${this.dbFile}:`, err.message)
36
+ console.log('[Data Load] Using fallback default data')
37
+ return {
38
+ posts: [{ id: 1, title: 'json-server', author: 'davert' }],
39
+ user: { name: 'john', password: '123456' },
40
+ }
41
+ }
42
+ }
43
+
44
+ reloadData() {
45
+ console.log('[Reload] Reloading data from file...')
46
+ this.data = this.loadData()
47
+ console.log('[Reload] Data reloaded successfully')
48
+ return this.data
49
+ }
50
+
51
+ saveData() {
52
+ try {
53
+ fs.writeFileSync(this.dbFile, JSON.stringify(this.data, null, 2))
54
+ console.log('[Save] Data saved to file')
55
+ // Force update modification time to ensure auto-reload works
56
+ const now = new Date()
57
+ fs.utimesSync(this.dbFile, now, now)
58
+ this.lastModified = now
59
+ console.log('[Save] File modification time updated')
60
+ } catch (err) {
61
+ console.warn(`[Save] Could not save data file ${this.dbFile}:`, err.message)
62
+ }
63
+ }
64
+
65
+ setupMiddleware() {
66
+ // Parse JSON bodies
67
+ this.app.use(express.json())
68
+
69
+ // Parse URL-encoded bodies
70
+ this.app.use(express.urlencoded({ extended: true }))
71
+
72
+ // CORS support
73
+ this.app.use((req, res, next) => {
74
+ res.header('Access-Control-Allow-Origin', '*')
75
+ res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE, OPTIONS')
76
+ res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, Authorization, X-Test')
77
+
78
+ if (req.method === 'OPTIONS') {
79
+ res.status(200).end()
80
+ return
81
+ }
82
+ next()
83
+ })
84
+
85
+ // Auto-reload middleware - check if file changed before each request
86
+ this.app.use((req, res, next) => {
87
+ try {
88
+ if (fs.existsSync(this.dbFile)) {
89
+ const stats = fs.statSync(this.dbFile)
90
+ if (!this.lastModified || stats.mtime > this.lastModified) {
91
+ console.log(`[Auto-reload] Database file changed (${this.dbFile}), reloading data...`)
92
+ console.log(`[Auto-reload] Old mtime: ${this.lastModified}, New mtime: ${stats.mtime}`)
93
+ this.reloadData()
94
+ this.lastModified = stats.mtime
95
+ console.log(`[Auto-reload] Data reloaded, user name is now: ${this.data.user?.name}`)
96
+ }
97
+ }
98
+ } catch (err) {
99
+ console.warn('[Auto-reload] Error checking file modification time:', err.message)
100
+ }
101
+ next()
102
+ })
103
+
104
+ // Logging middleware
105
+ this.app.use((req, res, next) => {
106
+ console.log(`${req.method} ${req.path}`)
107
+ next()
108
+ })
109
+ }
110
+
111
+ setupRoutes() {
112
+ // Reload endpoint (for testing)
113
+ this.app.post('/_reload', (req, res) => {
114
+ this.reloadData()
115
+ res.json({ message: 'Data reloaded', data: this.data })
116
+ })
117
+
118
+ // Headers endpoint (for header testing)
119
+ this.app.get('/headers', (req, res) => {
120
+ res.json(req.headers)
121
+ })
122
+
123
+ this.app.post('/headers', (req, res) => {
124
+ res.json(req.headers)
125
+ })
126
+
127
+ // User endpoints
128
+ this.app.get('/user', (req, res) => {
129
+ console.log(`[GET /user] Serving user data: ${JSON.stringify(this.data.user)}`)
130
+ res.json(this.data.user)
131
+ })
132
+
133
+ this.app.post('/user', (req, res) => {
134
+ this.data.user = { ...this.data.user, ...req.body }
135
+ this.saveData()
136
+ res.status(201).json(this.data.user)
137
+ })
138
+
139
+ this.app.patch('/user', (req, res) => {
140
+ this.data.user = { ...this.data.user, ...req.body }
141
+ this.saveData()
142
+ res.json(this.data.user)
143
+ })
144
+
145
+ this.app.put('/user', (req, res) => {
146
+ this.data.user = req.body
147
+ this.saveData()
148
+ res.json(this.data.user)
149
+ })
150
+
151
+ // Posts endpoints
152
+ this.app.get('/posts', (req, res) => {
153
+ res.json(this.data.posts)
154
+ })
155
+
156
+ this.app.get('/posts/:id', (req, res) => {
157
+ const id = parseInt(req.params.id)
158
+ const post = this.data.posts.find(p => p.id === id)
159
+
160
+ if (!post) {
161
+ // Return empty object instead of 404 for json-server compatibility
162
+ return res.json({})
163
+ }
164
+
165
+ res.json(post)
166
+ })
167
+
168
+ this.app.post('/posts', (req, res) => {
169
+ const newId = Math.max(...this.data.posts.map(p => p.id || 0)) + 1
170
+ const newPost = { id: newId, ...req.body }
171
+
172
+ this.data.posts.push(newPost)
173
+ this.saveData()
174
+ res.status(201).json(newPost)
175
+ })
176
+
177
+ this.app.put('/posts/:id', (req, res) => {
178
+ const id = parseInt(req.params.id)
179
+ const postIndex = this.data.posts.findIndex(p => p.id === id)
180
+
181
+ if (postIndex === -1) {
182
+ return res.status(404).json({ error: 'Post not found' })
183
+ }
184
+
185
+ this.data.posts[postIndex] = { id, ...req.body }
186
+ this.saveData()
187
+ res.json(this.data.posts[postIndex])
188
+ })
189
+
190
+ this.app.patch('/posts/:id', (req, res) => {
191
+ const id = parseInt(req.params.id)
192
+ const postIndex = this.data.posts.findIndex(p => p.id === id)
193
+
194
+ if (postIndex === -1) {
195
+ return res.status(404).json({ error: 'Post not found' })
196
+ }
197
+
198
+ this.data.posts[postIndex] = { ...this.data.posts[postIndex], ...req.body }
199
+ this.saveData()
200
+ res.json(this.data.posts[postIndex])
201
+ })
202
+
203
+ this.app.delete('/posts/:id', (req, res) => {
204
+ const id = parseInt(req.params.id)
205
+ const postIndex = this.data.posts.findIndex(p => p.id === id)
206
+
207
+ if (postIndex === -1) {
208
+ return res.status(404).json({ error: 'Post not found' })
209
+ }
210
+
211
+ const deletedPost = this.data.posts.splice(postIndex, 1)[0]
212
+ this.saveData()
213
+ res.json(deletedPost)
214
+ })
215
+
216
+ // File upload endpoint (basic implementation)
217
+ this.app.post('/upload', (req, res) => {
218
+ // Simple upload simulation - for more complex file uploads,
219
+ // multer would be needed but basic tests should work
220
+ res.json({
221
+ message: 'File upload endpoint available',
222
+ headers: req.headers,
223
+ body: req.body,
224
+ })
225
+ })
226
+
227
+ // Comments endpoints (for ApiDataFactory tests)
228
+ this.app.get('/comments', (req, res) => {
229
+ res.json(this.data.comments || [])
230
+ })
231
+
232
+ this.app.post('/comments', (req, res) => {
233
+ if (!this.data.comments) this.data.comments = []
234
+ const newId = Math.max(...this.data.comments.map(c => c.id || 0), 0) + 1
235
+ const newComment = { id: newId, ...req.body }
236
+
237
+ this.data.comments.push(newComment)
238
+ this.saveData()
239
+ res.status(201).json(newComment)
240
+ })
241
+
242
+ this.app.delete('/comments/:id', (req, res) => {
243
+ if (!this.data.comments) this.data.comments = []
244
+ const id = parseInt(req.params.id)
245
+ const commentIndex = this.data.comments.findIndex(c => c.id === id)
246
+
247
+ if (commentIndex === -1) {
248
+ return res.status(404).json({ error: 'Comment not found' })
249
+ }
250
+
251
+ const deletedComment = this.data.comments.splice(commentIndex, 1)[0]
252
+ this.saveData()
253
+ res.json(deletedComment)
254
+ })
255
+
256
+ // Generic catch-all for other endpoints
257
+ this.app.use((req, res) => {
258
+ res.status(404).json({ error: 'Endpoint not found' })
259
+ })
260
+ }
261
+
262
+ setupFileWatcher() {
263
+ if (fs.existsSync(this.dbFile)) {
264
+ fs.watchFile(this.dbFile, (current, previous) => {
265
+ if (current.mtime !== previous.mtime) {
266
+ console.log('Database file changed, reloading data...')
267
+ this.reloadData()
268
+ }
269
+ })
270
+ }
271
+ }
272
+
273
+ start() {
274
+ return new Promise((resolve, reject) => {
275
+ this.server = this.app.listen(this.port, this.host, err => {
276
+ if (err) {
277
+ reject(err)
278
+ } else {
279
+ console.log(`Test server running on http://${this.host}:${this.port}`)
280
+ resolve(this.server)
281
+ }
282
+ })
283
+ })
284
+ }
285
+
286
+ stop() {
287
+ return new Promise(resolve => {
288
+ if (this.server) {
289
+ this.server.close(() => {
290
+ console.log('Test server stopped')
291
+ resolve()
292
+ })
293
+ } else {
294
+ resolve()
295
+ }
296
+ })
297
+ }
298
+ }
299
+
300
+ module.exports = TestServer
301
+
302
+ // CLI usage
303
+ if (require.main === module) {
304
+ const config = {
305
+ port: process.env.PORT || 8010,
306
+ host: process.env.HOST || '0.0.0.0',
307
+ dbFile: process.argv[2] || path.join(__dirname, '../test/data/rest/db.json'),
308
+ }
309
+
310
+ const server = new TestServer(config)
311
+ server.start().catch(console.error)
312
+
313
+ // Graceful shutdown
314
+ process.on('SIGINT', () => {
315
+ console.log('\nShutting down test server...')
316
+ server.stop().then(() => process.exit(0))
317
+ })
318
+
319
+ process.on('SIGTERM', () => {
320
+ console.log('\nShutting down test server...')
321
+ server.stop().then(() => process.exit(0))
322
+ })
323
+ }
package/lib/timeout.js ADDED
@@ -0,0 +1,66 @@
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
+ class TimeoutError extends Error {
40
+ constructor(message) {
41
+ super(message)
42
+ this.name = 'TimeoutError'
43
+ }
44
+ }
45
+
46
+ class TestTimeoutError extends TimeoutError {
47
+ constructor(timeout) {
48
+ super(`Timeout ${timeout}s exceeded (with Before hook)`)
49
+ this.name = 'TestTimeoutError'
50
+ }
51
+ }
52
+
53
+ class StepTimeoutError extends TimeoutError {
54
+ constructor(timeout, step) {
55
+ super(`Step ${step.toCode().trim()} timed out after ${timeout}s`)
56
+ this.name = 'StepTimeoutError'
57
+ }
58
+ }
59
+
60
+ module.exports = {
61
+ TIMEOUT_ORDER,
62
+ getCurrentTimeout,
63
+ TimeoutError,
64
+ TestTimeoutError,
65
+ StepTimeoutError,
66
+ }