codeceptjs 4.0.0-beta.1 → 4.0.0-beta.10.esm-aria

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 (207) 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 +71 -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 +238 -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 +300 -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 +124 -50
  39. package/lib/container.js +751 -260
  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 +47 -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/mask_data.js +47 -0
  157. package/lib/utils.js +411 -228
  158. package/lib/workerStorage.js +37 -34
  159. package/lib/workers.js +532 -296
  160. package/package.json +115 -95
  161. package/translations/de-DE.js +5 -3
  162. package/translations/fr-FR.js +5 -4
  163. package/translations/index.js +22 -12
  164. package/translations/it-IT.js +4 -3
  165. package/translations/ja-JP.js +4 -3
  166. package/translations/nl-NL.js +76 -0
  167. package/translations/pl-PL.js +4 -3
  168. package/translations/pt-BR.js +4 -3
  169. package/translations/ru-RU.js +4 -3
  170. package/translations/utils.js +10 -0
  171. package/translations/zh-CN.js +4 -3
  172. package/translations/zh-TW.js +4 -3
  173. package/typings/index.d.ts +546 -185
  174. package/typings/promiseBasedTypes.d.ts +150 -879
  175. package/typings/types.d.ts +547 -996
  176. package/lib/cli.js +0 -249
  177. package/lib/dirname.js +0 -5
  178. package/lib/helper/Expect.js +0 -425
  179. package/lib/helper/ExpectHelper.js +0 -399
  180. package/lib/helper/MockServer.js +0 -223
  181. package/lib/helper/Nightmare.js +0 -1411
  182. package/lib/helper/Protractor.js +0 -1835
  183. package/lib/helper/SoftExpectHelper.js +0 -381
  184. package/lib/helper/TestCafe.js +0 -1410
  185. package/lib/helper/clientscripts/nightmare.js +0 -213
  186. package/lib/helper/testcafe/testControllerHolder.js +0 -42
  187. package/lib/helper/testcafe/testcafe-utils.js +0 -63
  188. package/lib/interfaces/bdd.js +0 -98
  189. package/lib/interfaces/featureConfig.js +0 -69
  190. package/lib/interfaces/gherkin.js +0 -195
  191. package/lib/listener/artifacts.js +0 -19
  192. package/lib/listener/retry.js +0 -68
  193. package/lib/listener/timeout.js +0 -109
  194. package/lib/mochaFactory.js +0 -110
  195. package/lib/plugin/allure.js +0 -15
  196. package/lib/plugin/commentStep.js +0 -136
  197. package/lib/plugin/debugErrors.js +0 -67
  198. package/lib/plugin/eachElement.js +0 -127
  199. package/lib/plugin/fakerTransform.js +0 -49
  200. package/lib/plugin/retryTo.js +0 -121
  201. package/lib/plugin/selenoid.js +0 -371
  202. package/lib/plugin/standardActingHelpers.js +0 -9
  203. package/lib/plugin/tryTo.js +0 -105
  204. package/lib/plugin/wdio.js +0 -246
  205. package/lib/scenario.js +0 -222
  206. package/lib/ui.js +0 -238
  207. 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
@@ -0,0 +1,47 @@
1
+ import { maskSensitiveData } from 'invisi-data'
2
+
3
+ /**
4
+ * Mask sensitive data utility for CodeceptJS
5
+ * Supports both boolean and object configuration formats
6
+ *
7
+ * @param {string} input - The string to mask
8
+ * @param {boolean|object} config - Masking configuration
9
+ * @returns {string} - Masked string
10
+ */
11
+ export function maskData(input, config) {
12
+ if (!config) {
13
+ return input
14
+ }
15
+
16
+ // Handle boolean config (backward compatibility)
17
+ if (typeof config === 'boolean' && config === true) {
18
+ return maskSensitiveData(input)
19
+ }
20
+
21
+ // Handle object config with custom patterns
22
+ if (typeof config === 'object' && config.enabled === true) {
23
+ const customPatterns = config.patterns || []
24
+ return maskSensitiveData(input, customPatterns)
25
+ }
26
+
27
+ return input
28
+ }
29
+
30
+ /**
31
+ * Check if masking is enabled based on global configuration
32
+ *
33
+ * @returns {boolean|object} - Current masking configuration
34
+ */
35
+ export function getMaskConfig() {
36
+ return global.maskSensitiveData || false
37
+ }
38
+
39
+ /**
40
+ * Check if data should be masked
41
+ *
42
+ * @returns {boolean} - True if masking is enabled
43
+ */
44
+ export function shouldMaskData() {
45
+ const config = getMaskConfig()
46
+ return config === true || (typeof config === 'object' && config.enabled === true)
47
+ }