codeceptjs 4.0.0-rc.1 → 4.0.0-rc.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 (47) hide show
  1. package/README.md +39 -27
  2. package/bin/mcp-server.js +610 -0
  3. package/docs/webapi/appendField.mustache +5 -0
  4. package/docs/webapi/attachFile.mustache +12 -0
  5. package/docs/webapi/checkOption.mustache +1 -1
  6. package/docs/webapi/clearField.mustache +5 -0
  7. package/docs/webapi/dontSeeCurrentPathEquals.mustache +10 -0
  8. package/docs/webapi/dontSeeElement.mustache +4 -0
  9. package/docs/webapi/dontSeeInField.mustache +5 -0
  10. package/docs/webapi/fillField.mustache +5 -0
  11. package/docs/webapi/moveCursorTo.mustache +5 -1
  12. package/docs/webapi/seeCurrentPathEquals.mustache +10 -0
  13. package/docs/webapi/seeElement.mustache +4 -0
  14. package/docs/webapi/seeInField.mustache +5 -0
  15. package/docs/webapi/selectOption.mustache +5 -0
  16. package/docs/webapi/uncheckOption.mustache +1 -1
  17. package/lib/codecept.js +20 -17
  18. package/lib/command/init.js +0 -3
  19. package/lib/command/run-workers.js +1 -0
  20. package/lib/container.js +19 -4
  21. package/lib/element/WebElement.js +81 -2
  22. package/lib/els.js +12 -6
  23. package/lib/helper/Appium.js +8 -8
  24. package/lib/helper/Playwright.js +219 -137
  25. package/lib/helper/Puppeteer.js +207 -69
  26. package/lib/helper/WebDriver.js +179 -64
  27. package/lib/helper/errors/MultipleElementsFound.js +27 -110
  28. package/lib/helper/extras/elementSelection.js +58 -0
  29. package/lib/helper/scripts/dropFile.js +11 -0
  30. package/lib/html.js +14 -1
  31. package/lib/listener/globalRetry.js +32 -6
  32. package/lib/mocha/cli.js +10 -0
  33. package/lib/plugin/aiTrace.js +464 -0
  34. package/lib/plugin/retryFailedStep.js +28 -19
  35. package/lib/plugin/stepByStepReport.js +5 -1
  36. package/lib/step/config.js +15 -2
  37. package/lib/step/record.js +1 -1
  38. package/lib/utils.js +48 -0
  39. package/lib/workers.js +49 -7
  40. package/package.json +5 -3
  41. package/typings/index.d.ts +19 -0
  42. package/lib/listener/enhancedGlobalRetry.js +0 -110
  43. package/lib/plugin/enhancedRetryFailedStep.js +0 -99
  44. package/lib/plugin/htmlReporter.js +0 -3648
  45. package/lib/retryCoordinator.js +0 -207
  46. package/typings/promiseBasedTypes.d.ts +0 -9469
  47. package/typings/types.d.ts +0 -11402
@@ -26,17 +26,24 @@ import {
26
26
  isModifierKey,
27
27
  requireWithFallback,
28
28
  normalizeSpacesInString,
29
+ normalizePath,
30
+ resolveUrl,
31
+ getMimeType,
32
+ base64EncodeFile,
29
33
  } from '../utils.js'
30
34
  import { isColorProperty, convertColorToRGBA } from '../colorUtils.js'
31
35
  import ElementNotFound from './errors/ElementNotFound.js'
36
+ import MultipleElementsFound from './errors/MultipleElementsFound.js'
32
37
  import RemoteBrowserConnectionRefused from './errors/RemoteBrowserConnectionRefused.js'
33
38
  import Popup from './extras/Popup.js'
34
39
  import Console from './extras/Console.js'
35
40
  import { highlightElement } from './scripts/highlightElement.js'
36
41
  import { blurElement } from './scripts/blurElement.js'
