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
package/lib/effects.js ADDED
@@ -0,0 +1,307 @@
1
+ import recorder from './recorder.js'
2
+ import output from './output.js'
3
+ import store from './store.js'
4
+ import event from './event.js'
5
+ import container from './container.js'
6
+ import MetaStep from './step/meta.js'
7
+ import { isAsyncFunction } from './utils.js'
8
+
9
+ /**
10
+ * @param {CodeceptJS.LocatorOrString} context
11
+ * @param {Function} fn
12
+ * @return {Promise<*> | undefined}
13
+ */
14
+ function within(context, fn) {
15
+ const helpers = store.dryRun ? {} : container.helpers()
16
+ const locator = typeof context === 'object' ? JSON.stringify(context) : context
17
+
18
+ return recorder.add(
19
+ 'register within wrapper',
20
+ () => {
21
+ const metaStep = new WithinStep(locator, fn)
22
+ const defineMetaStep = step => (step.metaStep = metaStep)
23
+ recorder.session.start('within')
24
+
25
+ event.dispatcher.prependListener(event.step.before, defineMetaStep)
26
+
27
+ Object.keys(helpers).forEach(helper => {
28
+ if (helpers[helper]._withinBegin) recorder.add(`[${helper}] start within`, () => helpers[helper]._withinBegin(context))
29
+ })
30
+
31
+ const finalize = () => {
32
+ event.dispatcher.removeListener(event.step.before, defineMetaStep)
33
+ recorder.add('Finalize session within session', () => {
34
+ output.stepShift = 1
35
+ recorder.session.restore('within')
36
+ })
37
+ }
38
+ const finishHelpers = () => {
39
+ Object.keys(helpers).forEach(helper => {
40
+ if (helpers[helper]._withinEnd) recorder.add(`[${helper}] finish within`, () => helpers[helper]._withinEnd())
41
+ })
42
+ }
43
+
44
+ if (isAsyncFunction(fn)) {
45
+ return fn()
46
+ .then(res => {
47
+ finishHelpers()
48
+ finalize()
49
+ return recorder.promise().then(() => res)
50
+ })
51
+ .catch(e => {
52
+ finalize()
53
+ recorder.throw(e)
54
+ })
55
+ }
56
+
57
+ let res
58
+ try {
59
+ res = fn()
60
+ } catch (err) {
61
+ recorder.throw(err)
62
+ } finally {
63
+ finishHelpers()
64
+ recorder.catch(err => {
65
+ output.stepShift = 1
66
+ throw err
67
+ })
68
+ }
69
+ finalize()
70
+ return recorder.promise().then(() => res)
71
+ },
72
+ false,
73
+ false,
74
+ )
75
+ }
76
+
77
+ class WithinStep extends MetaStep {
78
+ constructor(locator, fn) {
79
+ super('Within')
80
+ this.args = [locator]
81
+ }
82
+
83
+ toString() {
84
+ return `${this.prefix}Within ${this.humanizeArgs()}${this.suffix}`
85
+ }
86
+ }
87
+
88
+ /**
89
+ * A utility function for CodeceptJS tests that acts as a soft assertion.
90
+ * Executes a callback within a recorded session, ensuring errors are handled gracefully without failing the test immediately.
91
+ *
92
+ * @async
93
+ * @function hopeThat
94
+ * @param {Function} callback - The callback function containing the logic to validate.
95
+ * This function should perform the desired assertion or condition check.
96
+ * @returns {Promise<boolean|any>} A promise resolving to `true` if the assertion or condition was successful,
97
+ * or `false` if an error occurred.
98
+ *
99
+ * @description
100
+ * - Designed for use in CodeceptJS tests as a "soft assertion."
101
+ * Unlike standard assertions, it does not stop the test execution on failure.
102
+ * - Starts a new recorder session named 'hopeThat' and manages state restoration.
103
+ * - Logs errors and attaches them as notes to the test, enabling post-test reporting of soft assertion failures.
104
+ * - Resets the `store.hopeThat` flag after the execution, ensuring clean state for subsequent operations.
105
+ *
106
+ * @example
107
+ * const { hopeThat } = require('codeceptjs/effects')
108
+ * await hopeThat(() => {
109
+ * I.see('Welcome'); // Perform a soft assertion
110
+ * });
111
+ *
112
+ * @throws Will handle errors that occur during the callback execution. Errors are logged and attached as notes to the test.
113
+ */
114
+ async function hopeThat(callback) {
115
+ if (store.dryRun) return
116
+ const sessionName = 'hopeThat'
117
+
118
+ let result = false
119
+ return recorder.add(
120
+ 'hopeThat',
121
+ () => {
122
+ recorder.session.start(sessionName)
123
+ store.hopeThat = true
124
+ callback()
125
+ recorder.add(() => {
126
+ result = true
127
+ recorder.session.restore(sessionName)
128
+ return result
129
+ })
130
+ recorder.session.catch(err => {
131
+ result = false
132
+ const msg = err.inspect ? err.inspect() : err.toString()
133
+ output.debug(`Unsuccessful assertion > ${msg}`)
134
+ event.dispatcher.once(event.test.finished, test => {
135
+ if (!test.notes) test.notes = []
136
+ test.notes.push({ type: 'conditionalError', text: msg })
137
+ })
138
+ recorder.session.restore(sessionName)
139
+ return result
140
+ })
141
+ return recorder.add(
142
+ 'result',
143
+ () => {
144
+ store.hopeThat = undefined
145
+ return result
146
+ },
147
+ true,
148
+ false,
149
+ )
150
+ },
151
+ false,
152
+ false,
153
+ )
154
+ }
155
+
156
+ /**
157
+ * A CodeceptJS utility function to retry a step or callback multiple times with a specified polling interval.
158
+ *
159
+ * @async
160
+ * @function retryTo
161
+ * @param {Function} callback - The function to execute, which will be retried upon failure.
162
+ * Receives the current retry count as an argument.
163
+ * @param {number} maxTries - The maximum number of attempts to retry the callback.
164
+ * @param {number} [pollInterval=200] - The delay (in milliseconds) between retry attempts.
165
+ * @returns {Promise<void|any>} A promise that resolves when the callback executes successfully, or rejects after reaching the maximum retries.
166
+ *
167
+ * @description
168
+ * - This function is designed for use in CodeceptJS tests to handle intermittent or flaky test steps.
169
+ * - Starts a new recorder session for each retry attempt, ensuring proper state management and error handling.
170
+ * - Logs errors and retries the callback until it either succeeds or the maximum number of attempts is reached.
171
+ * - Restores the session state after each attempt, whether successful or not.
172
+ *
173
+ * @example
174
+ * const { retryTo } = require('codeceptjs/effects')
175
+ * await retryTo((tries) => {
176
+ * if (tries < 3) {
177
+ * I.see('Non-existent element'); // Simulates a failure
178
+ * } else {
179
+ * I.see('Welcome'); // Succeeds on the 3rd attempt
180
+ * }
181
+ * }, 5, 300); // Retry up to 5 times, with a 300ms interval
182
+ *
183
+ * @throws Will reject with the last error encountered if the maximum retries are exceeded.
184
+ */
185
+ async function retryTo(callback, maxTries, pollInterval = 200) {
186
+ const sessionName = 'retryTo'
187
+
188
+ return new Promise((done, reject) => {
189
+ let tries = 1
190
+
191
+ function handleRetryException(err) {
192
+ recorder.throw(err)
193
+ reject(err)
194
+ }
195
+
196
+ const tryBlock = async () => {
197
+ tries++
198
+ recorder.session.start(`${sessionName} ${tries}`)
199
+ try {
200
+ await callback(tries)
201
+ } catch (err) {
202
+ handleRetryException(err)
203
+ }
204
+
205
+ // Call done if no errors
206
+ recorder.add(() => {
207
+ recorder.session.restore(`${sessionName} ${tries}`)
208
+ done(null)
209
+ })
210
+
211
+ // Catch errors and retry
212
+ recorder.session.catch(err => {
213
+ recorder.session.restore(`${sessionName} ${tries}`)
214
+ if (tries <= maxTries) {
215
+ output.debug(`Error ${err}... Retrying`)
216
+ recorder.add(`${sessionName} ${tries}`, () => setTimeout(tryBlock, pollInterval))
217
+ } else {
218
+ // if maxTries reached
219
+ handleRetryException(err)
220
+ }
221
+ })
222
+ }
223
+
224
+ recorder.add(sessionName, tryBlock).catch(err => {
225
+ console.error('An error occurred:', err)
226
+ done(null)
227
+ })
228
+ })
229
+ }
230
+
231
+ /**
232
+ * A CodeceptJS utility function to attempt a step or callback without failing the test.
233
+ * If the step fails, the test continues execution without interruption, and the result is logged.
234
+ *
235
+ * @async
236
+ * @function tryTo
237
+ * @param {Function} callback - The function to execute, which may succeed or fail.
238
+ * This function contains the logic to be attempted.
239
+ * @returns {Promise<boolean|any>} A promise resolving to `true` if the step succeeds, or `false` if it fails.
240
+ *
241
+ * @description
242
+ * - Useful for scenarios where certain steps are optional or their failure should not interrupt the test flow.
243
+ * - Starts a new recorder session named 'tryTo' for isolation and error handling.
244
+ * - Captures errors during execution and logs them for debugging purposes.
245
+ * - Ensures the `store.tryTo` flag is reset after execution to maintain a clean state.
246
+ *
247
+ * @example
248
+ * const { tryTo } = require('codeceptjs/effects')
249
+ * const wasSuccessful = await tryTo(() => {
250
+ * I.see('Welcome'); // Attempt to find an element on the page
251
+ * });
252
+ *
253
+ * if (!wasSuccessful) {
254
+ * I.say('Optional step failed, but test continues.');
255
+ * }
256
+ *
257
+ * @throws Will handle errors internally, logging them and returning `false` as the result.
258
+ */
259
+ async function tryTo(callback) {
260
+ if (store.dryRun) return
261
+ const sessionName = 'tryTo'
262
+
263
+ let result = false
264
+ let isAutoRetriesEnabled = store.autoRetries
265
+ return recorder.add(
266
+ sessionName,
267
+ () => {
268
+ recorder.session.start(sessionName)
269
+ isAutoRetriesEnabled = store.autoRetries
270
+ if (isAutoRetriesEnabled) output.debug('Auto retries disabled inside tryTo effect')
271
+ store.autoRetries = false
272
+ callback()
273
+ recorder.add(() => {
274
+ result = true
275
+ recorder.session.restore(sessionName)
276
+ return result
277
+ })
278
+ recorder.session.catch(err => {
279
+ result = false
280
+ const msg = err.inspect ? err.inspect() : err.toString()
281
+ output.debug(`Unsuccessful try > ${msg}`)
282
+ recorder.session.restore(sessionName)
283
+ return result
284
+ })
285
+ return recorder.add(
286
+ 'result',
287
+ () => {
288
+ store.autoRetries = isAutoRetriesEnabled
289
+ return result
290
+ },
291
+ true,
292
+ false,
293
+ )
294
+ },
295
+ false,
296
+ false,
297
+ )
298
+ }
299
+
300
+ export { hopeThat, retryTo, tryTo, within }
301
+
302
+ export default {
303
+ hopeThat,
304
+ retryTo,
305
+ tryTo,
306
+ within,
307
+ }
@@ -0,0 +1,327 @@
1
+ import assert from 'assert'
2
+
3
+ /**
4
+ * Unified WebElement class that wraps native element instances from different helpers
5
+ * and provides a consistent API across all supported helpers (Playwright, WebDriver, Puppeteer).
6
+ */
7
+ class WebElement {
8
+ constructor(element, helper) {
9
+ this.element = element
10
+ this.helper = helper
11
+ this.helperType = this._detectHelperType(helper)
12
+ }
13
+
14
+ _detectHelperType(helper) {
15
+ if (!helper) return 'unknown'
16
+
17
+ const className = helper.constructor.name
18
+ if (className === 'Playwright') return 'playwright'
19
+ if (className === 'WebDriver') return 'webdriver'
20
+ if (className === 'Puppeteer') return 'puppeteer'
21
+
22
+ return 'unknown'
23
+ }
24
+
25
+ /**
26
+ * Get the native element instance
27
+ * @returns {ElementHandle|WebElement|ElementHandle} Native element
28
+ */
29
+ getNativeElement() {
30
+ return this.element
31
+ }
32
+
33
+ /**
34
+ * Get the helper instance
35
+ * @returns {Helper} Helper instance
36
+ */
37
+ getHelper() {
38
+ return this.helper
39
+ }
40
+
41
+ /**
42
+ * Get text content of the element
43
+ * @returns {Promise<string>} Element text content
44
+ */
45
+ async getText() {
46
+ switch (this.helperType) {
47
+ case 'playwright':
48
+ return this.element.textContent()
49
+ case 'webdriver':
50
+ return this.element.getText()
51
+ case 'puppeteer':
52
+ return this.element.evaluate(el => el.textContent)
53
+ default:
54
+ throw new Error(`Unsupported helper type: ${this.helperType}`)
55
+ }
56
+ }
57
+
58
+ /**
59
+ * Get attribute value of the element
60
+ * @param {string} name Attribute name
61
+ * @returns {Promise<string|null>} Attribute value
62
+ */
63
+ async getAttribute(name) {
64
+ switch (this.helperType) {
65
+ case 'playwright':
66
+ return this.element.getAttribute(name)
67
+ case 'webdriver':
68
+ return this.element.getAttribute(name)
69
+ case 'puppeteer':
70
+ return this.element.evaluate((el, attrName) => el.getAttribute(attrName), name)
71
+ default:
72
+ throw new Error(`Unsupported helper type: ${this.helperType}`)
73
+ }
74
+ }
75
+
76
+ /**
77
+ * Get property value of the element
78
+ * @param {string} name Property name
79
+ * @returns {Promise<any>} Property value
80
+ */
81
+ async getProperty(name) {
82
+ switch (this.helperType) {
83
+ case 'playwright':
84
+ return this.element.evaluate((el, propName) => el[propName], name)
85
+ case 'webdriver':
86
+ return this.element.getProperty(name)
87
+ case 'puppeteer':
88
+ return this.element.evaluate((el, propName) => el[propName], name)
89
+ default:
90
+ throw new Error(`Unsupported helper type: ${this.helperType}`)
91
+ }
92
+ }
93
+
94
+ /**
95
+ * Get innerHTML of the element
96
+ * @returns {Promise<string>} Element innerHTML
97
+ */
98
+ async getInnerHTML() {
99
+ switch (this.helperType) {
100
+ case 'playwright':
101
+ return this.element.innerHTML()
102
+ case 'webdriver':
103
+ return this.element.getProperty('innerHTML')
104
+ case 'puppeteer':
105
+ return this.element.evaluate(el => el.innerHTML)
106
+ default:
107
+ throw new Error(`Unsupported helper type: ${this.helperType}`)
108
+ }
109
+ }
110
+
111
+ /**
112
+ * Get value of the element (for input elements)
113
+ * @returns {Promise<string>} Element value
114
+ */
115
+ async getValue() {
116
+ switch (this.helperType) {
117
+ case 'playwright':
118
+ return this.element.inputValue()
119
+ case 'webdriver':
120
+ return this.element.getValue()
121
+ case 'puppeteer':
122
+ return this.element.evaluate(el => el.value)
123
+ default:
124
+ throw new Error(`Unsupported helper type: ${this.helperType}`)
125
+ }
126
+ }
127
+
128
+ /**
129
+ * Check if element is visible
130
+ * @returns {Promise<boolean>} True if element is visible
131
+ */
132
+ async isVisible() {
133
+ switch (this.helperType) {
134
+ case 'playwright':
135
+ return this.element.isVisible()
136
+ case 'webdriver':
137
+ return this.element.isDisplayed()
138
+ case 'puppeteer':
139
+ return this.element.evaluate(el => {
140
+ const style = window.getComputedStyle(el)
141
+ return style.display !== 'none' && style.visibility !== 'hidden' && style.opacity !== '0'
142
+ })
143
+ default:
144
+ throw new Error(`Unsupported helper type: ${this.helperType}`)
145
+ }
146
+ }
147
+
148
+ /**
149
+ * Check if element is enabled
150
+ * @returns {Promise<boolean>} True if element is enabled
151
+ */
152
+ async isEnabled() {
153
+ switch (this.helperType) {
154
+ case 'playwright':
155
+ return this.element.isEnabled()
156
+ case 'webdriver':
157
+ return this.element.isEnabled()
158
+ case 'puppeteer':
159
+ return this.element.evaluate(el => !el.disabled)
160
+ default:
161
+ throw new Error(`Unsupported helper type: ${this.helperType}`)
162
+ }
163
+ }
164
+
165
+ /**
166
+ * Check if element exists in DOM
167
+ * @returns {Promise<boolean>} True if element exists
168
+ */
169
+ async exists() {
170
+ try {
171
+ switch (this.helperType) {
172
+ case 'playwright':
173
+ // For Playwright, if we have the element, it exists
174
+ return await this.element.evaluate(el => !!el)
175
+ case 'webdriver':
176
+ // For WebDriver, if we have the element, it exists
177
+ return true
178
+ case 'puppeteer':
179
+ // For Puppeteer, if we have the element, it exists
180
+ return await this.element.evaluate(el => !!el)
181
+ default:
182
+ throw new Error(`Unsupported helper type: ${this.helperType}`)
183
+ }
184
+ } catch (e) {
185
+ return false
186
+ }
187
+ }
188
+
189
+ /**
190
+ * Get bounding box of the element
191
+ * @returns {Promise<Object>} Bounding box with x, y, width, height properties
192
+ */
193
+ async getBoundingBox() {
194
+ switch (this.helperType) {
195
+ case 'playwright':
196
+ return this.element.boundingBox()
197
+ case 'webdriver':
198
+ const rect = await this.element.getRect()
199
+ return {
200
+ x: rect.x,
201
+ y: rect.y,
202
+ width: rect.width,
203
+ height: rect.height,
204
+ }
205
+ case 'puppeteer':
206
+ return this.element.boundingBox()
207
+ default:
208
+ throw new Error(`Unsupported helper type: ${this.helperType}`)
209
+ }
210
+ }
211
+
212
+ /**
213
+ * Click the element
214
+ * @param {Object} options Click options
215
+ * @returns {Promise<void>}
216
+ */
217
+ async click(options = {}) {
218
+ switch (this.helperType) {
219
+ case 'playwright':
220
+ return this.element.click(options)
221
+ case 'webdriver':
222
+ return this.element.click()
223
+ case 'puppeteer':
224
+ return this.element.click(options)
225
+ default:
226
+ throw new Error(`Unsupported helper type: ${this.helperType}`)
227
+ }
228
+ }
229
+
230
+ /**
231
+ * Type text into the element
232
+ * @param {string} text Text to type
233
+ * @param {Object} options Type options
234
+ * @returns {Promise<void>}
235
+ */
236
+ async type(text, options = {}) {
237
+ switch (this.helperType) {
238
+ case 'playwright':
239
+ return this.element.type(text, options)
240
+ case 'webdriver':
241
+ return this.element.setValue(text)
242
+ case 'puppeteer':
243
+ return this.element.type(text, options)
244
+ default:
245
+ throw new Error(`Unsupported helper type: ${this.helperType}`)
246
+ }
247
+ }
248
+
249
+ /**
250
+ * Find first child element matching the locator
251
+ * @param {string|Object} locator Element locator
252
+ * @returns {Promise<WebElement|null>} WebElement instance or null if not found
253
+ */
254
+ async $(locator) {
255
+ let childElement
256
+
257
+ switch (this.helperType) {
258
+ case 'playwright':
259
+ childElement = await this.element.$(this._normalizeLocator(locator))
260
+ break
261
+ case 'webdriver':
262
+ try {
263
+ childElement = await this.element.$(this._normalizeLocator(locator))
264
+ } catch (e) {
265
+ return null
266
+ }
267
+ break
268
+ case 'puppeteer':
269
+ childElement = await this.element.$(this._normalizeLocator(locator))
270
+ break
271
+ default:
272
+ throw new Error(`Unsupported helper type: ${this.helperType}`)
273
+ }
274
+
275
+ return childElement ? new WebElement(childElement, this.helper) : null
276
+ }
277
+
278
+ /**
279
+ * Find all child elements matching the locator
280
+ * @param {string|Object} locator Element locator
281
+ * @returns {Promise<WebElement[]>} Array of WebElement instances
282
+ */
283
+ async $$(locator) {
284
+ let childElements
285
+
286
+ switch (this.helperType) {
287
+ case 'playwright':
288
+ childElements = await this.element.$$(this._normalizeLocator(locator))
289
+ break
290
+ case 'webdriver':
291
+ childElements = await this.element.$$(this._normalizeLocator(locator))
292
+ break
293
+ case 'puppeteer':
294
+ childElements = await this.element.$$(this._normalizeLocator(locator))
295
+ break
296
+ default:
297
+ throw new Error(`Unsupported helper type: ${this.helperType}`)
298
+ }
299
+
300
+ return childElements.map(el => new WebElement(el, this.helper))
301
+ }
302
+
303
+ /**
304
+ * Normalize locator for element search
305
+ * @param {string|Object} locator Locator to normalize
306
+ * @returns {string} Normalized CSS selector
307
+ * @private
308
+ */
309
+ _normalizeLocator(locator) {
310
+ if (typeof locator === 'string') {
311
+ return locator
312
+ }
313
+
314
+ if (typeof locator === 'object') {
315
+ // Handle CodeceptJS locator objects
316
+ if (locator.css) return locator.css
317
+ if (locator.xpath) return locator.xpath
318
+ if (locator.id) return `#${locator.id}`
319
+ if (locator.name) return `[name="${locator.name}"]`
320
+ if (locator.className) return `.${locator.className}`
321
+ }
322
+
323
+ return locator.toString()
324
+ }
325
+ }
326
+
327
+ export default WebElement