codeceptjs 4.0.0-beta.2 → 4.0.0-beta.21

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 (209) hide show
  1. package/README.md +133 -120
  2. package/bin/codecept.js +107 -96
  3. package/bin/test-server.js +64 -0
  4. package/docs/webapi/clearCookie.mustache +1 -1
  5. package/docs/webapi/click.mustache +5 -1
  6. package/lib/actor.js +73 -103
  7. package/lib/ai.js +159 -188
  8. package/lib/assert/empty.js +22 -24
  9. package/lib/assert/equal.js +30 -37
  10. package/lib/assert/error.js +14 -14
  11. package/lib/assert/include.js +43 -48
  12. package/lib/assert/throws.js +11 -11
  13. package/lib/assert/truth.js +22 -22
  14. package/lib/assert.js +20 -18
  15. package/lib/codecept.js +262 -162
  16. package/lib/colorUtils.js +50 -52
  17. package/lib/command/check.js +206 -0
  18. package/lib/command/configMigrate.js +56 -51
  19. package/lib/command/definitions.js +96 -109
  20. package/lib/command/dryRun.js +77 -79
  21. package/lib/command/generate.js +234 -194
  22. package/lib/command/gherkin/init.js +42 -33
  23. package/lib/command/gherkin/snippets.js +76 -74
  24. package/lib/command/gherkin/steps.js +20 -17
  25. package/lib/command/info.js +74 -38
  26. package/lib/command/init.js +301 -290
  27. package/lib/command/interactive.js +41 -32
  28. package/lib/command/list.js +28 -27
  29. package/lib/command/run-multiple/chunk.js +51 -48
  30. package/lib/command/run-multiple/collection.js +5 -5
  31. package/lib/command/run-multiple/run.js +5 -1
  32. package/lib/command/run-multiple.js +97 -97
  33. package/lib/command/run-rerun.js +19 -25
  34. package/lib/command/run-workers.js +68 -92
  35. package/lib/command/run.js +39 -27
  36. package/lib/command/utils.js +80 -64
  37. package/lib/command/workers/runTests.js +388 -226
  38. package/lib/config.js +109 -50
  39. package/lib/container.js +765 -261
  40. package/lib/data/context.js +60 -61
  41. package/lib/data/dataScenarioConfig.js +47 -47
  42. package/lib/data/dataTableArgument.js +32 -32
  43. package/lib/data/table.js +22 -22
  44. package/lib/effects.js +307 -0
  45. package/lib/element/WebElement.js +327 -0
  46. package/lib/els.js +160 -0
  47. package/lib/event.js +173 -163
  48. package/lib/globals.js +141 -0
  49. package/lib/heal.js +89 -85
  50. package/lib/helper/AI.js +131 -41
  51. package/lib/helper/ApiDataFactory.js +107 -75
  52. package/lib/helper/Appium.js +542 -404
  53. package/lib/helper/FileSystem.js +100 -79
  54. package/lib/helper/GraphQL.js +44 -43
  55. package/lib/helper/GraphQLDataFactory.js +52 -52
  56. package/lib/helper/JSONResponse.js +126 -88
  57. package/lib/helper/Mochawesome.js +54 -29
  58. package/lib/helper/Playwright.js +2547 -1316
  59. package/lib/helper/Puppeteer.js +1578 -1181
  60. package/lib/helper/REST.js +209 -68
  61. package/lib/helper/WebDriver.js +1482 -1342
  62. package/lib/helper/errors/ConnectionRefused.js +6 -6
  63. package/lib/helper/errors/ElementAssertion.js +11 -16
  64. package/lib/helper/errors/ElementNotFound.js +5 -9
  65. package/lib/helper/errors/RemoteBrowserConnectionRefused.js +5 -5
  66. package/lib/helper/extras/Console.js +11 -11
  67. package/lib/helper/extras/PlaywrightLocator.js +110 -0
  68. package/lib/helper/extras/PlaywrightPropEngine.js +18 -18
  69. package/lib/helper/extras/PlaywrightReactVueLocator.js +17 -8
  70. package/lib/helper/extras/PlaywrightRestartOpts.js +25 -11
  71. package/lib/helper/extras/Popup.js +22 -22
  72. package/lib/helper/extras/React.js +27 -28
  73. package/lib/helper/network/actions.js +36 -42
  74. package/lib/helper/network/utils.js +78 -84
  75. package/lib/helper/scripts/blurElement.js +5 -5
  76. package/lib/helper/scripts/focusElement.js +5 -5
  77. package/lib/helper/scripts/highlightElement.js +8 -8
  78. package/lib/helper/scripts/isElementClickable.js +34 -34
  79. package/lib/helper.js +2 -3
  80. package/lib/history.js +23 -19
  81. package/lib/hooks.js +8 -8
  82. package/lib/html.js +94 -104
  83. package/lib/index.js +38 -27
  84. package/lib/listener/config.js +30 -23
  85. package/lib/listener/emptyRun.js +54 -0
  86. package/lib/listener/enhancedGlobalRetry.js +110 -0
  87. package/lib/listener/exit.js +16 -18
  88. package/lib/listener/globalRetry.js +70 -0
  89. package/lib/listener/globalTimeout.js +181 -0
  90. package/lib/listener/helpers.js +76 -51
  91. package/lib/listener/mocha.js +10 -11
  92. package/lib/listener/result.js +11 -0
  93. package/lib/listener/retryEnhancer.js +85 -0
  94. package/lib/listener/steps.js +71 -59
  95. package/lib/listener/store.js +20 -0
  96. package/lib/locator.js +214 -197
  97. package/lib/mocha/asyncWrapper.js +274 -0
  98. package/lib/mocha/bdd.js +167 -0
  99. package/lib/mocha/cli.js +341 -0
  100. package/lib/mocha/factory.js +163 -0
  101. package/lib/mocha/featureConfig.js +89 -0
  102. package/lib/mocha/gherkin.js +231 -0
  103. package/lib/mocha/hooks.js +121 -0
  104. package/lib/mocha/index.js +21 -0
  105. package/lib/mocha/inject.js +46 -0
  106. package/lib/{interfaces → mocha}/scenarioConfig.js +58 -34
  107. package/lib/mocha/suite.js +89 -0
  108. package/lib/mocha/test.js +184 -0
  109. package/lib/mocha/types.d.ts +42 -0
  110. package/lib/mocha/ui.js +242 -0
  111. package/lib/output.js +141 -71
  112. package/lib/parser.js +54 -44
  113. package/lib/pause.js +173 -145
  114. package/lib/plugin/analyze.js +403 -0
  115. package/lib/plugin/{autoLogin.js → auth.js} +178 -79
  116. package/lib/plugin/autoDelay.js +36 -40
  117. package/lib/plugin/coverage.js +131 -78
  118. package/lib/plugin/customLocator.js +22 -21
  119. package/lib/plugin/customReporter.js +53 -0
  120. package/lib/plugin/enhancedRetryFailedStep.js +99 -0
  121. package/lib/plugin/heal.js +101 -110
  122. package/lib/plugin/htmlReporter.js +3648 -0
  123. package/lib/plugin/pageInfo.js +140 -0
  124. package/lib/plugin/pauseOnFail.js +12 -11
  125. package/lib/plugin/retryFailedStep.js +82 -47
  126. package/lib/plugin/screenshotOnFail.js +111 -92
  127. package/lib/plugin/stepByStepReport.js +159 -101
  128. package/lib/plugin/stepTimeout.js +20 -25
  129. package/lib/plugin/subtitles.js +38 -38
  130. package/lib/recorder.js +193 -130
  131. package/lib/rerun.js +94 -49
  132. package/lib/result.js +238 -0
  133. package/lib/retryCoordinator.js +207 -0
  134. package/lib/secret.js +20 -18
  135. package/lib/session.js +95 -89
  136. package/lib/step/base.js +239 -0
  137. package/lib/step/comment.js +10 -0
  138. package/lib/step/config.js +50 -0
  139. package/lib/step/func.js +46 -0
  140. package/lib/step/helper.js +50 -0
  141. package/lib/step/meta.js +99 -0
  142. package/lib/step/record.js +74 -0
  143. package/lib/step/retry.js +11 -0
  144. package/lib/step/section.js +55 -0
  145. package/lib/step.js +18 -329
  146. package/lib/steps.js +54 -0
  147. package/lib/store.js +38 -7
  148. package/lib/template/heal.js +3 -12
  149. package/lib/template/prompts/generatePageObject.js +31 -0
  150. package/lib/template/prompts/healStep.js +13 -0
  151. package/lib/template/prompts/writeStep.js +9 -0
  152. package/lib/test-server.js +334 -0
  153. package/lib/timeout.js +60 -0
  154. package/lib/transform.js +8 -8
  155. package/lib/translation.js +34 -21
  156. package/lib/utils/loaderCheck.js +124 -0
  157. package/lib/utils/mask_data.js +47 -0
  158. package/lib/utils/typescript.js +237 -0
  159. package/lib/utils.js +411 -228
  160. package/lib/workerStorage.js +37 -34
  161. package/lib/workers.js +532 -296
  162. package/package.json +124 -95
  163. package/translations/de-DE.js +5 -3
  164. package/translations/fr-FR.js +5 -4
  165. package/translations/index.js +22 -12
  166. package/translations/it-IT.js +4 -3
  167. package/translations/ja-JP.js +4 -3
  168. package/translations/nl-NL.js +76 -0
  169. package/translations/pl-PL.js +4 -3
  170. package/translations/pt-BR.js +4 -3
  171. package/translations/ru-RU.js +4 -3
  172. package/translations/utils.js +10 -0
  173. package/translations/zh-CN.js +4 -3
  174. package/translations/zh-TW.js +4 -3
  175. package/typings/index.d.ts +546 -185
  176. package/typings/promiseBasedTypes.d.ts +150 -875
  177. package/typings/types.d.ts +547 -992
  178. package/lib/cli.js +0 -249
  179. package/lib/dirname.js +0 -5
  180. package/lib/helper/Expect.js +0 -425
  181. package/lib/helper/ExpectHelper.js +0 -399
  182. package/lib/helper/MockServer.js +0 -223
  183. package/lib/helper/Nightmare.js +0 -1411
  184. package/lib/helper/Protractor.js +0 -1835
  185. package/lib/helper/SoftExpectHelper.js +0 -381
  186. package/lib/helper/TestCafe.js +0 -1410
  187. package/lib/helper/clientscripts/nightmare.js +0 -213
  188. package/lib/helper/testcafe/testControllerHolder.js +0 -42
  189. package/lib/helper/testcafe/testcafe-utils.js +0 -63
  190. package/lib/interfaces/bdd.js +0 -98
  191. package/lib/interfaces/featureConfig.js +0 -69
  192. package/lib/interfaces/gherkin.js +0 -195
  193. package/lib/listener/artifacts.js +0 -19
  194. package/lib/listener/retry.js +0 -68
  195. package/lib/listener/timeout.js +0 -109
  196. package/lib/mochaFactory.js +0 -110
  197. package/lib/plugin/allure.js +0 -15
  198. package/lib/plugin/commentStep.js +0 -136
  199. package/lib/plugin/debugErrors.js +0 -67
  200. package/lib/plugin/eachElement.js +0 -127
  201. package/lib/plugin/fakerTransform.js +0 -49
  202. package/lib/plugin/retryTo.js +0 -121
  203. package/lib/plugin/selenoid.js +0 -371
  204. package/lib/plugin/standardActingHelpers.js +0 -9
  205. package/lib/plugin/tryTo.js +0 -105
  206. package/lib/plugin/wdio.js +0 -246
  207. package/lib/scenario.js +0 -222
  208. package/lib/ui.js +0 -238
  209. package/lib/within.js +0 -70
