codeceptjs 4.0.0-rc.2 → 4.0.0-rc.8

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 (40) 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/dontSeeElement.mustache +4 -0
  8. package/docs/webapi/dontSeeInField.mustache +5 -0
  9. package/docs/webapi/fillField.mustache +5 -0
  10. package/docs/webapi/moveCursorTo.mustache +5 -1
  11. package/docs/webapi/seeElement.mustache +4 -0
  12. package/docs/webapi/seeInField.mustache +5 -0
  13. package/docs/webapi/selectOption.mustache +5 -0
  14. package/docs/webapi/uncheckOption.mustache +1 -1
  15. package/lib/codecept.js +20 -17
  16. package/lib/command/init.js +0 -3
  17. package/lib/command/run-workers.js +1 -0
  18. package/lib/container.js +19 -4
  19. package/lib/element/WebElement.js +52 -0
  20. package/lib/helper/Appium.js +8 -8
  21. package/lib/helper/Playwright.js +169 -87
  22. package/lib/helper/Puppeteer.js +181 -64
  23. package/lib/helper/WebDriver.js +141 -53
  24. package/lib/helper/errors/MultipleElementsFound.js +27 -110
  25. package/lib/helper/scripts/dropFile.js +11 -0
  26. package/lib/html.js +14 -1
  27. package/lib/listener/globalRetry.js +32 -6
  28. package/lib/mocha/cli.js +10 -0
  29. package/lib/plugin/aiTrace.js +464 -0
  30. package/lib/plugin/retryFailedStep.js +28 -19
  31. package/lib/plugin/stepByStepReport.js +5 -1
  32. package/lib/utils.js +48 -0
  33. package/lib/workers.js +49 -7
  34. package/package.json +5 -3
  35. package/lib/listener/enhancedGlobalRetry.js +0 -110
  36. package/lib/plugin/enhancedRetryFailedStep.js +0 -99
  37. package/lib/plugin/htmlReporter.js +0 -3648
  38. package/lib/retryCoordinator.js +0 -207
  39. package/typings/promiseBasedTypes.d.ts +0 -9469
  40. package/typings/types.d.ts +0 -11402
@@ -23,7 +23,11 @@ import {
23
23
  clearString,
24
24
  requireWithFallback,
25
25
  normalizeSpacesInString,
26
+ normalizePath,
27
+ resolveUrl,
26
28
  relativeDir,
29
+ getMimeType,
30
+ base64EncodeFile,
27
31
  } from '../utils.js'
28
32
  import { isColorProperty, convertColorToRGBA } from '../colorUtils.js'
29
33
  import ElementNotFound from './errors/ElementNotFound.js'
@@ -32,6 +36,7 @@ import RemoteBrowserConnectionRefused from './errors/RemoteBrowserConnectionRefu
32
36
  import Popup from './extras/Popup.js'
33
37
  import Console from './extras/Console.js'
34
38
  import { findReact, findVue, findByPlaywrightLocator } from './extras/PlaywrightReactVueLocator.js'
39
+ import { dropFile } from './scripts/dropFile.js'
35
40
  import WebElement from '../element/WebElement.js'
36
41
 
37
42
  let playwright
