codeceptjs 4.0.0-rc.2 → 4.0.0-rc.7

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.
@@ -1,5 +1,6 @@
1
1
  let webdriverio
2
2
 
3
+ import fs from 'fs'
3
4
  import assert from 'assert'
4
5
  import path from 'path'
5
6
  import crypto from 'crypto'
@@ -13,7 +14,20 @@ import output from '../output.js'
13
14
  const { debug } = output
14
15
  import { empty } from '../assert/empty.js'
15
16
  import { truth } from '../assert/truth.js'
16
- import { xpathLocator, fileExists, decodeUrl, chunkArray, convertCssPropertiesToCamelCase, screenshotOutputFolder, getNormalizedKeyAttributeValue, modifierKeys } from '../utils.js'
17
+ import {
18
+ xpathLocator,
19
+ fileExists,
20
+ decodeUrl,
21
+ chunkArray,
22
+ convertCssPropertiesToCamelCase,
23
+ screenshotOutputFolder,
24
+ getNormalizedKeyAttributeValue,
25
+ modifierKeys,
26
+ normalizePath,
27
+ resolveUrl,
28
+ getMimeType,
29
+ base64EncodeFile,
30
+ } from '../utils.js'
17
31
  import { isColorProperty, convertColorToRGBA } from '../colorUtils.js'
18
32
  import ElementNotFound from './errors/ElementNotFound.js'
19
33
  import ConnectionRefused from './errors/ConnectionRefused.js'
