codeceptjs 4.0.0-rc.18 → 4.0.0-rc.19

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 (220) hide show
  1. package/bin/codecept.js +5 -1
  2. package/bin/codeceptq.js +49 -0
  3. package/bin/mcp-server.js +250 -82
  4. package/docs/advanced.md +201 -0
  5. package/docs/agents.md +159 -0
  6. package/docs/ai.md +537 -0
  7. package/docs/aitrace.md +266 -0
  8. package/docs/api.md +332 -0
  9. package/docs/assertions.md +415 -0
  10. package/docs/auth.md +318 -0
  11. package/docs/basics.md +424 -0
  12. package/docs/bdd.md +539 -0
  13. package/docs/best.md +240 -0
  14. package/docs/bootstrap.md +132 -0
  15. package/docs/commands.md +352 -0
  16. package/docs/community-helpers.md +63 -0
  17. package/docs/configuration.md +230 -0
  18. package/docs/continuous-integration.md +497 -0
  19. package/docs/custom-helpers.md +297 -0
  20. package/docs/data.md +448 -0
  21. package/docs/debugging.md +332 -0
  22. package/docs/detox.md +235 -0
  23. package/docs/docker.md +136 -0
  24. package/docs/effects.md +179 -0
  25. package/docs/element-based-testing.md +295 -0
  26. package/docs/element-selection.md +125 -0
  27. package/docs/els.md +328 -0
  28. package/docs/examples.md +161 -0
  29. package/docs/heal.md +213 -0
  30. package/docs/helpers/ApiDataFactory.md +267 -0
  31. package/docs/helpers/Appium.md +1405 -0
  32. package/docs/helpers/Detox.md +665 -0
  33. package/docs/helpers/ExpectHelper.md +275 -0
  34. package/docs/helpers/FileSystem.md +152 -0
  35. package/docs/helpers/GraphQL.md +152 -0
  36. package/docs/helpers/GraphQLDataFactory.md +226 -0
  37. package/docs/helpers/JSONResponse.md +255 -0
  38. package/docs/helpers/Mochawesome.md +8 -0
  39. package/docs/helpers/MockRequest.md +377 -0
  40. package/docs/helpers/MockServer.md +212 -0
  41. package/docs/helpers/Playwright.md +2969 -0
  42. package/docs/helpers/Polly.md +44 -0
  43. package/docs/helpers/Protractor.md +1769 -0
  44. package/docs/helpers/Puppeteer-firefox.md +86 -0
  45. package/docs/helpers/Puppeteer.md +2690 -0
  46. package/docs/helpers/REST.md +289 -0
  47. package/docs/helpers/SoftExpectHelper.md +352 -0
  48. package/docs/helpers/WebDriver.md +2682 -0
  49. package/docs/hooks.md +339 -0
  50. package/docs/index.md +111 -0
  51. package/docs/installation.md +83 -0
  52. package/docs/internal-api.md +265 -0
  53. package/docs/internal-test-server.md +89 -0
  54. package/docs/locators.md +355 -0
  55. package/docs/mcp.md +485 -0
  56. package/docs/migration-4.md +556 -0
  57. package/docs/mobile.md +338 -0
  58. package/docs/pageobjects.md +399 -0
  59. package/docs/parallel.md +585 -0
  60. package/docs/playwright.md +714 -0
  61. package/docs/plugins.md +866 -0
  62. package/docs/puppeteer.md +314 -0
  63. package/docs/quickstart.md +120 -0
  64. package/docs/react.md +70 -0
  65. package/docs/reports.md +483 -0
  66. package/docs/retry.md +274 -0
  67. package/docs/secrets.md +150 -0
  68. package/docs/sessions.md +80 -0
  69. package/docs/shadow.md +68 -0
  70. package/docs/test-structure.md +275 -0
  71. package/docs/timeouts.md +183 -0
  72. package/docs/translation.md +247 -0
  73. package/docs/tutorial.md +271 -0
  74. package/docs/typescript.md +374 -0
  75. package/docs/web-element.md +251 -0
  76. package/docs/webdriver.md +708 -0
  77. package/docs/within.md +55 -0
  78. package/lib/command/dryRun.js +9 -3
  79. package/lib/command/init.js +247 -266
  80. package/lib/command/query.js +218 -0
  81. package/lib/config.js +9 -0
  82. package/lib/element/WebElement.js +37 -0
  83. package/lib/globals.js +11 -10
  84. package/lib/helper/Playwright.js +4 -1
  85. package/lib/html.js +3 -0
  86. package/lib/index.js +9 -1
  87. package/lib/locator.js +2 -2
  88. package/lib/mocha/factory.js +5 -1
  89. package/lib/mocha/inject.js +1 -1
  90. package/lib/parser.js +2 -2
  91. package/lib/plugin/browser.js +2 -1
  92. package/lib/plugin/expose.js +159 -0
  93. package/lib/workers.js +1 -15
  94. package/package.json +7 -5
  95. package/docs/webapi/amOnPage.mustache +0 -11
  96. package/docs/webapi/appendField.mustache +0 -16
  97. package/docs/webapi/attachFile.mustache +0 -24
  98. package/docs/webapi/blur.mustache +0 -18
  99. package/docs/webapi/checkOption.mustache +0 -13
  100. package/docs/webapi/clearCookie.mustache +0 -9
  101. package/docs/webapi/clearField.mustache +0 -14
  102. package/docs/webapi/click.mustache +0 -29
  103. package/docs/webapi/clickLink.mustache +0 -8
  104. package/docs/webapi/closeCurrentTab.mustache +0 -7
  105. package/docs/webapi/closeOtherTabs.mustache +0 -8
  106. package/docs/webapi/dontSee.mustache +0 -11
  107. package/docs/webapi/dontSeeCheckboxIsChecked.mustache +0 -10
  108. package/docs/webapi/dontSeeCookie.mustache +0 -8
  109. package/docs/webapi/dontSeeCurrentPathEquals.mustache +0 -10
  110. package/docs/webapi/dontSeeCurrentUrlEquals.mustache +0 -10
  111. package/docs/webapi/dontSeeElement.mustache +0 -12
  112. package/docs/webapi/dontSeeElementInDOM.mustache +0 -8
  113. package/docs/webapi/dontSeeInCurrentUrl.mustache +0 -4
  114. package/docs/webapi/dontSeeInField.mustache +0 -16
  115. package/docs/webapi/dontSeeInSource.mustache +0 -8
  116. package/docs/webapi/dontSeeInTitle.mustache +0 -8
  117. package/docs/webapi/dontSeeTraffic.mustache +0 -13
  118. package/docs/webapi/doubleClick.mustache +0 -13
  119. package/docs/webapi/downloadFile.mustache +0 -12
  120. package/docs/webapi/dragAndDrop.mustache +0 -9
  121. package/docs/webapi/dragSlider.mustache +0 -11
  122. package/docs/webapi/executeAsyncScript.mustache +0 -24
  123. package/docs/webapi/executeScript.mustache +0 -26
  124. package/docs/webapi/fillField.mustache +0 -21
  125. package/docs/webapi/flushNetworkTraffics.mustache +0 -5
  126. package/docs/webapi/focus.mustache +0 -13
  127. package/docs/webapi/forceClick.mustache +0 -28
  128. package/docs/webapi/forceRightClick.mustache +0 -18
  129. package/docs/webapi/grabAllWindowHandles.mustache +0 -7
  130. package/docs/webapi/grabAttributeFrom.mustache +0 -10
  131. package/docs/webapi/grabAttributeFromAll.mustache +0 -9
  132. package/docs/webapi/grabBrowserLogs.mustache +0 -9
  133. package/docs/webapi/grabCookie.mustache +0 -11
  134. package/docs/webapi/grabCssPropertyFrom.mustache +0 -11
  135. package/docs/webapi/grabCssPropertyFromAll.mustache +0 -10
  136. package/docs/webapi/grabCurrentUrl.mustache +0 -9
  137. package/docs/webapi/grabCurrentWindowHandle.mustache +0 -6
  138. package/docs/webapi/grabDataFromPerformanceTiming.mustache +0 -20
  139. package/docs/webapi/grabElementBoundingRect.mustache +0 -20
  140. package/docs/webapi/grabGeoLocation.mustache +0 -8
  141. package/docs/webapi/grabHTMLFrom.mustache +0 -10
  142. package/docs/webapi/grabHTMLFromAll.mustache +0 -9
  143. package/docs/webapi/grabNumberOfOpenTabs.mustache +0 -8
  144. package/docs/webapi/grabNumberOfVisibleElements.mustache +0 -9
  145. package/docs/webapi/grabPageScrollPosition.mustache +0 -8
  146. package/docs/webapi/grabPopupText.mustache +0 -5
  147. package/docs/webapi/grabRecordedNetworkTraffics.mustache +0 -10
  148. package/docs/webapi/grabSource.mustache +0 -8
  149. package/docs/webapi/grabTextFrom.mustache +0 -10
  150. package/docs/webapi/grabTextFromAll.mustache +0 -9
  151. package/docs/webapi/grabTitle.mustache +0 -8
  152. package/docs/webapi/grabValueFrom.mustache +0 -9
  153. package/docs/webapi/grabValueFromAll.mustache +0 -8
  154. package/docs/webapi/grabWebElement.mustache +0 -9
  155. package/docs/webapi/grabWebElements.mustache +0 -9
  156. package/docs/webapi/moveCursorTo.mustache +0 -16
  157. package/docs/webapi/openNewTab.mustache +0 -7
  158. package/docs/webapi/pressKey.mustache +0 -12
  159. package/docs/webapi/pressKeyDown.mustache +0 -12
  160. package/docs/webapi/pressKeyUp.mustache +0 -12
  161. package/docs/webapi/pressKeyWithKeyNormalization.mustache +0 -60
  162. package/docs/webapi/refreshPage.mustache +0 -6
  163. package/docs/webapi/resizeWindow.mustache +0 -6
  164. package/docs/webapi/rightClick.mustache +0 -14
  165. package/docs/webapi/saveElementScreenshot.mustache +0 -10
  166. package/docs/webapi/saveScreenshot.mustache +0 -12
  167. package/docs/webapi/say.mustache +0 -10
  168. package/docs/webapi/scrollIntoView.mustache +0 -11
  169. package/docs/webapi/scrollPageToBottom.mustache +0 -6
  170. package/docs/webapi/scrollPageToTop.mustache +0 -6
  171. package/docs/webapi/scrollTo.mustache +0 -12
  172. package/docs/webapi/see.mustache +0 -11
  173. package/docs/webapi/seeAttributesOnElements.mustache +0 -9
  174. package/docs/webapi/seeCheckboxIsChecked.mustache +0 -10
  175. package/docs/webapi/seeCookie.mustache +0 -8
  176. package/docs/webapi/seeCssPropertiesOnElements.mustache +0 -9
  177. package/docs/webapi/seeCurrentPathEquals.mustache +0 -10
  178. package/docs/webapi/seeCurrentUrlEquals.mustache +0 -11
  179. package/docs/webapi/seeElement.mustache +0 -12
  180. package/docs/webapi/seeElementInDOM.mustache +0 -8
  181. package/docs/webapi/seeFileDownloaded.mustache +0 -23
  182. package/docs/webapi/seeInCurrentUrl.mustache +0 -8
  183. package/docs/webapi/seeInField.mustache +0 -17
  184. package/docs/webapi/seeInPopup.mustache +0 -8
  185. package/docs/webapi/seeInSource.mustache +0 -7
  186. package/docs/webapi/seeInTitle.mustache +0 -8
  187. package/docs/webapi/seeNumberOfElements.mustache +0 -11
  188. package/docs/webapi/seeNumberOfVisibleElements.mustache +0 -10
  189. package/docs/webapi/seeTextEquals.mustache +0 -9
  190. package/docs/webapi/seeTitleEquals.mustache +0 -8
  191. package/docs/webapi/seeTraffic.mustache +0 -36
  192. package/docs/webapi/selectOption.mustache +0 -26
  193. package/docs/webapi/setCookie.mustache +0 -16
  194. package/docs/webapi/setGeoLocation.mustache +0 -12
  195. package/docs/webapi/startRecordingTraffic.mustache +0 -8
  196. package/docs/webapi/startRecordingWebSocketMessages.mustache +0 -8
  197. package/docs/webapi/stopRecordingTraffic.mustache +0 -5
  198. package/docs/webapi/stopRecordingWebSocketMessages.mustache +0 -7
  199. package/docs/webapi/switchTo.mustache +0 -9
  200. package/docs/webapi/switchToNextTab.mustache +0 -10
  201. package/docs/webapi/switchToPreviousTab.mustache +0 -10
  202. package/docs/webapi/type.mustache +0 -21
  203. package/docs/webapi/uncheckOption.mustache +0 -13
  204. package/docs/webapi/wait.mustache +0 -8
  205. package/docs/webapi/waitForClickable.mustache +0 -11
  206. package/docs/webapi/waitForCookie.mustache +0 -9
  207. package/docs/webapi/waitForDetached.mustache +0 -10
  208. package/docs/webapi/waitForDisabled.mustache +0 -6
  209. package/docs/webapi/waitForElement.mustache +0 -11
  210. package/docs/webapi/waitForEnabled.mustache +0 -6
  211. package/docs/webapi/waitForFunction.mustache +0 -17
  212. package/docs/webapi/waitForInvisible.mustache +0 -10
  213. package/docs/webapi/waitForNumberOfTabs.mustache +0 -9
  214. package/docs/webapi/waitForText.mustache +0 -13
  215. package/docs/webapi/waitForValue.mustache +0 -10
  216. package/docs/webapi/waitForVisible.mustache +0 -10
  217. package/docs/webapi/waitInUrl.mustache +0 -9
  218. package/docs/webapi/waitNumberOfVisibleElements.mustache +0 -10
  219. package/docs/webapi/waitToHide.mustache +0 -10
  220. package/docs/webapi/waitUrlEquals.mustache +0 -10
