codeceptjs 4.0.0-rc.9 → 4.0.0

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 (314) hide show
  1. package/README.md +9 -10
  2. package/bin/codecept.js +15 -2
  3. package/bin/codeceptq.js +49 -0
  4. package/bin/mcp-server.js +751 -172
  5. package/docs/advanced.md +201 -0
  6. package/docs/agents.md +181 -0
  7. package/docs/ai.md +489 -0
  8. package/docs/aitrace.md +266 -0
  9. package/docs/api.md +332 -0
  10. package/docs/architecture.md +235 -0
  11. package/docs/assertions.md +415 -0
  12. package/docs/auth.md +318 -0
  13. package/docs/basics.md +424 -0
  14. package/docs/bdd.md +539 -0
  15. package/docs/best.md +240 -0
  16. package/docs/bootstrap.md +132 -0
  17. package/docs/commands.md +352 -0
  18. package/docs/community-helpers.md +63 -0
  19. package/docs/configuration.md +185 -0
  20. package/docs/continuous-integration.md +431 -0
  21. package/docs/custom-helpers.md +297 -0
  22. package/docs/data.md +448 -0
  23. package/docs/debugging.md +332 -0
  24. package/docs/detox.md +235 -0
  25. package/docs/docker.md +107 -0
  26. package/docs/effects.md +179 -0
  27. package/docs/element-based-testing.md +295 -0
  28. package/docs/element-selection.md +125 -0
  29. package/docs/els.md +328 -0
  30. package/docs/environment-variables.md +131 -0
  31. package/docs/examples.md +160 -0
  32. package/docs/heal.md +213 -0
  33. package/docs/helpers/ApiDataFactory.md +267 -0
  34. package/docs/helpers/Appium.md +1419 -0
  35. package/docs/helpers/Detox.md +665 -0
  36. package/docs/helpers/ExpectHelper.md +275 -0
  37. package/docs/helpers/FileSystem.md +152 -0
  38. package/docs/helpers/GraphQL.md +152 -0
  39. package/docs/helpers/GraphQLDataFactory.md +226 -0
  40. package/docs/helpers/JSONResponse.md +255 -0
  41. package/docs/helpers/MockRequest.md +377 -0
  42. package/docs/helpers/Playwright.md +2970 -0
  43. package/docs/helpers/Puppeteer-firefox.md +86 -0
  44. package/docs/helpers/Puppeteer.md +2583 -0
  45. package/docs/helpers/REST.md +289 -0
  46. package/docs/helpers/WebDriver.md +2639 -0
  47. package/docs/hooks.md +148 -0
  48. package/docs/index.md +111 -0
  49. package/docs/installation.md +121 -0
  50. package/docs/internal-test-server.md +89 -0
  51. package/docs/locators.md +355 -0
  52. package/docs/mcp.md +485 -0
  53. package/docs/migrate-from-cypress.md +98 -0
  54. package/docs/migrate-from-java.md +108 -0
  55. package/docs/migrate-from-protractor.md +101 -0
  56. package/docs/migrate-from-testcafe.md +99 -0
  57. package/docs/migration-4.md +743 -0
  58. package/docs/mobile.md +338 -0
  59. package/docs/pageobjects.md +399 -0
  60. package/docs/parallel.md +187 -0
  61. package/docs/playwright.md +714 -0
  62. package/docs/plugins/aiTrace.md +49 -0
  63. package/docs/plugins/analyze.md +66 -0
  64. package/docs/plugins/auth.md +241 -0
  65. package/docs/plugins/autoDelay.md +48 -0
  66. package/docs/plugins/browser.md +41 -0
  67. package/docs/plugins/coverage.md +39 -0
  68. package/docs/plugins/customLocator.md +119 -0
  69. package/docs/plugins/customReporter.md +16 -0
  70. package/docs/plugins/expose.md +75 -0
  71. package/docs/plugins/heal.md +44 -0
  72. package/docs/plugins/junitReporter.md +51 -0
  73. package/docs/plugins/pageInfo.md +34 -0
  74. package/docs/plugins/pause.md +43 -0
  75. package/docs/plugins/pauseOnFail.md +18 -0
  76. package/docs/plugins/retryFailedStep.md +75 -0
  77. package/docs/plugins/screencast.md +55 -0
  78. package/docs/plugins/screenshot.md +58 -0
  79. package/docs/plugins/screenshotOnFail.md +18 -0
  80. package/docs/plugins/stepTimeout.md +65 -0
  81. package/docs/plugins.md +87 -0
  82. package/docs/puppeteer.md +314 -0
  83. package/docs/quickstart.md +120 -0
  84. package/docs/reports.md +198 -0
  85. package/docs/retry.md +311 -0
  86. package/docs/secrets.md +150 -0
  87. package/docs/sessions.md +80 -0
  88. package/docs/shadow.md +68 -0
  89. package/docs/store.md +94 -0
  90. package/docs/test-structure.md +275 -0
  91. package/docs/timeouts.md +183 -0
  92. package/docs/translation.md +247 -0
  93. package/docs/tutorial.md +323 -0
  94. package/docs/typescript.md +159 -0
  95. package/docs/web-element.md +251 -0
  96. package/docs/webdriver.md +641 -0
  97. package/docs/within.md +55 -0
  98. package/lib/actor.js +1 -36
  99. package/lib/ai.js +3 -2
  100. package/lib/aria.js +260 -0
  101. package/lib/assertions.js +18 -0
  102. package/lib/codecept.js +7 -7
  103. package/lib/command/check.js +2 -1
  104. package/lib/command/dryRun.js +24 -5
  105. package/lib/command/generate.js +2 -0
  106. package/lib/command/gherkin/snippets.js +5 -4
  107. package/lib/command/init.js +248 -266
  108. package/lib/command/list.js +150 -10
  109. package/lib/command/query.js +218 -0
  110. package/lib/command/run-multiple.js +3 -2
  111. package/lib/command/run-workers.js +1 -14
  112. package/lib/command/run.js +3 -17
  113. package/lib/command/utils.js +14 -0
  114. package/lib/command/workers/runTests.js +11 -15
  115. package/lib/config.js +77 -4
  116. package/lib/container.js +97 -15
  117. package/lib/effects.js +17 -0
  118. package/lib/element/WebElement.js +194 -2
  119. package/lib/els.js +12 -6
  120. package/lib/globals.js +32 -19
  121. package/lib/heal.js +7 -4
  122. package/lib/helper/ApiDataFactory.js +2 -1
  123. package/lib/helper/FileSystem.js +3 -2
  124. package/lib/helper/GraphQLDataFactory.js +2 -1
  125. package/lib/helper/Playwright.js +63 -70
  126. package/lib/helper/Puppeteer.js +20 -109
  127. package/lib/helper/WebDriver.js +13 -30
  128. package/lib/helper/errors/NonFocusedType.js +8 -0
  129. package/lib/helper/extras/Download.js +45 -0
  130. package/lib/helper/extras/PlaywrightLocator.js +10 -0
  131. package/lib/helper/extras/elementSelection.js +10 -3
  132. package/lib/helper/extras/focusCheck.js +43 -0
  133. package/lib/helper/extras/richTextEditor.js +178 -0
  134. package/lib/history.js +3 -2
  135. package/lib/html.js +90 -16
  136. package/lib/index.js +9 -1
  137. package/lib/listener/config.js +6 -4
  138. package/lib/listener/emptyRun.js +2 -1
  139. package/lib/listener/helpers.js +4 -1
  140. package/lib/listener/mocha.js +2 -1
  141. package/lib/listener/pageobjects.js +43 -0
  142. package/lib/listener/result.js +3 -2
  143. package/lib/locator.js +126 -16
  144. package/lib/mocha/cli.js +4 -2
  145. package/lib/mocha/factory.js +7 -2
  146. package/lib/mocha/inject.js +1 -1
  147. package/lib/mocha/scenarioConfig.js +2 -1
  148. package/lib/mocha/ui.js +5 -6
  149. package/lib/parser.js +2 -2
  150. package/lib/pause.js +38 -4
  151. package/lib/plugin/aiTrace.js +96 -103
  152. package/lib/plugin/analyze.js +9 -9
  153. package/lib/plugin/auth.js +3 -3
  154. package/lib/plugin/browser.js +77 -0
  155. package/lib/plugin/expose.js +159 -0
  156. package/lib/plugin/heal.js +47 -3
  157. package/lib/plugin/junitReporter.js +303 -0
  158. package/lib/plugin/pageInfo.js +54 -52
  159. package/lib/plugin/pause.js +131 -0
  160. package/lib/plugin/pauseOnFail.js +11 -33
  161. package/lib/plugin/retryFailedStep.js +15 -13
  162. package/lib/plugin/screencast.js +289 -0
  163. package/lib/plugin/screenshot.js +558 -0
  164. package/lib/plugin/screenshotOnFail.js +9 -170
  165. package/lib/plugin/stepTimeout.js +3 -2
  166. package/lib/recorder.js +1 -1
  167. package/lib/rerun.js +2 -1
  168. package/lib/result.js +2 -1
  169. package/lib/step/base.js +10 -9
  170. package/lib/step/comment.js +2 -2
  171. package/lib/step/config.js +7 -0
  172. package/lib/step/helper.js +4 -4
  173. package/lib/step/meta.js +3 -3
  174. package/lib/step/record.js +5 -5
  175. package/lib/store.js +72 -3
  176. package/lib/translation.js +2 -1
  177. package/lib/utils/mask_data.js +2 -1
  178. package/lib/utils/pluginParser.js +151 -0
  179. package/lib/utils/trace.js +297 -0
  180. package/lib/utils.js +29 -3
  181. package/lib/workers.js +14 -22
  182. package/package.json +17 -14
  183. package/typings/index.d.ts +0 -5
  184. package/docs/webapi/amOnPage.mustache +0 -11
  185. package/docs/webapi/appendField.mustache +0 -16
  186. package/docs/webapi/attachFile.mustache +0 -24
  187. package/docs/webapi/blur.mustache +0 -18
  188. package/docs/webapi/checkOption.mustache +0 -13
  189. package/docs/webapi/clearCookie.mustache +0 -9
  190. package/docs/webapi/clearField.mustache +0 -14
  191. package/docs/webapi/click.mustache +0 -29
  192. package/docs/webapi/clickLink.mustache +0 -8
  193. package/docs/webapi/closeCurrentTab.mustache +0 -7
  194. package/docs/webapi/closeOtherTabs.mustache +0 -8
  195. package/docs/webapi/dontSee.mustache +0 -11
  196. package/docs/webapi/dontSeeCheckboxIsChecked.mustache +0 -10
  197. package/docs/webapi/dontSeeCookie.mustache +0 -8
  198. package/docs/webapi/dontSeeCurrentPathEquals.mustache +0 -10
  199. package/docs/webapi/dontSeeCurrentUrlEquals.mustache +0 -10
  200. package/docs/webapi/dontSeeElement.mustache +0 -12
  201. package/docs/webapi/dontSeeElementInDOM.mustache +0 -8
  202. package/docs/webapi/dontSeeInCurrentUrl.mustache +0 -4
  203. package/docs/webapi/dontSeeInField.mustache +0 -16
  204. package/docs/webapi/dontSeeInSource.mustache +0 -8
  205. package/docs/webapi/dontSeeInTitle.mustache +0 -8
  206. package/docs/webapi/dontSeeTraffic.mustache +0 -13
  207. package/docs/webapi/doubleClick.mustache +0 -13
  208. package/docs/webapi/downloadFile.mustache +0 -12
  209. package/docs/webapi/dragAndDrop.mustache +0 -9
  210. package/docs/webapi/dragSlider.mustache +0 -11
  211. package/docs/webapi/executeAsyncScript.mustache +0 -24
  212. package/docs/webapi/executeScript.mustache +0 -26
  213. package/docs/webapi/fillField.mustache +0 -21
  214. package/docs/webapi/flushNetworkTraffics.mustache +0 -5
  215. package/docs/webapi/focus.mustache +0 -13
  216. package/docs/webapi/forceClick.mustache +0 -28
  217. package/docs/webapi/forceRightClick.mustache +0 -18
  218. package/docs/webapi/grabAllWindowHandles.mustache +0 -7
  219. package/docs/webapi/grabAttributeFrom.mustache +0 -10
  220. package/docs/webapi/grabAttributeFromAll.mustache +0 -9
  221. package/docs/webapi/grabBrowserLogs.mustache +0 -9
  222. package/docs/webapi/grabCookie.mustache +0 -11
  223. package/docs/webapi/grabCssPropertyFrom.mustache +0 -11
  224. package/docs/webapi/grabCssPropertyFromAll.mustache +0 -10
  225. package/docs/webapi/grabCurrentUrl.mustache +0 -9
  226. package/docs/webapi/grabCurrentWindowHandle.mustache +0 -6
  227. package/docs/webapi/grabDataFromPerformanceTiming.mustache +0 -20
  228. package/docs/webapi/grabElementBoundingRect.mustache +0 -20
  229. package/docs/webapi/grabGeoLocation.mustache +0 -8
  230. package/docs/webapi/grabHTMLFrom.mustache +0 -10
  231. package/docs/webapi/grabHTMLFromAll.mustache +0 -9
  232. package/docs/webapi/grabNumberOfOpenTabs.mustache +0 -8
  233. package/docs/webapi/grabNumberOfVisibleElements.mustache +0 -9
  234. package/docs/webapi/grabPageScrollPosition.mustache +0 -8
  235. package/docs/webapi/grabPopupText.mustache +0 -5
  236. package/docs/webapi/grabRecordedNetworkTraffics.mustache +0 -10
  237. package/docs/webapi/grabSource.mustache +0 -8
  238. package/docs/webapi/grabTextFrom.mustache +0 -10
  239. package/docs/webapi/grabTextFromAll.mustache +0 -9
  240. package/docs/webapi/grabTitle.mustache +0 -8
  241. package/docs/webapi/grabValueFrom.mustache +0 -9
  242. package/docs/webapi/grabValueFromAll.mustache +0 -8
  243. package/docs/webapi/grabWebElement.mustache +0 -9
  244. package/docs/webapi/grabWebElements.mustache +0 -9
  245. package/docs/webapi/moveCursorTo.mustache +0 -16
  246. package/docs/webapi/openNewTab.mustache +0 -7
  247. package/docs/webapi/pressKey.mustache +0 -12
  248. package/docs/webapi/pressKeyDown.mustache +0 -12
  249. package/docs/webapi/pressKeyUp.mustache +0 -12
  250. package/docs/webapi/pressKeyWithKeyNormalization.mustache +0 -60
  251. package/docs/webapi/refreshPage.mustache +0 -6
  252. package/docs/webapi/resizeWindow.mustache +0 -6
  253. package/docs/webapi/rightClick.mustache +0 -14
  254. package/docs/webapi/saveElementScreenshot.mustache +0 -10
  255. package/docs/webapi/saveScreenshot.mustache +0 -12
  256. package/docs/webapi/say.mustache +0 -10
  257. package/docs/webapi/scrollIntoView.mustache +0 -11
  258. package/docs/webapi/scrollPageToBottom.mustache +0 -6
  259. package/docs/webapi/scrollPageToTop.mustache +0 -6
  260. package/docs/webapi/scrollTo.mustache +0 -12
  261. package/docs/webapi/see.mustache +0 -11
  262. package/docs/webapi/seeAttributesOnElements.mustache +0 -9
  263. package/docs/webapi/seeCheckboxIsChecked.mustache +0 -10
  264. package/docs/webapi/seeCookie.mustache +0 -8
  265. package/docs/webapi/seeCssPropertiesOnElements.mustache +0 -9
  266. package/docs/webapi/seeCurrentPathEquals.mustache +0 -10
  267. package/docs/webapi/seeCurrentUrlEquals.mustache +0 -11
  268. package/docs/webapi/seeElement.mustache +0 -12
  269. package/docs/webapi/seeElementInDOM.mustache +0 -8
  270. package/docs/webapi/seeInCurrentUrl.mustache +0 -8
  271. package/docs/webapi/seeInField.mustache +0 -17
  272. package/docs/webapi/seeInPopup.mustache +0 -8
  273. package/docs/webapi/seeInSource.mustache +0 -7
  274. package/docs/webapi/seeInTitle.mustache +0 -8
  275. package/docs/webapi/seeNumberOfElements.mustache +0 -11
  276. package/docs/webapi/seeNumberOfVisibleElements.mustache +0 -10
  277. package/docs/webapi/seeTextEquals.mustache +0 -9
  278. package/docs/webapi/seeTitleEquals.mustache +0 -8
  279. package/docs/webapi/seeTraffic.mustache +0 -36
  280. package/docs/webapi/selectOption.mustache +0 -26
  281. package/docs/webapi/setCookie.mustache +0 -16
  282. package/docs/webapi/setGeoLocation.mustache +0 -12
  283. package/docs/webapi/startRecordingTraffic.mustache +0 -8
  284. package/docs/webapi/startRecordingWebSocketMessages.mustache +0 -8
  285. package/docs/webapi/stopRecordingTraffic.mustache +0 -5
  286. package/docs/webapi/stopRecordingWebSocketMessages.mustache +0 -7
  287. package/docs/webapi/switchTo.mustache +0 -9
  288. package/docs/webapi/switchToNextTab.mustache +0 -10
  289. package/docs/webapi/switchToPreviousTab.mustache +0 -10
  290. package/docs/webapi/type.mustache +0 -21
  291. package/docs/webapi/uncheckOption.mustache +0 -13
  292. package/docs/webapi/wait.mustache +0 -8
  293. package/docs/webapi/waitForClickable.mustache +0 -11
  294. package/docs/webapi/waitForCookie.mustache +0 -9
  295. package/docs/webapi/waitForDetached.mustache +0 -10
  296. package/docs/webapi/waitForDisabled.mustache +0 -6
  297. package/docs/webapi/waitForElement.mustache +0 -11
  298. package/docs/webapi/waitForEnabled.mustache +0 -6
  299. package/docs/webapi/waitForFunction.mustache +0 -17
  300. package/docs/webapi/waitForInvisible.mustache +0 -10
  301. package/docs/webapi/waitForNumberOfTabs.mustache +0 -9
  302. package/docs/webapi/waitForText.mustache +0 -13
  303. package/docs/webapi/waitForValue.mustache +0 -10
  304. package/docs/webapi/waitForVisible.mustache +0 -10
  305. package/docs/webapi/waitInUrl.mustache +0 -9
  306. package/docs/webapi/waitNumberOfVisibleElements.mustache +0 -10
  307. package/docs/webapi/waitToHide.mustache +0 -10
  308. package/docs/webapi/waitUrlEquals.mustache +0 -10
  309. package/lib/helper/AI.js +0 -214
  310. package/lib/helper/Mochawesome.js +0 -96
  311. package/lib/helper/extras/PlaywrightReactVueLocator.js +0 -52
  312. package/lib/helper/extras/React.js +0 -65
  313. package/lib/plugin/stepByStepReport.js +0 -431
  314. package/lib/plugin/subtitles.js +0 -89