@@ -1,4 +1,4 @@
1
- const { heal, ai } = require('codeceptjs')
1
+ import { heal, ai } from 'codeceptjs'
2
2
 
3
3
  heal.addRecipe('ai', {
4
4
  priority: 10,
@@ -6,17 +6,8 @@ heal.addRecipe('ai', {
6
6
  html: ({ I }) => I.grabHTMLFrom('body'),
7
7
  },
8
8
  suggest: true,
9
- steps: [
10
- 'click',
11
- 'fillField',
12
- 'appendField',
13
- 'selectOption',
14
- 'attachFile',
15
- 'checkOption',
16
- 'uncheckOption',
17
- 'doubleClick',
18
- ],
19
- fn: async (args) => {
9
+ steps: ['click', 'fillField', 'appendField', 'selectOption', 'attachFile', 'checkOption', 'uncheckOption', 'doubleClick'],
10
+ fn: async args => {
20
11
  return ai.healFailedStep(args)
21
12
  },
22
13
  })
@@ -0,0 +1,31 @@
1
+ export default (html, extraPrompt = '', rootLocator = null) => [
2
+ {
3
+ role: 'user',
4
+ content: `As a test automation engineer I am creating a Page Object for a web application using CodeceptJS.
5
+ Here is an sample page object:
6
+
7
+ const { I } = inject();
8
+
9
+ export default {
10
+
11
+ // setting locators
12
+ element1: '#selector',
13
+ element2: '.selector',
14
+ element3: locate().withText('text'),
15
+
16
+ // setting methods
17
+ doSomethingOnPage(params) {
18
+ // ...
19
+ },
20
+ }
21
+
22
+ I want to generate a Page Object for the page I provide.
23
+ Write JavaScript code in similar manner to list all locators on the page.
24
+ Use locators in order of preference: by text (use locate().withText()), label, CSS, XPath.
25
+ Avoid TailwindCSS, Bootstrap or React style formatting classes in locators.
26
+ Add methods to interact with page when needed.
27
+ ${extraPrompt}
28
+ ${rootLocator ? `All provided elements are inside '${rootLocator}'. Declare it as root variable and for every locator use locate(...).inside(root)` : ''}
29
+ Add only locators from this HTML: \n\n${html}`,
30
+ },
31
+ ]
@@ -0,0 +1,13 @@
1
+ export default (html, { step, error, prevSteps }) => {
2
+ return [
3
+ {
4
+ role: 'user',
5
+ content: `As a test automation engineer I am testing web application using CodeceptJS.
6
+ I want to heal a test that fails. Here is the list of executed steps: ${prevSteps.map(s => s.toString()).join(', ')}
7
+ Propose how to adjust ${step.toCode()} step to fix the test.
8
+ Use locators in order of preference: semantic locator by text, CSS, XPath. Use codeblocks marked with \`\`\`
9
+ Here is the error message: ${error.message}
10
+ Here is HTML code of a page where the failure has happened: \n\n${html}`,
11
+ },
12
+ ]
13
+ }
@@ -0,0 +1,9 @@
1
+ export default (html, input) => [
2
+ {
3
+ role: 'user',
4
+ content: `I am test engineer writing test in CodeceptJS
5
+ I have opened web page and I want to use CodeceptJS to ${input} on this page
6
+ Provide me valid CodeceptJS code to accomplish it
7
+ Use only locators from this HTML: \n\n${html}`,
8
+ },
9
+ ]
@@ -0,0 +1,334 @@
1
+ import express from 'express'
2
+ import fs from 'fs'
3
+ import path from '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.readOnly = config.readOnly || false
17
+ this.lastModified = null
18
+ this.data = this.loadData()
19
+
20
+ this.setupMiddleware()
21
+ this.setupRoutes()
22
+ this.setupFileWatcher()
23
+ }
24
+
25
+ loadData() {
26
+ try {
27
+ const content = fs.readFileSync(this.dbFile, 'utf8')
28
+ const data = JSON.parse(content)
29
+ // Update lastModified time when loading data
30
+ if (fs.existsSync(this.dbFile)) {
31
+ this.lastModified = fs.statSync(this.dbFile).mtime
32
+ }
33
+ console.log('[Data Load] Loaded data from file:', JSON.stringify(data))
34
+ return data
35
+ } catch (err) {
36
+ console.warn(`[Data Load] Could not load data file ${this.dbFile}:`, err.message)
37
+ console.log('[Data Load] Using fallback default data')
38
+ return {
39
+ posts: [{ id: 1, title: 'json-server', author: 'davert' }],
40
+ user: { name: 'john', password: '123456' },
41
+ }
42
+ }
43
+ }
44
+
45
+ reloadData() {
46
+ console.log('[Reload] Reloading data from file...')
47
+ this.data = this.loadData()
48
+ console.log('[Reload] Data reloaded successfully')
49
+ return this.data
50
+ }
51
+
52
+ saveData() {
53
+ if (this.readOnly) {
54
+ console.log('[Save] Skipping save - running in read-only mode')
55
+ return
56
+ }
57
+ try {
58
+ fs.writeFileSync(this.dbFile, JSON.stringify(this.data, null, 2))
59
+ console.log('[Save] Data saved to file')
60
+ // Force update modification time to ensure auto-reload works
61
+ const now = new Date()
62
+ fs.utimesSync(this.dbFile, now, now)
63
+ this.lastModified = now
64
+ console.log('[Save] File modification time updated')
65
+ } catch (err) {
66
+ console.warn(`[Save] Could not save data file ${this.dbFile}:`, err.message)
67
+ }
68
+ }
69
+
70
+ setupMiddleware() {
71
+ // Parse JSON bodies
72
+ this.app.use(express.json())
73
+
74
+ // Parse URL-encoded bodies
75
+ this.app.use(express.urlencoded({ extended: true }))
76
+
77
+ // CORS support
78
+ this.app.use((req, res, next) => {
79
+ res.header('Access-Control-Allow-Origin', '*')
80
+ res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE, OPTIONS')
81
+ res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, Authorization, X-Test')
82
+
83
+ if (req.method === 'OPTIONS') {
84
+ res.status(200).end()
85
+ return
86
+ }
87
+ next()
88
+ })
89
+
90
+ // Auto-reload middleware - check if file changed before each request
91
+ this.app.use((req, res, next) => {
92
+ try {
93
+ if (fs.existsSync(this.dbFile)) {
94
+ const stats = fs.statSync(this.dbFile)
95
+ if (!this.lastModified || stats.mtime > this.lastModified) {
96
+ console.log(`[Auto-reload] Database file changed (${this.dbFile}), reloading data...`)
97
+ console.log(`[Auto-reload] Old mtime: ${this.lastModified}, New mtime: ${stats.mtime}`)
98
+ this.reloadData()
99
+ this.lastModified = stats.mtime
100
+ console.log(`[Auto-reload] Data reloaded, user name is now: ${this.data.user?.name}`)
101
+ }
102
+ }
103
+ } catch (err) {
104
+ console.warn('[Auto-reload] Error checking file modification time:', err.message)
105
+ }
106
+ next()
107
+ })
108
+
109
+ // Logging middleware
110
+ this.app.use((req, res, next) => {
111
+ console.log(`${req.method} ${req.path}`)
112
+ next()
113
+ })
114
+ }
115
+
116
+ setupRoutes() {
117
+ // Reload endpoint (for testing)
118
+ this.app.post('/_reload', (req, res) => {
119
+ this.reloadData()
120
+ res.json({ message: 'Data reloaded', data: this.data })
121
+ })
122
+
123
+ // Headers endpoint (for header testing)
124
+ this.app.get('/headers', (req, res) => {
125
+ res.json(req.headers)
126
+ })
127
+
128
+ this.app.post('/headers', (req, res) => {
129
+ res.json(req.headers)
130
+ })
131
+
132
+ // User endpoints
133
+ this.app.get('/user', (req, res) => {
134
+ console.log(`[GET /user] Serving user data: ${JSON.stringify(this.data.user)}`)
135
+ res.json(this.data.user)
136
+ })
137
+
138
+ this.app.post('/user', (req, res) => {
139
+ this.data.user = { ...this.data.user, ...req.body }
140
+ this.saveData()
141
+ res.status(201).json(this.data.user)
142
+ })
143
+
144
+ this.app.patch('/user', (req, res) => {
145
+ this.data.user = { ...this.data.user, ...req.body }
146
+ this.saveData()
147
+ res.json(this.data.user)
148
+ })
149
+
150
+ this.app.put('/user', (req, res) => {
151
+ this.data.user = req.body
152
+ this.saveData()
153
+ res.json(this.data.user)
154
+ })
155
+
156
+ // Posts endpoints
157
+ this.app.get('/posts', (req, res) => {
158
+ res.json(this.data.posts)
159
+ })
160
+
161
+ this.app.get('/posts/:id', (req, res) => {
162
+ const id = parseInt(req.params.id)
163
+ const post = this.data.posts.find(p => p.id === id)
164
+
165
+ if (!post) {
166
+ // Return empty object instead of 404 for json-server compatibility
167
+ return res.json({})
168
+ }
169
+
170
+ res.json(post)
171
+ })
172
+
173
+ this.app.post('/posts', (req, res) => {
174
+ const newId = Math.max(...this.data.posts.map(p => p.id || 0)) + 1
175
+ const newPost = { id: newId, ...req.body }
176
+
177
+ this.data.posts.push(newPost)
178
+ this.saveData()
179
+ res.status(201).json(newPost)
180
+ })
181
+
182
+ this.app.put('/posts/:id', (req, res) => {
183
+ const id = parseInt(req.params.id)
184
+ const postIndex = this.data.posts.findIndex(p => p.id === id)
185
+
186
+ if (postIndex === -1) {
187
+ return res.status(404).json({ error: 'Post not found' })
188
+ }
189
+
190
+ this.data.posts[postIndex] = { id, ...req.body }
191
+ this.saveData()
192
+ res.json(this.data.posts[postIndex])
193
+ })
194
+
195
+ this.app.patch('/posts/:id', (req, res) => {
196
+ const id = parseInt(req.params.id)
197
+ const postIndex = this.data.posts.findIndex(p => p.id === id)
198
+
199
+ if (postIndex === -1) {
200
+ return res.status(404).json({ error: 'Post not found' })
201
+ }
202
+
203
+ this.data.posts[postIndex] = { ...this.data.posts[postIndex], ...req.body }
204
+ this.saveData()
205
+ res.json(this.data.posts[postIndex])
206
+ })
207
+
208
+ this.app.delete('/posts/:id', (req, res) => {
209
+ const id = parseInt(req.params.id)
210
+ const postIndex = this.data.posts.findIndex(p => p.id === id)
211
+
212
+ if (postIndex === -1) {
213
+ return res.status(404).json({ error: 'Post not found' })
214
+ }
215
+
216
+ const deletedPost = this.data.posts.splice(postIndex, 1)[0]
217
+ this.saveData()
218
+ res.json(deletedPost)
219
+ })
220
+
221
+ // File upload endpoint (basic implementation)
222
+ this.app.post('/upload', (req, res) => {
223
+ // Simple upload simulation - for more complex file uploads,
224
+ // multer would be needed but basic tests should work
225
+ res.json({
226
+ message: 'File upload endpoint available',
227
+ headers: req.headers,
228
+ body: req.body,
229
+ })
230
+ })
231
+
232
+ // Comments endpoints (for ApiDataFactory tests)
233
+ this.app.get('/comments', (req, res) => {
234
+ res.json(this.data.comments || [])
235
+ })
236
+
237
+ this.app.post('/comments', (req, res) => {
238
+ if (!this.data.comments) this.data.comments = []
239
+ const newId = Math.max(...this.data.comments.map(c => c.id || 0), 0) + 1
240
+ const newComment = { id: newId, ...req.body }
241
+
242
+ this.data.comments.push(newComment)
243
+ this.saveData()
244
+ res.status(201).json(newComment)
245
+ })
246
+
247
+ this.app.delete('/comments/:id', (req, res) => {
248
+ if (!this.data.comments) this.data.comments = []
249
+ const id = parseInt(req.params.id)
250
+ const commentIndex = this.data.comments.findIndex(c => c.id === id)
251
+
252
+ if (commentIndex === -1) {
253
+ return res.status(404).json({ error: 'Comment not found' })
254
+ }
255
+
256
+ const deletedComment = this.data.comments.splice(commentIndex, 1)[0]
257
+ this.saveData()
258
+ res.json(deletedComment)
259
+ })
260
+
261
+ // Generic catch-all for other endpoints
262
+ this.app.use((req, res) => {
263
+ res.status(404).json({ error: 'Endpoint not found' })
264
+ })
265
+ }
266
+
267
+ setupFileWatcher() {
268
+ if (fs.existsSync(this.dbFile)) {
269
+ fs.watchFile(this.dbFile, (current, previous) => {
270
+ if (current.mtime !== previous.mtime) {
271
+ console.log('Database file changed, reloading data...')
272
+ this.reloadData()
273
+ }
274
+ })
275
+ }
276
+ }
277
+
278
+ start() {
279
+ return new Promise((resolve, reject) => {
280
+ this.server = this.app.listen(this.port, this.host, err => {
281
+ if (err) {
282
+ reject(err)
283
+ } else {
284
+ console.log(`Test server running on http://${this.host}:${this.port}`)
285
+ resolve(this.server)
286
+ }
287
+ })
288
+ })
289
+ }
290
+
291
+ stop() {
292
+ return new Promise(resolve => {
293
+ if (this.server) {
294
+ this.server.close(() => {
295
+ console.log('Test server stopped')
296
+ resolve()
297
+ })
298
+ } else {
299
+ resolve()
300
+ }
301
+ })
302
+ }
303
+ }
304
+
305
+ export default TestServer
306
+
307
+ // CLI usage - Import meta for ESM
308
+ import { fileURLToPath } from 'url'
309
+ import { dirname } from 'path'
310
+
311
+ const __filename = fileURLToPath(import.meta.url)
312
+ const __dirname = dirname(__filename)
313
+
314
+ if (import.meta.url === `file://${process.argv[1]}`) {
315
+ const config = {
316
+ port: process.env.PORT || 8010,
317
+ host: process.env.HOST || '0.0.0.0',
318
+ dbFile: process.argv[2] || path.join(__dirname, '../test/data/rest/db.json'),
319
+ }
320
+
321
+ const server = new TestServer(config)
322
+ server.start().catch(console.error)
323
+
324
+ // Graceful shutdown
325
+ process.on('SIGINT', () => {
326
+ console.log('\nShutting down test server...')
327
+ server.stop().then(() => process.exit(0))
328
+ })
329
+
330
+ process.on('SIGTERM', () => {
331
+ console.log('\nShutting down test server...')
332
+ server.stop().then(() => process.exit(0))
333
+ })
334
+ }
package/lib/timeout.js ADDED
@@ -0,0 +1,60 @@
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
+ export { TIMEOUT_ORDER, getCurrentTimeout, TimeoutError, TestTimeoutError, StepTimeoutError }
package/lib/transform.js CHANGED
@@ -1,26 +1,26 @@
1
1
  const transformers = {
2
2
  'gherkin.examples': [],
3
- };
3
+ }
4
4
 