@@ -0,0 +1,179 @@
1
+ ---
2
+ permalink: /effects
3
+ title: Effects
4
+ ---
5
+
6
+ # Effects
7
+
8
+ Effects are functions that can modify scenario flow. They provide ways to handle conditional steps, retries, scoped contexts, and test flow control.
9
+
10
+ ## Installation
11
+
12
+ Effects can be imported directly from CodeceptJS:
13
+
14
+ ```js
15
+ import { tryTo, retryTo, hopeThat, within } from 'codeceptjs/effects'
16
+ ```
17
+
18
+ > 📝 Note: Prior to v4, `tryTo` and `retryTo` were enabled via plugins (`tryTo`, `retryTo`) that registered them as globals. Those plugins are removed in v4 — import effects from `codeceptjs/effects` instead.
19
+
20
+ ## tryTo
21
+
22
+ The `tryTo` effect allows you to attempt steps that may fail without stopping test execution. It's useful for handling optional steps or conditions that aren't critical for the test flow.
23
+
24
+ ```js
25
+ import { tryTo } from 'codeceptjs/effects'
26
+
27
+ // inside a test
28
+ const success = await tryTo(() => {
29
+ // These steps may fail but won't stop the test
30
+ I.see('Cookie banner')
31
+ I.click('Accept cookies')
32
+ })
33
+
34
+ if (!success) {
35
+ I.say('Cookie banner was not found')
36
+ }
37
+ ```
38
+
39
+ If the steps inside `tryTo` fail:
40
+
41
+ - The test will continue execution
42
+ - The failure will be logged in debug output
43
+ - `tryTo` returns `false`
44
+ - Auto-retries are disabled inside `tryTo` blocks
45
+
46
+ ## hopeThat
47
+
48
+ `hopeThat` is the soft-assertion effect. It wraps a block of steps; if any step inside fails, the failure is recorded as a note on the test and `hopeThat` returns `false`, but the scenario keeps running. Call `hopeThat.noErrors()` once at the end to fail the scenario if any soft assertion failed.
49
+
50
+ ```js
51
+ import { hopeThat } from 'codeceptjs/effects'
52
+
53
+ Scenario('form shows every validation error', ({ I }) => {
54
+ I.amOnPage('/signup')
55
+ I.click('Submit')
56
+
57
+ await hopeThat(() => I.see('Email is required', '#email-error'))
58
+ await hopeThat(() => I.see('Password is required', '#password-error'))
59
+ await hopeThat(() => I.see('You must accept the terms', '#terms-error'))
60
+
61
+ hopeThat.noErrors() // throws once, listing every recorded failure
62
+ })
63
+ ```
64
+
65
+ `hopeThat` returns `Promise<boolean>` — `true` on success, `false` on caught failure — which is handy for branching:
66
+
67
+ ```js
68
+ const cookieAccepted = await hopeThat(() => I.click('Accept cookies'))
69
+ if (!cookieAccepted) I.say('No cookie banner')
70
+ ```
71
+
72
+ > 💡 In 3.x, soft assertions were provided by `SoftExpectHelper` (`I.softAssert`, `I.softExpectEqual`, `I.flushSoftAssertions`). That helper is gone in 4.x — use `hopeThat()` and `hopeThat.noErrors()` instead. `hopeThat` works with **any** assertion you can write inside a step: built-in `I.see*`, custom-helper assertions, `expect()` from your own assertion library, plain `assert` from Node — anything that throws on failure.
73
+
74
+ The same `hopeThat` is also re-exported from `codeceptjs/assertions` if you prefer that subpath:
75
+
76
+ ```js
77
+ import { hopeThat } from 'codeceptjs/assertions'
78
+ ```
79
+
80
+ ## retryTo
81
+
82
+ The `retryTo` effect allows you to retry a set of steps multiple times until they succeed. This is useful for handling flaky elements or conditions that may need multiple attempts.
83
+
84
+ ```js
85
+ import { retryTo } from 'codeceptjs/effects'
86
+
87
+ // Retry up to 5 times with 200ms between attempts
88
+ await retryTo(() => {
89
+ I.switchTo('#editor-frame')
90
+ I.fillField('textarea', 'Hello world')
91
+ }, 5)
92
+ ```
93
+
94
+ Parameters:
95
+
96
+ - `callback` - Function containing steps to retry
97
+ - `maxTries` - Maximum number of retry attempts
98
+ - `pollInterval` - (optional) Delay between retries in milliseconds (default: 200ms)
99
+
100
+ The callback receives the current retry count as an argument:
101
+
102
+ ```js
103
+ import { retryTo } from 'codeceptjs/effects'
104
+
105
+ // inside a test...
106
+ await retryTo(tries => {
107
+ I.say(`Attempt ${tries}`)
108
+ I.click('Submit')
109
+ I.see('Success')
110
+ }, 3)
111
+ ```
112
+
113
+ ## within
114
+
115
+ The `within` effect scopes all actions inside it to a specific element on the page — useful when working with repeated UI components or narrowing interaction to a specific section.
116
+
117
+ ```js
118
+ import { within } from 'codeceptjs/effects'
119
+
120
+ // inside a test...
121
+ await within('.js-signup-form', () => {
122
+ I.fillField('user[login]', 'User')
123
+ I.fillField('user[email]', 'user@user.com')
124
+ I.fillField('user[password]', 'user@user.com')
125
+ I.click('button')
126
+ })
127
+ I.see('There were problems creating your account.')
128
+ ```
129
+
130
+ > ⚠ `within` can cause problems when used incorrectly. If you see unexpected behavior, refactor to use the context parameter on individual actions instead (e.g. `I.click('Login', '.nav')`). Keep `within` for the simplest cases.
131
+
132
+ > ⚠ Since `within` returns a Promise, always `await` it when you need its return value.
133
+
134
+ ### IFrames
135
+
136
+ Use a `frame` locator to scope actions inside an iframe:
137
+
138
+ ```js
139
+ await within({ frame: '#editor' }, () => {
140
+ I.see('Page')
141
+ I.fillField('Body', 'Hello world')
142
+ })
143
+ ```
144
+
145
+ Nested iframes _(WebDriver & Puppeteer only)_:
146
+
147
+ ```js
148
+ await within({ frame: ['.content', '#editor'] }, () => {
149
+ I.see('Page')
150
+ })
151
+ ```
152
+
153
+ > ℹ IFrames can also be accessed via `I.switchTo` command.
154
+
155
+ ### Returning Values
156
+
157
+ `within` can return a value for use in the scenario:
158
+
159
+ ```js
160
+ const val = await within('#sidebar', () => {
161
+ return I.grabTextFrom({ css: 'h1' })
162
+ })
163
+ I.fillField('Description', val)
164
+ ```
165
+
166
+ When running steps inside a `within` block, they will be shown indented in the output.
167
+
168
+ ## Usage with TypeScript
169
+
170
+ Effects are fully typed and work well with TypeScript:
171
+
172
+ ```ts
173
+ import { tryTo, retryTo, within } from 'codeceptjs/effects'
174
+
175
+ const success = await tryTo(async () => {
176
+ await I.see('Element')
177
+ })
178
+ ```
179
+
@@ -0,0 +1,295 @@
1
+ # Element-Based Testing
2
+
3
+ CodeceptJS offers multiple ways to write tests. While the traditional `I.*` actions provide a clean, readable syntax, element-based testing gives you more control and flexibility when working with complex DOM structures.
4
+
5
+ ## Why Element-Based Testing?
6
+
7
+ Element-based testing is useful when:
8
+
9
+ - **You need direct access to DOM properties** - Inspect attributes, computed styles, or form values
10
+ - **Working with lists and collections** - Iterate over multiple elements with custom logic
11
+ - **Complex assertions** - Validate conditions that built-in methods don't cover
12
+ - **Performance optimization** - Reduce redundant lookups by reusing element references
13
+ - **Custom interactions** - Perform actions not available in standard helper methods
14
+
15
+ ## The CodeceptJS Hybrid Approach
16
+
17
+ CodeceptJS uniquely combines both styles. You can freely mix `I.*` actions with element-based operations in the same test:
18
+
19
+ ```js
20
+ // Import element functions
21
+ import { element, eachElement, expectElement } from 'codeceptjs/els'
22
+
23
+ Scenario('checkout flow', async ({ I }) => {
24
+ // Use I.* for navigation and high-level actions
25
+ I.amOnPage('/products')
26
+ I.click('Add to Cart')
27
+
28
+ // Use element-based for detailed validation
29
+ await element('.cart-summary', async cart => {
30
+ const total = await cart.getAttribute('data-total')
31
+ console.log('Cart total:', total)
32
+ })
33
+
34
+ // Continue with I.* actions
35
+ I.click('Checkout')
36
+ })
37
+ ```
38
+
39
+ This hybrid approach gives you the best of both worlds - readable high-level actions mixed with low-level control when needed.
40
+
41
+ ## Quick Comparison
42
+
43
+ ### Traditional I.* Approach
44
+
45
+ ```js
46
+ Scenario('form validation', async ({ I }) => {
47
+ I.amOnPage('/register')
48
+ I.fillField('Email', 'test@example.com')
49
+ I.fillField('Password', 'secret123')
50
+ I.click('Register')
51
+ I.see('Welcome')
52
+ })
53
+ ```
54
+
55
+ ### Element-Based Approach
56
+
57
+ ```js
58
+ import { element, expectElement } from 'codeceptjs/els'
59
+
60
+ Scenario('form validation', async ({ I }) => {
61
+ I.amOnPage('/register')
62
+
63
+ // Direct form manipulation
64
+ await element('#email', async input => {
65
+ await input.type('test@example.com')
66
+ })
67
+
68
+ await element('#password', async input => {
69
+ await input.type('secret123')
70
+ })
71
+
72
+ await element('button[type="submit"]', async btn => {
73
+ await btn.click()
74
+ })
75
+
76
+ // Custom assertion
77
+ await expectElement('.welcome-message', async msg => {
78
+ const text = await msg.getText()
79
+ return text.includes('Welcome')
80
+ })
81
+ })
82
+ ```
83
+
84
+ ### When to Use Each
85
+
86
+ | Use `I.*` actions when... | Use element-based when... |
87
+ |---------------------------|---------------------------|
88
+ | Simple navigation and clicks | Complex DOM traversal |
89
+ | Standard form interactions | Custom validation logic |
90
+ | Built-in assertions suffice | Need specific element properties |
91
+ | Readability is priority | Working with element collections |
92
+ | Single-step operations | Chaining multiple operations on same element |
93
+
94
+ ## Element Chaining
95
+
96
+ Element-based testing allows you to chain queries to find child elements, reducing redundant lookups:
97
+
98
+ ```js
99
+ import { element } from 'codeceptjs/els'
100
+
101
+ Scenario('product list', async ({ I }) => {
102
+ I.amOnPage('/products')
103
+
104
+ // Chain into child elements
105
+ await element('.product-list', async list => {
106
+ const firstProduct = await list.$('.product-item')
107
+ const title = await firstProduct.$('.title')
108
+ const price = await firstProduct.$('.price')
109
+
110
+ const titleText = await title.getText()
111
+ const priceValue = await price.getText()
112
+
113
+ console.log(`${titleText}: ${priceValue}`)
114
+ })
115
+ })
116
+ ```
117
+
118
+ ## Real-World Examples
119
+
120
+ ### Example 1: Form Validation
121
+
122
+ Validate complex form requirements that built-in methods don't cover:
123
+
124
+ ```js
125
+ import { element, eachElement } from 'codeceptjs/els'
126
+ import { expect } from 'chai'
127
+
128
+ Scenario('validate form fields', async ({ I }) => {
129
+ I.amOnPage('/register')
130
+
131
+ // Check all required fields are properly marked
132
+ await eachElement('[required]', async field => {
133
+ const ariaRequired = await field.getAttribute('aria-required')
134
+ const required = await field.getAttribute('required')
135
+ if (!ariaRequired && !required) {
136
+ throw new Error('Required field missing indicators')
137
+ }
138
+ })
139
+
140
+ // Fill form with custom validation
141
+ await element('#email', async input => {
142
+ await input.type('test@example.com')
143
+ const value = await input.getValue()
144
+ expect(value).to.include('@')
145
+ })
146
+
147
+ I.click('Submit')
148
+ })
149
+ ```
150
+
151
+ ### Example 2: Data Table Processing
152
+
153
+ Work with tabular data using iteration and child element queries:
154
+
155
+ ```js
156
+ import { eachElement, element } from 'codeceptjs/els'
157
+
158
+ Scenario('verify table data', async ({ I }) => {
159
+ I.amOnPage('/dashboard')
160
+
161
+ // Get table row count
162
+ await element('table tbody', async tbody => {
163
+ const rows = await tbody.$$('tr')
164
+ console.log(`Table has ${rows.length} rows`)
165
+ })
166
+
167
+ // Verify each row has expected structure
168
+ await eachElement('table tbody tr', async (row, index) => {
169
+ const cells = await row.$$('td')
170
+ if (cells.length < 3) {
171
+ throw new Error(`Row ${index} should have at least 3 columns`)
172
+ }
173
+ })
174
+ })
175
+ ```
176
+
177
+ ### Example 3: Dynamic Content Waiting
178
+
179
+ Wait for and validate dynamic content with custom conditions:
180
+
181
+ ```js
182
+ import { element, expectElement } from 'codeceptjs/els'
183
+
184
+ Scenario('wait for dynamic content', async ({ I }) => {
185
+ I.amOnPage('/search')
186
+ I.fillField('query', 'test')
187
+ I.click('Search')
188
+
189
+ // Wait for results with custom validation
190
+ await expectElement('.search-results', async results => {
191
+ const items = await results.$$('.result-item')
192
+ return items.length > 0
193
+ })
194
+ })
195
+ ```
196
+
197
+ ### Example 4: Shopping Cart Operations
198
+
199
+ Calculate and verify cart totals by iterating through items:
200
+
201
+ ```js
202
+ import { element, eachElement } from 'codeceptjs/els'
203
+ import { expect } from 'chai'
204
+
205
+ Scenario('calculate cart total', async ({ I }) => {
206
+ I.amOnPage('/cart')
207
+
208
+ let total = 0
209
+
210
+ // Sum up all item prices
211
+ await eachElement('.cart-item .price', async priceEl => {
212
+ const priceText = await priceEl.getText()
213
+ const price = parseFloat(priceText.replace('$', ''))
214
+ total += price
215
+ })
216
+
217
+ // Verify displayed total matches calculated sum
218
+ await element('.cart-total', async totalEl => {
219
+ const displayedTotal = await totalEl.getText()
220
+ const displayedValue = parseFloat(displayedTotal.replace('$', ''))
221
+ expect(displayedValue).to.equal(total)
222
+ })
223
+ })
224
+ ```
225
+
226
+ ### Example 5: List Filtering and Validation
227
+
228
+ Validate filtered results meet specific criteria:
229
+
230
+ ```js
231
+ import { element, eachElement, expectAnyElement } from 'codeceptjs/els'
232
+ import { expect } from 'chai'
233
+
234
+ Scenario('filter products by price', async ({ I }) => {
235
+ I.amOnPage('/products')
236
+ I.click('Under $100')
237
+
238
+ // Verify all displayed products are under $100
239
+ await eachElement('.product-item', async product => {
240
+ const priceEl = await product.$('.price')
241
+ const priceText = await priceEl.getText()
242
+ const price = parseFloat(priceText.replace('$', ''))
243
+ expect(price).to.be.below(100)
244
+ })
245
+
246
+ // Check at least one product exists
247
+ await expectAnyElement('.product-item', async () => true)
248
+ })
249
+ ```
250
+
251
+ ## Best Practices
252
+
253
+ 1. **Mix styles appropriately** - Use `I.*` for navigation and high-level actions, element-based for complex validation
254
+
255
+ 2. **Use descriptive purposes** - Add purpose strings for better debugging logs:
256
+ ```js
257
+ await element(
258
+ 'verify discount applied',
259
+ '.price',
260
+ async el => { /* ... */ }
261
+ )
262
+ ```
263
+
264
+ 3. **Reuse element references** - Chain `$(locator)` to avoid redundant lookups
265
+
266
+ 4. **Handle empty results** - Always check if elements exist before accessing properties
267
+
268
+ 5. **Prefer standard assertions** - Use `I.see()`, `I.dontSee()` when possible for readability
269
+
270
+ 6. **Consider page objects** - Combine with Page Objects for reusable element logic
271
+
272
+ ## API Reference
273
+
274
+ - **[Element Access](els.md)** - Complete reference for `element()`, `eachElement()`, `expectElement()`, `expectAnyElement()`, `expectAllElements()` functions
275
+ - **[WebElement API](web-element.md)** - Complete reference for WebElement class methods (`getText()`, `getAttribute()`, `click()`, `$$()`, etc.)
276
+
277
+ ## Portability
278
+
279
+ Elements are wrapped in a `WebElement` class that provides a consistent API across all helpers (Playwright, WebDriver, Puppeteer). Your element-based tests will work the same way regardless of which helper you're using:
280
+
281
+ ```js
282
+ // This test works identically with Playwright, WebDriver, or Puppeteer
283
+ import { element } from 'codeceptjs/els'
284
+
285
+ Scenario('portable test', async ({ I }) => {
286
+ I.amOnPage('/')
287
+
288
+ await element('.main-title', async title => {
289
+ const text = await title.getText() // Works on all helpers
290
+ const className = await title.getAttribute('class')
291
+ const visible = await title.isVisible()
292
+ const enabled = await title.isEnabled()
293
+ })
294
+ })
295
+ ```
@@ -0,0 +1,125 @@
1
+ ---
2
+ permalink: /element-selection
3
+ title: Element Selection
4
+ ---
5
+
6
+ # Element Selection
7
+
8
+ When you write `I.click('a')` and there are multiple links on a page, CodeceptJS clicks the **first** one it finds. Most of the time this is exactly what you need — your locators are specific enough that there's only one match, or the first match happens to be the right one.
9
+
10
+ But what happens when it's not?
11
+
12
+ ## Picking a Specific Element
13
+
14
+ Say you have a list of items and you want to click the second one. You could write a more specific CSS selector, but sometimes the simplest approach is to tell CodeceptJS which element you want by position:
15
+
16
+ ```js
17
+ import step from 'codeceptjs/steps'
18
+
19
+ // click the 2nd link
20
+ I.click('a', step.opts({ elementIndex: 2 }))
21
+
22
+ // click the last link
23
+ I.click('a', step.opts({ elementIndex: 'last' }))
24
+
25
+ // fill the last matching input
26
+ I.fillField('.email-input', 'test@example.com', step.opts({ elementIndex: -1 }))
27
+ ```
28
+
29
+ The `elementIndex` option accepts:
30
+
31
+ * **Positive numbers** (1-based) — `1` is first, `2` is second, `3` is third
32
+ * **Negative numbers** — `-1` is last, `-2` is second-to-last
33
+ * **`'first'`** and **`'last'`** as readable aliases
34
+
35
+ This works with any action that targets a single element: `click`, `doubleClick`, `rightClick`, `fillField`, `appendField`, `clearField`, `checkOption`, `selectOption`, `attachFile`, and others.
36
+
37
+ If only one element matches the locator, `elementIndex` is silently ignored — you always get that single element regardless of the index value. This is convenient when the number of matches depends on page state: you won't get an error if the list happens to have just one item.
38
+
39
+ When multiple elements exist but the index is out of range, CodeceptJS throws a clear error:
40
+
41
+ ```
42
+ elementIndex 100 exceeds the number of elements found (3) for "a"
43
+ ```
44
+
45
+ You can combine `elementIndex` with other step options:
46
+
47
+ ```js
48
+ I.click('a', step.opts({ elementIndex: 2 }).timeout(5).retry(3))
49
+ ```
50
+
51
+ ## Strict Mode
52
+
53
+ If you'd rather not silently click the first of many matches, enable `strict: true` in your helper configuration. This makes CodeceptJS throw an error whenever a locator matches more than one element, forcing you to write precise locators:
54
+
55
+ ```js
56
+ // codecept.conf.js
57
+ helpers: {
58
+ Playwright: {
59
+ url: 'http://localhost',
60
+ browser: 'chromium',
61
+ strict: true,
62
+ }
63
+ }
64
+ ```
65
+
66
+ Now any ambiguous locator will fail immediately:
67
+
68
+ ```js
69
+ I.click('a') // MultipleElementsFound: Multiple elements (3) found for "a" in strict mode
70
+ ```
71
+
72
+ This is useful on projects where you want to catch accidental matches early — clicking the wrong button because of a vague locator is a common source of flaky tests.
73
+
74
+ When a test fails in strict mode, the error includes a `fetchDetails()` method that lists the matched elements with their XPath and simplified HTML, so you can see exactly what was found and write a better locator:
75
+
76
+ ```js
77
+ // Multiple elements (3) found for "a" in strict mode. Call fetchDetails() for full information.
78
+ // After fetchDetails():
79
+ // /html/body/div/a[1] <a id="first-link">First</a>
80
+ // /html/body/div/a[2] <a id="second-link">Second</a>
81
+ // /html/body/div/a[3] <a id="third-link">Third</a>
82
+ // Use a more specific locator or grabWebElements() to work with multiple elements
83
+ ```
84
+
85
+ Strict mode is supported in **Playwright**, **Puppeteer**, and **WebDriver** helpers.
86
+
87
+ ### Per-Step Strict Mode with `exact`
88
+
89
+ You don't have to enable strict mode globally. Use `exact: true` to enforce it on a single step — handy when most of your tests are fine with default behavior but a particular action needs to be precise:
90
+
91
+ ```js
92
+ import step from 'codeceptjs/steps'
93
+
94
+ I.click('a', step.opts({ exact: true }))
95
+ // throws MultipleElementsFound if more than one link matches
96
+ ```
97
+
98
+ `strictMode: true` is an alias if you prefer a more descriptive name:
99
+
100
+ ```js
101
+ I.click('a', step.opts({ strictMode: true }))
102
+ ```
103
+
104
+ It works the other way too. If your helper has `strict: true` globally but you need to relax it for one step, use `exact: false`:
105
+
106
+ ```js
107
+ // strict: true in config, but this step allows multiple matches
108
+ I.click('a', step.opts({ exact: false }))
109
+ ```
110
+
111
+ And when you know there are multiple matches and want a specific one, `elementIndex` also overrides the strict check — no error is thrown because you've explicitly chosen which element to use:
112
+
113
+ ```js
114
+ // strict: true in config, but this works without error
115
+ I.click('a', step.opts({ elementIndex: 2 }))
116
+ ```
117
+
118
+ ## Summary
119
+
120
+ | Situation | Approach |
121
+ |-----------|----------|
122
+ | You want to catch ambiguous locators early | Enable `strict: true` in helper config |
123
+ | You need a specific element from a known list | Use `step.opts({ elementIndex: N })` |
124
+ | You want to iterate over all matching elements | Use [`eachElement`](/els) from the `els` module |
125
+ | You need full control over element inspection | Use [`grabWebElements`](/WebElement) to get all matches |