@@ -1255,8 +1269,8 @@ class WebDriver extends Helper {
1255
1269
  * {{ custom }}
1256
1270
  *
1257
1271
  */
1258
- async fillField(field, value) {
1259
- const res = await findFields.call(this, field)
1272
+ async fillField(field, value, context = null) {
1273
+ const res = await findFields.call(this, field, context)
1260
1274
  assertElementExists(res, field, 'Field')
1261
1275
  const elem = usingFirstElement(res)
1262
1276
  highlightActiveElement.call(this, elem)
@@ -1278,8 +1292,8 @@ class WebDriver extends Helper {
1278
1292
  * {{> appendField }}
1279
1293
  * {{ react }}
1280
1294
  */
1281
- async appendField(field, value) {
1282
- const res = await findFields.call(this, field)
1295
+ async appendField(field, value, context = null) {
1296
+ const res = await findFields.call(this, field, context)
1283
1297
  assertElementExists(res, field, 'Field')
1284
1298
  const elem = usingFirstElement(res)
1285
1299
  highlightActiveElement.call(this, elem)
@@ -1290,8 +1304,8 @@ class WebDriver extends Helper {
1290
1304
  * {{> clearField }}
1291
1305
  *
1292
1306
  */
1293
- async clearField(field) {
1294
- const res = await findFields.call(this, field)
1307
+ async clearField(field, context = null) {
1308
+ const res = await findFields.call(this, field, context)
1295
1309
  assertElementExists(res, field, 'Field')
1296
1310
  const elem = usingFirstElement(res)
1297
1311
  highlightActiveElement.call(this, elem)
@@ -1301,13 +1315,14 @@ class WebDriver extends Helper {
1301
1315
  /**
1302
1316
  * {{> selectOption }}
1303
1317
  */
1304
- async selectOption(select, option) {
1318
+ async selectOption(select, option, context = null) {
1319
+ const locateFn = prepareLocateFn.call(this, context)
1305
1320
  const matchedLocator = new Locator(select)
1306
1321
 
1307
1322
  // Strict locator
1308
1323
  if (!matchedLocator.isFuzzy()) {
1309
1324
  this.debugSection('SelectOption', `Strict: ${JSON.stringify(select)}`)
1310
- const els = await this._locate(select)
1325
+ const els = await locateFn(select)
1311
1326
  assertElementExists(els, select, 'Selectable element')
1312
1327
  return proceedSelectOption.call(this, usingFirstElement(els), option)
1313
1328
  }
@@ -1322,7 +1337,7 @@ class WebDriver extends Helper {
1322
1337
  if (els?.length) return proceedSelectOption.call(this, usingFirstElement(els), option)
1323
1338
 
1324
1339
  // Fuzzy: try native select
1325
- const res = await findFields.call(this, select)
1340
+ const res = await findFields.call(this, select, context)
1326
1341
  assertElementExists(res, select, 'Selectable field')
1327
1342
  return proceedSelectOption.call(this, usingFirstElement(res), option)
1328
1343
  }
@@ -1332,28 +1347,49 @@ class WebDriver extends Helper {
1332
1347
  *
1333
1348
  * {{> attachFile }}
1334
1349
  */
1335
- async attachFile(locator, pathToFile) {
1350
+ async attachFile(locator, pathToFile, context = null) {
1336
1351
  let file = path.join(global.codecept_dir, pathToFile)
1337
1352
  if (!fileExists(file)) {
1338
1353
  throw new Error(`File at ${file} can not be found on local system`)
1339
1354
  }
1340
1355
 
1341
- const res = await findFields.call(this, locator)
1356
+ const res = await findFields.call(this, locator, context)
1342
1357
  this.debug(`Uploading ${file}`)
1343
- assertElementExists(res, locator, 'File field')
1344
- const el = usingFirstElement(res)
1345
1358
 
1346
- // Remote Upload (when running Selenium Server)
1347
- if (this.options.remoteFileUpload) {
1348
- try {
1349
- this.debugSection('File', 'Uploading file to remote server')
1350
- file = await this.browser.uploadFile(file)
1351
- } catch (err) {
1352
- throw new Error(`File can't be transferred to remote server. Set \`remoteFileUpload: false\` in config to upload file locally.\n${err.message}`)
1359
+ if (res.length) {
1360
+ const el = usingFirstElement(res)
1361
+ const tag = await this.browser.execute(function (elem) { return elem.tagName }, el)
1362
+ const type = await this.browser.execute(function (elem) { return elem.type }, el)
1363
+ if (tag === 'INPUT' && type === 'file') {
1364
+ if (this.options.remoteFileUpload) {
1365
+ try {
1366
+ this.debugSection('File', 'Uploading file to remote server')
1367
+ file = await this.browser.uploadFile(file)
1368
+ } catch (err) {
1369
+ throw new Error(`File can't be transferred to remote server. Set \`remoteFileUpload: false\` in config to upload file locally.\n${err.message}`)
1370
+ }
1371
+ }
1372
+ return el.addValue(file)
1353
1373
  }
1354
1374
  }
1355
1375
 
1356
- return el.addValue(file)
1376
+ const targetRes = res.length ? res : await this._locate(locator)
1377
+ assertElementExists(targetRes, locator, 'Element')
1378
+ const targetEl = usingFirstElement(targetRes)
1379
+ const base64Content = base64EncodeFile(file)
1380
+ const fileName = path.basename(file)
1381
+ const mimeType = getMimeType(fileName)
1382
+ return this.browser.execute(function (el, data) {
1383
+ var binaryStr = atob(data.base64Content)
1384
+ var bytes = new Uint8Array(binaryStr.length)
1385
+ for (var i = 0; i < binaryStr.length; i++) bytes[i] = binaryStr.charCodeAt(i)
1386
+ var fileObj = new File([bytes], data.fileName, { type: data.mimeType })
1387
+ var dataTransfer = new DataTransfer()
1388
+ dataTransfer.items.add(fileObj)
1389
+ el.dispatchEvent(new DragEvent('dragenter', { dataTransfer: dataTransfer, bubbles: true }))
1390
+ el.dispatchEvent(new DragEvent('dragover', { dataTransfer: dataTransfer, bubbles: true }))
1391
+ el.dispatchEvent(new DragEvent('drop', { dataTransfer: dataTransfer, bubbles: true }))
1392
+ }, targetEl, { base64Content, fileName, mimeType })
1357
1393
  }
1358
1394
 
1359
1395
  /**
@@ -1586,18 +1622,18 @@ class WebDriver extends Helper {
1586
1622
  * {{> seeInField }}
1587
1623
  *
1588
1624
  */
1589
- async seeInField(field, value) {
1625
+ async seeInField(field, value, context = null) {
1590
1626
  const _value = typeof value === 'boolean' ? value : value.toString()
1591
- return proceedSeeField.call(this, 'assert', field, _value)
1627
+ return proceedSeeField.call(this, 'assert', field, _value, context)
1592
1628
  }
1593
1629
 
1594
1630
  /**
1595
1631
  * {{> dontSeeInField }}
1596
1632
  *
1597
1633
  */
1598
- async dontSeeInField(field, value) {
1634
+ async dontSeeInField(field, value, context = null) {
1599
1635
  const _value = typeof value === 'boolean' ? value : value.toString()
1600
- return proceedSeeField.call(this, 'negate', field, _value)
1636
+ return proceedSeeField.call(this, 'negate', field, _value, context)
1601
1637
  }
1602
1638
 
1603
1639
  /**
@@ -1621,8 +1657,9 @@ class WebDriver extends Helper {
1621
1657
  * {{ react }}
1622
1658
  *
1623
1659
  */
1624
- async seeElement(locator) {
1625
- const res = await this._locate(locator, true)
1660
+ async seeElement(locator, context = null) {
1661
+ const locateFn = prepareLocateFn.call(this, context)
1662
+ const res = context ? await locateFn(locator) : await this._locate(locator, true)
1626
1663
  assertElementExists(res, locator)
1627
1664
  const selected = await forEachAsync(res, async el => el.isDisplayed())
1628
1665
  try {
@@ -1636,8 +1673,9 @@ class WebDriver extends Helper {
1636
1673
  * {{> dontSeeElement }}
1637
1674
  * {{ react }}
1638
1675
  */
1639
- async dontSeeElement(locator) {
1640
- const res = await this._locate(locator, false)
1676
+ async dontSeeElement(locator, context = null) {
1677
+ const locateFn = prepareLocateFn.call(this, context)
1678
+ const res = context ? await locateFn(locator) : await this._locate(locator, false)
1641
1679
  if (!res || res.length === 0) {
1642
1680
  return truth(`elements of ${new Locator(locator)}`, 'to be seen').negate(false)
1643
1681
  }
@@ -1851,7 +1889,7 @@ class WebDriver extends Helper {
1851
1889
  const currentUrl = await this.browser.getUrl()
1852
1890
  const baseUrl = this.options.url || 'http://localhost'
1853
1891
  const actualPath = new URL(currentUrl, baseUrl).pathname
1854
- return equals('url path').assert(path, actualPath)
1892
+ return equals('url path').assert(normalizePath(path), normalizePath(actualPath))
1855
1893
  }
1856
1894
 
1857
1895
  /**
@@ -1861,7 +1899,7 @@ class WebDriver extends Helper {
1861
1899
  const currentUrl = await this.browser.getUrl()
1862
1900
  const baseUrl = this.options.url || 'http://localhost'
1863
1901
  const actualPath = new URL(currentUrl, baseUrl).pathname
1864
- return equals('url path').negate(path, actualPath)
1902
+ return equals('url path').negate(normalizePath(path), normalizePath(actualPath))
1865
1903
  }
1866
1904
 
1867
1905
  /**
@@ -2487,6 +2525,7 @@ class WebDriver extends Helper {
2487
2525
  async waitInUrl(urlPart, sec = null) {
2488
2526
  const client = this.browser
2489
2527
  const aSec = sec || this.options.waitForTimeoutInSeconds
2528
+ const expectedUrl = resolveUrl(urlPart, this.options.url)
2490
2529
  let currUrl = ''
2491
2530
 
2492
2531
  return client
@@ -2494,7 +2533,7 @@ class WebDriver extends Helper {
2494
2533
  function () {
2495
2534
  return this.getUrl().then(res => {
2496
2535
  currUrl = decodeUrl(res)
2497
- return currUrl.indexOf(urlPart) > -1
2536
+ return currUrl.indexOf(expectedUrl) > -1
2498
2537
  })
2499
2538
  },
2500
2539
  { timeout: aSec * 1000 },
@@ -2502,7 +2541,7 @@ class WebDriver extends Helper {
2502
2541
  .catch(e => {
2503
2542
  e = wrapError(e)
2504
2543
  if (e.message.indexOf('timeout')) {
2505
- throw new Error(`expected url to include ${urlPart}, but found ${currUrl}`)
2544
+ throw new Error(`expected url to include ${expectedUrl}, but found ${currUrl}`)
2506
2545
  }
2507
2546
  throw e
2508
2547
  })
@@ -2513,22 +2552,47 @@ class WebDriver extends Helper {
2513
2552
  */
2514
2553
  async waitUrlEquals(urlPart, sec = null) {
2515
2554
  const aSec = sec || this.options.waitForTimeoutInSeconds
2516
- const baseUrl = this.options.url
2517
- if (urlPart.indexOf('http') < 0) {
2518
- urlPart = baseUrl + urlPart
2519
- }
2555
+ const expectedUrl = resolveUrl(urlPart, this.options.url)
2520
2556
  let currUrl = ''
2521
2557
  return this.browser
2522
2558
  .waitUntil(function () {
2523
2559
  return this.getUrl().then(res => {
2524
2560
  currUrl = decodeUrl(res)
2525
- return currUrl === urlPart
2561
+ return currUrl === expectedUrl
2526
2562
  })
2527
2563
  }, aSec * 1000)
2528
2564
  .catch(e => {
2529
2565
  e = wrapError(e)
2530
2566
  if (e.message.indexOf('timeout')) {
2531
- throw new Error(`expected url to be ${urlPart}, but found ${currUrl}`)
2567
+ throw new Error(`expected url to be ${expectedUrl}, but found ${currUrl}`)
2568
+ }
2569
+ throw e
2570
+ })
2571
+ }
2572
+
2573
+ /**
2574
+ * {{> waitCurrentPathEquals }}
2575
+ */
2576
+ async waitCurrentPathEquals(path, sec = null) {
2577
+ const aSec = sec || this.options.waitForTimeoutInSeconds
2578
+ const normalizedPath = normalizePath(path)
2579
+ const baseUrl = this.options.url || 'http://localhost'
2580
+ let actualPath = ''
2581
+
2582
+ return this.browser
2583
+ .waitUntil(
2584
+ async () => {
2585
+ const currUrl = await this.browser.getUrl()
2586
+ const url = new URL(currUrl, baseUrl)
2587
+ actualPath = url.pathname
2588
+ return normalizePath(actualPath) === normalizedPath
2589
+ },
2590
+ { timeout: aSec * 1000 },
2591
+ )
2592
+ .catch(e => {
2593
+ e = wrapError(e)
2594
+ if (e.message.indexOf('timeout')) {
2595
+ throw new Error(`expected path to be ${normalizedPath}, but found ${normalizePath(actualPath)}`)
2532
2596
  }
2533
2597
  throw e
2534
2598
  })
@@ -3010,32 +3074,33 @@ async function findClickable(locator, locateFn) {
3010
3074
  return await locateFn(locator.value) // by css or xpath
3011
3075
  }
3012
3076
 
3013
- async function findFields(locator) {
3077
+ async function findFields(locator, context = null) {
3078
+ const locateFn = prepareLocateFn.call(this, context)
3014
3079
  locator = new Locator(locator)
3015
3080
 
3016
3081
  if (this._isCustomLocator(locator)) {
3017
- return this._locate(locator)
3082
+ return locateFn(locator)
3018
3083
  }
3019
3084
 
3020
- if (locator.isAccessibilityId() && !this.isWeb) return this._locate(locator, true)
3021
- if (locator.isRole()) return this._locate(locator, true)
3022
- if (!locator.isFuzzy()) return this._locate(locator, true)
3085
+ if (locator.isAccessibilityId() && !this.isWeb) return locateFn(locator)
3086
+ if (locator.isRole()) return locateFn(locator)
3087
+ if (!locator.isFuzzy()) return locateFn(locator)
3023
3088
 
3024
3089
  const literal = xpathLocator.literal(locator.value)
3025
- let els = await this._locate(Locator.field.labelEquals(literal))
3090
+ let els = await locateFn(Locator.field.labelEquals(literal))
3026
3091
  if (els.length) return els
3027
3092
 
3028
- els = await this._locate(Locator.field.labelContains(literal))
3093
+ els = await locateFn(Locator.field.labelContains(literal))
3029
3094
  if (els.length) return els
3030
3095
 
3031
- els = await this._locate(Locator.field.byName(literal))
3096
+ els = await locateFn(Locator.field.byName(literal))
3032
3097
  if (els.length) return els
3033
3098
 
3034
- return await this._locate(locator.value) // by css or xpath
3099
+ return await locateFn(locator.value) // by css or xpath
3035
3100
  }
3036
3101
 
3037
- async function proceedSeeField(assertType, field, value) {
3038
- const res = await findFields.call(this, field)
3102
+ async function proceedSeeField(assertType, field, value, context) {
3103
+ const res = await findFields.call(this, field, context)
3039
3104
  assertElementExists(res, field, 'Field')
3040
3105
  const elem = usingFirstElement(res)
3041
3106
  const elemId = getElementId(elem)
@@ -5,16 +5,27 @@ import { isNotSet } from '../utils.js'
5
5
 
6
6
  const hooks = ['Before', 'After', 'BeforeSuite', 'AfterSuite']
7
7
 
8
+ const RETRY_PRIORITIES = {
9
+ MANUAL_STEP: 100,
10
+ STEP_PLUGIN: 50,
11
+ SCENARIO_CONFIG: 30,
12
+ FEATURE_CONFIG: 20,
13
+ HOOK_CONFIG: 10,
14
+ }
15
+
8
16
  export default function () {
9
17
  event.dispatcher.on(event.suite.before, suite => {
10
18
  let retryConfig = Config.get('retry')
11
19
  if (!retryConfig) return
12
20
 
13
21
  if (Number.isInteger(+retryConfig)) {
14
- // is number
15
22
  const retryNum = +retryConfig
16
23
  output.log(`Retries: ${retryNum}`)
17
- suite.retries(retryNum)
24
+
25
+ if (suite.retries() === -1 || (suite.opts.retryPriority || 0) <= RETRY_PRIORITIES.FEATURE_CONFIG) {
26
+ suite.retries(retryNum)
27
+ suite.opts.retryPriority = RETRY_PRIORITIES.FEATURE_CONFIG
28
+ }
18
29
  return
19
30
  }
20
31
 
@@ -30,11 +41,18 @@ export default function () {
30
41
  hooks
31
42
  .filter(hook => !!config[hook])
32
43
  .forEach(hook => {
33
- if (isNotSet(suite.opts[`retry${hook}`])) suite.opts[`retry${hook}`] = config[hook]
44
+ const retryKey = `retry${hook}`
45
+ if (isNotSet(suite.opts[retryKey])) {
46
+ suite.opts[retryKey] = config[hook]
47
+ suite.opts[`${retryKey}Priority`] = RETRY_PRIORITIES.HOOK_CONFIG
48
+ }
34
49
  })
35
50
 
36
51
  if (config.Feature) {
37
- if (isNotSet(suite.retries())) suite.retries(config.Feature)
52
+ if (suite.retries() === -1 || (suite.opts.retryPriority || 0) <= RETRY_PRIORITIES.FEATURE_CONFIG) {
53
+ suite.retries(config.Feature)
54
+ suite.opts.retryPriority = RETRY_PRIORITIES.FEATURE_CONFIG
55
+ }
38
56
  }
39
57
 
40
58
  output.log(`Retries: ${JSON.stringify(config)}`)
@@ -46,7 +64,10 @@ export default function () {
46
64
  if (!retryConfig) return
47
65
 
48
66
  if (Number.isInteger(+retryConfig)) {
49
- if (test.retries() === -1) test.retries(retryConfig)
67
+ if (test.retries() === -1) {
68
+ test.retries(retryConfig)
69
+ test.opts.retryPriority = RETRY_PRIORITIES.SCENARIO_CONFIG
70
+ }
50
71
  return
51
72
  }
52
73
 
@@ -62,9 +83,14 @@ export default function () {
62
83
  }
63
84
 
64
85
  if (config.Scenario) {
65
- if (test.retries() === -1) test.retries(config.Scenario)
86
+ if (test.retries() === -1 || (test.opts.retryPriority || 0) <= RETRY_PRIORITIES.SCENARIO_CONFIG) {
87
+ test.retries(config.Scenario)
88
+ test.opts.retryPriority = RETRY_PRIORITIES.SCENARIO_CONFIG
89
+ }
66
90
  output.log(`Retries: ${config.Scenario}`)
67
91
  }
68
92
  }
69
93
  })
70
94
  }
95
+
96
+ export { RETRY_PRIORITIES }