42
+ import { dropFile } from './scripts/dropFile.js'
37
43
  import { dontSeeElementError, seeElementError, dontSeeElementInDOMError, seeElementInDOMError } from './errors/ElementAssertion.js'
38
44
  import { dontSeeTraffic, seeTraffic, grabRecordedNetworkTraffics, stopRecordingTraffic, flushNetworkTraffics } from './network/actions.js'
39
45
  import WebElement from '../element/WebElement.js'
46
+ import { selectElement } from './extras/elementSelection.js'
40
47
 
41
48
  let puppeteer
42
49
 
@@ -266,6 +273,7 @@ class Puppeteer extends Helper {
266
273
  show: false,
267
274
  defaultPopupAction: 'accept',
268
275
  highlightElement: false,
276
+ strict: false,
269
277
  }
270
278
 
271
279
  return Object.assign(defaults, config)
@@ -814,9 +822,26 @@ class Puppeteer extends Helper {
814
822
  * {{ react }}
815
823
  */
816
824
  async moveCursorTo(locator, offsetX = 0, offsetY = 0) {
817
- const el = await this._locateElement(locator)
818
- if (!el) {
819
- throw new ElementNotFound(locator, 'Element to move cursor to')
825
+ let context = null
826
+ if (typeof offsetX !== 'number') {
827
+ context = offsetX
828
+ offsetX = 0
829
+ }
830
+
831
+ let el
832
+ if (context) {
833
+ const contextEls = await findElements.call(this, this.page, context)
834
+ assertElementExists(contextEls, context, 'Context element')
835
+ const els = await findElements.call(this, contextEls[0], locator)
836
+ if (!els || els.length === 0) {
837
+ throw new ElementNotFound(locator, 'Element to move cursor to')
838
+ }
839
+ el = els[0]
840
+ } else {
841
+ el = await this._locateElement(locator)
842
+ if (!el) {
843
+ throw new ElementNotFound(locator, 'Element to move cursor to')
844
+ }
820
845
  }
821
846
 
822
847
  // Use manual mouse.move instead of .hover() so the offset can be added to the coordinates
@@ -984,6 +1009,14 @@ class Puppeteer extends Helper {
984
1009
  */
985
1010
  async _locateElement(locator) {
986
1011
  const context = await this.context
1012
+ const elementIndex = store.currentStep?.opts?.elementIndex
1013
+ if (this.options.strict || elementIndex) {
1014
+ const elements = await findElements.call(this, context, locator)
1015
+ if (elements.length === 0) {
1016
+ throw new ElementNotFound(locator, 'Element', 'was not found')
1017
+ }
1018
+ return selectElement(elements, locator, this)
1019
+ }
987
1020
  return findElement.call(this, context, locator)
988
1021
  }
989
1022
 
@@ -1001,7 +1034,7 @@ class Puppeteer extends Helper {
1001
1034
  if (!els || els.length === 0) {
1002
1035
  throw new ElementNotFound(locator, 'Checkbox or radio')
1003
1036
  }
1004
- return els[0]
1037
+ return selectElement(els, locator, this)
1005
1038
  }
1006
1039
 
1007
1040
  /**
@@ -1158,8 +1191,16 @@ class Puppeteer extends Helper {
1158
1191
  * {{> seeElement }}
1159
1192
  * {{ react }}
1160
1193
  */
1161
- async seeElement(locator) {
1162
- let els = await this._locate(locator)
1194
+ async seeElement(locator, context = null) {
1195
+ let els
1196
+ if (context) {
1197
+ const contextPage = await this.context
1198
+ const contextEls = await findElements.call(this, contextPage, context)
1199
+ assertElementExists(contextEls, context, 'Context element')
1200
+ els = await findElements.call(this, contextEls[0], locator)
1201
+ } else {
1202
+ els = await this._locate(locator)
1203
+ }
1163
1204
  els = (await Promise.all(els.map(el => el.boundingBox() && el))).filter(v => v)
1164
1205
  // Puppeteer visibility was ignored? | Remove when Puppeteer is fixed
1165
1206
  els = await Promise.all(els.map(async el => (await el.evaluate(node => window.getComputedStyle(node).visibility !== 'hidden' && window.getComputedStyle(node).display !== 'none')) && el))
@@ -1174,8 +1215,16 @@ class Puppeteer extends Helper {
1174
1215
  * {{> dontSeeElement }}
1175
1216
  * {{ react }}
1176
1217
  */
1177
- async dontSeeElement(locator) {
1178
- let els = await this._locate(locator)
1218
+ async dontSeeElement(locator, context = null) {
1219
+ let els
1220
+ if (context) {
1221
+ const contextPage = await this.context
1222
+ const contextEls = await findElements.call(this, contextPage, context)
1223
+ assertElementExists(contextEls, context, 'Context element')
1224
+ els = await findElements.call(this, contextEls[0], locator)
1225
+ } else {
1226
+ els = await this._locate(locator)
1227
+ }
1179
1228
  els = (await Promise.all(els.map(el => el.boundingBox() && el))).filter(v => v)
1180
1229
  // Puppeteer visibility was ignored? | Remove when Puppeteer is fixed
1181
1230
  els = await Promise.all(els.map(async el => (await el.evaluate(node => window.getComputedStyle(node).visibility !== 'hidden' && window.getComputedStyle(node).display !== 'none')) && el))
@@ -1541,10 +1590,10 @@ class Puppeteer extends Helper {
1541
1590
  * {{> fillField }}
1542
1591
  * {{ react }}
1543
1592
  */
1544
- async fillField(field, value) {
1545
- const els = await findVisibleFields.call(this, field)
1593
+ async fillField(field, value, context = null) {
1594
+ const els = await findVisibleFields.call(this, field, context)
1546
1595
  assertElementExists(els, field, 'Field')
1547
- const el = els[0]
1596
+ const el = selectElement(els, field, this)
1548
1597
  const tag = await el.getProperty('tagName').then(el => el.jsonValue())
1549
1598
  const editable = await el.getProperty('contenteditable').then(el => el.jsonValue())
1550
1599
  if (tag === 'INPUT' || tag === 'TEXTAREA') {
@@ -1562,8 +1611,8 @@ class Puppeteer extends Helper {
1562
1611
  /**
1563
1612
  * {{> clearField }}
1564
1613
  */
1565
- async clearField(field) {
1566
- return this.fillField(field, '')
1614
+ async clearField(field, context = null) {
1615
+ return this.fillField(field, '', context)
1567
1616
  }
1568
1617
 
1569
1618
  /**
@@ -1571,29 +1620,30 @@ class Puppeteer extends Helper {
1571
1620
  *
1572
1621
  * {{ react }}
1573
1622
  */
1574
- async appendField(field, value) {
1575
- const els = await findVisibleFields.call(this, field)
1623
+ async appendField(field, value, context = null) {
1624
+ const els = await findVisibleFields.call(this, field, context)
1576
1625
  assertElementExists(els, field, 'Field')
1577
- highlightActiveElement.call(this, els[0], await this._getContext())
1578
- await els[0].press('End')
1579
- await els[0].type(value.toString(), { delay: this.options.pressKeyDelay })
1626
+ const el = selectElement(els, field, this)
1627
+ highlightActiveElement.call(this, el, await this._getContext())
1628
+ await el.press('End')
1629
+ await el.type(value.toString(), { delay: this.options.pressKeyDelay })
1580
1630
  return this._waitForAction()
1581
1631
  }
1582
1632
 
1583
1633
  /**
1584
1634
  * {{> seeInField }}
1585
1635
  */
1586
- async seeInField(field, value) {
1636
+ async seeInField(field, value, context = null) {
1587
1637
  const _value = typeof value === 'boolean' ? value : value.toString()
1588
- return proceedSeeInField.call(this, 'assert', field, _value)
1638
+ return proceedSeeInField.call(this, 'assert', field, _value, context)
1589
1639
  }
1590
1640
 
1591
1641
  /**
1592
1642
  * {{> dontSeeInField }}
1593
1643
  */
1594
- async dontSeeInField(field, value) {
1644
+ async dontSeeInField(field, value, context = null) {
1595
1645
  const _value = typeof value === 'boolean' ? value : value.toString()
1596
- return proceedSeeInField.call(this, 'negate', field, _value)
1646
+ return proceedSeeInField.call(this, 'negate', field, _value, context)
1597
1647
  }
1598
1648
 
1599
1649
  /**
@@ -1601,46 +1651,71 @@ class Puppeteer extends Helper {
1601
1651
  *
1602
1652
  * {{> attachFile }}
1603
1653
  */
1604
- async attachFile(locator, pathToFile) {
1654
+ async attachFile(locator, pathToFile, context = null) {
1605
1655
  const file = path.join(global.codecept_dir, pathToFile)
1606
1656
 
1607
1657
  if (!fileExists(file)) {
1608
1658
  throw new Error(`File at ${file} can not be found on local system`)
1609
1659
  }
1610
- const els = await findFields.call(this, locator)
1611
- assertElementExists(els, locator, 'Field')
1612
- await els[0].uploadFile(file)
1660
+ const els = await findFields.call(this, locator, context)
1661
+ if (els.length) {
1662
+ const el = selectElement(els, locator, this)
1663
+ const tag = await el.evaluate(el => el.tagName)
1664
+ const type = await el.evaluate(el => el.type)
1665
+ if (tag === 'INPUT' && type === 'file') {
1666
+ await el.uploadFile(file)
1667
+ return this._waitForAction()
1668
+ }
1669
+ }
1670
+
1671
+ const targetEls = els.length ? els : await this._locate(locator)
1672
+ assertElementExists(targetEls, locator, 'Element')
1673
+ const el = selectElement(targetEls, locator, this)
1674
+ const fileData = {
1675
+ base64Content: base64EncodeFile(file),
1676
+ fileName: path.basename(file),
1677
+ mimeType: getMimeType(path.basename(file)),
1678
+ }
1679
+ await el.evaluate(dropFile, fileData)
1613
1680
  return this._waitForAction()
1614
1681
  }
1615
1682
 
1616
1683
  /**
1617
1684
  * {{> selectOption }}
1618
1685
  */
1619
- async selectOption(select, option) {
1620
- const context = await this._getContext()
1686
+ async selectOption(select, option, context = null) {
1687
+ const pageContext = await this._getContext()
1621
1688
  const matchedLocator = new Locator(select)
1622
1689
 
1690
+ let contextEl
1691
+ if (context) {
1692
+ const contextEls = await findElements.call(this, pageContext, context)
1693
+ assertElementExists(contextEls, context, 'Context element')
1694
+ contextEl = contextEls[0]
1695
+ }
1696
+
1623
1697
  // Strict locator
1624
1698
  if (!matchedLocator.isFuzzy()) {
1625
1699
  this.debugSection('SelectOption', `Strict: ${JSON.stringify(select)}`)
1626
- const els = await this._locate(select)
1700
+ const els = contextEl ? await findElements.call(this, contextEl, select) : await this._locate(select)
1627
1701
  assertElementExists(els, select, 'Selectable element')
1628
- return proceedSelect.call(this, context, els[0], option)
1702
+ return proceedSelect.call(this, pageContext, selectElement(els, select, this), option)
1629
1703
  }
1630
1704
 
1631
1705
  // Fuzzy: try combobox
1632
1706
  this.debugSection('SelectOption', `Fuzzy: "${matchedLocator.value}"`)
1633
- let els = await findByRole(context, { role: 'combobox', name: matchedLocator.value })
1634
- if (els?.length) return proceedSelect.call(this, context, els[0], option)
1707
+ const comboboxSearchCtx = contextEl || pageContext
1708
+ let els = await findByRole(comboboxSearchCtx, { role: 'combobox', name: matchedLocator.value })
1709
+ if (els?.length) return proceedSelect.call(this, pageContext, selectElement(els, select, this), option)
1635
1710
 
1636
1711
  // Fuzzy: try listbox
1637
- els = await findByRole(context, { role: 'listbox', name: matchedLocator.value })
1638
- if (els?.length) return proceedSelect.call(this, context, els[0], option)
1712
+ els = await findByRole(comboboxSearchCtx, { role: 'listbox', name: matchedLocator.value })
1713
+ if (els?.length) return proceedSelect.call(this, pageContext, selectElement(els, select, this), option)
1639
1714
 
1640
1715
  // Fuzzy: try native select
1641
- const visibleEls = await findVisibleFields.call(this, select)
1716
+ const visibleEls = await findVisibleFields.call(this, select, context)
1642
1717
  assertElementExists(visibleEls, select, 'Selectable field')
1643
- return proceedSelect.call(this, context, visibleEls[0], option)
1718
+ return proceedSelect.call(this, pageContext, selectElement(visibleEls, select, this), option)
1644
1719
  }
1645
1720
 
1646
1721
  /**
@@ -1684,6 +1759,26 @@ class Puppeteer extends Helper {
1684
1759
  urlEquals(this.options.url).negate(url, await this._getPageUrl())
1685
1760
  }
1686
1761
 
1762
+ /**
1763
+ * {{> seeCurrentPathEquals }}
1764
+ */
1765
+ async seeCurrentPathEquals(path) {
1766
+ const currentUrl = await this._getPageUrl()
1767
+ const baseUrl = this.options.url || 'http://localhost'
1768
+ const actualPath = new URL(currentUrl, baseUrl).pathname
1769
+ return equals('url path').assert(normalizePath(path), normalizePath(actualPath))
1770
+ }
1771
+
1772
+ /**
1773
+ * {{> dontSeeCurrentPathEquals }}
1774
+ */
1775
+ async dontSeeCurrentPathEquals(path) {
1776
+ const currentUrl = await this._getPageUrl()
1777
+ const baseUrl = this.options.url || 'http://localhost'
1778
+ const actualPath = new URL(currentUrl, baseUrl).pathname
1779
+ return equals('url path').negate(normalizePath(path), normalizePath(actualPath))
1780
+ }
1781
+
1687
1782
  /**
1688
1783
  * {{> see }}
1689
1784
  *
@@ -2421,6 +2516,7 @@ class Puppeteer extends Helper {
2421
2516
  */
2422
2517
  async waitInUrl(urlPart, sec = null) {
2423
2518
  const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout
2519
+ const expectedUrl = resolveUrl(urlPart, this.options.url)
2424
2520
 
2425
2521
  return this.page
2426
2522
  .waitForFunction(
@@ -2429,12 +2525,12 @@ class Puppeteer extends Helper {
2429
2525
  return currUrl.indexOf(urlPart) > -1
2430
2526
  },
2431
2527
  { timeout: waitTimeout },
2432
- urlPart,
2528
+ expectedUrl,
2433
2529
  )
2434
2530
  .catch(async e => {
2435
- const currUrl = await this._getPageUrl() // Required because the waitForFunction can't return data.
2531
+ const currUrl = await this._getPageUrl()
2436
2532
  if (/Waiting failed:/i.test(e.message) || /failed: timeout/i.test(e.message)) {
2437
- throw new Error(`expected url to include ${urlPart}, but found ${currUrl}`)
2533
+ throw new Error(`expected url to include ${expectedUrl}, but found ${currUrl}`)
2438
2534
  } else {
2439
2535
  throw e
2440
2536
  }
@@ -2446,18 +2542,13 @@ class Puppeteer extends Helper {
2446
2542
  */
2447
2543
  async waitUrlEquals(urlPart, sec = null) {
2448
2544
  const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout
2449
-
2450
- const baseUrl = this.options.url
2451
- let expectedUrl = urlPart
2452
- if (urlPart.indexOf('http') < 0) {
2453
- expectedUrl = baseUrl + urlPart
2454
- }
2545
+ const expectedUrl = resolveUrl(urlPart, this.options.url)
2455
2546
 
2456
2547
  return this.page
2457
2548
  .waitForFunction(
2458
2549
  url => {
2459
2550
  const currUrl = decodeURIComponent(window.location.href)
2460
- return currUrl.indexOf(url) > -1
2551
+ return currUrl === url
2461
2552
  },
2462
2553
  { timeout: waitTimeout },
2463
2554
  expectedUrl,
@@ -2465,11 +2556,36 @@ class Puppeteer extends Helper {
2465
2556
  .catch(async e => {
2466
2557
  const currUrl = await this._getPageUrl()
2467
2558
  if (/Waiting failed/i.test(e.message) || /failed: timeout/i.test(e.message)) {
2468
- if (!currUrl.includes(expectedUrl)) {
2469
- throw new Error(`expected url to be ${expectedUrl}, but found ${currUrl}`)
2470
- } else {
2471
- throw new Error(`expected url not loaded, error message: ${e.message}`)
2472
- }
2559
+ throw new Error(`expected url to be ${expectedUrl}, but found ${currUrl}`)
2560
+ } else {
2561
+ throw e
2562
+ }
2563
+ })
2564
+ }
2565
+
2566
+ /**
2567
+ * {{> waitCurrentPathEquals }}
2568
+ */
2569
+ async waitCurrentPathEquals(path, sec = null) {
2570
+ const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout
2571
+ const normalizedPath = normalizePath(path)
2572
+
2573
+ return this.page
2574
+ .waitForFunction(
2575
+ expectedPath => {
2576
+ const actualPath = window.location.pathname
2577
+ const normalizePath = p => (p === '' || p === '/' ? '/' : p.replace(/\/+/g, '/').replace(/\/$/, '') || '/')
2578
+ return normalizePath(actualPath) === expectedPath
2579
+ },
2580
+ { timeout: waitTimeout },
2581
+ normalizedPath,
2582
+ )
2583
+ .catch(async e => {
2584
+ const currUrl = await this._getPageUrl()
2585
+ const baseUrl = this.options.url || 'http://localhost'
2586
+ const actualPath = new URL(currUrl, baseUrl).pathname
2587
+ if (/Waiting failed/i.test(e.message) || /failed: timeout/i.test(e.message)) {
2588
+ throw new Error(`expected path to be ${normalizedPath}, but found ${normalizePath(actualPath)}`)
2473
2589
  } else {
2474
2590
  throw e
2475
2591
  }
@@ -3006,10 +3122,11 @@ async function proceedClick(locator, context = null, options = {}) {
3006
3122
  } else {
3007
3123
  assertElementExists(els, locator, 'Clickable element')
3008
3124
  }
3125
+ const el = selectElement(els, locator, this)
3009
3126
 
3010
- highlightActiveElement.call(this, els[0], await this._getContext())
3127
+ highlightActiveElement.call(this, el, await this._getContext())
3011
3128
 
3012
- await els[0].click(options)
3129
+ await el.click(options)
3013
3130
  const promises = []
3014
3131
  if (options.waitForNavigation) {
3015
3132
  promises.push(this.waitForNavigation())
@@ -3140,43 +3257,57 @@ async function proceedIsChecked(assertType, option) {
3140
3257
  return truth(`checkable ${option}`, 'to be checked')[assertType](selected)
3141
3258
  }
3142
3259
 
3143
- async function findVisibleFields(locator) {
3144
- const els = await findFields.call(this, locator)
3260
+ async function findVisibleFields(locator, context = null) {
3261
+ const els = await findFields.call(this, locator, context)
3145
3262
  const visible = await Promise.all(els.map(el => el.boundingBox()))
3146
3263
  return els.filter((el, index) => visible[index])
3147
3264
  }
3148
3265
 
3149
- async function findFields(locator) {
3266
+ async function findFields(locator, context = null) {
3267
+ let contextEl
3268
+ if (context) {
3269
+ const contextPage = await this.context
3270
+ const contextEls = await findElements.call(this, contextPage, context)
3271
+ assertElementExists(contextEls, context, 'Context element')
3272
+ contextEl = contextEls[0]
3273
+ }
3274
+
3275
+ const locateFn = contextEl
3276
+ ? loc => findElements.call(this, contextEl, loc)
3277
+ : loc => this._locate(loc)
3278
+
3150
3279
  const matchedLocator = new Locator(locator)
3151
3280
  if (!matchedLocator.isFuzzy()) {
3152
- return this._locate(matchedLocator)
3281
+ return locateFn(matchedLocator)
3153
3282
  }
3154
3283
  const literal = xpathLocator.literal(matchedLocator.value)
3155
3284
 
3156
- let els = await this._locate({ xpath: Locator.field.labelEquals(literal) })
3285
+ let els = await locateFn({ xpath: Locator.field.labelEquals(literal) })
3157
3286
  if (els.length) {
3158
3287
  return els
3159
3288
  }
3160
3289
 
3161
- els = await this._locate({ xpath: Locator.field.labelContains(literal) })
3290
+ els = await locateFn({ xpath: Locator.field.labelContains(literal) })
3162
3291
  if (els.length) {
3163
3292
  return els
3164
3293
  }
3165
- els = await this._locate({ xpath: Locator.field.byName(literal) })
3294
+ els = await locateFn({ xpath: Locator.field.byName(literal) })
3166
3295
  if (els.length) {
3167
3296
  return els
3168
3297
  }
3169
3298
 
3170
3299
  // Try ARIA selector for accessible name
3171
- try {
3172
- const page = await this.context
3173
- els = await page.$$(`::-p-aria(${matchedLocator.value})`)
3174
- if (els.length) return els
3175
- } catch (err) {
3176
- // ARIA selector not supported or failed
3300
+ if (!contextEl) {
3301
+ try {
3302
+ const page = await this.context
3303
+ els = await page.$$(`::-p-aria(${matchedLocator.value})`)
3304
+ if (els.length) return els
3305
+ } catch (err) {
3306
+ // ARIA selector not supported or failed
3307
+ }
3177
3308
  }
3178
3309
 
3179
- return this._locate({ css: matchedLocator.value })
3310
+ return locateFn({ css: matchedLocator.value })
3180
3311
  }
3181
3312
 
3182
3313
  async function proceedDragAndDrop(sourceLocator, destinationLocator) {
@@ -3205,8 +3336,8 @@ async function proceedDragAndDrop(sourceLocator, destinationLocator) {
3205
3336
  await this._waitForAction()
3206
3337
  }
3207
3338
 
3208
- async function proceedSeeInField(assertType, field, value) {
3209
- const els = await findVisibleFields.call(this, field)
3339
+ async function proceedSeeInField(assertType, field, value, context) {
3340
+ const els = await findVisibleFields.call(this, field, context)
3210
3341
  assertElementExists(els, field, 'Field')
3211
3342
  const el = els[0]
3212
3343
  const tag = await el.getProperty('tagName').then(el => el.jsonValue())
@@ -3319,6 +3450,13 @@ function assertElementExists(res, locator, prefix, suffix) {
3319
3450
  }
3320
3451
  }
3321
3452
 
3453
+ function assertOnlyOneElement(elements, locator, helper) {
3454
+ if (elements.length > 1) {
3455
+ const webElements = elements.map(el => new WebElement(el, helper))
3456
+ throw new MultipleElementsFound(locator, webElements)
3457
+ }
3458
+ }
3459
+
3322
3460
  function $XPath(element, selector) {
3323
3461
  const found = document.evaluate(selector, element || document.body, null, 5, null)
3324
3462
  const res = []