@@ -1490,8 +1495,23 @@ class Playwright extends Helper {
1490
1495
  *
1491
1496
  */
1492
1497
  async moveCursorTo(locator, offsetX = 0, offsetY = 0) {
1493
- const el = await this._locateElement(locator)
1494
- assertElementExists(el, locator)
1498
+ let context = null
1499
+ if (typeof offsetX !== 'number') {
1500
+ context = offsetX
1501
+ offsetX = 0
1502
+ }
1503
+
1504
+ let el
1505
+ if (context) {
1506
+ const contextEls = await this._locate(context)
1507
+ assertElementExists(contextEls, context, 'Context element')
1508
+ el = await findElements.call(this, contextEls[0], locator)
1509
+ assertElementExists(el, locator)
1510
+ el = el[0]
1511
+ } else {
1512
+ el = await this._locateElement(locator)
1513
+ assertElementExists(el, locator)
1514
+ }
1495
1515
 
1496
1516
  // Use manual mouse.move instead of .hover() so the offset can be added to the coordinates
1497
1517
  const { x, y } = await clickablePoint(el)
@@ -1759,7 +1779,7 @@ class Playwright extends Helper {
1759
1779
  if (elements.length === 0) {
1760
1780
  throw new ElementNotFound(locator, 'Element', 'was not found')
1761
1781
  }
1762
- if (this.options.strict) assertOnlyOneElement(elements, locator)
1782
+ if (this.options.strict) assertOnlyOneElement(elements, locator, this)
1763
1783
  return elements[0]
1764
1784
  }
1765
1785
 
@@ -1775,7 +1795,7 @@ class Playwright extends Helper {
1775
1795
  const context = providedContext || (await this._getContext())
1776
1796
  const els = await findCheckable.call(this, locator, context)
1777
1797
  assertElementExists(els[0], locator, 'Checkbox or radio')
1778
- if (this.options.strict) assertOnlyOneElement(els, locator)
1798
+ if (this.options.strict) assertOnlyOneElement(els, locator, this)
1779
1799
  return els[0]
1780
1800
  }
1781
1801
 
@@ -1944,8 +1964,15 @@ class Playwright extends Helper {
1944
1964
  * {{> seeElement }}
1945
1965
  *
1946
1966
  */
1947
- async seeElement(locator) {
1948
- let els = await this._locate(locator)
1967
+ async seeElement(locator, context = null) {
1968
+ let els
1969
+ if (context) {
1970
+ const contextEls = await this._locate(context)
1971
+ assertElementExists(contextEls, context, 'Context element')
1972
+ els = await findElements.call(this, contextEls[0], locator)
1973
+ } else {
1974
+ els = await this._locate(locator)
1975
+ }
1949
1976
  els = await Promise.all(els.map(el => el.isVisible()))
1950
1977
  try {
1951
1978
  return empty('visible elements').negate(els.filter(v => v).fill('ELEMENT'))
@@ -1958,8 +1985,15 @@ class Playwright extends Helper {
1958
1985
  * {{> dontSeeElement }}
1959
1986
  *
1960
1987
  */
1961
- async dontSeeElement(locator) {
1962
- let els = await this._locate(locator)
1988
+ async dontSeeElement(locator, context = null) {
1989
+ let els
1990
+ if (context) {
1991
+ const contextEls = await this._locate(context)
1992
+ assertElementExists(contextEls, context, 'Context element')
1993
+ els = await findElements.call(this, contextEls[0], locator)
1994
+ } else {
1995
+ els = await this._locate(locator)
1996
+ }
1963
1997
  els = await Promise.all(els.map(el => el.isVisible()))
1964
1998
  try {
1965
1999
  return empty('visible elements').assert(els.filter(v => v).fill('ELEMENT'))
@@ -2245,10 +2279,10 @@ class Playwright extends Helper {
2245
2279
  * {{> fillField }}
2246
2280
  *
2247
2281
  */
2248
- async fillField(field, value) {
2249
- const els = await findFields.call(this, field)
2282
+ async fillField(field, value, context = null) {
2283
+ const els = await findFields.call(this, field, context)
2250
2284
  assertElementExists(els, field, 'Field')
2251
- if (this.options.strict) assertOnlyOneElement(els, field)
2285
+ if (this.options.strict) assertOnlyOneElement(els, field, this)
2252
2286
  const el = els[0]
2253
2287
 
2254
2288
  await el.clear()
@@ -2262,26 +2296,12 @@ class Playwright extends Helper {
2262
2296
  }
2263
2297
 
2264
2298
  /**
2265
- * Clears the text input element: `<input>`, `<textarea>` or `[contenteditable]` .
2266
- *
2267
- *
2268
- * Examples:
2269
- *
2270
- * ```js
2271
- * I.clearField('.text-area')
2272
- *
2273
- * // if this doesn't work use force option
2274
- * I.clearField('#submit', { force: true })
2275
- * ```
2276
- * Use `force` to bypass the [actionability](https://playwright.dev/docs/actionability) checks.
2277
- *
2278
- * @param {CodeceptJS.LocatorOrString} locator field located by label|name|CSS|XPath|strict locator.
2279
- * @param {any} [options] [Additional options](https://playwright.dev/docs/api/class-locator#locator-clear) for available options object as 2nd argument.
2299
+ * {{> clearField }}
2280
2300
  */
2281
- async clearField(locator, options = {}) {
2282
- const els = await findFields.call(this, locator)
2301
+ async clearField(locator, context = null) {
2302
+ const els = await findFields.call(this, locator, context)
2283
2303
  assertElementExists(els, locator, 'Field to clear')
2284
- if (this.options.strict) assertOnlyOneElement(els, locator)
2304
+ if (this.options.strict) assertOnlyOneElement(els, locator, this)
2285
2305
 
2286
2306
  const el = els[0]
2287
2307
 
@@ -2295,10 +2315,10 @@ class Playwright extends Helper {
2295
2315
  /**
2296
2316
  * {{> appendField }}
2297
2317
  */
2298
- async appendField(field, value) {
2299
- const els = await findFields.call(this, field)
2318
+ async appendField(field, value, context = null) {
2319
+ const els = await findFields.call(this, field, context)
2300
2320
  assertElementExists(els, field, 'Field')
2301
- if (this.options.strict) assertOnlyOneElement(els, field)
2321
+ if (this.options.strict) assertOnlyOneElement(els, field, this)
2302
2322
  await highlightActiveElement.call(this, els[0])
2303
2323
  await els[0].press('End')
2304
2324
  await els[0].type(value.toString(), { delay: this.options.pressKeyDelay })
@@ -2308,63 +2328,86 @@ class Playwright extends Helper {
2308
2328
  /**
2309
2329
  * {{> seeInField }}
2310
2330
  */
2311
- async seeInField(field, value) {
2331
+ async seeInField(field, value, context = null) {
2312
2332
  const _value = typeof value === 'boolean' ? value : value.toString()
2313
- return proceedSeeInField.call(this, 'assert', field, _value)
2333
+ return proceedSeeInField.call(this, 'assert', field, _value, context)
2314
2334
  }
2315
2335
 
2316
2336
  /**
2317
2337
  * {{> dontSeeInField }}
2318
2338
  */
2319
- async dontSeeInField(field, value) {
2339
+ async dontSeeInField(field, value, context = null) {
2320
2340
  const _value = typeof value === 'boolean' ? value : value.toString()
2321
- return proceedSeeInField.call(this, 'negate', field, _value)
2341
+ return proceedSeeInField.call(this, 'negate', field, _value, context)
2322
2342
  }
2323
2343
 
2324
2344
  /**
2325
2345
  * {{> attachFile }}
2326
2346
  *
2327
2347
  */
2328
- async attachFile(locator, pathToFile) {
2348
+ async attachFile(locator, pathToFile, context = null) {
2329
2349
  const file = path.join(global.codecept_dir, pathToFile)
2330
2350
 
2331
2351
  if (!fileExists(file)) {
2332
2352
  throw new Error(`File at ${file} can not be found on local system`)
2333
2353
  }
2334
- const els = await findFields.call(this, locator)
2335
- assertElementExists(els, locator, 'Field')
2336
- await els[0].setInputFiles(file)
2354
+ const els = await findFields.call(this, locator, context)
2355
+ if (els.length) {
2356
+ const tag = await els[0].evaluate(el => el.tagName)
2357
+ const type = await els[0].evaluate(el => el.type)
2358
+ if (tag === 'INPUT' && type === 'file') {
2359
+ await els[0].setInputFiles(file)
2360
+ return this._waitForAction()
2361
+ }
2362
+ }
2363
+
2364
+ const targetEls = els.length ? els : await this._locate(locator)
2365
+ assertElementExists(targetEls, locator, 'Element')
2366
+ const fileData = {
2367
+ base64Content: base64EncodeFile(file),
2368
+ fileName: path.basename(file),
2369
+ mimeType: getMimeType(path.basename(file)),
2370
+ }
2371
+ await targetEls[0].evaluate(dropFile, fileData)
2337
2372
  return this._waitForAction()
2338
2373
  }
2339
2374
 
2340
2375
  /**
2341
2376
  * {{> selectOption }}
2342
2377
  */
2343
- async selectOption(select, option) {
2344
- const context = await this.context
2378
+ async selectOption(select, option, context = null) {
2379
+ const pageContext = await this.context
2345
2380
  const matchedLocator = new Locator(select)
2346
2381
 
2382
+ let contextEl
2383
+ if (context) {
2384
+ const contextEls = await this._locate(context)
2385
+ assertElementExists(contextEls, context, 'Context element')
2386
+ contextEl = contextEls[0]
2387
+ }
2388
+
2347
2389
  // Strict locator
2348
2390
  if (!matchedLocator.isFuzzy()) {
2349
2391
  this.debugSection('SelectOption', `Strict: ${JSON.stringify(select)}`)
2350
- const els = await this._locate(matchedLocator)
2392
+ const els = contextEl ? await findElements.call(this, contextEl, matchedLocator) : await this._locate(matchedLocator)
2351
2393
  assertElementExists(els, select, 'Selectable element')
2352
- return proceedSelect.call(this, context, els[0], option)
2394
+ return proceedSelect.call(this, pageContext, els[0], option)
2353
2395
  }
2354
2396
 
2355
2397
  // Fuzzy: try combobox
2356
2398
  this.debugSection('SelectOption', `Fuzzy: "${matchedLocator.value}"`)
2357
- let els = await findByRole(context, { role: 'combobox', name: matchedLocator.value })
2358
- if (els?.length) return proceedSelect.call(this, context, els[0], option)
2399
+ const comboboxSearchCtx = contextEl || pageContext
2400
+ let els = await findByRole(comboboxSearchCtx, { role: 'combobox', name: matchedLocator.value })
2401
+ if (els?.length) return proceedSelect.call(this, pageContext, els[0], option)
2359
2402
 
2360
2403
  // Fuzzy: try listbox
2361
- els = await findByRole(context, { role: 'listbox', name: matchedLocator.value })
2362
- if (els?.length) return proceedSelect.call(this, context, els[0], option)
2404
+ els = await findByRole(comboboxSearchCtx, { role: 'listbox', name: matchedLocator.value })
2405
+ if (els?.length) return proceedSelect.call(this, pageContext, els[0], option)
2363
2406
 
2364
2407
  // Fuzzy: try native select
2365
- els = await findFields.call(this, select)
2408
+ els = await findFields.call(this, select, context)
2366
2409
  assertElementExists(els, select, 'Selectable element')
2367
- return proceedSelect.call(this, context, els[0], option)
2410
+ return proceedSelect.call(this, pageContext, els[0], option)
2368
2411
  }
2369
2412
 
2370
2413
  /**
@@ -2412,7 +2455,7 @@ class Playwright extends Helper {
2412
2455
  const currentUrl = await this._getPageUrl()
2413
2456
  const baseUrl = this.options.url || 'http://localhost'
2414
2457
  const actualPath = new URL(currentUrl, baseUrl).pathname
2415
- return equals('url path').assert(path, actualPath)
2458
+ return equals('url path').assert(normalizePath(path), normalizePath(actualPath))
2416
2459
  }
2417
2460
 
2418
2461
  /**
@@ -2422,7 +2465,7 @@ class Playwright extends Helper {
2422
2465
  const currentUrl = await this._getPageUrl()
2423
2466
  const baseUrl = this.options.url || 'http://localhost'
2424
2467
  const actualPath = new URL(currentUrl, baseUrl).pathname
2425
- return equals('url path').negate(path, actualPath)
2468
+ return equals('url path').negate(normalizePath(path), normalizePath(actualPath))
2426
2469
  }
2427
2470
 
2428
2471
  /**
@@ -3382,6 +3425,7 @@ class Playwright extends Helper {
3382
3425
  */
3383
3426
  async waitInUrl(urlPart, sec = null) {
3384
3427
  const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout
3428
+ const expectedUrl = resolveUrl(urlPart, this.options.url)
3385
3429
 
3386
3430
  return this.page
3387
3431
  .waitForFunction(
@@ -3389,13 +3433,13 @@ class Playwright extends Helper {
3389
3433
  const currUrl = decodeURIComponent(decodeURIComponent(decodeURIComponent(window.location.href)))
3390
3434
  return currUrl.indexOf(urlPart) > -1
3391
3435
  },
3392
- urlPart,
3436
+ expectedUrl,
3393
3437
  { timeout: waitTimeout },
3394
3438
  )
3395
3439
  .catch(async e => {
3396
- const currUrl = await this._getPageUrl() // Required because the waitForFunction can't return data.
3440
+ const currUrl = await this._getPageUrl()
3397
3441
  if (/Timeout/i.test(e.message)) {
3398
- throw new Error(`expected url to include ${urlPart}, but found ${currUrl}`)
3442
+ throw new Error(`expected url to include ${expectedUrl}, but found ${currUrl}`)
3399
3443
  } else {
3400
3444
  throw e
3401
3445
  }
@@ -3407,26 +3451,46 @@ class Playwright extends Helper {
3407
3451
  */
3408
3452
  async waitUrlEquals(urlPart, sec = null) {
3409
3453
  const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout
3410
-
3411
- const baseUrl = this.options.url
3412
- let expectedUrl = urlPart
3413
- if (urlPart.indexOf('http') < 0) {
3414
- expectedUrl = baseUrl + urlPart
3415
- }
3454
+ const expectedUrl = resolveUrl(urlPart, this.options.url)
3416
3455
 
3417
3456
  try {
3418
3457
  await this.page.waitForURL(
3419
- url => url.href.includes(expectedUrl),
3458
+ url => url.href === expectedUrl,
3420
3459
  { timeout: waitTimeout },
3421
3460
  )
3422
3461
  } catch (e) {
3423
3462
  const currUrl = await this._getPageUrl()
3424
3463
  if (/Timeout/i.test(e.message)) {
3425
- if (!currUrl.includes(expectedUrl)) {
3426
- throw new Error(`expected url to be ${expectedUrl}, but found ${currUrl}`)
3427
- } else {
3428
- throw new Error(`expected url not loaded, error message: ${e.message}`)
3429
- }
3464
+ throw new Error(`expected url to be ${expectedUrl}, but found ${currUrl}`)
3465
+ } else {
3466
+ throw e
3467
+ }
3468
+ }
3469
+ }
3470
+
3471
+ /**
3472
+ * {{> waitCurrentPathEquals }}
3473
+ */
3474
+ async waitCurrentPathEquals(path, sec = null) {
3475
+ const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout
3476
+ const normalizedPath = normalizePath(path)
3477
+
3478
+ try {
3479
+ await this.page.waitForFunction(
3480
+ expectedPath => {
3481
+ const actualPath = window.location.pathname
3482
+ const normalizePath = p => (p === '' || p === '/' ? '/' : p.replace(/\/+/g, '/').replace(/\/$/, '') || '/')
3483
+ return normalizePath(actualPath) === expectedPath
3484
+ },
3485
+ normalizedPath,
3486
+ { timeout: waitTimeout },
3487
+ )
3488
+ } catch (e) {
3489
+ const currentUrl = await this._getPageUrl()
3490
+ const baseUrl = this.options.url || 'http://localhost'
3491
+ const actualPath = new URL(currentUrl, baseUrl).pathname
3492
+ if (/Timeout/i.test(e.message)) {
3493
+ throw new Error(`expected path to be ${normalizedPath}, but found ${normalizePath(actualPath)}`)
3430
3494
  } else {
3431
3495
  throw e
3432
3496
  }
@@ -4112,9 +4176,15 @@ class Playwright extends Helper {
4112
4176
 
4113
4177
  export default Playwright
4114
4178
 
4115
- function buildLocatorString(locator) {
4179
+ export function buildLocatorString(locator) {
4116
4180
  if (locator.isXPath()) {
4117
- return `xpath=${locator.value}`
4181
+ // Make XPath relative so it works correctly within scoped contexts (e.g. within()).
4182
+ // Playwright's XPath engine auto-converts "//..." to ".//..." when the root is not a Document,
4183
+ // but only when the selector starts with "/". Locator methods like at() wrap XPath in
4184
+ // parentheses (e.g. "(//...)[position()=1]"), bypassing that auto-conversion.
4185
+ // We fix this by prepending "." before the first "//" that follows any leading parentheses.
4186
+ const value = locator.value.replace(/^(\(*)\/\//, '$1.//')
4187
+ return `xpath=${value}`
4118
4188
  }
4119
4189
  if (locator.isShadow()) {
4120
4190
  // Convert shadow locator to CSS with >> chaining operator
@@ -4238,7 +4308,7 @@ async function findClickable(matcher, locator) {
4238
4308
 
4239
4309
  if (!matchedLocator.isFuzzy()) {
4240
4310
  const els = await findElements.call(this, matcher, matchedLocator)
4241
- if (this.options.strict) assertOnlyOneElement(els, locator)
4311
+ if (this.options.strict) assertOnlyOneElement(els, locator, this)
4242
4312
  return els
4243
4313
  }
4244
4314
 
@@ -4248,7 +4318,7 @@ async function findClickable(matcher, locator) {
4248
4318
  try {
4249
4319
  els = await matcher.getByRole('button', { name: matchedLocator.value }).all()
4250
4320
  if (els.length) {
4251
- if (this.options.strict) assertOnlyOneElement(els, locator)
4321
+ if (this.options.strict) assertOnlyOneElement(els, locator, this)
4252
4322
  return els
4253
4323
  }
4254
4324
  } catch (err) {
@@ -4258,7 +4328,7 @@ async function findClickable(matcher, locator) {
4258
4328
  try {
4259
4329
  els = await matcher.getByRole('link', { name: matchedLocator.value }).all()
4260
4330
  if (els.length) {
4261
- if (this.options.strict) assertOnlyOneElement(els, locator)
4331
+ if (this.options.strict) assertOnlyOneElement(els, locator, this)
4262
4332
  return els
4263
4333
  }
4264
4334
  } catch (err) {
@@ -4267,20 +4337,20 @@ async function findClickable(matcher, locator) {
4267
4337
 
4268
4338
  els = await findElements.call(this, matcher, Locator.clickable.narrow(literal))
4269
4339
  if (els.length) {
4270
- if (this.options.strict) assertOnlyOneElement(els, locator)
4340
+ if (this.options.strict) assertOnlyOneElement(els, locator, this)
4271
4341
  return els
4272
4342
  }
4273
4343
 
4274
4344
  els = await findElements.call(this, matcher, Locator.clickable.wide(literal))
4275
4345
  if (els.length) {
4276
- if (this.options.strict) assertOnlyOneElement(els, locator)
4346
+ if (this.options.strict) assertOnlyOneElement(els, locator, this)
4277
4347
  return els
4278
4348
  }
4279
4349
 
4280
4350
  try {
4281
4351
  els = await findElements.call(this, matcher, Locator.clickable.self(literal))
4282
4352
  if (els.length) {
4283
- if (this.options.strict) assertOnlyOneElement(els, locator)
4353
+ if (this.options.strict) assertOnlyOneElement(els, locator, this)
4284
4354
  return els
4285
4355
  }
4286
4356
  } catch (err) {
@@ -4355,34 +4425,45 @@ async function proceedIsChecked(assertType, option) {
4355
4425
  return truth(`checkable ${option}`, 'to be checked')[assertType](selected)
4356
4426
  }
4357
4427
 
4358
- async function findFields(locator) {
4428
+ async function findFields(locator, context = null) {
4429
+ let contextEl
4430
+ if (context) {
4431
+ const contextEls = await this._locate(context)
4432
+ assertElementExists(contextEls, context, 'Context element')
4433
+ contextEl = contextEls[0]
4434
+ }
4435
+
4436
+ const locateFn = contextEl
4437
+ ? loc => findElements.call(this, contextEl, loc)
4438
+ : loc => this._locate(loc)
4439
+
4359
4440
  // Handle role locators with text/exact options
4360
4441
  if (isRoleLocatorObject(locator)) {
4361
- const page = await this.page
4362
- const roleElements = await handleRoleLocator(page, locator)
4442
+ const matcher = contextEl || (await this.page)
4443
+ const roleElements = await handleRoleLocator(matcher, locator)
4363
4444
  if (roleElements) return roleElements
4364
4445
  }
4365
4446
 
4366
4447
  const matchedLocator = new Locator(locator)
4367
4448
  if (!matchedLocator.isFuzzy()) {
4368
- return this._locate(matchedLocator)
4449
+ return locateFn(matchedLocator)
4369
4450
  }
4370
4451
  const literal = xpathLocator.literal(locator)
4371
4452
 
4372
- let els = await this._locate({ xpath: Locator.field.labelEquals(literal) })
4453
+ let els = await locateFn({ xpath: Locator.field.labelEquals(literal) })
4373
4454
  if (els.length) {
4374
4455
  return els
4375
4456
  }
4376
4457
 
4377
- els = await this._locate({ xpath: Locator.field.labelContains(literal) })
4458
+ els = await locateFn({ xpath: Locator.field.labelContains(literal) })
4378
4459
  if (els.length) {
4379
4460
  return els
4380
4461
  }
4381
- els = await this._locate({ xpath: Locator.field.byName(literal) })
4462
+ els = await locateFn({ xpath: Locator.field.byName(literal) })
4382
4463
  if (els.length) {
4383
4464
  return els
4384
4465
  }
4385
- return this._locate({ css: locator })
4466
+ return locateFn({ css: locator })
4386
4467
  }
4387
4468
 
4388
4469
  async function proceedSelect(context, el, option) {
@@ -4431,8 +4512,8 @@ async function proceedSelect(context, el, option) {
4431
4512
  return this._waitForAction()
4432
4513
  }
4433
4514
 
4434
- async function proceedSeeInField(assertType, field, value) {
4435
- const els = await findFields.call(this, field)
4515
+ async function proceedSeeInField(assertType, field, value, context) {
4516
+ const els = await findFields.call(this, field, context)
4436
4517
  assertElementExists(els, field, 'Field')
4437
4518
  const el = els[0]
4438
4519
  const tag = await el.evaluate(e => e.tagName)
@@ -4546,9 +4627,10 @@ function assertElementExists(res, locator, prefix, suffix) {
4546
4627
  }
4547
4628
  }
4548
4629
 
4549
- function assertOnlyOneElement(elements, locator) {
4630
+ function assertOnlyOneElement(elements, locator, helper) {
4550
4631
  if (elements.length > 1) {
4551
- throw new MultipleElementsFound(locator, elements)
4632
+ const webElements = elements.map(el => new WebElement(el, helper))
4633
+ throw new MultipleElementsFound(locator, webElements)
4552
4634
  }
4553
4635
  }
4554
4636