@@ -8,6 +8,7 @@ import promiseRetry from 'promise-retry'
8
8
  import Locator from '../locator.js'
9
9
  import recorder from '../recorder.js'
10
10
  import store from '../store.js'
11
+ import { checkFocusBeforeType, checkFocusBeforePressKey } from './extras/focusCheck.js'
11
12
  import { includes as stringIncludes } from '../assert/include.js'
12
13
  import { urlEquals, equals } from '../assert/equal.js'
13
14
  import { empty } from '../assert/empty.js'
@@ -44,6 +45,7 @@ import { dontSeeElementError, seeElementError, dontSeeElementInDOMError, seeElem
44
45
  import { dontSeeTraffic, seeTraffic, grabRecordedNetworkTraffics, stopRecordingTraffic, flushNetworkTraffics } from './network/actions.js'
45
46
  import WebElement from '../element/WebElement.js'
46
47
  import { selectElement } from './extras/elementSelection.js'
48
+ import { fillRichEditor } from './extras/richTextEditor.js'
47
49
 
48
50
  let puppeteer
49
51
 
@@ -751,7 +753,7 @@ class Puppeteer extends Helper {
751
753
  }
752
754
 
753
755
  if (this.options.trace) {
754
- const fileName = `${`${global.output_dir}${path.sep}trace${path.sep}${uuidv4()}_${clearString(this.currentRunningTest.title)}`.slice(0, 245)}.json`
756
+ const fileName = `${`${store.outputDir}${path.sep}trace${path.sep}${uuidv4()}_${clearString(this.currentRunningTest.title)}`.slice(0, 245)}.json`
755
757
  const dir = path.dirname(fileName)
756
758
  if (!fileExists(dir)) fs.mkdirSync(dir)
757
759
  await this.page.tracing.start({ screenshots: true, path: fileName })
@@ -819,7 +821,6 @@ class Puppeteer extends Helper {
819
821
 
820
822
  /**
821
823
  * {{> moveCursorTo }}
822
- * {{ react }}
823
824
  */
824
825
  async moveCursorTo(locator, offsetX = 0, offsetY = 0) {
825
826
  let context = null
@@ -990,7 +991,6 @@ class Puppeteer extends Helper {
990
991
  * const elements = await this.helpers['Puppeteer']._locate({name: 'password'});
991
992
  * ```
992
993
  *
993
- * {{ react }}
994
994
  */
995
995
  async _locate(locator) {
996
996
  const context = await this.context
@@ -1005,7 +1005,6 @@ class Puppeteer extends Helper {
1005
1005
  * const element = await this.helpers['Puppeteer']._locateElement({name: 'password'});
1006
1006
  * ```
1007
1007
  *
1008
- * {{ react }}
1009
1008
  */
1010
1009
  async _locateElement(locator) {
1011
1010
  const context = await this.context
@@ -1189,7 +1188,6 @@ class Puppeteer extends Helper {
1189
1188
 
1190
1189
  /**
1191
1190
  * {{> seeElement }}
1192
- * {{ react }}
1193
1191
  */
1194
1192
  async seeElement(locator, context = null) {
1195
1193
  let els
@@ -1213,7 +1211,6 @@ class Puppeteer extends Helper {
1213
1211
 
1214
1212
  /**
1215
1213
  * {{> dontSeeElement }}
1216
- * {{ react }}
1217
1214
  */
1218
1215
  async dontSeeElement(locator, context = null) {
1219
1216
  let els
@@ -1262,7 +1259,6 @@ class Puppeteer extends Helper {
1262
1259
  /**
1263
1260
  * {{> click }}
1264
1261
  *
1265
- * {{ react }}
1266
1262
  */
1267
1263
  async click(locator = '//body', context = null) {
1268
1264
  return proceedClick.call(this, locator, context)
@@ -1271,7 +1267,6 @@ class Puppeteer extends Helper {
1271
1267
  /**
1272
1268
  * {{> forceClick }}
1273
1269
  *
1274
- * {{ react }}
1275
1270
  */
1276
1271
  async forceClick(locator, context = null) {
1277
1272
  let matcher = await this.context
@@ -1301,7 +1296,6 @@ class Puppeteer extends Helper {
1301
1296
  /**
1302
1297
  * {{> clickLink }}
1303
1298
  *
1304
- * {{ react }}
1305
1299
  */
1306
1300
  async clickLink(locator, context = null) {
1307
1301
  return proceedClick.call(this, locator, context, { waitForNavigation: true })
@@ -1325,7 +1319,7 @@ class Puppeteer extends Helper {
1325
1319
  * @param {string} [downloadPath='downloads'] change this parameter to set another directory for saving
1326
1320
  */
1327
1321
  async handleDownloads(downloadPath = 'downloads') {
1328
- downloadPath = path.join(global.output_dir, downloadPath)
1322
+ downloadPath = path.join(store.outputDir, downloadPath)
1329
1323
  if (!fs.existsSync(downloadPath)) {
1330
1324
  fs.mkdirSync(downloadPath, '0777')
1331
1325
  }
@@ -1387,7 +1381,7 @@ class Puppeteer extends Helper {
1387
1381
  },
1388
1382
  })
1389
1383
 
1390
- const outputFile = path.join(`${global.output_dir}/${fileName}`)
1384
+ const outputFile = path.join(`${store.outputDir}/${fileName}`)
1391
1385
 
1392
1386
  try {
1393
1387
  await new Promise((resolve, reject) => {
@@ -1409,7 +1403,6 @@ class Puppeteer extends Helper {
1409
1403
  /**
1410
1404
  * {{> doubleClick }}
1411
1405
  *
1412
- * {{ react }}
1413
1406
  */
1414
1407
  async doubleClick(locator, context = null) {
1415
1408
  return proceedClick.call(this, locator, context, { clickCount: 2 })
@@ -1418,7 +1411,6 @@ class Puppeteer extends Helper {
1418
1411
  /**
1419
1412
  * {{> rightClick }}
1420
1413
  *
1421
- * {{ react }}
1422
1414
  */
1423
1415
  async rightClick(locator, context = null) {
1424
1416
  return proceedClick.call(this, locator, context, { button: 'right' })
@@ -1547,6 +1539,7 @@ class Puppeteer extends Helper {
1547
1539
  * {{> pressKeyWithKeyNormalization }}
1548
1540
  */
1549
1541
  async pressKey(key) {
1542
+ await checkFocusBeforePressKey(this, key)
1550
1543
  const modifiers = []
1551
1544
  if (Array.isArray(key)) {
1552
1545
  for (let k of key) {
@@ -1575,6 +1568,8 @@ class Puppeteer extends Helper {
1575
1568
  * {{> type }}
1576
1569
  */
1577
1570
  async type(keys, delay = null) {
1571
+ await checkFocusBeforeType(this)
1572
+
1578
1573
  if (!Array.isArray(keys)) {
1579
1574
  keys = keys.toString()
1580
1575
  keys = keys.split('')
@@ -1588,12 +1583,20 @@ class Puppeteer extends Helper {
1588
1583
 
1589
1584
  /**
1590
1585
  * {{> fillField }}
1591
- * {{ react }}
1592
1586
  */
1593
1587
  async fillField(field, value, context = null) {
1594
- const els = await findVisibleFields.call(this, field, context)
1588
+ let els = await findVisibleFields.call(this, field, context)
1589
+ if (!els.length) {
1590
+ els = await findFields.call(this, field, context)
1591
+ }
1595
1592
  assertElementExists(els, field, 'Field')
1596
1593
  const el = selectElement(els, field, this)
1594
+
1595
+ if (await fillRichEditor(this, el, value)) {
1596
+ highlightActiveElement.call(this, el, await this._getContext())
1597
+ return this._waitForAction()
1598
+ }
1599
+
1597
1600
  const tag = await el.getProperty('tagName').then(el => el.jsonValue())
1598
1601
  const editable = await el.getProperty('contenteditable').then(el => el.jsonValue())
1599
1602
  if (tag === 'INPUT' || tag === 'TEXTAREA') {
@@ -1618,7 +1621,6 @@ class Puppeteer extends Helper {
1618
1621
  /**
1619
1622
  * {{> appendField }}
1620
1623
  *
1621
- * {{ react }}
1622
1624
  */
1623
1625
  async appendField(field, value, context = null) {
1624
1626
  const els = await findVisibleFields.call(this, field, context)
@@ -1652,7 +1654,7 @@ class Puppeteer extends Helper {
1652
1654
  * {{> attachFile }}
1653
1655
  */
1654
1656
  async attachFile(locator, pathToFile, context = null) {
1655
- const file = path.join(global.codecept_dir, pathToFile)
1657
+ const file = path.join(store.codeceptDir, pathToFile)
1656
1658
 
1657
1659
  if (!fileExists(file)) {
1658
1660
  throw new Error(`File at ${file} can not be found on local system`)
@@ -1720,7 +1722,6 @@ class Puppeteer extends Helper {
1720
1722
 
1721
1723
  /**
1722
1724
  * {{> grabNumberOfVisibleElements }}
1723
- * {{ react }}
1724
1725
  */
1725
1726
  async grabNumberOfVisibleElements(locator) {
1726
1727
  let els = await this._locate(locator)
@@ -1782,7 +1783,6 @@ class Puppeteer extends Helper {
1782
1783
  /**
1783
1784
  * {{> see }}
1784
1785
  *
1785
- * {{ react }}
1786
1786
  */
1787
1787
  async see(text, context = null) {
1788
1788
  return proceedSee.call(this, 'assert', text, context)
@@ -1798,7 +1798,6 @@ class Puppeteer extends Helper {
1798
1798
  /**
1799
1799
  * {{> dontSee }}
1800
1800
  *
1801
- * {{ react }}
1802
1801
  */
1803
1802
  async dontSee(text, context = null) {
1804
1803
  return proceedSee.call(this, 'negate', text, context)
@@ -1852,7 +1851,6 @@ class Puppeteer extends Helper {
1852
1851
  /**
1853
1852
  * {{> seeNumberOfElements }}
1854
1853
  *
1855
- * {{ react }}
1856
1854
  */
1857
1855
  async seeNumberOfElements(locator, num) {
1858
1856
  const elements = await this._locate(locator)
@@ -1862,7 +1860,6 @@ class Puppeteer extends Helper {
1862
1860
  /**
1863
1861
  * {{> seeNumberOfVisibleElements }}
1864
1862
  *
1865
- * {{ react }}
1866
1863
  */
1867
1864
  async seeNumberOfVisibleElements(locator, num) {
1868
1865
  const res = await this.grabNumberOfVisibleElements(locator)
@@ -1989,7 +1986,6 @@ class Puppeteer extends Helper {
1989
1986
 
1990
1987
  /**
1991
1988
  * {{> grabTextFromAll }}
1992
- * {{ react }}
1993
1989
  */
1994
1990
  async grabTextFromAll(locator) {
1995
1991
  const els = await this._locate(locator)
@@ -2002,7 +1998,6 @@ class Puppeteer extends Helper {
2002
1998
 
2003
1999
  /**
2004
2000
  * {{> grabTextFrom }}
2005
- * {{ react }}
2006
2001
  */
2007
2002
  async grabTextFrom(locator) {
2008
2003
  const texts = await this.grabTextFromAll(locator)
@@ -2063,7 +2058,6 @@ class Puppeteer extends Helper {
2063
2058
 
2064
2059
  /**
2065
2060
  * {{> grabCssPropertyFromAll }}
2066
- * {{ react }}
2067
2061
  */
2068
2062
  async grabCssPropertyFromAll(locator, cssProperty) {
2069
2063
  const els = await this._locate(locator)
@@ -2075,7 +2069,6 @@ class Puppeteer extends Helper {
2075
2069
 
2076
2070
  /**
2077
2071
  * {{> grabCssPropertyFrom }}
2078
- * {{ react }}
2079
2072
  */
2080
2073
  async grabCssPropertyFrom(locator, cssProperty) {
2081
2074
  const cssValues = await this.grabCssPropertyFromAll(locator, cssProperty)
@@ -2090,7 +2083,6 @@ class Puppeteer extends Helper {
2090
2083
 
2091
2084
  /**
2092
2085
  * {{> seeCssPropertiesOnElements }}
2093
- * {{ react }}
2094
2086
  */
2095
2087
  async seeCssPropertiesOnElements(locator, cssProperties) {
2096
2088
  const res = await this._locate(locator)
@@ -2125,7 +2117,6 @@ class Puppeteer extends Helper {
2125
2117
 
2126
2118
  /**
2127
2119
  * {{> seeAttributesOnElements }}
2128
- * {{ react }}
2129
2120
  */
2130
2121
  async seeAttributesOnElements(locator, attributes) {
2131
2122
  const elements = await this._locate(locator)
@@ -2163,7 +2154,6 @@ class Puppeteer extends Helper {
2163
2154
 
2164
2155
  /**
2165
2156
  * {{> dragSlider }}
2166
- * {{ react }}
2167
2157
  */
2168
2158
  async dragSlider(locator, offsetX = 0) {
2169
2159
  const src = await this._locate(locator)
@@ -2185,7 +2175,6 @@ class Puppeteer extends Helper {
2185
2175
 
2186
2176
  /**
2187
2177
  * {{> grabAttributeFromAll }}
2188
- * {{ react }}
2189
2178
  */
2190
2179
  async grabAttributeFromAll(locator, attr) {
2191
2180
  const els = await this._locate(locator)
@@ -2199,7 +2188,6 @@ class Puppeteer extends Helper {
2199
2188
 
2200
2189
  /**
2201
2190
  * {{> grabAttributeFrom }}
2202
- * {{ react }}
2203
2191
  */
2204
2192
  async grabAttributeFrom(locator, attr) {
2205
2193
  const attrs = await this.grabAttributeFromAll(locator, attr)
@@ -2362,7 +2350,6 @@ class Puppeteer extends Helper {
2362
2350
 
2363
2351
  /**
2364
2352
  * {{> waitNumberOfVisibleElements }}
2365
- * {{ react }}
2366
2353
  */
2367
2354
  async waitNumberOfVisibleElements(locator, num, sec) {
2368
2355
  const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout
@@ -2410,7 +2397,6 @@ class Puppeteer extends Helper {
2410
2397
 
2411
2398
  /**
2412
2399
  * {{> waitForElement }}
2413
- * {{ react }}
2414
2400
  */
2415
2401
  async waitForElement(locator, sec) {
2416
2402
  const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout
@@ -2431,7 +2417,6 @@ class Puppeteer extends Helper {
2431
2417
  /**
2432
2418
  * {{> waitForVisible }}
2433
2419
  *
2434
- * {{ react }}
2435
2420
  */
2436
2421
  async waitForVisible(locator, sec) {
2437
2422
  const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout
@@ -3008,10 +2993,6 @@ export default Puppeteer
3008
2993
  * @returns {Promise<Array>} Array of ElementHandle objects
3009
2994
  */
3010
2995
  async function findElements(matcher, locator) {
3011
- // Check if locator is a Locator object with react type, or a raw object with react property
3012
- const isReactLocator = locator.type === 'react' || (locator.locator && locator.locator.react) || locator.react
3013
- if (isReactLocator) return findReactElements.call(this, locator)
3014
-
3015
2996
  locator = new Locator(locator, 'css')
3016
2997
 
3017
2998
  // Check if locator is a role locator and call findByRole
@@ -3078,7 +3059,6 @@ async function findElements(matcher, locator) {
3078
3059
  * @returns {Promise<Object>} Single ElementHandle object
3079
3060
  */
3080
3061
  async function findElement(matcher, locator) {
3081
- if (locator.react) return findReactElements.call(this, locator)
3082
3062
  locator = new Locator(locator, 'css')
3083
3063
 
3084
3064
  // Check if locator is a role locator and call findByRole
@@ -3564,7 +3544,7 @@ function getNormalizedKey(key) {
3564
3544
  }
3565
3545
 
3566
3546
  function highlightActiveElement(element, context) {
3567
- if (this.options.highlightElement && global.debugMode) {
3547
+ if (this.options.highlightElement && store.debugMode) {
3568
3548
  highlightElement(element, context)
3569
3549
  }
3570
3550
  }
@@ -3577,75 +3557,6 @@ function _waitForElement(locator, options) {
3577
3557
  }
3578
3558
  }
3579
3559
 
3580
- async function findReactElements(locator) {
3581
- // Handle both Locator objects and raw locator objects
3582
- const resolved = locator.locator ? locator.locator : toLocatorConfig(locator, 'react')
3583
- this.debug(`Finding React elements: ${JSON.stringify(resolved)}`)
3584
-
3585
- // Use createRequire to access require.resolve in ESM
3586
- const { createRequire } = await import('module')
3587
- const require = createRequire(import.meta.url)
3588
- const resqScript = await fs.promises.readFile(require.resolve('resq'), 'utf-8')
3589
- await this.page.evaluate(resqScript.toString())
3590
-
3591
- await this.page.evaluate(() => window.resq.waitToLoadReact())
3592
- const arrayHandle = await this.page.evaluateHandle(
3593
- obj => {
3594
- const { selector, props, state } = obj
3595
- let elements = window.resq.resq$$(selector)
3596
- if (Object.keys(props).length) {
3597
- elements = elements.byProps(props)
3598
- }
3599
- if (Object.keys(state).length) {
3600
- elements = elements.byState(state)
3601
- }
3602
-
3603
- if (!elements.length) {
3604
- return []
3605
- }
3606
-
3607
- // resq returns an array of HTMLElements if the React component is a fragment
3608
- // this avoids having nested arrays of nodes which the driver does not understand
3609
- // [[div, div], [div, div]] => [div, div, div, div]
3610
- let nodes = []
3611
-
3612
- elements.forEach(element => {
3613
- let { node, isFragment } = element
3614
-
3615
- if (!node) {
3616
- isFragment = true
3617
- node = element.children
3618
- }
3619
-
3620
- if (isFragment) {
3621
- nodes = nodes.concat(node)
3622
- } else {
3623
- nodes.push(node)
3624
- }
3625
- })
3626
-
3627
- return [...nodes]
3628
- },
3629
- {
3630
- selector: resolved.react,
3631
- props: resolved.props || {},
3632
- state: resolved.state || {},
3633
- },
3634
- )
3635
-
3636
- const properties = await arrayHandle.getProperties()
3637
- const result = []
3638
- for (const property of properties.values()) {
3639
- const elementHandle = property.asElement()
3640
- if (elementHandle) {
3641
- result.push(elementHandle)
3642
- }
3643
- }
3644
-
3645
- await arrayHandle.dispose()
3646
- return result
3647
- }
3648
-
3649
3560
  async function findByRole(matcher, locator) {
3650
3561
  const resolved = toLocatorConfig(locator, 'role')
3651
3562
  const roleSelector = buildRoleSelector(resolved)
@@ -10,6 +10,7 @@ import promiseRetry from 'promise-retry'
10
10
  import { includes as stringIncludes } from '../assert/include.js'
11
11
  import { urlEquals, equals } from '../assert/equal.js'
12
12
  import store from '../store.js'
13
+ import { checkFocusBeforeType, checkFocusBeforePressKey } from './extras/focusCheck.js'
13
14
  import output from '../output.js'
14
15
  const { debug } = output
15
16
  import { empty } from '../assert/empty.js'
@@ -41,6 +42,7 @@ import { dropFile } from './scripts/dropFile.js'
41
42
  import { dontSeeTraffic, seeTraffic, grabRecordedNetworkTraffics, stopRecordingTraffic, flushNetworkTraffics } from './network/actions.js'
42
43
  import WebElement from '../element/WebElement.js'
43
44
  import { selectElement } from './extras/elementSelection.js'
45
+ import { fillRichEditor } from './extras/richTextEditor.js'
44
46
 
45
47
  const SHADOW = 'shadow'
46
48
  const webRoot = 'body'
@@ -95,11 +97,7 @@ const config = {}
95
97
  * WebDriver helper which wraps [webdriverio](http://webdriver.io/) library to
96
98
  * manipulate browser using Selenium WebDriver or PhantomJS.
97
99
  *
98
- * WebDriver requires Selenium Server and ChromeDriver/GeckoDriver to be installed. Those tools can be easily installed via NPM. Please check [Testing with WebDriver](https://codecept.io/webdriver/#testing-with-webdriver) for more details.
99
- *
100
- * With the release of WebdriverIO version v8.14.0, and onwards, all driver management hassles are now a thing of the past 🙌. Read more [here](https://webdriver.io/blog/2023/07/31/driver-management/).
101
- * One of the significant advantages of this update is that you can now get rid of any driver services you previously had to manage, such as
102
- * `wdio-chromedriver-service`, `wdio-geckodriver-service`, `wdio-edgedriver-service`, `wdio-safaridriver-service`, and even `@wdio/selenium-standalone-service`.
100
+ * No Selenium Server, ChromeDriver, or GeckoDriver to install or start. Since WebdriverIO 9, driver management is fully automatic — WebdriverIO downloads and starts the matching driver for you. Read more [here](https://webdriver.io/blog/2023/07/31/driver-management/). Please check [Testing with WebDriver](https://codecept.io/webdriver/#testing-with-webdriver) for more details.
103
101
  *
104
102
  * For those who require custom driver options, fear not; WebDriver Helper allows you to pass in driver options through custom WebDriver configuration.
105
103
  * If you have a custom grid, use a cloud service, or prefer to run your own driver, there's no need to worry since WebDriver Helper will only start a driver when there are no other connection information settings like hostname or port specified.
@@ -906,13 +904,6 @@ class WebDriver extends Helper {
906
904
  return els
907
905
  }
908
906
 
909
- // special locator type for React
910
- if (locator.react) {
911
- const els = await this.browser.react$$(locator.react, locator.props || undefined, locator.state || undefined)
912
- this.debugSection('Elements', `Found ${els.length} react components`)
913
- return els
914
- }
915
-
916
907
  // special locator type for ARIA roles
917
908
  if (locator.role) {
918
909
  return this._locateByRole(locator)
@@ -1082,7 +1073,6 @@ class WebDriver extends Helper {
1082
1073
  /**
1083
1074
  * {{> click }}
1084
1075
  *
1085
- * {{ react }}
1086
1076
  */
1087
1077
  async click(locator, context = null) {
1088
1078
  const clickMethod = this.browser.isMobile && this.browser.capabilities.platformName !== 'android' ? 'touchClick' : 'elementClick'
@@ -1102,7 +1092,6 @@ class WebDriver extends Helper {
1102
1092
  /**
1103
1093
  * {{> forceClick }}
1104
1094
  *
1105
- * {{ react }}
1106
1095
  */
1107
1096
  async forceClick(locator, context = null) {
1108
1097
  const locateFn = prepareLocateFn.call(this, context)
@@ -1129,7 +1118,6 @@ class WebDriver extends Helper {
1129
1118
  /**
1130
1119
  * {{> doubleClick }}
1131
1120
  *
1132
- * {{ react }}
1133
1121
  */
1134
1122
  async doubleClick(locator, context = null) {
1135
1123
  const locateFn = prepareLocateFn.call(this, context)
@@ -1149,7 +1137,6 @@ class WebDriver extends Helper {
1149
1137
  /**
1150
1138
  * {{> rightClick }}
1151
1139
  *
1152
- * {{ react }}
1153
1140
  */
1154
1141
  async rightClick(locator, context) {
1155
1142
  const locateFn = prepareLocateFn.call(this, context)
@@ -1244,7 +1231,6 @@ class WebDriver extends Helper {
1244
1231
  /**
1245
1232
  * {{> forceRightClick }}
1246
1233
  *
1247
- * {{ react }}
1248
1234
  */
1249
1235
  async forceRightClick(locator, context = null) {
1250
1236
  const locateFn = prepareLocateFn.call(this, context)
@@ -1269,7 +1255,6 @@ class WebDriver extends Helper {
1269
1255
 
1270
1256
  /**
1271
1257
  * {{> fillField }}
1272
- * {{ react }}
1273
1258
  * {{ custom }}
1274
1259
  *
1275
1260
  */
@@ -1278,6 +1263,11 @@ class WebDriver extends Helper {
1278
1263
  assertElementExists(res, field, 'Field')
1279
1264
  const elem = selectElement(res, field, this)
1280
1265
  highlightActiveElement.call(this, elem)
1266
+
1267
+ if (await fillRichEditor(this, elem, value)) {
1268
+ return
1269
+ }
1270
+
1281
1271
  try {
1282
1272
  await elem.clearValue()
1283
1273
  } catch (err) {
@@ -1294,7 +1284,6 @@ class WebDriver extends Helper {
1294
1284
 
1295
1285
  /**
1296
1286
  * {{> appendField }}
1297
- * {{ react }}
1298
1287
  */
1299
1288
  async appendField(field, value, context = null) {
1300
1289
  const res = await findFields.call(this, field, context)
@@ -1352,7 +1341,7 @@ class WebDriver extends Helper {
1352
1341
  * {{> attachFile }}
1353
1342
  */
1354
1343
  async attachFile(locator, pathToFile, context = null) {
1355
- let file = path.join(global.codecept_dir, pathToFile)
1344
+ let file = path.join(store.codeceptDir, pathToFile)
1356
1345
  if (!fileExists(file)) {
1357
1346
  throw new Error(`File at ${file} can not be found on local system`)
1358
1347
  }
@@ -1592,7 +1581,6 @@ class WebDriver extends Helper {
1592
1581
  /**
1593
1582
  * {{> see }}
1594
1583
  *
1595
- * {{ react }}
1596
1584
  */
1597
1585
  async see(text, context = null) {
1598
1586
  return proceedSee.call(this, 'assert', text, context)
@@ -1608,7 +1596,6 @@ class WebDriver extends Helper {
1608
1596
  /**
1609
1597
  * {{> dontSee }}
1610
1598
  *
1611
- * {{ react }}
1612
1599
  */
1613
1600
  async dontSee(text, context = null) {
1614
1601
  return proceedSee.call(this, 'negate', text, context)
@@ -1650,7 +1637,6 @@ class WebDriver extends Helper {
1650
1637
 
1651
1638
  /**
1652
1639
  * {{> seeElement }}
1653
- * {{ react }}
1654
1640
  *
1655
1641
  */
1656
1642
  async seeElement(locator, context = null) {
@@ -1667,7 +1653,6 @@ class WebDriver extends Helper {
1667
1653
 
1668
1654
  /**
1669
1655
  * {{> dontSeeElement }}
1670
- * {{ react }}
1671
1656
  */
1672
1657
  async dontSeeElement(locator, context = null) {
1673
1658
  const locateFn = prepareLocateFn.call(this, context)
@@ -1752,7 +1737,6 @@ class WebDriver extends Helper {
1752
1737
 
1753
1738
  /**
1754
1739
  * {{> seeNumberOfElements }}
1755
- * {{ react }}
1756
1740
  */
1757
1741
  async seeNumberOfElements(locator, num) {
1758
1742
  const res = await this._locate(locator)
@@ -1761,7 +1745,6 @@ class WebDriver extends Helper {
1761
1745
 
1762
1746
  /**
1763
1747
  * {{> seeNumberOfVisibleElements }}
1764
- * {{ react }}
1765
1748
  */
1766
1749
  async seeNumberOfVisibleElements(locator, num) {
1767
1750
  const res = await this.grabNumberOfVisibleElements(locator)
@@ -2237,6 +2220,7 @@ class WebDriver extends Helper {
2237
2220
  * {{> pressKeyWithKeyNormalization }}
2238
2221
  */
2239
2222
  async pressKey(key) {
2223
+ await checkFocusBeforePressKey(this, key)
2240
2224
  const modifiers = []
2241
2225
  if (Array.isArray(key)) {
2242
2226
  for (let k of key) {
@@ -2283,6 +2267,8 @@ class WebDriver extends Helper {
2283
2267
  * {{> type }}
2284
2268
  */
2285
2269
  async type(keys, delay = null) {
2270
+ await checkFocusBeforeType(this)
2271
+
2286
2272
  if (!Array.isArray(keys)) {
2287
2273
  keys = keys.toString()
2288
2274
  keys = keys.split('')
@@ -3480,7 +3466,7 @@ function isModifierKey(key) {
3480
3466
  }
3481
3467
 
3482
3468
  function highlightActiveElement(element) {
3483
- if (this.options.highlightElement && global.debugMode) {
3469
+ if (this.options.highlightElement && store.debugMode) {
3484
3470
  highlightElement(element, this.browser)
3485
3471
  }
3486
3472
  }
@@ -3491,9 +3477,6 @@ function prepareLocateFn(context) {
3491
3477
  l = new Locator(l, 'css')
3492
3478
  return this._locate(context, true).then(async res => {
3493
3479
  assertElementExists(res, context, 'Context element')
3494
- if (l.react) {
3495
- return res[0].react$$(l.react, l.props || undefined)
3496
- }
3497
3480
  return res[0].$$(l.simplify())
3498
3481
  })
3499
3482
  }
@@ -0,0 +1,8 @@
1
+ class NonFocusedType extends Error {
2
+ constructor(message) {
3
+ super(message)
4
+ this.name = 'NonFocusedType'
5
+ }
6
+ }
7
+
8
+ export default NonFocusedType
@@ -0,0 +1,45 @@
1
+ import fs from 'fs'
2
+ import path from 'path'
3
+ import minimatch from 'minimatch'
4
+ import store from '../../store.js'
5
+ import assert from 'assert'
6
+
7
+ function getDownloadDir() {
8
+ return path.join(store.outputDir, 'downloads')
9
+ }
10
+
11
+ function getNewFiles(downloadDir, sinceTimestamp) {
12
+ if (!fs.existsSync(downloadDir)) return []
13
+ return fs.readdirSync(downloadDir).filter(name => {
14
+ const stat = fs.statSync(path.join(downloadDir, name))
15
+ return stat.isFile() && stat.mtimeMs >= sinceTimestamp
16
+ })
17
+ }
18
+
19
+ function seeFileDownloaded(arg) {
20
+ const downloadDir = getDownloadDir()
21
+ const files = getNewFiles(downloadDir, this._downloadStartTimestamp)
22
+
23
+ if (arg === undefined || arg === null) {
24
+ assert.ok(files.length > 0, `No files downloaded to ${downloadDir}`)
25
+ return
26
+ }
27
+ if (typeof arg === 'number') {
28
+ assert.strictEqual(files.length, arg, `Expected ${arg} downloaded file(s), found ${files.length}: [${files.join(', ')}]`)
29
+ return
30
+ }
31
+ const regexMatch = arg.match(/^\/(.+)\/$/)
32
+ if (regexMatch) {
33
+ const re = new RegExp(regexMatch[1])
34
+ assert.ok(files.some(f => re.test(f)), `No file matches ${arg}. Downloaded: [${files.join(', ')}]`)
35
+ return
36
+ }
37
+ if (/[*?[\]]/.test(arg)) {
38
+ const matched = minimatch.match(files, arg)
39
+ assert.ok(matched.length > 0, `No file matches glob "${arg}". Downloaded: [${files.join(', ')}]`)
40
+ return
41
+ }
42
+ assert.ok(files.includes(arg), `File "${arg}" not downloaded. Downloaded: [${files.join(', ')}]`)
43
+ }
44
+
45
+ export { seeFileDownloaded, getDownloadDir }
@@ -0,0 +1,10 @@
1
+ async function findByPlaywrightLocator(matcher, locator) {
2
+ const pwLocator = locator.locator || locator
3
+ if (pwLocator && pwLocator.toString && pwLocator.toString().includes(process.env.testIdAttribute)) {
4
+ return matcher.getByTestId(pwLocator.pw.value.split('=')[1])
5
+ }
6
+ const pwValue = typeof pwLocator.pw === 'string' ? pwLocator.pw : pwLocator.pw
7
+ return matcher.locator(pwValue).all()
8
+ }
9
+
10
+ export { findByPlaywrightLocator }
@@ -9,8 +9,15 @@ function resolveElementIndex(value) {
9
9
  return value
10
10
  }
11
11
 
12
+ function isStrictStep(opts, helper) {
13
+ if (opts?.exact === true || opts?.strictMode === true) return true
14
+ if (opts?.exact === false || opts?.strictMode === false) return false
15
+ return helper.options.strict
16
+ }
17
+
12
18
  function selectElement(els, locator, helper) {
13
- const rawIndex = store.currentStep?.opts?.elementIndex
19
+ const opts = store.currentStep?.opts
20
+ const rawIndex = opts?.elementIndex
14
21
  const elementIndex = resolveElementIndex(rawIndex)
15
22
 
16
23
  if (elementIndex != null) {
@@ -37,9 +44,9 @@ function selectElement(els, locator, helper) {
37
44
  return els[idx]
38
45
  }
39
46
 
40
- if (helper.options.strict) {
47
+ if (isStrictStep(opts, helper)) {
41
48
  if (els.length > 1) {
42
- const webElements = els.map(el => new WebElement(el, helper))
49
+ const webElements = Array.from(els).map(el => new WebElement(el, helper))
43
50
  throw new MultipleElementsFound(locator, webElements)
44
51
  }
45
52
  }