codeceptjs 3.6.10 → 3.7.0-beta.10

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 (127) hide show
  1. package/README.md +89 -119
  2. package/bin/codecept.js +9 -2
  3. package/docs/webapi/clearCookie.mustache +1 -1
  4. package/lib/actor.js +66 -102
  5. package/lib/ai.js +130 -121
  6. package/lib/assert/empty.js +3 -5
  7. package/lib/assert/equal.js +4 -7
  8. package/lib/assert/include.js +4 -6
  9. package/lib/assert/throws.js +2 -4
  10. package/lib/assert/truth.js +2 -2
  11. package/lib/codecept.js +87 -83
  12. package/lib/command/check.js +186 -0
  13. package/lib/command/configMigrate.js +2 -4
  14. package/lib/command/definitions.js +8 -26
  15. package/lib/command/generate.js +10 -14
  16. package/lib/command/gherkin/snippets.js +10 -8
  17. package/lib/command/gherkin/steps.js +1 -1
  18. package/lib/command/info.js +1 -3
  19. package/lib/command/init.js +8 -12
  20. package/lib/command/interactive.js +2 -2
  21. package/lib/command/list.js +1 -1
  22. package/lib/command/run-multiple.js +12 -35
  23. package/lib/command/run-workers.js +5 -57
  24. package/lib/command/utils.js +5 -6
  25. package/lib/command/workers/runTests.js +68 -232
  26. package/lib/container.js +354 -237
  27. package/lib/data/context.js +10 -13
  28. package/lib/data/dataScenarioConfig.js +8 -8
  29. package/lib/data/dataTableArgument.js +6 -6
  30. package/lib/data/table.js +5 -11
  31. package/lib/effects.js +218 -0
  32. package/lib/els.js +158 -0
  33. package/lib/event.js +19 -17
  34. package/lib/heal.js +88 -80
  35. package/lib/helper/AI.js +2 -1
  36. package/lib/helper/ApiDataFactory.js +3 -6
  37. package/lib/helper/Appium.js +45 -51
  38. package/lib/helper/FileSystem.js +3 -3
  39. package/lib/helper/GraphQLDataFactory.js +3 -3
  40. package/lib/helper/JSONResponse.js +57 -37
  41. package/lib/helper/Nightmare.js +35 -53
  42. package/lib/helper/Playwright.js +211 -252
  43. package/lib/helper/Protractor.js +54 -77
  44. package/lib/helper/Puppeteer.js +139 -232
  45. package/lib/helper/REST.js +5 -17
  46. package/lib/helper/TestCafe.js +21 -44
  47. package/lib/helper/WebDriver.js +131 -169
  48. package/lib/helper/testcafe/testcafe-utils.js +26 -27
  49. package/lib/listener/emptyRun.js +55 -0
  50. package/lib/listener/exit.js +7 -10
  51. package/lib/listener/{retry.js → globalRetry.js} +5 -5
  52. package/lib/listener/globalTimeout.js +165 -0
  53. package/lib/listener/helpers.js +15 -15
  54. package/lib/listener/mocha.js +1 -1
  55. package/lib/listener/result.js +12 -0
  56. package/lib/listener/steps.js +20 -18
  57. package/lib/listener/store.js +20 -0
  58. package/lib/mocha/asyncWrapper.js +216 -0
  59. package/lib/{interfaces → mocha}/bdd.js +3 -3
  60. package/lib/mocha/cli.js +308 -0
  61. package/lib/mocha/factory.js +104 -0
  62. package/lib/{interfaces → mocha}/featureConfig.js +24 -12
  63. package/lib/{interfaces → mocha}/gherkin.js +26 -28
  64. package/lib/mocha/hooks.js +112 -0
  65. package/lib/mocha/index.js +12 -0
  66. package/lib/mocha/inject.js +29 -0
  67. package/lib/{interfaces → mocha}/scenarioConfig.js +21 -6
  68. package/lib/mocha/suite.js +81 -0
  69. package/lib/mocha/test.js +159 -0
  70. package/lib/mocha/types.d.ts +42 -0
  71. package/lib/mocha/ui.js +219 -0
  72. package/lib/output.js +82 -62
  73. package/lib/pause.js +155 -138
  74. package/lib/plugin/analyze.js +349 -0
  75. package/lib/plugin/autoDelay.js +6 -6
  76. package/lib/plugin/autoLogin.js +6 -7
  77. package/lib/plugin/commentStep.js +6 -1
  78. package/lib/plugin/coverage.js +10 -19
  79. package/lib/plugin/customLocator.js +3 -3
  80. package/lib/plugin/customReporter.js +52 -0
  81. package/lib/plugin/eachElement.js +1 -1
  82. package/lib/plugin/fakerTransform.js +1 -1
  83. package/lib/plugin/heal.js +36 -9
  84. package/lib/plugin/pageInfo.js +140 -0
  85. package/lib/plugin/retryFailedStep.js +4 -4
  86. package/lib/plugin/retryTo.js +18 -118
  87. package/lib/plugin/screenshotOnFail.js +17 -49
  88. package/lib/plugin/selenoid.js +15 -35
  89. package/lib/plugin/standardActingHelpers.js +4 -1
  90. package/lib/plugin/stepByStepReport.js +56 -17
  91. package/lib/plugin/stepTimeout.js +5 -12
  92. package/lib/plugin/subtitles.js +4 -4
  93. package/lib/plugin/tryTo.js +17 -107
  94. package/lib/plugin/wdio.js +8 -10
  95. package/lib/recorder.js +146 -125
  96. package/lib/rerun.js +43 -42
  97. package/lib/result.js +161 -0
  98. package/lib/secret.js +1 -1
  99. package/lib/step/base.js +228 -0
  100. package/lib/step/config.js +50 -0
  101. package/lib/step/func.js +46 -0
  102. package/lib/step/helper.js +50 -0
  103. package/lib/step/meta.js +99 -0
  104. package/lib/step/record.js +74 -0
  105. package/lib/step/retry.js +11 -0
  106. package/lib/step/section.js +55 -0
  107. package/lib/step.js +21 -332
  108. package/lib/steps.js +50 -0
  109. package/lib/store.js +10 -2
  110. package/lib/template/heal.js +2 -11
  111. package/lib/timeout.js +66 -0
  112. package/lib/utils.js +317 -216
  113. package/lib/within.js +73 -55
  114. package/lib/workers.js +259 -275
  115. package/package.json +56 -54
  116. package/typings/index.d.ts +175 -186
  117. package/typings/promiseBasedTypes.d.ts +164 -17
  118. package/typings/types.d.ts +284 -115
  119. package/lib/cli.js +0 -256
  120. package/lib/helper/ExpectHelper.js +0 -391
  121. package/lib/helper/SoftExpectHelper.js +0 -381
  122. package/lib/listener/artifacts.js +0 -19
  123. package/lib/listener/timeout.js +0 -109
  124. package/lib/mochaFactory.js +0 -113
  125. package/lib/plugin/debugErrors.js +0 -67
  126. package/lib/scenario.js +0 -224
  127. package/lib/ui.js +0 -236
