codeceptjs 4.0.0-beta.6.esm-aria → 4.0.0-beta.8.esm-aria

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 (69) hide show
  1. package/README.md +46 -3
  2. package/bin/codecept.js +9 -0
  3. package/bin/test-server.js +64 -0
  4. package/docs/webapi/click.mustache +5 -1
  5. package/lib/ai.js +66 -102
  6. package/lib/codecept.js +99 -24
  7. package/lib/command/generate.js +33 -1
  8. package/lib/command/init.js +7 -3
  9. package/lib/command/run-workers.js +31 -2
  10. package/lib/command/run.js +15 -0
  11. package/lib/command/workers/runTests.js +331 -58
  12. package/lib/config.js +16 -5
  13. package/lib/container.js +15 -13
  14. package/lib/effects.js +1 -1
  15. package/lib/element/WebElement.js +327 -0
  16. package/lib/event.js +10 -1
  17. package/lib/helper/AI.js +11 -11
  18. package/lib/helper/ApiDataFactory.js +34 -6
  19. package/lib/helper/Appium.js +156 -42
  20. package/lib/helper/GraphQL.js +3 -3
  21. package/lib/helper/GraphQLDataFactory.js +4 -4
  22. package/lib/helper/JSONResponse.js +48 -40
  23. package/lib/helper/Mochawesome.js +24 -2
  24. package/lib/helper/Playwright.js +841 -153
  25. package/lib/helper/Puppeteer.js +263 -67
  26. package/lib/helper/REST.js +21 -0
  27. package/lib/helper/WebDriver.js +116 -26
  28. package/lib/helper/clientscripts/PollyWebDriverExt.js +1 -1
  29. package/lib/helper/extras/PlaywrightReactVueLocator.js +52 -0
  30. package/lib/helper/extras/PlaywrightRestartOpts.js +12 -1
  31. package/lib/helper/network/actions.js +8 -6
  32. package/lib/listener/config.js +11 -3
  33. package/lib/listener/enhancedGlobalRetry.js +110 -0
  34. package/lib/listener/globalTimeout.js +19 -4
  35. package/lib/listener/helpers.js +8 -2
  36. package/lib/listener/retryEnhancer.js +85 -0
  37. package/lib/listener/steps.js +12 -0
  38. package/lib/mocha/asyncWrapper.js +13 -3
  39. package/lib/mocha/cli.js +1 -1
  40. package/lib/mocha/factory.js +3 -0
  41. package/lib/mocha/gherkin.js +1 -1
  42. package/lib/mocha/test.js +6 -0
  43. package/lib/mocha/ui.js +13 -0
  44. package/lib/output.js +62 -18
  45. package/lib/plugin/coverage.js +16 -3
  46. package/lib/plugin/enhancedRetryFailedStep.js +99 -0
  47. package/lib/plugin/htmlReporter.js +3648 -0
  48. package/lib/plugin/retryFailedStep.js +1 -0
  49. package/lib/plugin/stepByStepReport.js +1 -1
  50. package/lib/recorder.js +28 -3
  51. package/lib/result.js +100 -23
  52. package/lib/retryCoordinator.js +207 -0
  53. package/lib/step/base.js +1 -1
  54. package/lib/step/comment.js +2 -2
  55. package/lib/step/meta.js +1 -1
  56. package/lib/template/heal.js +1 -1
  57. package/lib/template/prompts/generatePageObject.js +31 -0
  58. package/lib/template/prompts/healStep.js +13 -0
  59. package/lib/template/prompts/writeStep.js +9 -0
  60. package/lib/test-server.js +334 -0
  61. package/lib/utils/mask_data.js +47 -0
  62. package/lib/utils.js +87 -6
  63. package/lib/workerStorage.js +2 -1
  64. package/lib/workers.js +179 -23
  65. package/package.json +60 -52
  66. package/translations/utils.js +2 -10
  67. package/typings/index.d.ts +19 -7
  68. package/typings/promiseBasedTypes.d.ts +5525 -3759
  69. package/typings/types.d.ts +5791 -3781