5
5
  function transform(target, value) {
6
6
  if (target in transformers) {
7
7
  for (const transform of transformers[target]) {
8
- value = transform(value);
8
+ value = transform(value)
9
9
  }
10
10
  }
11
- return value;
11
+ return value
12
12
  }
13
13
 
14
14
  transform.addTransformer = function (target, transformer) {
15
15
  if (target in transformers) {
16
- transformers[target].push(transformer);
16
+ transformers[target].push(transformer)
17
17
  }
18
- };
18
+ }
19
19
 
20
20
  transform.addTransformerBeforeAll = function (target, transformer) {
21
21
  if (target in transformers) {
22
- transformers[target].unshift(transformer);
22
+ transformers[target].unshift(transformer)
23
23
  }
24
- };
24
+ }
25
25
 
26
- export default transform;
26
+ export default transform
@@ -1,56 +1,69 @@
1
- import merge from 'lodash.merge';
2
- import path from 'path';
3
-
4
- import importSync from 'import-sync';
5
- import * as translations from '../translations/index.js';
1
+ import merge from 'lodash.merge'
2
+ import path from 'path'
3
+ import { createRequire } from 'module'
6
4
 
7
5
  const defaultVocabulary = {
8
6
  I: 'I',
9
7
  actions: {},
10
- };
8
+ }
11
9
 