@@ -7,19 +7,11 @@ const Helper = require('@codeceptjs/helper')
7
7
  const promiseRetry = require('promise-retry')
8
8
  const stringIncludes = require('../assert/include').includes
9
9
  const { urlEquals, equals } = require('../assert/equal')
10
+ const store = require('../store')
10
11
  const { debug } = require('../output')
11
12
  const { empty } = require('../assert/empty')
12
13
  const { truth } = require('../assert/truth')
13
- const {
14
- xpathLocator,
15
- fileExists,
16
- decodeUrl,
17
- chunkArray,
18
- convertCssPropertiesToCamelCase,
19
- screenshotOutputFolder,
20
- getNormalizedKeyAttributeValue,
21
- modifierKeys,
22
- } = require('../utils')
14
+ const { xpathLocator, fileExists, decodeUrl, chunkArray, convertCssPropertiesToCamelCase, screenshotOutputFolder, getNormalizedKeyAttributeValue, modifierKeys } = require('../utils')
23
15
  const { isColorProperty, convertColorToRGBA } = require('../colorUtils')
24
16
  const ElementNotFound = require('./errors/ElementNotFound')
25
17
  const ConnectionRefused = require('./errors/ConnectionRefused')
@@ -27,22 +19,12 @@ const Locator = require('../locator')
27
19
  const { highlightElement } = require('./scripts/highlightElement')
28
20
  const { focusElement } = require('./scripts/focusElement')
29
21
  const { blurElement } = require('./scripts/blurElement')
30
- const {
31
- dontSeeElementError,
32
- seeElementError,
33
- seeElementInDOMError,
34
- dontSeeElementInDOMError,
35
- } = require('./errors/ElementAssertion')
36
- const {
37
- dontSeeTraffic,
38
- seeTraffic,
39
- grabRecordedNetworkTraffics,
40
- stopRecordingTraffic,
41
- flushNetworkTraffics,
42
- } = require('./network/actions')
22
+ const { dontSeeElementError, seeElementError, seeElementInDOMError, dontSeeElementInDOMError } = require('./errors/ElementAssertion')
23
+ const { dontSeeTraffic, seeTraffic, grabRecordedNetworkTraffics, stopRecordingTraffic, flushNetworkTraffics } = require('./network/actions')
43
24
 
44
25
  const SHADOW = 'shadow'
45
26
  const webRoot = 'body'
27
+ let browserLogs = []
46
28
 
47
29
  /**
48
30
  * ## Configuration
@@ -595,10 +577,7 @@ class WebDriver extends Helper {
595
577
  }
596
578
 
597
579
  async _res(locator) {
598
- const res =
599
- this._isShadowLocator(locator) || this._isCustomLocator(locator)
600
- ? await this._locate(locator)
601
- : await this.$$(withStrictLocator(locator))
580
+ const res = this._isShadowLocator(locator) || this._isCustomLocator(locator) ? await this._locate(locator) : await this.$$(withStrictLocator(locator))
602
581
  return res
603
582
  }
604
583
 
@@ -631,7 +610,7 @@ class WebDriver extends Helper {
631
610
  this.$$ = this.browser.$$.bind(this.browser)
632
611
 
633
612
  if (this._isCustomLocatorStrategyDefined()) {
634
- Object.keys(this.customLocatorStrategies).forEach(async (customLocator) => {
613
+ Object.keys(this.customLocatorStrategies).forEach(async customLocator => {
635
614
  this.debugSection('Weddriver', `adding custom locator strategy: ${customLocator}`)
636
615
  const locatorFunction = this._lookupCustomLocator(customLocator)
637
616
  this.browser.addLocatorStrategy(customLocator, locatorFunction)
@@ -642,6 +621,11 @@ class WebDriver extends Helper {
642
621
  this.browser.capabilities.platformName = this.browser.capabilities.platformName.toLowerCase()
643
622
  }
644
623
 
624
+ this.browser.on('dialog', () => {})
625
+
626
+ await this.browser.sessionSubscribe({ events: ['log.entryAdded'] })
627
+ this.browser.on('log.entryAdded', logEvents)
628
+
645
629
  return this.browser
646
630
  }
647
631
 
@@ -667,7 +651,7 @@ class WebDriver extends Helper {
667
651
  this.isRunning = false
668
652
  return this.browser.deleteSession()
669
653
  }
670
- if (this.browser.isInsideFrame) await this.browser.switchToFrame(null)
654
+ if (this.browser.isInsideFrame) await this.browser.switchFrame(null)
671
655
 
672
656
  if (this.options.keepBrowserState) return
673
657
 
@@ -675,10 +659,11 @@ class WebDriver extends Helper {
675
659
  this.debugSection('Session', 'cleaning cookies and localStorage')
676
660
  await this.browser.deleteCookies()
677
661
  }
678
- await this.browser.execute('localStorage.clear();').catch((err) => {
662
+ await this.browser.execute('localStorage.clear();').catch(err => {
679
663
  if (!(err.message.indexOf("Storage is disabled inside 'data:' URLs.") > -1)) throw err
680
664
  })
681
665
  await this.closeOtherTabs()
666
+ browserLogs = []
682
667
  return this.browser
683
668
  }
684
669
 
@@ -705,17 +690,17 @@ class WebDriver extends Helper {
705
690
 
706
691
  return browser
707
692
  },
708
- stop: async (browser) => {
693
+ stop: async browser => {
709
694
  if (!browser) return
710
695
  return browser.deleteSession()
711
696
  },
712
- loadVars: async (browser) => {
697
+ loadVars: async browser => {
713
698
  if (this.context !== this.root) throw new Error("Can't start session inside within block")
714
699
  this.browser = browser
715
700
  this.$$ = this.browser.$$.bind(this.browser)
716
701
  this.sessionWindows[this.activeSessionName] = browser
717
702
  },
718
- restoreVars: async (session) => {
703
+ restoreVars: async session => {
719
704
  if (!session) {
720
705
  this.activeSessionName = ''
721
706
  }
@@ -757,7 +742,7 @@ class WebDriver extends Helper {
757
742
  this.browser.isInsideFrame = true
758
743
  if (Array.isArray(frame)) {
759
744
  // this.switchTo(null);
760
- await forEachAsync(frame, async (f) => this.switchTo(f))
745
+ await forEachAsync(frame, async f => this.switchTo(f))
761
746
  return
762
747
  }
763
748
  await this.switchTo(frame)
@@ -835,10 +820,7 @@ class WebDriver extends Helper {
835
820
  * @param {object} locator
836
821
  */