@@ -1,15 +1,16 @@
1
- let webdriverio
2
-
3
- const fs = require('fs')
4
- const axios = require('axios').default
5
- const { v4: uuidv4 } = require('uuid')
6
-
7
- const Webdriver = require('./WebDriver')
8
- const AssertionFailedError = require('../assert/error')
9
- const { truth } = require('../assert/truth')
10
- const recorder = require('../recorder')
11
- const Locator = require('../locator')
12
- const ConnectionRefused = require('./errors/ConnectionRefused')
1
+ import * as webdriverio from 'webdriverio'
2
+ import fs from 'fs'
3
+ import axios from 'axios'
4
+ import { v4 as uuidv4 } from 'uuid'
5
+
6
+ import Webdriver from './WebDriver.js'
7
+ import AssertionFailedError from '../assert/error.js'
8
+ import { truth } from '../assert/truth.js'
9
+ import recorder from '../recorder.js'
10
+ import Locator from '../locator.js'
11
+ import ConnectionRefused from './errors/ConnectionRefused.js'
12
+ import ElementNotFound from './errors/ElementNotFound.js'
13
+ import { dontSeeElementError } from './errors/ElementAssertion.js'
13
14
 
14
15
  const mobileRoot = '//*'
15
16
  const webRoot = 'body'