12
- export default class Translation {
10
+ class Translation {
13
11
  constructor(vocabulary, loaded) {
14
- this.vocabulary = vocabulary;
15
- this.loaded = loaded !== false;
12
+ this.vocabulary = vocabulary
13
+ this.loaded = loaded !== false
16
14
  }
17
15
 
18
16
  loadVocabulary(vocabularyFile) {
19
- if (!vocabularyFile) return;
20
- const filePath = path.join(global.codecept_dir, vocabularyFile);
17
+ if (!vocabularyFile) return
18
+ const filePath = path.join(global.codecept_dir, vocabularyFile)
21
19
 
22
20
  try {
23
- const vocabulary = importSync(filePath);
24
- this.vocabulary = merge(this.vocabulary, vocabulary);
21
+ const require = createRequire(import.meta.url)
22
+ const vocabulary = require(filePath)
23
+ this.vocabulary = merge(this.vocabulary, vocabulary)
25
24
  } catch (err) {
26
- throw new Error(`Can't load vocabulary from ${filePath}; ${err}`);
25
+ throw new Error(`Can't load vocabulary from ${filePath}; ${err}`)
27
26
  }
28
27
  }
29
28
 
30
29
  value(val) {
31
- return this.vocabulary[val];
30
+ return this.vocabulary[val]
32
31
  }
33
32
 
34
33
  actionAliasFor(actualActionName) {
35
34
  if (this.vocabulary.actions && this.vocabulary.actions[actualActionName]) {
36
- return this.vocabulary.actions[actualActionName];
35
+ return this.vocabulary.actions[actualActionName]
37
36
  }
38
- return actualActionName;
37
+ return actualActionName
39
38
  }
40
39
 
41
40
  get I() {
42
- return this.vocabulary.I;
41
+ return this.vocabulary.I
42
+ }
43
+
44
+ static async getLangs() {
45
+ const translations = await import('../translations/index.js')
46
+ return translations.default
43
47
  }
44
48
 
45
49
  static get langs() {
46
- return translations;
50
+ // Synchronous fallback - may be empty initially
51
+ if (!this._cachedLangs) {
52
+ this.getLangs().then(langs => {
53
+ this._cachedLangs = langs
54
+ })
55
+ return {}
56
+ }
57
+ return this._cachedLangs
47
58
  }
48
59
 
49
60
  static createDefault() {
50
- return new Translation(defaultVocabulary, true);
61
+ return new Translation(defaultVocabulary, true)
51
62
  }
52
63
 
53
64
  static createEmpty() {
54
- return new Translation(defaultVocabulary, false);
65
+ return new Translation(defaultVocabulary, false)
55
66
  }
56
67
  }
68
+
69
+ export default Translation