837
822
  async _smartWait(locator) {
838
- this.debugSection(
839
- `SmartWait (${this.options.smartWait}ms)`,
840
- `Locating ${JSON.stringify(locator)} in ${this.options.smartWait}`,
841
- )
823
+ this.debugSection(`SmartWait (${this.options.smartWait}ms)`, `Locating ${JSON.stringify(locator)} in ${this.options.smartWait}`)
842
824
  await this.defineTimeout({ implicit: this.options.smartWait })
843
825
  }
844
826
 
@@ -913,7 +895,7 @@ class WebDriver extends Helper {
913
895
  * @param {CodeceptJS.LocatorOrString} locator element located by CSS|XPath|strict locator.
914
896
  */
915
897
  async _locateCheckable(locator) {
916
- return findCheckable.call(this, locator, this.$$.bind(this)).then((res) => res)
898
+ return findCheckable.call(this, locator, this.$$.bind(this)).then(res => res)
917
899
  }
918
900
 
919
901
  /**
@@ -941,7 +923,7 @@ class WebDriver extends Helper {
941
923
  * @param {CodeceptJS.LocatorOrString} locator element located by CSS|XPath|strict locator.
942
924
  */
943
925
  async _locateFields(locator) {
944
- return findFields.call(this, locator).then((res) => res)
926
+ return findFields.call(this, locator).then(res => res)
945
927
  }
946
928
 
947
929
  /**
@@ -1025,7 +1007,7 @@ class WebDriver extends Helper {
1025
1007
  const elem = usingFirstElement(res)
1026
1008
  highlightActiveElement.call(this, elem)
1027
1009
 
1028
- return this.executeScript((el) => {
1010
+ return this.executeScript(el => {
1029
1011
  if (document.activeElement instanceof HTMLElement) {
1030
1012
  document.activeElement.blur()
1031
1013
  }
@@ -1097,7 +1079,7 @@ class WebDriver extends Helper {
1097
1079
  }
1098
1080
  const elem = usingFirstElement(res)
1099
1081
 
1100
- return this.executeScript((el) => {
1082
+ return this.executeScript(el => {
1101
1083
  if (document.activeElement instanceof HTMLElement) {
1102
1084
  document.activeElement.blur()
1103
1085
  }
@@ -1160,15 +1142,9 @@ class WebDriver extends Helper {
1160
1142
  }
1161
1143
 
1162
1144
  // select options by visible text
1163
- let els = await forEachAsync(option, async (opt) =>
1164
- this.browser.findElementsFromElement(
1165
- getElementId(elem),
1166
- 'xpath',
1167
- Locator.select.byVisibleText(xpathLocator.literal(opt)),
1168
- ),
1169
- )
1145
+ let els = await forEachAsync(option, async opt => this.browser.findElementsFromElement(getElementId(elem), 'xpath', Locator.select.byVisibleText(xpathLocator.literal(opt))))
1170
1146
 
1171
- const clickOptionFn = async (el) => {
1147
+ const clickOptionFn = async el => {
1172
1148
  if (el[0]) el = el[0]
1173
1149
  const elementId = getElementId(el)
1174
1150
  if (elementId) return this.browser.elementClick(elementId)
@@ -1178,19 +1154,9 @@ class WebDriver extends Helper {
1178
1154
  return forEachAsync(els, clickOptionFn)
1179
1155
  }
1180
1156
  // select options by value
1181
- els = await forEachAsync(option, async (opt) =>
1182
- this.browser.findElementsFromElement(
1183
- getElementId(elem),
1184
- 'xpath',
1185
- Locator.select.byValue(xpathLocator.literal(opt)),
1186
- ),
1187
- )
1157
+ els = await forEachAsync(option, async opt => this.browser.findElementsFromElement(getElementId(elem), 'xpath', Locator.select.byValue(xpathLocator.literal(opt))))
1188
1158
  if (els.length === 0) {
1189
- throw new ElementNotFound(
1190
- select,
1191
- `Option "${option}" in`,
1192
- 'was not found neither by a visible text nor by a value',
1193
- )
1159
+ throw new ElementNotFound(select, `Option "${option}" in`, 'was not found neither by a visible text nor by a value')
1194
1160
  }
1195
1161
  return forEachAsync(els, clickOptionFn)
1196
1162
  }
@@ -1217,9 +1183,7 @@ class WebDriver extends Helper {
1217
1183
  this.debugSection('File', 'Uploading file to remote server')
1218
1184
  file = await this.browser.uploadFile(file)
1219
1185
  } catch (err) {
1220
- throw new Error(
1221
- `File can't be transferred to remote server. Set \`remoteFileUpload: false\` in config to upload file locally.\n${err.message}`,
1222
- )
1186
+ throw new Error(`File can't be transferred to remote server. Set \`remoteFileUpload: false\` in config to upload file locally.\n${err.message}`)
1223
1187
  }
1224
1188
  }
1225
1189
 
@@ -1272,7 +1236,11 @@ class WebDriver extends Helper {
1272
1236
  */
1273
1237
  async grabTextFromAll(locator) {
1274
1238
  const res = await this._locate(locator, true)
1275
- const val = await forEachAsync(res, (el) => this.browser.getElementText(getElementId(el)))
1239
+ let val = []
1240
+ await forEachAsync(res, async el => {
1241
+ const text = await this.browser.getElementText(getElementId(el))
1242
+ val.push(text)
1243
+ })
1276
1244
  this.debugSection('GrabText', String(val))
1277
1245
  return val
1278
1246
  }
@@ -1297,7 +1265,7 @@ class WebDriver extends Helper {
1297
1265
  */
1298
1266
  async grabHTMLFromAll(locator) {
1299
1267
  const elems = await this._locate(locator, true)
1300
- const html = await forEachAsync(elems, (elem) => elem.getHTML(false))
1268
+ const html = await forEachAsync(elems, elem => elem.getHTML(false))
1301
1269
  this.debugSection('GrabHTML', String(html))
1302
1270
  return html
1303
1271
  }
@@ -1322,7 +1290,7 @@ class WebDriver extends Helper {
1322
1290
  */
1323
1291
  async grabValueFromAll(locator) {
1324
1292
  const res = await this._locate(locator, true)
1325
- const val = await forEachAsync(res, (el) => el.getValue())
1293
+ const val = await forEachAsync(res, el => el.getValue())
1326
1294
  this.debugSection('GrabValue', String(val))
1327
1295
 
1328
1296
  return val
@@ -1347,7 +1315,7 @@ class WebDriver extends Helper {
1347
1315
  */
1348
1316
  async grabCssPropertyFromAll(locator, cssProperty) {
1349
1317
  const res = await this._locate(locator, true)
1350
- const val = await forEachAsync(res, async (el) => this.browser.getElementCSSValue(getElementId(el), cssProperty))
1318
+ const val = await forEachAsync(res, async el => this.browser.getElementCSSValue(getElementId(el), cssProperty))
1351
1319
  this.debugSection('Grab', String(val))
1352
1320
  return val
1353
1321
  }
@@ -1371,7 +1339,7 @@ class WebDriver extends Helper {
1371
1339
  */
1372
1340
  async grabAttributeFromAll(locator, attr) {
1373
1341
  const res = await this._locate(locator, true)
1374
- const val = await forEachAsync(res, async (el) => el.getAttribute(attr))
1342
+ const val = await forEachAsync(res, async el => el.getAttribute(attr))
1375
1343
  this.debugSection('GrabAttribute', String(val))
1376
1344
  return val
1377
1345
  }
@@ -1488,7 +1456,7 @@ class WebDriver extends Helper {
1488
1456
  async seeElement(locator) {
1489
1457
  const res = await this._locate(locator, true)
1490
1458
  assertElementExists(res, locator)
1491
- const selected = await forEachAsync(res, async (el) => el.isDisplayed())
1459
+ const selected = await forEachAsync(res, async el => el.isDisplayed())
1492
1460
  try {
1493
1461
  return truth(`elements of ${new Locator(locator)}`, 'to be seen').assert(selected)
1494
1462
  } catch (e) {
@@ -1505,7 +1473,7 @@ class WebDriver extends Helper {
1505
1473
  if (!res || res.length === 0) {
1506
1474
  return truth(`elements of ${new Locator(locator)}`, 'to be seen').negate(false)
1507
1475
  }
1508
- const selected = await forEachAsync(res, async (el) => el.isDisplayed())
1476
+ const selected = await forEachAsync(res, async el => el.isDisplayed())
1509
1477
  try {
1510
1478
  return truth(`elements of ${new Locator(locator)}`, 'to be seen').negate(selected)
1511
1479
  } catch (e) {
@@ -1560,11 +1528,7 @@ class WebDriver extends Helper {
1560
1528
  * {{> grabBrowserLogs }}
1561
1529
  */
1562
1530
  async grabBrowserLogs() {
1563
- if (this.browser.isW3C) {
1564
- this.debug('Logs not available in W3C specification')
1565
- return
1566
- }
1567
- return this.browser.getLogs('browser')
1531
+ return browserLogs
1568
1532
  }
1569
1533
 
1570
1534
  /**
@@ -1590,11 +1554,7 @@ class WebDriver extends Helper {
1590
1554
  */
1591
1555
  async seeNumberOfElements(locator, num) {
1592
1556
  const res = await this._locate(locator)
1593
- return assert.equal(
1594
- res.length,
1595
- num,
1596
- `expected number of elements (${new Locator(locator)}) is ${num}, but found ${res.length}`,
1597
- )
1557
+ return assert.equal(res.length, num, `expected number of elements (${new Locator(locator)}) is ${num}, but found ${res.length}`)
1598
1558
  }
1599
1559
 
1600
1560
  /**
@@ -1603,11 +1563,7 @@ class WebDriver extends Helper {
1603
1563
  */
1604
1564
  async seeNumberOfVisibleElements(locator, num) {
1605
1565
  const res = await this.grabNumberOfVisibleElements(locator)
1606
- return assert.equal(
1607
- res,
1608
- num,
1609
- `expected number of visible elements (${new Locator(locator)}) is ${num}, but found ${res}`,
1610
- )
1566
+ return assert.equal(res, num, `expected number of visible elements (${new Locator(locator)}) is ${num}, but found ${res}`)
1611
1567
  }
1612
1568
 
1613
1569
  /**
@@ -1632,19 +1588,16 @@ class WebDriver extends Helper {
1632
1588
  }
1633
1589
  }
1634
1590
 
1635
- const values = Object.keys(cssPropertiesCamelCase).map((key) => cssPropertiesCamelCase[key])
1591
+ const values = Object.keys(cssPropertiesCamelCase).map(key => cssPropertiesCamelCase[key])
1636
1592
  if (!Array.isArray(props)) props = [props]
1637
1593
  let chunked = chunkArray(props, values.length)
1638
- chunked = chunked.filter((val) => {
1594
+ chunked = chunked.filter(val => {
1639
1595
  for (let i = 0; i < val.length; ++i) {
1640
- // eslint-disable-next-line eqeqeq
1641
1596
  if (val[i] != values[i]) return false
1642
1597
  }
1643
1598
  return true
1644
1599
  })
1645
- return equals(
1646
- `all elements (${new Locator(locator)}) to have CSS property ${JSON.stringify(cssProperties)}`,
1647
- ).assert(chunked.length, elemAmount)
1600
+ return equals(`all elements (${new Locator(locator)}) to have CSS property ${JSON.stringify(cssProperties)}`).assert(chunked.length, elemAmount)
1648
1601
  }
1649
1602
 
1650
1603
  /**
@@ -1655,28 +1608,24 @@ class WebDriver extends Helper {
1655
1608
  assertElementExists(res, locator)
1656
1609
  const elemAmount = res.length
1657
1610
 
1658
- let attrs = await forEachAsync(res, async (el) => {
1659
- return forEachAsync(Object.keys(attributes), async (attr) => el.getAttribute(attr))
1611
+ let attrs = await forEachAsync(res, async el => {
1612
+ return forEachAsync(Object.keys(attributes), async attr => el.getAttribute(attr))
1660
1613
  })
1661
1614
 
1662
- const values = Object.keys(attributes).map((key) => attributes[key])
1615
+ const values = Object.keys(attributes).map(key => attributes[key])
1663
1616
  if (!Array.isArray(attrs)) attrs = [attrs]
1664
1617
  let chunked = chunkArray(attrs, values.length)
1665
- chunked = chunked.filter((val) => {
1618
+ chunked = chunked.filter(val => {
1666
1619
  for (let i = 0; i < val.length; ++i) {
1667
1620
  const _actual = Number.isNaN(val[i]) || typeof values[i] === 'string' ? val[i] : Number.parseInt(val[i], 10)
1668
- const _expected =
1669
- Number.isNaN(values[i]) || typeof values[i] === 'string' ? values[i] : Number.parseInt(values[i], 10)
1621
+ const _expected = Number.isNaN(values[i]) || typeof values[i] === 'string' ? values[i] : Number.parseInt(values[i], 10)
1670
1622
  // the attribute could be a boolean
1671
1623
  if (typeof _actual === 'boolean') return _actual === _expected
1672
1624
  if (_actual !== _expected) return false
1673
1625
  }
1674
1626
  return true
1675
1627
  })
1676
- return assert.ok(
1677
- chunked.length === elemAmount,
1678
- `expected all elements (${new Locator(locator)}) to have attributes ${JSON.stringify(attributes)}`,
1679
- )
1628
+ return assert.ok(chunked.length === elemAmount, `expected all elements (${new Locator(locator)}) to have attributes ${JSON.stringify(attributes)}`)
1680
1629
  }
1681
1630
 
1682
1631
  /**
@@ -1685,9 +1634,9 @@ class WebDriver extends Helper {
1685
1634
  async grabNumberOfVisibleElements(locator) {
1686
1635
  const res = await this._locate(locator)
1687
1636
 
1688
- let selected = await forEachAsync(res, async (el) => el.isDisplayed())
1637
+ let selected = await forEachAsync(res, async el => el.isDisplayed())
1689
1638
  if (!Array.isArray(selected)) selected = [selected]
1690
- selected = selected.filter((val) => val === true)
1639
+ selected = selected.filter(val => val === true)
1691
1640
  return selected.length
1692
1641
  }
1693
1642
 
@@ -1841,18 +1790,25 @@ class WebDriver extends Helper {
1841
1790
 
1842
1791
  if (browser) {
1843
1792
  this.debug(`Screenshot of ${sessionName} session has been saved to ${outputFile}`)
1844
- return browser.saveScreenshot(outputFile)
1793
+ await browser.saveScreenshot(outputFile)
1845
1794
  }
1846
1795
  }
1847
1796
  }
1848
1797
 
1849
1798
  if (!fullPage) {
1850
1799
  this.debug(`Screenshot has been saved to ${outputFile}`)
1851
- return this.browser.saveScreenshot(outputFile)
1800
+ await this.browser.saveScreenshot(outputFile)
1852
1801
  }
1853
1802
 
1854
1803
  const originalWindowSize = await this.browser.getWindowSize()
1855
1804
 
1805
+ // this case running on device, so we could not set the windowSize
1806
+ if (this.browser.isMobile) {
1807
+ this.debug(`Screenshot has been saved to ${outputFile}, size: ${originalWindowSize.width}x${originalWindowSize.height}`)
1808
+ const buffer = await this.browser.saveScreenshot(outputFile)
1809
+ return buffer
1810
+ }
1811
+
1856
1812
  let { width, height } = await this.browser
1857
1813
  .execute(function () {
1858
1814
  return {
@@ -1860,7 +1816,7 @@ class WebDriver extends Helper {
1860
1816
  width: document.body.scrollWidth,
1861
1817
  }
1862
1818
  })
1863
- .then((res) => res)
1819
+ .then(res => res)
1864
1820
 
1865
1821
  if (height < 100) height = 500 // errors for very small height
1866
1822
 
@@ -1928,7 +1884,7 @@ class WebDriver extends Helper {
1928
1884
 
1929
1885
  return promiseRetry(
1930
1886
  async (retry, number) => {
1931
- const _grabCookie = async (name) => {
1887
+ const _grabCookie = async name => {
1932
1888
  const cookie = await this.browser.getCookies([name])
1933
1889
  if (cookie.length === 0) throw Error(`Cookie ${name} is not found after ${retries}s`)
1934
1890
  }
@@ -1952,11 +1908,10 @@ class WebDriver extends Helper {
1952
1908
  * libraries](http://jster.net/category/windows-modals-popups).
1953
1909
  */
1954
1910
  async acceptPopup() {
1955
- return this.browser.getAlertText().then((res) => {
1956
- if (res !== null) {
1957
- return this.browser.acceptAlert()
1958
- }
1959
- })
1911
+ const text = await this.browser.getAlertText()
1912
+ if (text) {
1913
+ return await this.browser.acceptAlert()
1914
+ }
1960
1915
  }
1961
1916
 
1962
1917
  /**
@@ -1964,11 +1919,10 @@ class WebDriver extends Helper {
1964
1919
  *
1965
1920
  */
1966
1921
  async cancelPopup() {
1967
- return this.browser.getAlertText().then((res) => {
1968
- if (res !== null) {
1969
- return this.browser.dismissAlert()
1970
- }
1971
- })
1922
+ const text = await this.browser.getAlertText()
1923
+ if (text) {
1924
+ return await this.browser.dismissAlert()
1925
+ }
1972
1926
  }
1973
1927
 
1974
1928
  /**
@@ -1978,7 +1932,7 @@ class WebDriver extends Helper {
1978
1932
  * @param {string} text value to check.
1979
1933
  */
1980
1934
  async seeInPopup(text) {
1981
- return this.browser.getAlertText().then((res) => {
1935
+ return await this.browser.getAlertText().then(res => {
1982
1936
  if (res === null) {
1983
1937
  throw new Error('Popup is not opened')
1984
1938
  }
@@ -2259,9 +2213,9 @@ class WebDriver extends Helper {
2259
2213
  async closeOtherTabs() {
2260
2214
  const handles = await this.browser.getWindowHandles()
2261
2215
  const currentHandle = await this.browser.getWindowHandle()
2262
- const otherHandles = handles.filter((handle) => handle !== currentHandle)
2216
+ const otherHandles = handles.filter(handle => handle !== currentHandle)
2263
2217
 
2264
- await forEachAsync(otherHandles, async (handle) => {
2218
+ await forEachAsync(otherHandles, async handle => {
2265
2219
  await this.browser.switchToWindow(handle)
2266
2220
  await this.browser.closeWindow()
2267
2221
  })
@@ -2272,7 +2226,7 @@ class WebDriver extends Helper {
2272
2226
  * {{> wait }}
2273
2227
  */
2274
2228
  async wait(sec) {
2275
- return new Promise((resolve) => {
2229
+ return new Promise(resolve => {
2276
2230
  setTimeout(resolve, sec * 1000)
2277
2231
  })
2278
2232
  }
@@ -2289,9 +2243,9 @@ class WebDriver extends Helper {
2289
2243
  if (!res || res.length === 0) {
2290
2244
  return false
2291
2245
  }
2292
- const selected = await forEachAsync(res, async (el) => this.browser.isElementEnabled(getElementId(el)))
2246
+ const selected = await forEachAsync(res, async el => this.browser.isElementEnabled(getElementId(el)))
2293
2247
  if (Array.isArray(selected)) {
2294
- return selected.filter((val) => val === true).length > 0
2248
+ return selected.filter(val => val === true).length > 0
2295
2249
  }
2296
2250
  return selected
2297
2251
  },
@@ -2346,14 +2300,14 @@ class WebDriver extends Helper {
2346
2300
  return client
2347
2301
  .waitUntil(
2348
2302
  function () {
2349
- return this.getUrl().then((res) => {
2303
+ return this.getUrl().then(res => {
2350
2304
  currUrl = decodeUrl(res)
2351
2305
  return currUrl.indexOf(urlPart) > -1
2352
2306
  })
2353
2307
  },
2354
2308
  { timeout: aSec * 1000 },
2355
2309
  )
2356
- .catch((e) => {
2310
+ .catch(e => {
2357
2311
  if (e.message.indexOf('timeout')) {
2358
2312
  throw new Error(`expected url to include ${urlPart}, but found ${currUrl}`)
2359
2313
  }
@@ -2373,12 +2327,12 @@ class WebDriver extends Helper {
2373
2327
  let currUrl = ''
2374
2328
  return this.browser
2375
2329
  .waitUntil(function () {
2376
- return this.getUrl().then((res) => {
2330
+ return this.getUrl().then(res => {
2377
2331
  currUrl = decodeUrl(res)
2378
2332
  return currUrl === urlPart
2379
2333
  })
2380
2334
  }, aSec * 1000)
2381
- .catch((e) => {
2335
+ .catch(e => {
2382
2336
  if (e.message.indexOf('timeout')) {
2383
2337
  throw new Error(`expected url to be ${urlPart}, but found ${currUrl}`)
2384
2338
  }
@@ -2398,9 +2352,9 @@ class WebDriver extends Helper {
2398
2352
  async () => {
2399
2353
  const res = await this.$$(withStrictLocator.call(this, _context))
2400
2354
  if (!res || res.length === 0) return false
2401
- const selected = await forEachAsync(res, async (el) => this.browser.getElementText(getElementId(el)))
2355
+ const selected = await forEachAsync(res, async el => this.browser.getElementText(getElementId(el)))
2402
2356
  if (Array.isArray(selected)) {
2403
- return selected.filter((part) => part.indexOf(text) >= 0).length > 0
2357
+ return selected.filter(part => part.indexOf(text) >= 0).length > 0
2404
2358
  }
2405
2359
  return selected.indexOf(text) >= 0
2406
2360
  },
@@ -2422,9 +2376,9 @@ class WebDriver extends Helper {
2422
2376
  async () => {
2423
2377
  const res = await findFields.call(this, field)
2424
2378
  if (!res || res.length === 0) return false
2425
- const selected = await forEachAsync(res, async (el) => el.getValue())
2379
+ const selected = await forEachAsync(res, async el => el.getValue())
2426
2380
  if (Array.isArray(selected)) {
2427
- return selected.filter((part) => part.indexOf(value) >= 0).length > 0
2381
+ return selected.filter(part => part.indexOf(value) >= 0).length > 0
2428
2382
  }
2429
2383
  return selected.indexOf(value) >= 0
2430
2384
  },
@@ -2446,9 +2400,9 @@ class WebDriver extends Helper {
2446
2400
  async () => {
2447
2401
  const res = await this._res(locator)
2448
2402
  if (!res || res.length === 0) return false
2449
- const selected = await forEachAsync(res, async (el) => el.isDisplayed())
2403
+ const selected = await forEachAsync(res, async el => el.isDisplayed())
2450
2404
  if (Array.isArray(selected)) {
2451
- return selected.filter((val) => val === true).length > 0
2405
+ return selected.filter(val => val === true).length > 0
2452
2406
  }
2453
2407
  return selected
2454
2408
  },
@@ -2469,10 +2423,10 @@ class WebDriver extends Helper {
2469
2423
  async () => {
2470
2424
  const res = await this._res(locator)
2471
2425
  if (!res || res.length === 0) return false
2472
- let selected = await forEachAsync(res, async (el) => el.isDisplayed())
2426
+ let selected = await forEachAsync(res, async el => el.isDisplayed())
2473
2427
 
2474
2428
  if (!Array.isArray(selected)) selected = [selected]
2475
- selected = selected.filter((val) => val === true)
2429
+ selected = selected.filter(val => val === true)
2476
2430
  return selected.length === num
2477
2431
  },
2478
2432
  {
@@ -2492,7 +2446,7 @@ class WebDriver extends Helper {
2492
2446
  async () => {
2493
2447
  const res = await this._res(locator)
2494
2448
  if (!res || res.length === 0) return true
2495
- const selected = await forEachAsync(res, async (el) => el.isDisplayed())
2449
+ const selected = await forEachAsync(res, async el => el.isDisplayed())
2496
2450
  return !selected.length
2497
2451
  },
2498
2452
  { timeout: aSec * 1000, timeoutMsg: `element (${new Locator(locator)}) still visible after ${aSec} sec` },
@@ -2568,17 +2522,14 @@ class WebDriver extends Helper {
2568
2522
  */
2569
2523
  async switchTo(locator) {
2570
2524
  this.browser.isInsideFrame = true
2571
- if (Number.isInteger(locator)) {
2572
- return this.browser.switchToFrame(locator)
2573
- }
2574
2525
  if (!locator) {
2575
- return this.browser.switchToFrame(null)
2526
+ return this.browser.switchFrame(null)
2576
2527
  }
2577
2528
 
2578
2529
  let res = await this._locate(locator, true)
2579
2530
  assertElementExists(res, locator)
2580
2531
  res = usingFirstElement(res)
2581
- return this.browser.switchToFrame(res)
2532
+ return this.browser.switchFrame(res)
2582
2533
  }
2583
2534
 
2584
2535
  /**
@@ -2591,7 +2542,7 @@ class WebDriver extends Helper {
2591
2542
 
2592
2543
  await this.browser.waitUntil(
2593
2544
  async () => {
2594
- await this.browser.getWindowHandles().then((handles) => {
2545
+ await this.browser.getWindowHandles().then(handles => {
2595
2546
  if (handles.indexOf(current) + num + 1 <= handles.length) {
2596
2547
  target = handles[handles.indexOf(current) + num]
2597
2548
  }
@@ -2613,7 +2564,7 @@ class WebDriver extends Helper {
2613
2564
 
2614
2565
  await this.browser.waitUntil(
2615
2566
  async () => {
2616
- await this.browser.getWindowHandles().then((handles) => {
2567
+ await this.browser.getWindowHandles().then(handles => {
2617
2568
  if (handles.indexOf(current) - num > -1) {
2618
2569
  target = handles[handles.indexOf(current) - num]
2619
2570
  }
@@ -2683,10 +2634,7 @@ class WebDriver extends Helper {
2683
2634
  return client.execute(function () {
2684
2635
  const body = document.body
2685
2636
  const html = document.documentElement
2686
- window.scrollTo(
2687
- 0,
2688
- Math.max(body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight),
2689
- )
2637
+ window.scrollTo(0, Math.max(body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight))
2690
2638
  })
2691
2639
  }
2692
2640
 
@@ -2760,10 +2708,17 @@ async function proceedSee(assertType, text, context, strict = false) {
2760
2708
  const smartWaitEnabled = assertType === 'assert'
2761
2709
  const res = await this._locate(withStrictLocator(context), smartWaitEnabled)
2762
2710
  assertElementExists(res, context)
2763
- const selected = await forEachAsync(res, async (el) => this.browser.getElementText(getElementId(el)))
2711
+ let selected = await forEachAsync(res, async el => this.browser.getElementText(getElementId(el)))
2712
+
2713
+ // apply ignoreCase option
2714
+ if (store?.currentStep?.opts?.ignoreCase === true) {
2715
+ text = text.toLowerCase()
2716
+ selected = selected.map(elText => elText.toLowerCase())
2717
+ }
2718
+
2764
2719
  if (strict) {
2765
2720
  if (Array.isArray(selected) && selected.length !== 0) {
2766
- return selected.map((elText) => equals(description)[assertType](text, elText))
2721
+ return selected.map(elText => equals(description)[assertType](text, elText))
2767
2722
  }
2768
2723
  return equals(description)[assertType](text, selected)
2769
2724
  }
@@ -2791,7 +2746,7 @@ async function forEachAsync(array, callback, options = { expandArrayResults: tru
2791
2746
  const res = await callback(inputArray[index], index, inputArray)
2792
2747
 
2793
2748
  if (Array.isArray(res) && expandArrayResults) {
2794
- res.forEach((val) => values.push(val))
2749
+ res.forEach(val => values.push(val))
2795
2750
  } else if (res) {
2796
2751
  values.push(res)
2797
2752
  }
@@ -2877,11 +2832,11 @@ async function proceedSeeField(assertType, field, value) {
2877
2832
  const elem = usingFirstElement(res)
2878
2833
  const elemId = getElementId(elem)
2879
2834
 
2880
- const proceedMultiple = async (fields) => {
2835
+ const proceedMultiple = async fields => {
2881
2836
  const fieldResults = toArray(
2882
- await forEachAsync(fields, async (el) => {
2837
+ await forEachAsync(fields, async el => {
2883
2838
  const elementId = getElementId(el)
2884
- return this.browser.isW3C ? el.getValue() : this.browser.getElementAttribute(elementId, 'value')
2839
+ return this.browser.getElementAttribute(elementId, 'value')
2885
2840
  }),
2886
2841
  )
2887
2842
 
@@ -2890,27 +2845,24 @@ async function proceedSeeField(assertType, field, value) {
2890
2845
  } else {
2891
2846
  // Assert that results were found so the forEach assert does not silently pass
2892
2847
  equals(`no. of items matching > 0: ${field}`)[assertType](true, !!fieldResults.length)
2893
- fieldResults.forEach((val) => stringIncludes(`fields by ${field}`)[assertType](value, val))
2848
+ fieldResults.forEach(val => stringIncludes(`fields by ${field}`)[assertType](value, val))
2894
2849
  }
2895
2850
  }
2896
2851
 
2897
- const proceedSingle = (el) =>
2898
- el.getValue().then((res) => {
2852
+ const proceedSingle = el =>
2853
+ el.getValue().then(res => {
2899
2854
  if (res === null) {
2900
2855
  throw new Error(`Element ${el.selector} has no value attribute`)
2901
2856
  }
2902
2857
  stringIncludes(`fields by ${field}`)[assertType](value, res)
2903
2858
  })
2904
2859
 
2905
- const filterBySelected = async (elements) =>
2906
- filterAsync(elements, async (el) => this.browser.isElementSelected(getElementId(el)))
2860
+ const filterBySelected = async elements => filterAsync(elements, async el => this.browser.isElementSelected(getElementId(el)))
2907
2861
 
2908
2862
  const filterSelectedByValue = async (elements, value) => {
2909
- return filterAsync(elements, async (el) => {
2863
+ return filterAsync(elements, async el => {
2910
2864
  const elementId = getElementId(el)
2911
- const currentValue = this.browser.isW3C
2912
- ? await el.getValue()
2913
- : await this.browser.getElementAttribute(elementId, 'value')
2865
+ const currentValue = await this.browser.getElementAttribute(elementId, 'value')
2914
2866
  const isSelected = await this.browser.isElementSelected(elementId)
2915
2867
  return currentValue === value && isSelected
2916
2868
  })
@@ -2918,7 +2870,13 @@ async function proceedSeeField(assertType, field, value) {
2918
2870
 
2919
2871
  const tag = await elem.getTagName()
2920
2872
  if (tag === 'select') {
2921
- const subOptions = await this.browser.findElementsFromElement(elemId, 'css', 'option')
2873
+ let subOptions
2874
+
2875
+ try {
2876
+ subOptions = await this.browser.findElementsFromElement(elemId, 'css', 'option')
2877
+ } catch (e) {
2878
+ subOptions = await this.browser.findElementsFromElement(elemId, 'xpath', 'option')
2879
+ }
2922
2880
 
2923
2881
  if (value === '') {
2924
2882
  // Don't filter by value
@@ -2959,7 +2917,7 @@ async function proceedSeeCheckbox(assertType, field) {
2959
2917
  const res = await findFields.call(this, field)
2960
2918
  assertElementExists(res, field, 'Field')
2961
2919
 
2962
- const selected = await forEachAsync(res, async (el) => this.browser.isElementSelected(getElementId(el)))
2920
+ const selected = await forEachAsync(res, async el => this.browser.isElementSelected(getElementId(el)))
2963
2921
  return truth(`checkable field "${field}"`, 'to be checked')[assertType](selected)
2964
2922
  }
2965
2923
 
@@ -3160,7 +3118,7 @@ function getNormalizedKey(key) {
3160
3118
  return convertKeyToRawKey(normalizedKey)
3161
3119
  }
3162
3120
 
3163
- const unicodeModifierKeys = modifierKeys.map((k) => convertKeyToRawKey(k))
3121
+ const unicodeModifierKeys = modifierKeys.map(k => convertKeyToRawKey(k))
3164
3122
  function isModifierKey(key) {
3165
3123
  return unicodeModifierKeys.includes(key)
3166
3124
  }
@@ -3173,9 +3131,9 @@ function highlightActiveElement(element) {
3173
3131
 
3174
3132
  function prepareLocateFn(context) {
3175
3133
  if (!context) return this._locate.bind(this)
3176
- return (l) => {
3134
+ return l => {
3177
3135
  l = new Locator(l, 'css')
3178
- return this._locate(context, true).then(async (res) => {
3136
+ return this._locate(context, true).then(async res => {
3179
3137
  assertElementExists(res, context, 'Context element')
3180
3138
  if (l.react) {
3181
3139
  return res[0].react$$(l.react, l.props || undefined)
@@ -3185,4 +3143,8 @@ function prepareLocateFn(context) {
3185
3143
  }
3186
3144
  }
3187
3145
 
3146
+ function logEvents(event) {
3147
+ browserLogs.push(event.text) // add log message to the array
3148
+ }
3149
+
3188
3150
  module.exports = WebDriver