@@ -181,7 +182,6 @@ class Appium extends Webdriver {
181
182
  this.appiumV2 = config.appiumV2 || true
182
183
  this.axios = axios.create()
183
184
 
184
- webdriverio = require('webdriverio')
185
185
  if (!config.appiumV2) {
186
186
  console.log('The Appium core team does not maintain Appium 1.x anymore since the 1st of January 2022. Appium 2.x is used by default.')
187
187
  console.log('More info: https://bit.ly/appium-v2-migration')
@@ -261,11 +261,13 @@ class Appium extends Webdriver {
261
261
 
262
262
  this.platform = null
263
263
  if (config.capabilities[`${vendorPrefix.appium}:platformName`]) {
264
- this.platform = config.capabilities[`${vendorPrefix.appium}:platformName`].toLowerCase()
264
+ config.capabilities[`${vendorPrefix.appium}:platformName`] = config.capabilities[`${vendorPrefix.appium}:platformName`].toLowerCase()
265
+ this.platform = config.capabilities[`${vendorPrefix.appium}:platformName`]
265
266
  }
266
267
 
267
268
  if (config.capabilities.platformName) {
268
- this.platform = config.capabilities.platformName.toLowerCase()
269
+ config.capabilities.platformName = config.capabilities.platformName.toLowerCase()
270
+ this.platform = config.capabilities.platformName
269
271
  }
270
272
 
271
273
  return config
@@ -275,7 +277,7 @@ class Appium extends Webdriver {
275
277
  const _convertedCaps = {}
276
278
  for (const [key, value] of Object.entries(capabilities)) {
277
279
  if (!key.startsWith(vendorPrefix.appium)) {
278
- if (key !== 'platformName' && key !== 'bstack:options') {
280
+ if (key !== 'platformName' && key !== 'bstack:options' && key !== 'sauce:options') {
279
281
  _convertedCaps[`${vendorPrefix.appium}:${key}`] = value
280
282
  } else {
281
283
  _convertedCaps[`${key}`] = value
@@ -389,6 +391,29 @@ class Appium extends Webdriver {
389
391
  return `${protocol}://${hostname}:${port}${normalizedPath}/session/${this.browser.sessionId}`
390
392
  }
391
393
 
394
+ /**
395
+ * Helper method to safely call isDisplayed() on mobile elements.
396
+ * Handles the case where webdriverio tries to use execute/sync which isn't supported in Appium.
397
+ * @private
398
+ */
399
+ async _isDisplayedSafe(element) {
400
+ if (this.isWeb) {
401
+ // For web contexts, use the normal isDisplayed
402
+ return element.isDisplayed()
403
+ }
404
+
405
+ try {
406
+ return await element.isDisplayed()
407
+ } catch (err) {
408
+ // If isDisplayed fails due to execute/sync not being supported in native mobile contexts,
409
+ // fall back to assuming the element is displayed (since we found it)
410
+ if (err.message && err.message.includes('Method is not implemented')) {
411
+ return true
412
+ }
413
+ throw err
414
+ }
415
+ }
416
+
392
417
  /**
393
418
  * Execute code only on iOS
394
419
  *
@@ -617,6 +642,7 @@ class Appium extends Webdriver {
617
642
  */
618
643
  async resetApp() {
619
644
  onlyForApps.call(this)
645
+ this.isWeb = false // Reset to native context after app reset
620
646
  return this.axios({
621
647
  method: 'post',
622
648
  url: `${this._buildAppiumEndpoint()}/appium/app/reset`,
@@ -1132,7 +1158,7 @@ class Appium extends Webdriver {
1132
1158
  ],
1133
1159
  },
1134
1160
  ])
1135
- await this.browser.pause(1000)
1161
+ await this.browser.pause(2000)
1136
1162
  }
1137
1163
 
1138
1164
  /**
@@ -1294,28 +1320,26 @@ class Appium extends Webdriver {
1294
1320
  let currentSource
1295
1321
  return browser
1296
1322
  .waitUntil(
1297
- () => {
1323
+ async () => {
1298
1324
  if (err) {
1299
1325
  return new Error(`Scroll to the end and element ${searchableLocator} was not found`)
1300
1326
  }
1301
- return browser
1302
- .$$(parseLocator.call(this, searchableLocator))
1303
- .then(els => els.length && els[0].isDisplayed())
1304
- .then(res => {
1305
- if (res) {
1306
- return true
1307
- }
1308
- return this[direction](scrollLocator, offset, speed)
1309
- .getSource()
1310
- .then(source => {
1311
- if (source === currentSource) {
1312
- err = true
1313
- } else {
1314
- currentSource = source
1315
- return false
1316
- }
1317
- })
1318
- })
1327
+ const els = await browser.$$(parseLocator.call(this, searchableLocator))
1328
+ if (els.length) {
1329
+ const displayed = await this._isDisplayedSafe(els[0])
1330
+ if (displayed) {
1331
+ return true
1332
+ }
1333
+ }
1334
+
1335
+ await this[direction](scrollLocator, offset, speed)
1336
+ const source = await this.browser.getPageSource()
1337
+ if (source === currentSource) {
1338
+ err = true
1339
+ } else {
1340
+ currentSource = source
1341
+ return false
1342
+ }
1319
1343
  },
1320
1344
  timeout * 1000,
1321
1345
  errorMsg,
@@ -1521,7 +1545,26 @@ class Appium extends Webdriver {
1521
1545
  */
1522
1546
  async dontSeeElement(locator) {
1523
1547
  if (this.isWeb) return super.dontSeeElement(locator)
1524
- return super.dontSeeElement(parseLocator.call(this, locator))
1548
+
1549
+ // For mobile native apps, use safe isDisplayed wrapper
1550
+ const parsedLocator = parseLocator.call(this, locator)
1551
+ const res = await this._locate(parsedLocator, false)
1552
+
1553
+ if (!res || res.length === 0) {
1554
+ return truth(`elements of ${Locator.build(parsedLocator)}`, 'to be seen').negate(false)
1555
+ }
1556
+
1557
+ const selected = []
1558
+ for (const el of res) {
1559
+ const displayed = await this._isDisplayedSafe(el)
1560
+ if (displayed) selected.push(true)
1561
+ }
1562
+
1563
+ try {
1564
+ return truth(`elements of ${Locator.build(parsedLocator)}`, 'to be seen').negate(selected)
1565
+ } catch (err) {
1566
+ throw err
1567
+ }
1525
1568
  }
1526
1569
 
1527
1570
  /**
@@ -1575,7 +1618,18 @@ class Appium extends Webdriver {
1575
1618
  */
1576
1619
  async grabNumberOfVisibleElements(locator) {
1577
1620
  if (this.isWeb) return super.grabNumberOfVisibleElements(locator)
1578
- return super.grabNumberOfVisibleElements(parseLocator.call(this, locator))
1621
+
1622
+ // For mobile native apps, use safe isDisplayed wrapper
1623
+ const parsedLocator = parseLocator.call(this, locator)
1624
+ const res = await this._locate(parsedLocator)
1625
+
1626
+ const selected = []
1627
+ for (const el of res) {
1628
+ const displayed = await this._isDisplayedSafe(el)
1629
+ if (displayed) selected.push(true)
1630
+ }
1631
+
1632
+ return selected.length
1579
1633
  }
1580
1634
 
1581
1635
  /**
@@ -1654,7 +1708,26 @@ class Appium extends Webdriver {
1654
1708
  */
1655
1709
  async seeElement(locator) {
1656
1710
  if (this.isWeb) return super.seeElement(locator)
1657
- return super.seeElement(parseLocator.call(this, locator))
1711
+
1712
+ // For mobile native apps, use safe isDisplayed wrapper
1713
+ const parsedLocator = parseLocator.call(this, locator)
1714
+ const res = await this._locate(parsedLocator, true)
1715
+
1716
+ if (!res || res.length === 0) {
1717
+ throw new ElementNotFound(parsedLocator)
1718
+ }
1719
+
1720
+ const selected = []
1721
+ for (const el of res) {
1722
+ const displayed = await this._isDisplayedSafe(el)
1723
+ if (displayed) selected.push(true)
1724
+ }
1725
+
1726
+ try {
1727
+ return truth(`elements of ${Locator.build(parsedLocator)}`, 'to be seen').assert(selected)
1728
+ } catch (e) {
1729
+ dontSeeElementError(parsedLocator)
1730
+ }
1658
1731
  }
1659
1732
 
1660
1733
  /**
@@ -1701,7 +1774,29 @@ class Appium extends Webdriver {
1701
1774
  */
1702
1775
  async waitForVisible(locator, sec = null) {
1703
1776
  if (this.isWeb) return super.waitForVisible(locator, sec)
1704
- return super.waitForVisible(parseLocator.call(this, locator), sec)
1777
+
1778
+ // For mobile native apps, use safe isDisplayed wrapper
1779
+ const parsedLocator = parseLocator.call(this, locator)
1780
+ const aSec = sec || this.options.waitForTimeoutInSeconds
1781
+
1782
+ return this.browser.waitUntil(
1783
+ async () => {
1784
+ const res = await this._res(parsedLocator)
1785
+ if (!res || res.length === 0) return false
1786
+
1787
+ const selected = []
1788
+ for (const el of res) {
1789
+ const displayed = await this._isDisplayedSafe(el)
1790
+ if (displayed) selected.push(true)
1791
+ }
1792
+
1793
+ return selected.length > 0
1794
+ },
1795
+ {
1796
+ timeout: aSec * 1000,
1797
+ timeoutMsg: `element (${Locator.build(parsedLocator)}) still not visible after ${aSec} sec`,
1798
+ },
1799
+ )
1705
1800
  }
1706
1801
 
1707
1802
  /**
@@ -1710,7 +1805,26 @@ class Appium extends Webdriver {
1710
1805
  */
1711
1806
  async waitForInvisible(locator, sec = null) {
1712
1807
  if (this.isWeb) return super.waitForInvisible(locator, sec)
1713
- return super.waitForInvisible(parseLocator.call(this, locator), sec)
1808
+
1809
+ // For mobile native apps, use safe isDisplayed wrapper
1810
+ const parsedLocator = parseLocator.call(this, locator)
1811
+ const aSec = sec || this.options.waitForTimeoutInSeconds
1812
+
1813
+ return this.browser.waitUntil(
1814
+ async () => {
1815
+ const res = await this._res(parsedLocator)
1816
+ if (!res || res.length === 0) return true
1817
+
1818
+ const selected = []
1819
+ for (const el of res) {
1820
+ const displayed = await this._isDisplayedSafe(el)
1821
+ if (displayed) selected.push(true)
1822
+ }
1823
+
1824
+ return selected.length === 0
1825
+ },
1826
+ { timeout: aSec * 1000, timeoutMsg: `element (${Locator.build(parsedLocator)}) still visible after ${aSec} sec` },
1827
+ )
1714
1828
  }
1715
1829
 
1716
1830
  /**
@@ -1786,4 +1900,4 @@ function onlyForApps(expectedPlatform) {
1786
1900
  }
1787
1901
  }
1788
1902
 
1789
- module.exports = Appium
1903
+ export default Appium
@@ -1,5 +1,5 @@
1
- const axios = require('axios').default
2
- const HelperModule = require('@codeceptjs/helper')
1
+ import axios from 'axios'
2
+ import HelperModule from '@codeceptjs/helper'
3
3
 
4
4
  /**
5
5
  * GraphQL helper allows to send additional requests to a GraphQl endpoint during acceptance tests.
@@ -227,4 +227,4 @@ class GraphQL extends Helper {
227
227
  this.haveRequestHeaders({ Authorization: `Bearer ${accessToken}` })
228
228
  }
229
229
  }
230
- module.exports = GraphQL
230
+ export default GraphQL
@@ -1,7 +1,7 @@
1
- const path = require('path')
1
+ import path from 'path'
2
2
 
3
- const HelperModule = require('@codeceptjs/helper')
4
- const GraphQL = require('./GraphQL')
3
+ import HelperModule from '@codeceptjs/helper'
4
+ import GraphQL from './GraphQL.js'
5
5
 
6
6
  /**
7
7
  * Helper for managing remote data using GraphQL queries.
@@ -305,4 +305,4 @@ class GraphQLDataFactory extends Helper {
305
305
  }
306
306
  }
307
307
 
308
- module.exports = GraphQLDataFactory
308
+ export default GraphQLDataFactory
@@ -1,6 +1,6 @@
1
1
  import Helper from '@codeceptjs/helper'
2
2
  import assert from 'assert'
3
- import joi from 'joi'
3
+ import { z } from 'zod'
4
4
 
5
5
  /**
6
6
  * This helper allows performing assertions on JSON responses paired with following helpers:
@@ -69,17 +69,13 @@ class JSONResponse extends Helper {
69
69
 
70
70
  _beforeSuite() {
71
71
  this.response = null
72
- try {
73
- if (!this.helpers[this.options.requestHelper]) {
74
- throw new Error(`Error setting JSONResponse, helper ${this.options.requestHelper} is not enabled in config, helpers: ${Object.keys(this.helpers)}`)
75
- }
76
- // connect to REST helper
77
- this.helpers[this.options.requestHelper].config.onResponse = response => {
78
- this.response = response
79
- }
80
- } catch (e) {
81
- // Temporary workaround for ESM transition - helpers access issue
82
- console.log('[JSONResponse] Warning: Could not connect to REST helper during ESM transition:', e.message)
72
+ if (!this.helpers[this.options.requestHelper]) {
73
+ throw new Error(`Error setting JSONResponse, helper ${this.options.requestHelper} is not enabled in config, helpers: ${Object.keys(this.helpers)}`)
74
+ }
75
+ const origOnResponse = this.helpers[this.options.requestHelper].config.onResponse
76
+ this.helpers[this.options.requestHelper].config.onResponse = response => {
77
+ this.response = response
78
+ if (typeof origOnResponse === 'function') origOnResponse(response)
83
79
  }
84
80
  }
85
81
 
@@ -87,16 +83,7 @@ class JSONResponse extends Helper {
87
83
  this.response = null
88
84
  }
89
85
 
90
- static _checkRequirements() {
91
- try {
92
- // In ESM, joi is already imported at the top
93
- // The import will fail at module load time if joi is missing
94
- return null
95
- } catch (e) {
96
- return ['joi']
97
- }
98
- }
99
-
86
+
100
87
  /**
101
88
  * Checks that response code is equal to the provided one
102
89
  *
@@ -308,28 +295,28 @@ class JSONResponse extends Helper {
308
295
  }
309
296
 
310
297
  /**
311
- * Validates JSON structure of response using [joi library](https://joi.dev).
312
- * See [joi API](https://joi.dev/api/) for complete reference on usage.
298
+ * Validates JSON structure of response using [Zod library](https://zod.dev).
299
+ * See [Zod API](https://zod.dev/) for complete reference on usage.
313
300
  *
314
- * Use pre-initialized joi instance by passing function callback:
301
+ * Use pre-initialized Zod instance by passing function callback:
315
302
  *
316
303
  * ```js
317
304
  * // response.data is { name: 'jon', id: 1 }
318
305
  *
319
- * I.seeResponseMatchesJsonSchema(joi => {
320
- * return joi.object({
321
- * name: joi.string(),
322
- * id: joi.number()
306
+ * I.seeResponseMatchesJsonSchema(z => {
307
+ * return z.object({
308
+ * name: z.string(),
309
+ * id: z.number()
323
310
  * })
324
311
  * });
325
312
  *
326
313
  * // or pass a valid schema
327
- * const joi = require('joi');
314
+ * import { z } from 'zod';
328
315
  *
329
- * I.seeResponseMatchesJsonSchema(joi.object({
330
- * name: joi.string(),
331
- * id: joi.number();
332
- * });
316
+ * I.seeResponseMatchesJsonSchema(z.object({
317
+ * name: z.string(),
318
+ * id: z.number()
319
+ * }));
333
320
  * ```
334
321
  *
335
322
  * @param {any} fnOrSchema
@@ -338,14 +325,17 @@ class JSONResponse extends Helper {
338
325
  this._checkResponseReady()
339
326
  let schema = fnOrSchema
340
327
  if (typeof fnOrSchema === 'function') {
341
- schema = fnOrSchema(joi)
328
+ schema = fnOrSchema(z)
342
329
  const body = fnOrSchema.toString()
343
330
  fnOrSchema.toString = () => `${body.split('\n')[1]}...`
344
331
  }
345
- if (!schema) throw new Error('Empty Joi schema provided, see https://joi.dev/ for details')
346
- if (!joi.isSchema(schema)) throw new Error('Invalid Joi schema provided, see https://joi.dev/ for details')
347
- schema.toString = () => schema.describe()
348
- joi.assert(this.response.data, schema)
332
+ if (!schema) throw new Error('Empty Zod schema provided, see https://zod.dev/ for details')
333
+ if (!(schema instanceof z.ZodType)) throw new Error('Invalid Zod schema provided, see https://zod.dev/ for details')
334
+ schema.toString = () => schema._def.description || JSON.stringify(schema._def)
335
+ const result = schema.parse(this.response.data)
336
+ if (!result) {
337
+ throw new Error('Schema validation failed')
338
+ }
349
339
  }
350
340
 
351
341
  _checkResponseReady() {
@@ -356,7 +346,25 @@ class JSONResponse extends Helper {
356
346
  for (const key in expected) {
357
347
  assert(key in actual, `Key "${key}" not found in ${JSON.stringify(actual)}`)
358
348
  if (typeof expected[key] === 'object' && expected[key] !== null) {
359
- this._assertContains(actual[key], expected[key])
349
+ if (Array.isArray(expected[key])) {
350
+ // Handle array comparison: each expected element should have a match in actual array
351
+ assert(Array.isArray(actual[key]), `Expected array for key "${key}", but got ${typeof actual[key]}`)
352
+ for (const expectedItem of expected[key]) {
353
+ let found = false
354
+ for (const actualItem of actual[key]) {
355
+ try {
356
+ this._assertContains(actualItem, expectedItem)
357
+ found = true
358
+ break
359
+ } catch (err) {
360
+ continue
361
+ }
362
+ }
363
+ assert(found, `No matching element found in array for ${JSON.stringify(expectedItem)}`)
364
+ }
365
+ } else {
366
+ this._assertContains(actual[key], expected[key])
367
+ }
360
368
  } else {
361
369
  assert.deepStrictEqual(actual[key], expected[key], `Values for key "${key}" don't match`)
362
370
  }
@@ -40,7 +40,20 @@ class Mochawesome extends Helper {
40
40
  }
41
41
 
42
42
  _test(test) {
43
- currentTest = { test }
43
+ // If this is a retried test, we want to add context to the retried test
44
+ // but also potentially preserve context from the original test
45
+ const originalTest = test.retriedTest && test.retriedTest()
46
+ if (originalTest) {
47
+ // This is a retried test - use the retried test for context
48
+ currentTest = { test }
49
+
50
+ // Optionally copy context from original test if it exists
51
+ // Note: mochawesome context is stored in test.ctx, but we need to be careful
52
+ // not to break the mocha context structure
53
+ } else {
54
+ // Normal test (not a retry)
55
+ currentTest = { test }
56
+ }
44
57
  }
45
58
 
46
59
  _failed(test) {
@@ -67,7 +80,16 @@ class Mochawesome extends Helper {
67
80
 
68
81
  addMochawesomeContext(context) {
69
82
  if (currentTest === '') currentTest = { test: currentSuite.ctx.test }
70
- return this._addContext(currentTest, context)
83
+
84
+ // For retried tests, make sure we're adding context to the current (retried) test
85
+ // not the original test
86
+ let targetTest = currentTest
87
+ if (currentTest.test && currentTest.test.retriedTest && currentTest.test.retriedTest()) {
88
+ // This test has been retried, make sure we're using the current test for context
89
+ targetTest = { test: currentTest.test }
90
+ }
91
+
92
+ return this._addContext(targetTest, context)
71
93
  }
72
94
  }
73
95