Repository: Codeception/CodeceptJS
Pull request #2231 (2020-02-25 07:11:04)

Improve Scenario DX
## Motivation/Description of the PR
- Description of this PR, which problem it solves
- Resolves #1972, #2150, #2330 
Applicable helpers:

- [ ] WebDriver
- [ ] Puppeteer
- [ ] Nightmare
- [ ] REST
- [ ] FileHelper
- [ ] Appium
- [ ] Protractor
- [ ] TestCafe
- [ ] Playwright

Applicable plugins:

- [ ] allure
- [ ] autoDelay
- [ ] autoLogin
- [ ] customLocator
- [ ] pauseOnFail
- [ ] puppeteerCoverage
- [ ] retryFailedStep
- [ ] screenshotOnFail
- [ ] selenoid
- [ ] stepByStepReport
- [ ] wdio

## Type of change

- [x] :fire: Breaking changes
- [x] :rocket: New functionality
- [ ] :bug: Bug fix
- [ ] :clipboard: Documentation changes/updates
- [ ] :hotsprings: Hot fix
- [ ] :hammer: Markdown files fix - not related to source code
- [ ] :nail_care: Polish code

## Checklist:

- [x] Tests have been added
- [ ] Documentation has been added (Run `npm run docs`)
- [ ] Lint checking (Run `npm run lint`)
- [ ] Local tests are passed (Run `npm test`)



Patch:
diff --git a/.circleci/config.yml b/.circleci/config.yml
index b27d52d2..2eb6dc65 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -7,10 +7,6 @@ defaults: &defaults
   steps:
     - checkout
     - run: .circleci/build.sh
-    - run:
-        command: docker-compose run --rm test-unit
-        working_directory: test
-        when: always
     - run:
         command: docker-compose run --rm test-rest
         working_directory: test
diff --git a/README.md b/README.md
index b696d269..b44ccb62 100644
--- a/README.md
+++ b/README.md
@@ -13,7 +13,7 @@ A simple test that verifies the "Welcome" text is present on a main page of a si
 ```js
 Feature('CodeceptJS demo');
 
-Scenario('check Welcome page on site', (I) => {
+Scenario('check Welcome page on site', ({ I }) => {
   I.amOnPage('/');
   I.see('Welcome');
 });
@@ -100,7 +100,7 @@ Let's see how we can handle basic form testing:
 ```js
 Feature('CodeceptJS Demonstration');
 
-Scenario('test some forms', (I) => {
+Scenario('test some forms', ({ I }) => {
   I.amOnPage('http://simple-form-bootstrap.plataformatec.com.br/documentation');
   I.fillField('Email', 'hello@world.com');
   I.fillField('Password', '123456');
@@ -186,7 +186,7 @@ const assert = require('assert');
 
 Feature('CodeceptJS Demonstration');
 
-Scenario('test page title', async (I) => {
+Scenario('test page title', async ({ I }) => {
   I.amOnPage('http://simple-form-bootstrap.plataformatec.com.br/documentation');
   const title = await I.grabTitle();
   assert.equal(title, 'Example application with SimpleForm and Twitter Bootstrap');
@@ -202,17 +202,17 @@ Common preparation steps like opening a web page, logging in a user, can be plac
 ```js
 Feature('CodeceptJS Demonstration');
 
-Before((I) => { // or Background
+Before(({ I }) => { // or Background
   I.amOnPage('http://simple-form-bootstrap.plataformatec.com.br/documentation');
 });
 
-Scenario('test some forms', (I) => {
+Scenario('test some forms', ({ I }) => {
   I.click('Create User');
   I.see('User is valid');
   I.dontSeeInCurrentUrl('/documentation');
 });
 
-Scenario('test title', (I) => {
+Scenario('test title', ({ I }) => {
   I.seeInTitle('Example application');
 });
 ```
@@ -252,7 +252,7 @@ You can easily inject it to test by providing its name in test arguments:
 ```js
 Feature('CodeceptJS Demonstration');
 
-Before((I) => { // or Background
+Before(({ I }) => { // or Background
   I.amOnPage('http://simple-form-bootstrap.plataformatec.com.br/documentation');
 });
 
diff --git a/docs/webapi/waitForVisible.mustache b/docs/webapi/waitForVisible.mustache
index 1e97e17f..b4e05e46 100644
--- a/docs/webapi/waitForVisible.mustache
+++ b/docs/webapi/waitForVisible.mustache
@@ -6,4 +6,4 @@ I.waitForVisible('#popup');
 ```
 
 @param {CodeceptJS.LocatorOrString} locator element located by CSS|XPath|strict locator.
-@param {number} [sec=1] (optional, `1` by default) time in seconds to wait
\ No newline at end of file
+@param {number} [sec=1] (optional, `1` by default) time in seconds to wait
diff --git a/lib/command/definitions.js b/lib/command/definitions.js
index 04e77ae3..b219009e 100644
--- a/lib/command/definitions.js
+++ b/lib/command/definitions.js
@@ -13,7 +13,6 @@ ${importPaths.join('\n')}
 
 declare namespace CodeceptJS {
   interface SupportObject ${convertMapToType(supportObject)}
-  interface CallbackOrder { ${convertMapToIndexedInterface(supportObject)} }
   ${helperNames.length > 0 ? `interface Methods extends ${helperNames.join(', ')} {}` : ''}
   interface I extends ${hasCustomStepsFile ? 'ReturnType<steps_file>' : 'WithTranslation<Methods>'} {}
   namespace Translation {
@@ -123,13 +122,6 @@ function getImportString(testsPath, targetFolderPath, pathsToType, pathsToValue)
   return importStrings;
 }
 
-/**
- * @param {Map} map
- */
-function convertMapToIndexedInterface(map) {
-  return [...map.values()].map((value, i) => `[${i}]: ${value}`).join('; ');
-}
-
 /**
  * @param {Map} map
  */
diff --git a/lib/command/run-workers.js b/lib/command/run-workers.js
index 459e2341..952383b1 100644
--- a/lib/command/run-workers.js
+++ b/lib/command/run-workers.js
@@ -42,7 +42,7 @@ module.exports = function (workerCount, options) {
     output.test.passed(successTest);
   });
 
-  workers.on(event.all.result, (_status, _completedTest, workerStats) => {
+  workers.on(event.all.result, () => {
     workers.printResults();
   });
 };
diff --git a/lib/helper/Playwright.js b/lib/helper/Playwright.js
index d8806e8a..ee8454eb 100644
--- a/lib/helper/Playwright.js
+++ b/lib/helper/Playwright.js
@@ -10,7 +10,6 @@ const { urlEquals } = require('../assert/equal');
 const { equals } = require('../assert/equal');
 const { empty } = require('../assert/empty');
 const { truth } = require('../assert/truth');
-const isElementClickable = require('./scripts/isElementClickable');
 const {
   xpathLocator,
   ucfirst,
@@ -1751,7 +1750,7 @@ class Playwright extends Helper {
   /**
    * {{> waitForClickable }}
    */
-  async waitForClickable(locator, waitTimeout) {
+  async waitForClickable() {
     console.log('I.waitForClickable is DEPRECATED: This is no longer needed, Playwright automatically waits for element to be clikable');
     console.log('Remove usage of this function');
   }
diff --git a/lib/helper/Puppeteer.js b/lib/helper/Puppeteer.js
index 961c759a..e71b4dc5 100644
--- a/lib/helper/Puppeteer.js
+++ b/lib/helper/Puppeteer.js
@@ -304,12 +304,12 @@ class Puppeteer extends Helper {
 
   _session() {
     return {
-      start: async (name = '', config) => {
+      start: async (name = '') => {
         this.debugSection('Incognito Tab', 'opened');
         this.activeSessionName = name;
 
         const bc = await this.browser.createIncognitoBrowserContext();
-        const page = await bc.newPage();
+        await bc.newPage();
 
         // Create a new page inside context.
         return bc;
diff --git a/lib/helper/extras/PlaywrightPropEngine.js b/lib/helper/extras/PlaywrightPropEngine.js
index 8da86260..c74f17f1 100644
--- a/lib/helper/extras/PlaywrightPropEngine.js
+++ b/lib/helper/extras/PlaywrightPropEngine.js
@@ -2,6 +2,7 @@ module.exports.createValueEngine = () => {
   return {
     // Creates a selector that matches given target when queried at the root.
     // Can return undefined if unable to create one.
+    // eslint-disable-next-line no-unused-vars
     create(root, target) {
       return null;
     },
@@ -28,6 +29,7 @@ module.exports.createDisabledEngine = () => {
   return {
     // Creates a selector that matches given target when queried at the root.
     // Can return undefined if unable to create one.
+    // eslint-disable-next-line no-unused-vars
     create(root, target) {
       return null;
     },
diff --git a/lib/parser.js b/lib/parser.js
index e9b93ae7..596cc70c 100644
--- a/lib/parser.js
+++ b/lib/parser.js
@@ -1,15 +1,20 @@
 function _interopDefault(ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex.default : ex; }
-const parser = _interopDefault(require('parse-function'))({ ecmaVersion: 2017 });
+const acorn = require('acorn');
+const parser = _interopDefault(require('parse-function'))({ parse: acorn.parse, ecmaVersion: 11, plugins: ['objectRestSpread'] });
+
+
+parser.use(destructuredArgs);
 
 module.exports.getParamsToString = function (fn) {
-  return getParams(fn).join(', ');
+  const newFn = fn.toString().replace(/^async/, 'async function');
+  return getParams(newFn).join(', ');
 };
 
 function getParams(fn) {
   if (fn.isSinonProxy) return [];
-  const newFn = fn.toString().replace(/^async/, 'async function');
   try {
-    const reflected = parser.parse(newFn);
+    const reflected = parser.parse(fn);
+    if (reflected.destructuredArgs.length > 0) reflected.args = [...reflected.destructuredArgs];
     const params = reflected.args.map((p) => {
       const def = reflected.defaults[p];
       if (def) {
@@ -19,7 +24,48 @@ function getParams(fn) {
     });
     return params;
   } catch (err) {
-    console.log(`Error in ${newFn.toString()}`);
+    console.log(`Error in ${fn.toString()}`);
     console.error(err);
   }
 }
+
+module.exports.getParams = getParams;
+
+function destructuredArgs() {
+  return (node, result) => {
+    result.destructuredArgs = result.destructuredArgs || [];
+
+    if (node.type === 'ObjectExpression' && node.properties.length > 0) {
+      node.properties.forEach((prop) => {
+        if (prop.value && prop.value.params.length > 0) {
+          result.destructuredArgs = parseDestructuredArgs(prop.value);
+        }
+      });
+
+      return result;
+    }
+
+    if (!Array.isArray(node.params)) return result;
+    result.destructuredArgs = parseDestructuredArgs(node);
+
+    return result;
+  };
+}
+
+function parseDestructuredArgs(node) {
+  const destructuredArgs = [];
+  node.params.forEach((param) => {
+    if (
+      param.type === 'ObjectPattern'
+        && param.properties
+        && param.properties.length > 0
+    ) {
+      param.properties.forEach((prop) => {
+        const { name } = prop.value;
+        destructuredArgs.push(name);
+      });
+    }
+  });
+
+  return destructuredArgs;
+}
diff --git a/lib/plugin/commentStep.js b/lib/plugin/commentStep.js
index 0077e91d..6a9183f3 100644
--- a/lib/plugin/commentStep.js
+++ b/lib/plugin/commentStep.js
@@ -100,7 +100,7 @@ const defaultGlobalName = '__';
  * ```
  */
 module.exports = function (config) {
-  event.dispatcher.on(event.test.started, (test) => {
+  event.dispatcher.on(event.test.started, () => {
     currentCommentStep = null;
   });
 
diff --git a/lib/scenario.js b/lib/scenario.js
index 8007ac49..bf66cf68 100644
--- a/lib/scenario.js
+++ b/lib/scenario.js
@@ -1,7 +1,8 @@
 const event = require('./event');
 const recorder = require('./recorder');
 const assertThrown = require('./assert/throws');
-const { getParamNames, isAsyncFunction } = require('./utils');
+const { isAsyncFunction } = require('./utils');
+const parser = require('./parser');
 
 const injectHook = function (inject, suite) {
   try {
@@ -53,7 +54,7 @@ module.exports.test = (test) => {
 
     if (isAsyncFunction(testFn)) {
       event.emit(event.test.started, test);
-      testFn.apply(test, getInjectedArguments(testFn, test)).then(() => {
+      testFn.call(test, getInjectedArguments(testFn, test)).then(() => {
         recorder.add('fire test.passed', () => {
           event.emit(event.test.passed, test);
           event.emit(event.test.finished, test);
@@ -75,7 +76,7 @@ module.exports.test = (test) => {
     }
     try {
       event.emit(event.test.started, test);
-      testFn.apply(test, getInjectedArguments(testFn, test));
+      testFn.call(test, getInjectedArguments(testFn, test));
     } catch (err) {
       recorder.throw(err);
     } finally {
@@ -121,7 +122,7 @@ module.exports.injected = function (fn, suite, hookName) {
     this.test.body = fn.toString();
 
     if (isAsyncFunction(fn)) {
-      fn.apply(this, getInjectedArguments(fn)).then(() => {
+      fn.call(this, getInjectedArguments(fn)).then(() => {
         recorder.add('fire hook.passed', () => event.emit(event.hook.passed, suite));
         recorder.add(`finish ${hookName} hook`, () => done());
         recorder.catch();
@@ -137,7 +138,7 @@ module.exports.injected = function (fn, suite, hookName) {
     }
 
     try {
-      fn.apply(this, getInjectedArguments(fn));
+      fn.call(this, getInjectedArguments(fn));
       recorder.add('fire hook.passed', () => event.emit(event.hook.passed, suite));
       recorder.add(`finish ${hookName} hook`, () => done());
       recorder.catch();
@@ -185,21 +186,23 @@ module.exports.suiteTeardown = function (suite) {
 
 const getInjectedArguments = (fn, test) => {
   const container = require('./container');
-  const testArguments = [];
-  const params = getParamNames(fn) || [];
+  const testArgs = {};
+  const params = parser.getParams(fn) || [];
   const objects = container.support();
-  for (const key in params) {
-    const obj = params[key];
-    if (test && test.inject && test.inject[obj]) {
-      testArguments.push(test.inject[obj]);
+  for (const key of params) {
+    testArgs[key] = {};
+    if (test && test.inject && test.inject[key]) {
+      // @FIX: need fix got inject
+      testArgs[key] = test.inject[key];
       continue;
     }
-    if (!objects[obj]) {
-      throw new Error(`Object of type ${obj} is not defined in container`);
+    if (!objects[key]) {
+      throw new Error(`Object of type ${key} is not defined in container`);
     }
-    testArguments.push(container.support(obj));
+    testArgs[key] = container.support(key);
   }
-  return testArguments;
+
+  return testArgs;
 };
 
 module.exports.getInjectedArguments = getInjectedArguments;
diff --git a/lib/workers.js b/lib/workers.js
index 8604cdc7..f9605027 100644
--- a/lib/workers.js
+++ b/lib/workers.js
@@ -280,7 +280,7 @@ class Workers extends EventEmitter {
   run() {
     this.stats.start = new Date();
     recorder.startUnlessRunning();
-    recorder.add('bootstrapAll hook', () => new Promise((res, rej) => {
+    recorder.add('bootstrapAll hook', () => new Promise((res) => {
       runHook(this.codecept.config.bootstrapAll, () => {
         res(true);
       }, 'bootstrapAll');
diff --git a/package.json b/package.json
index 0aceadb4..96c21c56 100644
--- a/package.json
+++ b/package.json
@@ -75,7 +75,7 @@
     "mocha": "^6.2.2",
     "mocha-junit-reporter": "^1.23.1",
     "ms": "^2.1.2",
-    "parse-function": "^5.5.0",
+    "parse-function": "^5.6.4",
     "promise-retry": "^1.1.1",
     "requireg": "^0.2.2",
     "resq": "^1.7.0",
@@ -88,10 +88,12 @@
     "@pollyjs/adapter-puppeteer": "^2.6.3",
     "@pollyjs/core": "^2.6.3",
     "@types/inquirer": "^0.0.35",
+    "@types/jscodeshift": "^0.6.3",
     "@types/node": "^8.10.54",
     "@wdio/sauce-service": "^5.16.10",
     "@wdio/selenium-standalone-service": "^5.16.10",
     "@wdio/utils": "^5.16.15",
+    "acorn": "^7.1.0",
     "apollo-server-express": "^2.9.16",
     "chai": "^3.4.1",
     "chai-as-promised": "^5.2.0",
@@ -106,6 +108,7 @@
     "faker": "^4.1.0",
     "graphql": "^14.5.8",
     "husky": "^1.2.1",
+    "jscodeshift": "^0.7.0",
     "jsdoc": "^3.6.2",
     "jsdoc-typeof-plugin": "^1.0.0",
     "json-server": "^0.10.1",
@@ -137,7 +140,7 @@
   "husky": {
     "hooks": {
       "pre-commit": "npm run lint",
-      "pre-push": "npm run lint && npm run test:unit"
+      "pre-push": "npm run test:unit"
     }
   }
 }
diff --git a/typings/index.d.ts b/typings/index.d.ts
index 83040ac8..7e60b025 100644
--- a/typings/index.d.ts
+++ b/typings/index.d.ts
@@ -13,7 +13,6 @@ declare namespace CodeceptJS {
   interface I {}
   interface IHook {}
   interface IScenario {}
-  interface CallbackOrder extends Array<any> {}
   interface SupportObject {
     I: CodeceptJS.I;
   }
@@ -44,28 +43,17 @@ declare namespace CodeceptJS {
 
   type LocatorOrString = string | ILocator | Locator;
 
-  interface HookCallback<U extends any[]> { (...args: U): void; }
+  interface HookCallback { (args: SupportObject): void; }
   interface Scenario extends IScenario { only: IScenario, skip: IScenario, todo:  IScenario}
   interface IData { Scenario: IScenario, only: { Scenario: IScenario } }
 
   interface IScenario {
     // Scenario.todo can be called only with a title.
-    <T extends any[] = CallbackOrder>(
-      title: string
-    ): ScenarioConfig;
-    <T extends any[] = CallbackOrder>(
-      title: string,
-      callback: HookCallback<T>
-    ): ScenarioConfig;
-    <T extends any[] = CallbackOrder>(
-      title: string,
-      opts: { [key: string]: any },
-      callback: HookCallback<T>
-    ): ScenarioConfig;
-  }
-  interface IHook {
-    <T extends any[] = CallbackOrder>(callback: HookCallback<T>): void;
+    (title: string): ScenarioConfig;
+    (title: string, callback: HookCallback): ScenarioConfig;
+    (title: string, opts: { [key: string]: any }, callback: HookCallback): ScenarioConfig;
   }
+  interface IHook { (callback: HookCallback): void; }
 
   interface Globals {
     codeceptjs: typeof codeceptjs;


Test patch:
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 5818c4c9..83984b50 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -1,6 +1,12 @@
 name: Run Unit tests
 
-on: [push]
+on:
+  push:
+    branches:
+      - master
+  pull_request:
+    branches:
+      - '**'
 
 jobs:
   build:
@@ -9,7 +15,7 @@ jobs:
 
     strategy:
       matrix:
-        node-version: [10.x, 12.x]
+        node-version: [12.x]
 
     steps:
     - uses: actions/checkout@v1
diff --git a/lib/command/workers/runTests.js b/lib/command/workers/runTests.js
index 31a623d0..e57a4ca6 100644
--- a/lib/command/workers/runTests.js
+++ b/lib/command/workers/runTests.js
@@ -10,9 +10,10 @@ if (!tty.getWindowSize) {
 const { parentPort, workerData } = require('worker_threads');
 const event = require('../../event');
 const container = require('../../container');
-const { getConfig, getTestRoot } = require('../utils');
+const { getConfig } = require('../utils');
 const { tryOrDefault } = require('../../utils');
 
+// eslint-disable-next-line no-unused-vars
 let stdout = '';
 /* eslint-enable no-unused-vars */
 
diff --git a/test/acceptance/config_test.js b/test/acceptance/config_test.js
index 48c2d7bc..9869ed46 100644
--- a/test/acceptance/config_test.js
+++ b/test/acceptance/config_test.js
@@ -1,23 +1,23 @@
 Feature('Dynamic Config').config({ url: 'https://google.com' });
 
-Scenario('change config 1 @WebDriverIO @Puppeteer @Playwright @Protractor @Nightmare', (I) => {
+Scenario('change config 1 @WebDriverIO @Puppeteer @Playwright @Protractor @Nightmare', ({ I }) => {
   I.amOnPage('/');
   I.dontSeeInCurrentUrl('github.com');
   I.seeInCurrentUrl('google.com');
 });
 
-Scenario('change config 2 @WebDriverIO @Puppeteer @Playwright @Protractor @Nightmare', (I) => {
+Scenario('change config 2 @WebDriverIO @Puppeteer @Playwright @Protractor @Nightmare', ({ I }) => {
   I.amOnPage('/');
   I.seeInCurrentUrl('github.com');
 }).config({ url: 'https://github.com' });
 
-Scenario('change config 3 @WebDriverIO @Puppeteer @Playwright @Protractor @Nightmare', (I) => {
+Scenario('change config 3 @WebDriverIO @Puppeteer @Playwright @Protractor @Nightmare', ({ I }) => {
   I.amOnPage('/');
   I.dontSeeInCurrentUrl('github.com');
   I.seeInCurrentUrl('google.com');
 });
 
-Scenario('change config 4 @WebDriverIO @Puppeteer @Playwright @Protractor @Nightmare', (I) => {
+Scenario('change config 4 @WebDriverIO @Puppeteer @Playwright @Protractor @Nightmare', ({ I }) => {
   I.amOnPage('/');
   I.seeInCurrentUrl('codecept.io');
 }).config((test) => {
@@ -25,13 +25,13 @@ Scenario('change config 4 @WebDriverIO @Puppeteer @Playwright @Protractor @Night
 });
 
 
-Scenario('change config 5 @WebDriverIO @Puppeteer @Playwright @Protractor @Nightmare', (I) => {
+Scenario('change config 5 @WebDriverIO @Puppeteer @Playwright @Protractor @Nightmare', ({ I }) => {
   I.amOnPage('/');
   I.dontSeeInCurrentUrl('github.com');
   I.seeInCurrentUrl('google.com');
 });
 
-Scenario('change config 6 @WebDriverIO @Puppeteer @Playwright @Protractor @Nightmare', (I) => {
+Scenario('change config 6 @WebDriverIO @Puppeteer @Playwright @Protractor @Nightmare', ({ I }) => {
   I.amOnPage('/');
   I.seeInCurrentUrl('github.com');
 }).config(async () => {
diff --git a/test/acceptance/mock_test.js b/test/acceptance/mock_test.js
index 8c24bd4c..f14e9242 100644
--- a/test/acceptance/mock_test.js
+++ b/test/acceptance/mock_test.js
@@ -1,7 +1,7 @@
 Feature('Mocking');
 
 
-Scenario('change statusCode @Puppeteer @WebDriver', (I) => {
+Scenario('change statusCode @Puppeteer @WebDriver', ({ I }) => {
   I.amOnPage('/form/fetch_call');
   I.mockRequest('GET', 'https://jsonplaceholder.typicode.com/*', 404);
   I.click('GET POSTS');
@@ -9,7 +9,7 @@ Scenario('change statusCode @Puppeteer @WebDriver', (I) => {
   I.stopMocking();
 });
 
-Scenario('change response data @Puppeteer @WebDriver', (I) => {
+Scenario('change response data @Puppeteer @WebDriver', ({ I }) => {
   I.amOnPage('/form/fetch_call');
   I.mockRequest('GET', 'https://jsonplaceholder.typicode.com/*', {
     modified: 'This is modified from mocking',
@@ -19,7 +19,7 @@ Scenario('change response data @Puppeteer @WebDriver', (I) => {
   I.stopMocking();
 });
 
-Scenario('change response data for multiple requests @Puppeteer @WebDriver', (I) => {
+Scenario('change response data for multiple requests @Puppeteer @WebDriver', ({ I }) => {
   I.amOnPage('/form/fetch_call');
   I.mockRequest(
     'GET',
@@ -44,7 +44,7 @@ Scenario('change response data for multiple requests @Puppeteer @WebDriver', (I)
 // we should replace it with other service - https://jsonplaceholder.typicode.com not works
 xScenario(
   'should request for original data after mocking stopped @Puppeteer @WebDriver',
-  (I) => {
+  ({ I }) => {
     I.amOnPage('/form/fetch_call');
     I.mockRequest('GET', 'https://jsonplaceholder.typicode.com/*', {
       comment: 'CUSTOM _uniqueId_u4805sd23',
diff --git a/test/acceptance/react_test.js b/test/acceptance/react_test.js
index 031a6959..c0aff7ea 100644
--- a/test/acceptance/react_test.js
+++ b/test/acceptance/react_test.js
@@ -1,6 +1,6 @@
 Feature('React Selectors');
 
-Scenario('props @WebDriver @Puppeteer', (I) => {
+Scenario('props @WebDriver @Puppeteer', ({ I }) => {
   I.amOnPage('https://ahfarmer.github.io/calculator/');
   I.click('7');
   I.seeElement({ react: 't', props: { name: '5' } });
@@ -11,7 +11,7 @@ Scenario('props @WebDriver @Puppeteer', (I) => {
   I.seeElement({ react: 't', props: { value: '81' } });
 });
 
-Scenario('component name @Puppeteer', (I) => {
+Scenario('component name @Puppeteer', ({ I }) => {
   I.amOnPage('http://negomi.github.io/react-burger-menu/');
   I.click({ react: 'BurgerIcon' });
   I.waitForVisible('#slide', 10);
diff --git a/test/acceptance/session_test.js b/test/acceptance/session_test.js
index 8973d106..15122d8c 100644
--- a/test/acceptance/session_test.js
+++ b/test/acceptance/session_test.js
@@ -4,7 +4,7 @@ const { event } = codeceptjs;
 
 Feature('Session');
 
-Scenario('simple session @WebDriverIO @Protractor @Puppeteer @Playwright', (I) => {
+Scenario('simple session @WebDriverIO @Protractor @Puppeteer @Playwright', ({ I }) => {
   I.amOnPage('/info');
   session('john', () => {
     I.amOnPage('https://github.com');
@@ -15,7 +15,7 @@ Scenario('simple session @WebDriverIO @Protractor @Puppeteer @Playwright', (I) =
   I.seeInCurrentUrl('/info');
 });
 
-Scenario('screenshots reflect the current page of current session @Puppeteer @Playwright @WebDriver', async (I) => {
+Scenario('screenshots reflect the current page of current session @Puppeteer @Playwright @WebDriver', async ({ I }) => {
   I.amOnPage('/');
   I.saveScreenshot('session_default_1.png');
 
@@ -45,7 +45,7 @@ Scenario('screenshots reflect the current page of current session @Puppeteer @Pl
   assert.notEqual(default1Digest, john1Digest);
 });
 
-Scenario('Different cookies for different sessions @WebDriverIO @Protractor @Playwright @Puppeteer', async (I) => {
+Scenario('Different cookies for different sessions @WebDriverIO @Protractor @Playwright @Puppeteer', async ({ I }) => {
   const cookiePage = 'https://www.microsoft.com/en-au/';
   const cookieName = 'MUID';
   const cookies = {};
@@ -78,7 +78,7 @@ Scenario('Different cookies for different sessions @WebDriverIO @Protractor @Pla
 });
 
 
-Scenario('should save screenshot for active session @WebDriverIO @Puppeteer @Playwright', async function (I) {
+Scenario('should save screenshot for active session @WebDriverIO @Puppeteer @Playwright', async function ({ I }) {
   I.amOnPage('/form/bug1467');
   I.saveScreenshot('original.png');
   I.amOnPage('/');
@@ -99,7 +99,7 @@ Scenario('should save screenshot for active session @WebDriverIO @Puppeteer @Pla
 });
 
 
-Scenario('should throw exception and close correctly @WebDriverIO @Protractor @Puppeteer @Playwright', (I) => {
+Scenario('should throw exception and close correctly @WebDriverIO @Protractor @Puppeteer @Playwright', ({ I }) => {
   I.amOnPage('/form/bug1467#session1');
   I.checkOption('Yes');
   session('john', () => {
@@ -111,7 +111,7 @@ Scenario('should throw exception and close correctly @WebDriverIO @Protractor @P
   I.amOnPage('/info');
 }).fails();
 
-Scenario('async/await @WebDriverIO @Protractor', (I) => {
+Scenario('async/await @WebDriverIO @Protractor', ({ I }) => {
   I.amOnPage('/form/bug1467#session1');
   I.checkOption('Yes');
   session('john', async () => {
@@ -122,7 +122,7 @@ Scenario('async/await @WebDriverIO @Protractor', (I) => {
   I.seeCheckboxIsChecked({ css: 'input[value=Yes]' });
 });
 
-Scenario('exception on async/await @WebDriverIO @Protractor @Puppeteer @Playwright', (I) => {
+Scenario('exception on async/await @WebDriverIO @Protractor @Puppeteer @Playwright', ({ I }) => {
   I.amOnPage('/form/bug1467#session1');
   I.checkOption('Yes');
   session('john', async () => {
@@ -133,7 +133,7 @@ Scenario('exception on async/await @WebDriverIO @Protractor @Puppeteer @Playwrig
   I.seeCheckboxIsChecked({ css: 'input[value=Yes]' });
 }).throws(/to be checked/);
 
-Scenario('should work with within @WebDriverIO @Protractor @Puppeteer @Playwright', (I) => {
+Scenario('should work with within @WebDriverIO @Protractor @Puppeteer @Playwright', ({ I }) => {
   I.amOnPage('/form/bug1467');
   session('john', () => {
     I.amOnPage('/form/bug1467');
@@ -154,7 +154,7 @@ Scenario('should work with within @WebDriverIO @Protractor @Puppeteer @Playwrigh
   });
 });
 
-Scenario('change page emulation @Playwright', async (I) => {
+Scenario('change page emulation @Playwright', async ({ I }) => {
   const assert = require('assert');
   I.amOnPage('/');
   session('mobile user', {
@@ -167,7 +167,7 @@ Scenario('change page emulation @Playwright', async (I) => {
 });
 
 
-Scenario('emulate iPhone @Playwright', async (I) => {
+Scenario('emulate iPhone @Playwright', async ({ I }) => {
   const { devices } = require('playwright');
   if (process.env.BROWSER === 'firefox') return;
   const assert = require('assert');
@@ -179,7 +179,7 @@ Scenario('emulate iPhone @Playwright', async (I) => {
   });
 });
 
-xScenario('should use different base URL @Protractor @Puppeteer @Playwright', (I) => { // nah, that's broken
+xScenario('should use different base URL @Protractor @Puppeteer @Playwright', ({ I }) => { // nah, that's broken
   I.amOnPage('/');
   I.see('Welcome to test app');
   session('john', { url: 'https://github.com' }, () => {
@@ -190,7 +190,7 @@ xScenario('should use different base URL @Protractor @Puppeteer @Playwright', (I
   I.see('Welcome to test app');
 });
 
-xScenario('should start firefox', async (I) => { // requires firefox :)
+xScenario('should start firefox', async ({ I }) => { // requires firefox :)
   I.amOnPage('/form/bug1467#session1');
   I.checkOption('Yes');
   session('john', { browser: 'firefox' }, async () => {
@@ -205,7 +205,7 @@ xScenario('should start firefox', async (I) => { // requires firefox :)
   assert(isChrome);
 });
 
-Scenario('should return a value in @WebDriverIO @Protractor @Puppeteer @Playwright', async (I) => {
+Scenario('should return a value in @WebDriverIO @Protractor @Puppeteer @Playwright', async ({ I }) => {
   I.amOnPage('/form/textarea');
   const val = await session('john', () => {
     I.amOnPage('/info');
@@ -217,7 +217,7 @@ Scenario('should return a value in @WebDriverIO @Protractor @Puppeteer @Playwrig
 });
 
 
-Scenario('should return a value @WebDriverIO @Protractor @Puppeteer @Playwright in async', async (I) => {
+Scenario('should return a value @WebDriverIO @Protractor @Puppeteer @Playwright in async', async ({ I }) => {
   I.amOnPage('/form/textarea');
   const val = await session('john', async () => {
     I.amOnPage('/info');
diff --git a/test/acceptance/within_test.js b/test/acceptance/within_test.js
index 272f45d5..6b380aae 100644
--- a/test/acceptance/within_test.js
+++ b/test/acceptance/within_test.js
@@ -1,6 +1,6 @@
 Feature('within', { retries: 3 });
 
-Scenario('within on form @WebDriverIO @Puppeteer @Playwright @Protractor @Nightmare ', (I) => {
+Scenario('within on form @WebDriverIO @Puppeteer @Playwright @Protractor @Nightmare ', ({ I }) => {
   I.amOnPage('/form/bug1467');
   I.see('TEST TEST');
   within({ css: '[name=form2]' }, () => {
@@ -11,7 +11,7 @@ Scenario('within on form @WebDriverIO @Puppeteer @Playwright @Protractor @Nightm
   I.dontSeeCheckboxIsChecked({ css: 'form[name=form1] input[name=first_test_radio]' });
 });
 
-Scenario('switch iframe manually @WebDriverIO @Puppeteer @Playwright @Protractor', (I) => {
+Scenario('switch iframe manually @WebDriverIO @Puppeteer @Playwright @Protractor', ({ I }) => {
   I.amOnPage('/iframe');
 
   I.switchTo('iframe');
@@ -24,7 +24,7 @@ Scenario('switch iframe manually @WebDriverIO @Puppeteer @Playwright @Protractor
   I.dontSee('Email Address');
 });
 
-Scenario('within on iframe @WebDriverIO @Puppeteer @Playwright @Protractor @Nightmare', (I) => {
+Scenario('within on iframe @WebDriverIO @Puppeteer @Playwright @Protractor @Nightmare', ({ I }) => {
   I.amOnPage('/iframe');
   within({ frame: 'iframe' }, () => {
     I.fillField('rus', 'Updated');
@@ -35,7 +35,7 @@ Scenario('within on iframe @WebDriverIO @Puppeteer @Playwright @Protractor @Nigh
   I.dontSee('Email Address');
 });
 
-Scenario('within on iframe without iframe navigation @WebDriverIO @Puppeteer @Playwright @Protractor @Nightmare', (I) => {
+Scenario('within on iframe without iframe navigation @WebDriverIO @Puppeteer @Playwright @Protractor @Nightmare', ({ I }) => {
   I.amOnPage('/iframe');
   within({ frame: 'iframe' }, () => {
     I.fillField('rus', 'Updated');
@@ -45,7 +45,7 @@ Scenario('within on iframe without iframe navigation @WebDriverIO @Puppeteer @Pl
   I.dontSee('Sign in!');
 });
 
-Scenario('within on nested iframe without iframe navigation depth 2 @WebDriverIO @Puppeteer @Playwright @Protractor @Nightmare', (I) => {
+Scenario('within on nested iframe without iframe navigation depth 2 @WebDriverIO @Puppeteer @Playwright @Protractor @Nightmare', ({ I }) => {
   I.amOnPage('/iframe_nested');
   within({ frame: ['[name=wrapper]', '[name=content]'] }, () => {
     I.fillField('rus', 'Updated');
@@ -55,7 +55,7 @@ Scenario('within on nested iframe without iframe navigation depth 2 @WebDriverIO
   I.dontSee('Sign in!');
 });
 
-Scenario('within on nested iframe depth 1 @WebDriverIO @Puppeteer @Playwright @Protractor @Nightmare', (I) => {
+Scenario('within on nested iframe depth 1 @WebDriverIO @Puppeteer @Playwright @Protractor @Nightmare', ({ I }) => {
   I.amOnPage('/iframe');
   within({ frame: ['[name=content]'] }, () => {
     I.fillField('rus', 'Updated');
@@ -66,7 +66,7 @@ Scenario('within on nested iframe depth 1 @WebDriverIO @Puppeteer @Playwright @P
   I.dontSee('Email Address');
 });
 
-Scenario('within on nested iframe depth 2 @WebDriverIO @Puppeteer @Playwright @Protractor', (I) => {
+Scenario('within on nested iframe depth 2 @WebDriverIO @Puppeteer @Playwright @Protractor', ({ I }) => {
   I.amOnPage('/iframe_nested');
   within({ frame: ['[name=wrapper]', '[name=content]'] }, () => {
     I.fillField('rus', 'Updated');
@@ -77,7 +77,7 @@ Scenario('within on nested iframe depth 2 @WebDriverIO @Puppeteer @Playwright @P
   I.dontSee('Email Address');
 });
 
-Scenario('within on nested iframe depth 2 and mixed id and xpath selector @WebDriverIO @Puppeteer @Playwright @Protractor', (I) => {
+Scenario('within on nested iframe depth 2 and mixed id and xpath selector @WebDriverIO @Puppeteer @Playwright @Protractor', ({ I }) => {
   I.amOnPage('/iframe_nested');
   within({ frame: ['#wrapperId', '[name=content]'] }, () => {
     I.fillField('rus', 'Updated');
@@ -88,7 +88,7 @@ Scenario('within on nested iframe depth 2 and mixed id and xpath selector @WebDr
   I.dontSee('Email Address');
 });
 
-Scenario('within on nested iframe depth 2 and mixed class and xpath selector @WebDriverIO @Puppeteer @Playwright @Protractor', (I) => {
+Scenario('within on nested iframe depth 2 and mixed class and xpath selector @WebDriverIO @Puppeteer @Playwright @Protractor', ({ I }) => {
   I.amOnPage('/iframe_nested');
   within({ frame: ['.wrapperClass', '[name=content]'] }, () => {
     I.fillField('rus', 'Updated');
@@ -99,14 +99,14 @@ Scenario('within on nested iframe depth 2 and mixed class and xpath selector @We
   I.dontSee('Email Address');
 });
 
-Scenario('should throw exception if element not found @WebDriverIO @Puppeteer @Playwright @Protractor @Nightmare', async (I) => {
+Scenario('should throw exception if element not found @WebDriverIO @Puppeteer @Playwright @Protractor @Nightmare', async ({ I }) => {
   I.amOnPage('/form/textarea');
   within('#grab-multiple', () => {
     return I.grabTextFrom('#first-link');
   });
 }).throws(/found/);
 
-Scenario('should return a value @WebDriverIO @Puppeteer @Playwright @Protractor @Nightmare', async (I) => {
+Scenario('should return a value @WebDriverIO @Puppeteer @Playwright @Protractor @Nightmare', async ({ I }) => {
   I.amOnPage('/info');
   const val = await within('#grab-multiple', () => {
     return I.grabTextFrom('#first-link');
diff --git a/test/data/inject-fail-example/first_test.js b/test/data/inject-fail-example/first_test.js
index 05a3e750..a65f765d 100644
--- a/test/data/inject-fail-example/first_test.js
+++ b/test/data/inject-fail-example/first_test.js
@@ -1,5 +1,5 @@
 Feature('asdas');
 
-Scenario('qw', async (I, page) => {
+Scenario('qw', async ({ page }) => {
   await page.type('asda');
 });
diff --git a/test/data/sandbox/base_test_session.js b/test/data/sandbox/base_test_session.js
index ff2ccf18..b424acb6 100644
--- a/test/data/sandbox/base_test_session.js
+++ b/test/data/sandbox/base_test_session.js
@@ -1,6 +1,6 @@
 Feature('Session');
 
-Scenario('basic session @1', (I) => {
+Scenario('basic session @1', ({ I }) => {
   I.do('writing');
   session('davert', () => {
     I.do('reading');
@@ -22,7 +22,7 @@ Scenario('basic session @1', (I) => {
   I.do('waving');
 });
 
-Scenario('session defined not used @2', (I) => {
+Scenario('session defined not used @2', ({ I }) => {
   session('davert');
   I.do('writing');
   I.do('playing');
diff --git a/test/data/sandbox/base_test_within.js b/test/data/sandbox/base_test_within.js
index 5bc32c95..732f9b88 100644
--- a/test/data/sandbox/base_test_within.js
+++ b/test/data/sandbox/base_test_within.js
@@ -1,6 +1,6 @@
 Feature('Within');
 
-Scenario('Check within without generator', (I) => {
+Scenario('Check within without generator', ({ I }) => {
   I.smallPromise();
   within('blabla', () => {
     I.smallPromise();
@@ -24,7 +24,7 @@ Scenario('Check within with generator. Should stop test execution after fail in
   throw new Error('fail');
 });
 
-Scenario('Check within with async/await. Await is first in order', async (I) => {
+Scenario('Check within with async/await. Await is first in order', async ({ I }) => {
   I.smallPromise();
   const test = await I.smallYield();
   console.log(test, 'await');
@@ -35,7 +35,7 @@ Scenario('Check within with async/await. Await is first in order', async (I) =>
   });
 });
 
-Scenario('Check within with async/await. Await is second in order', async (I) => {
+Scenario('Check within with async/await. Await is second in order', async ({ I }) => {
   I.smallPromise();
   const test = await I.smallYield();
   console.log(test, 'await');
diff --git a/test/data/sandbox/browser_test.multiple.js b/test/data/sandbox/browser_test.multiple.js
index c37d6ab1..cc27846d 100644
--- a/test/data/sandbox/browser_test.multiple.js
+++ b/test/data/sandbox/browser_test.multiple.js
@@ -1,5 +1,5 @@
 Feature('print browser');
 
-Scenario('print browser info', (I) => {
+Scenario('print browser info', ({ I }) => {
   I.printBrowser();
 });
diff --git a/test/data/sandbox/configs/allure/before_suite_test_failed.js b/test/data/sandbox/configs/allure/before_suite_test_failed.js
index c019a8e1..0945cf0c 100644
--- a/test/data/sandbox/configs/allure/before_suite_test_failed.js
+++ b/test/data/sandbox/configs/allure/before_suite_test_failed.js
@@ -4,10 +4,10 @@ BeforeSuite(async () => {
   throw new Error('the before suite setup failed');
 });
 
-Scenario('failing setup test 1', async I => {
+Scenario('failing setup test 1', async ({ I }) => {
   I.say('Test was fine.');
 });
 
-Scenario('failing setup test 2', async I => {
+Scenario('failing setup test 2', async ({ I }) => {
   I.say('Test was fine.');
 });
diff --git a/test/data/sandbox/configs/allure/failed_ansi_test.js b/test/data/sandbox/configs/allure/failed_ansi_test.js
index a521116c..66be6f1c 100644
--- a/test/data/sandbox/configs/allure/failed_ansi_test.js
+++ b/test/data/sandbox/configs/allure/failed_ansi_test.js
@@ -1,7 +1,7 @@
 
 Feature('Filesystem').tag('main');
 
-Scenario('check error with ansi symbols', (I) => {
+Scenario('check error with ansi symbols', ({ I }) => {
   I.amInPath('.');
   I.say('hello world');
   throw new Error('message with ANSI symbols \n \u001b[32mgreen\u001b[39m');
diff --git a/test/data/sandbox/configs/allure/success_test.js b/test/data/sandbox/configs/allure/success_test.js
index 6ec6d34a..d6ed9775 100644
--- a/test/data/sandbox/configs/allure/success_test.js
+++ b/test/data/sandbox/configs/allure/success_test.js
@@ -1,6 +1,6 @@
 Feature('Filesystem').tag('main');
 
-Scenario('check error with ansi symbols', (I) => {
+Scenario('check error with ansi symbols', ({ I }) => {
   I.amInPath('.');
   I.say('hello world');
 }).tag('fast').tag('@important');
diff --git a/test/data/sandbox/configs/bootstrap/failed_test.js b/test/data/sandbox/configs/bootstrap/failed_test.js
index e42c260f..503b4891 100644
--- a/test/data/sandbox/configs/bootstrap/failed_test.js
+++ b/test/data/sandbox/configs/bootstrap/failed_test.js
@@ -1,12 +1,12 @@
 Feature('Filesystem').tag('main');
 
-Scenario('check current dir', (I) => {
+Scenario('check current dir', ({ I }) => {
   I.amInPath('.');
   I.say('hello world');
   I.seeFile('unknown.js');
 }).tag('slow').tag('@important');
 
-Scenario('check current dir with wait', (I) => {
+Scenario('check current dir with wait', ({ I }) => {
   I.amInPath('.');
   I.say('hello world');
   I.waitForFile('unknown.js', 1);
diff --git a/test/data/sandbox/configs/bootstrap/fs_test.js b/test/data/sandbox/configs/bootstrap/fs_test.js
index 6b716c24..4feb17fa 100644
--- a/test/data/sandbox/configs/bootstrap/fs_test.js
+++ b/test/data/sandbox/configs/bootstrap/fs_test.js
@@ -1,13 +1,13 @@
 Feature('Filesystem').tag('main');
 
-Scenario('see content in file', (I) => {
+Scenario('see content in file', ({ I }) => {
   I.amInPath('.');
   I.say('hello world');
   I.seeFile('fs_test.js');
   I.seeFileContentsEqualReferenceFile(__filename);
 }).tag('slow').tag('@important');
 
-Scenario('wait for file in current dir', (I) => {
+Scenario('wait for file in current dir', ({ I }) => {
   I.amInPath('.');
   I.say('hello world');
   I.waitForFile('fs_test.js');
diff --git a/test/data/sandbox/configs/bootstrap/invalid_require_test.js b/test/data/sandbox/configs/bootstrap/invalid_require_test.js
index 131bb1ba..f513394b 100644
--- a/test/data/sandbox/configs/bootstrap/invalid_require_test.js
+++ b/test/data/sandbox/configs/bootstrap/invalid_require_test.js
@@ -2,7 +2,7 @@ require('invalidRequire');
 
 Feature('Filesystem');
 
-Scenario('check current dir', (I) => {
+Scenario('check current dir', ({ I }) => {
   I.amInPath('.');
   I.say('hello world');
   I.seeFile('codecept.json');
diff --git a/test/data/sandbox/configs/commentStep/first_test.js b/test/data/sandbox/configs/commentStep/first_test.js
index 1f3a5467..4d479391 100644
--- a/test/data/sandbox/configs/commentStep/first_test.js
+++ b/test/data/sandbox/configs/commentStep/first_test.js
@@ -10,7 +10,7 @@ const pageObject = {
   },
 };
 
-Scenario('global var', (I) => {
+Scenario('global var', ({ I }) => {
   __`Prepare user base`;
   I.print('other thins');
 
@@ -21,7 +21,7 @@ Scenario('global var', (I) => {
   I.print('see everything works');
 });
 
-Scenario('local vars', (I) => {
+Scenario('local vars', ({ I }) => {
   given`Prepare project`;
   I.print('other thins');
 
@@ -32,7 +32,7 @@ Scenario('local vars', (I) => {
   I.print('see everything works');
 });
 
-Scenario('from page object', (I) => {
+Scenario('from page object', ({ I }) => {
   __`Prepare project`;
   I.print('other thins');
   pageObject.metaPrint('login user');
diff --git a/test/data/sandbox/configs/pageObjects/first_test.js b/test/data/sandbox/configs/pageObjects/first_test.js
index ef4520de..7d0f6cf1 100644
--- a/test/data/sandbox/configs/pageObjects/first_test.js
+++ b/test/data/sandbox/configs/pageObjects/first_test.js
@@ -1,12 +1,12 @@
 Feature('PageObject');
 
-Scenario('@ClassPageObject', async (I, classpage) => {
+Scenario('@ClassPageObject', async ({ classpage }) => {
   await classpage.type('Class Page Type');
   await classpage.purgeDomains();
 });
 
 
-Scenario('@NestedClassPageObject', (I, classnestedpage) => {
+Scenario('@NestedClassPageObject', ({ classnestedpage }) => {
   classnestedpage.type('Nested Class Page Type');
   classnestedpage.purgeDomains();
 });
diff --git a/test/data/sandbox/configs/run-rerun/first_ftest.js b/test/data/sandbox/configs/run-rerun/first_ftest.js
index d385a54f..7b9e899f 100644
--- a/test/data/sandbox/configs/run-rerun/first_ftest.js
+++ b/test/data/sandbox/configs/run-rerun/first_ftest.js
@@ -1,13 +1,13 @@
 /* eslint-disable radix */
 Feature('Run Rerun - Command');
 
-Scenario('@RunRerun - Fail all attempt', (I) => {
+Scenario('@RunRerun - Fail all attempt', ({ I }) => {
   I.printMessage('RunRerun');
   throw new Error('Test Error');
 });
 
 
-Scenario('@RunRerun - fail second test', (I) => {
+Scenario('@RunRerun - fail second test', ({ I }) => {
   I.printMessage('RunRerun');
   process.env.FAIL_ATTEMPT = parseInt(process.env.FAIL_ATTEMPT) + 1;
   console.log(process.env.FAIL_ATTEMPT);
diff --git a/test/data/sandbox/configs/run-rerun/first_test.js b/test/data/sandbox/configs/run-rerun/first_test.js
index 450890a3..973c6216 100644
--- a/test/data/sandbox/configs/run-rerun/first_test.js
+++ b/test/data/sandbox/configs/run-rerun/first_test.js
@@ -1,5 +1,5 @@
 Feature('Run Rerun - Command');
 
-Scenario('@RunRerun', (I) => {
+Scenario('@RunRerun', ({ I }) => {
   I.printMessage('RunRerun');
 });
diff --git a/test/data/sandbox/custom-worker/base_test.worker.js b/test/data/sandbox/custom-worker/base_test.worker.js
index 9cae4f5b..a89c6a25 100644
--- a/test/data/sandbox/custom-worker/base_test.worker.js
+++ b/test/data/sandbox/custom-worker/base_test.worker.js
@@ -1,11 +1,11 @@
 Feature('Workers');
 
-Scenario('say something', (I) => {
+Scenario('say something', ({ I }) => {
   I.say('Hello Workers');
   share({ fromWorker: true });
 });
 
-Scenario('glob current dir', (I) => {
+Scenario('glob current dir', ({ I }) => {
   I.amInPath('.');
   I.say('hello world');
   I.seeFile('codecept.glob.json');
diff --git a/test/data/sandbox/custom-worker/custom_test.worker.js b/test/data/sandbox/custom-worker/custom_test.worker.js
index 13c38e70..7bafb43a 100644
--- a/test/data/sandbox/custom-worker/custom_test.worker.js
+++ b/test/data/sandbox/custom-worker/custom_test.worker.js
@@ -1,6 +1,6 @@
 Feature('Custom Workers');
 
-Scenario('say custom something', (I) => {
+Scenario('say custom something', ({ I }) => {
   I.say('Hello Workers');
   I.sayCustomMessage();
 });
diff --git a/test/data/sandbox/custom-worker/share_test.worker.js b/test/data/sandbox/custom-worker/share_test.worker.js
index c687ce9b..551928b6 100644
--- a/test/data/sandbox/custom-worker/share_test.worker.js
+++ b/test/data/sandbox/custom-worker/share_test.worker.js
@@ -2,14 +2,14 @@ const assert = require('assert');
 
 Feature('Shared Memory in Workers');
 
-Scenario('Should get the data shared from main process', (I) => {
+Scenario('Should get the data shared from main process', ({ I }) => {
   I.say('Hello Workers');
   const { fromMain } = inject();
   console.log(fromMain);
   assert.equal(fromMain, true);
 });
 
-Scenario('Should get the data shared from other worker', (I) => {
+Scenario('Should get the data shared from other worker', ({ I }) => {
   I.amInPath('.');
   I.say('hello world');
   const { fromWorker } = inject();
diff --git a/test/data/sandbox/ddt_test.addt.js b/test/data/sandbox/ddt_test.addt.js
index b98aa95f..95295c58 100644
--- a/test/data/sandbox/ddt_test.addt.js
+++ b/test/data/sandbox/ddt_test.addt.js
@@ -1,5 +1,5 @@
 Feature('ADDT');
 
-Data(['1', '2', '3']).only.Scenario('Should log array of strings', (I, current) => {
-  console.log(`Got array item ${current}`);
+Data(['1', '2', '3']).only.Scenario('Should log array of strings', ({ current }) => {
+  console.log(`Got array item ${(current)}`);
 });
diff --git a/test/data/sandbox/ddt_test.ddt.js b/test/data/sandbox/ddt_test.ddt.js
index 5c4086f5..caf3018a 100644
--- a/test/data/sandbox/ddt_test.ddt.js
+++ b/test/data/sandbox/ddt_test.ddt.js
@@ -8,28 +8,28 @@ const accounts2 = new DataTable(['login', 'password']);
 accounts2.add(['andrey', '555555']);
 accounts2.add(['collaborator', '222222']);
 
-Data(accounts1).Scenario('Should log accounts1', (I, current) => {
+Data(accounts1).Scenario('Should log accounts1', ({ current }) => {
   console.log(`Got login ${current.login} and password ${current.password}`);
 });
 
-Data(accounts2).Scenario('Should log accounts2', (I, current) => {
+Data(accounts2).Scenario('Should log accounts2', ({ current }) => {
   console.log(`Got changed login ${current.login} and password ${current.password}`);
 });
 
 Data(function* () {
   yield ['nick', 'pick'];
   yield ['jack', 'sacj'];
-}).Scenario('Should log accounts3', (I, current) => {
+}).Scenario('Should log accounts3', ({ current }) => {
   console.log(`Got changed login ${current[0]}`);
 });
 
 Data(function* () {
   yield { user: 'nick' };
   yield { user: 'pick' };
-}).Scenario('Should log accounts4', (I, current) => {
+}).Scenario('Should log accounts4', ({ current }) => {
   console.log(`Got generator login ${current.user}`);
 });
 
-Data(['1', '2', '3']).Scenario('Should log array of strings', (I, current) => {
+Data(['1', '2', '3']).Scenario('Should log array of strings', ({ current }) => {
   console.log(`Got array item ${current}`);
 });
diff --git a/test/data/sandbox/ddt_test.gddt.js b/test/data/sandbox/ddt_test.gddt.js
index 9322aacd..cd1679a8 100644
--- a/test/data/sandbox/ddt_test.gddt.js
+++ b/test/data/sandbox/ddt_test.gddt.js
@@ -3,6 +3,6 @@ Feature('ADDT');
 Data(function* () {
   yield { user: 'nick' };
   yield { user: 'pick' };
-}).only.Scenario('Should log generator of strings', (I, current) => {
+}).only.Scenario('Should log generator of strings', ({ current }) => {
   console.log(`Got generator login ${current.user}`);
 });
diff --git a/test/data/sandbox/flaky_test.retry.js b/test/data/sandbox/flaky_test.retry.js
index dba7a469..01dbcb31 100644
--- a/test/data/sandbox/flaky_test.retry.js
+++ b/test/data/sandbox/flaky_test.retry.js
@@ -6,7 +6,7 @@ let tries = 0;
 
 Feature('Retry');
 
-Scenario('flaky step @test1', async (I) => {
+Scenario('flaky step @test1', async ({ I }) => {
   tries++;
   await I.retry(3).failWhen(() => {
     tries++;
@@ -15,7 +15,7 @@ Scenario('flaky step @test1', async (I) => {
   assert.equal(tries, 4);
 });
 
-Scenario('flaky step passed globally @test2', (I) => {
+Scenario('flaky step passed globally @test2', ({ I }) => {
   recorder.retry({
     retries: 3,
     when: () => false,
diff --git a/test/data/sandbox/flaky_test.retryFailed.js b/test/data/sandbox/flaky_test.retryFailed.js
index d605d2e9..54433657 100644
--- a/test/data/sandbox/flaky_test.retryFailed.js
+++ b/test/data/sandbox/flaky_test.retryFailed.js
@@ -4,7 +4,7 @@ let tries = 0;
 
 Feature('Retry');
 
-Scenario('auto repeat failing step @test1', async (I) => {
+Scenario('auto repeat failing step @test1', async ({ I }) => {
   tries++;
   await I.failWhen(() => {
     tries++;
@@ -14,7 +14,7 @@ Scenario('auto repeat failing step @test1', async (I) => {
   console.log(`[T] Retries: ${tries}`);
 });
 
-Scenario('no repeat for waiter @test2', async (I) => {
+Scenario('no repeat for waiter @test2', async ({ I }) => {
   await I.waitForFail(() => {
     tries++;
     return tries < 5;
@@ -22,7 +22,7 @@ Scenario('no repeat for waiter @test2', async (I) => {
   assert.equal(tries, 1);
 });
 
-Scenario('no retries if disabled per test @test3', async (I) => {
+Scenario('no retries if disabled per test @test3', async ({ I }) => {
   await I.failWhen(() => {
     tries++;
     return tries < 5;
diff --git a/test/data/sandbox/fs_test.glob.js b/test/data/sandbox/fs_test.glob.js
index bb652540..c222696f 100644
--- a/test/data/sandbox/fs_test.glob.js
+++ b/test/data/sandbox/fs_test.glob.js
@@ -1,6 +1,6 @@
 Feature('Filesystem');
 
-Scenario('glob current dir', (I) => {
+Scenario('glob current dir', ({ I }) => {
   I.amInPath('.');
   I.say('hello world');
   I.seeFile('codecept.glob.json');
diff --git a/test/data/sandbox/fs_test.js b/test/data/sandbox/fs_test.js
index c158a216..07b1c32a 100644
--- a/test/data/sandbox/fs_test.js
+++ b/test/data/sandbox/fs_test.js
@@ -1,6 +1,6 @@
 Feature('Filesystem').tag('main');
 
-Scenario('check current dir', (I) => {
+Scenario('check current dir', ({ I }) => {
   I.amInPath('.');
   I.say('hello world');
   I.seeFile('codecept.json');
diff --git a/test/data/sandbox/fs_test.po.js b/test/data/sandbox/fs_test.po.js
index 5dcc894a..ec5c8dfd 100644
--- a/test/data/sandbox/fs_test.po.js
+++ b/test/data/sandbox/fs_test.po.js
@@ -1,6 +1,6 @@
 Feature('Filesystem');
 
-Scenario('check current dir', (I, MyPage) => {
+Scenario('check current dir', ({ I, MyPage }) => {
   I.openDir('aaa');
   I.seeFile('codecept.json');
   MyPage.hasFile('uu');
diff --git a/test/data/sandbox/fs_test_failed.js b/test/data/sandbox/fs_test_failed.js
index daa6206d..cdf2f1de 100644
--- a/test/data/sandbox/fs_test_failed.js
+++ b/test/data/sandbox/fs_test_failed.js
@@ -1,6 +1,6 @@
 Feature('Not-A-Filesystem');
 
-Scenario('file is not in dir', (I) => {
+Scenario('file is not in dir', ({ I }) => {
   I.amInPath('.');
   I.seeFile('not-a-codecept.json');
 });
diff --git a/test/data/sandbox/grep_test.multiple.js b/test/data/sandbox/grep_test.multiple.js
index 56a0ea99..c2fc36c7 100644
--- a/test/data/sandbox/grep_test.multiple.js
+++ b/test/data/sandbox/grep_test.multiple.js
@@ -1,5 +1,5 @@
 Feature('@grep print browser size');
 
-Scenario('print browser size info', (I) => {
+Scenario('print browser size info', ({ I }) => {
   I.printWindowSize();
 });
diff --git a/test/data/sandbox/testevents_test.testevents.js b/test/data/sandbox/testevents_test.testevents.js
index 4f589219..971269fb 100644
--- a/test/data/sandbox/testevents_test.testevents.js
+++ b/test/data/sandbox/testevents_test.testevents.js
@@ -16,42 +16,42 @@ AfterSuite(() => {
   console.log('I\'m simple AfterSuite hook');
 });
 
-BeforeSuite(function* (I) {
+BeforeSuite(function* ({ I }) {
   const text = yield I.stringWithHook('BeforeSuite');
   console.log(text);
 });
 
-Before(function* (I) {
+Before(function* ({ I }) {
   const text = yield I.stringWithHook('Before');
   console.log(text);
 });
 
-After(function* (I) {
+After(function* ({ I }) {
   const text = yield I.stringWithHook('After');
   console.log(text);
 });
 
-AfterSuite(function* (I) {
+AfterSuite(function* ({ I }) {
   const text = yield I.stringWithHook('AfterSuite');
   console.log(text);
 });
 
-BeforeSuite(async (I) => {
+BeforeSuite(async ({ I }) => {
   const text = await I.asyncStringWithHook('BeforeSuite');
   console.log(text);
 });
 
-Before(async (I) => {
+Before(async ({ I }) => {
   const text = await I.asyncStringWithHook('Before');
   console.log(text);
 });
 
-After(async (I) => {
+After(async ({ I }) => {
   const text = await I.asyncStringWithHook('After');
   console.log(text);
 });
 
-AfterSuite(async (I) => {
+AfterSuite(async ({ I }) => {
   const text = await I.asyncStringWithHook('AfterSuite');
   console.log(text);
 });
diff --git a/test/data/sandbox/testhooks_test.testhooks.different.order.js b/test/data/sandbox/testhooks_test.testhooks.different.order.js
index 71f4a0d7..10c8c513 100644
--- a/test/data/sandbox/testhooks_test.testhooks.different.order.js
+++ b/test/data/sandbox/testhooks_test.testhooks.different.order.js
@@ -1,11 +1,11 @@
 Feature('Test hooks');
 
-BeforeSuite(async (I) => {
+BeforeSuite(async ({ I }) => {
   const text = await I.asyncStringWithHook('BeforeSuite');
   console.log(text);
 });
 
-Before(async (I) => {
+Before(async ({ I }) => {
   const text = await I.asyncStringWithHook('Before');
   console.log(text);
 });
@@ -14,12 +14,12 @@ Scenario('Simple test 1', () => {
   console.log('Scenario: It\'s first test');
 });
 
-After(async (I) => {
+After(async ({ I }) => {
   const text = await I.asyncStringWithHook('After');
   console.log(text);
 });
 
-AfterSuite(async (I) => {
+AfterSuite(async ({ I }) => {
   const text = await I.asyncStringWithHook('AfterSuite');
   console.log(text);
 });
diff --git a/test/data/sandbox/testhooks_test.testhooks.js b/test/data/sandbox/testhooks_test.testhooks.js
index a6022f86..d6bc011f 100644
--- a/test/data/sandbox/testhooks_test.testhooks.js
+++ b/test/data/sandbox/testhooks_test.testhooks.js
@@ -16,42 +16,42 @@ AfterSuite(() => {
   console.log('Test: I\'m simple AfterSuite hook');
 });
 
-BeforeSuite(async (I) => {
+BeforeSuite(async ({ I }) => {
   const text = await I.stringWithHook('BeforeSuite');
   console.log(text);
 });
 
-Before(async (I) => {
+Before(async ({ I }) => {
   const text = await I.stringWithHook('Before');
   console.log(text);
 });
 
-After(async (I) => {
+After(async ({ I }) => {
   const text = await I.stringWithHook('After');
   console.log(text);
 });
 
-AfterSuite(async (I) => {
+AfterSuite(async ({ I }) => {
   const text = await I.stringWithHook('AfterSuite');
   console.log(text);
 });
 
-BeforeSuite(async (I) => {
+BeforeSuite(async ({ I }) => {
   const text = await I.asyncStringWithHook('BeforeSuite');
   console.log(text);
 });
 
-Before(async (I) => {
+Before(async ({ I }) => {
   const text = await I.asyncStringWithHook('Before');
   console.log(text);
 });
 
-After(async (I) => {
+After(async ({ I }) => {
   const text = await I.asyncStringWithHook('After');
   console.log(text);
 });
 
-AfterSuite(async (I) => {
+AfterSuite(async ({ I }) => {
   const text = await I.asyncStringWithHook('AfterSuite');
   console.log(text);
 });
diff --git a/test/data/sandbox/tests_test_override.multiple.js b/test/data/sandbox/tests_test_override.multiple.js
index d8cb6c27..cb3271c4 100644
--- a/test/data/sandbox/tests_test_override.multiple.js
+++ b/test/data/sandbox/tests_test_override.multiple.js
@@ -1,5 +1,5 @@
 Feature('print browser size');
 
-Scenario('print browser size info', (I) => {
+Scenario('print browser size info', ({ I }) => {
   I.printWindowSize();
 });
diff --git a/test/data/sandbox/testscenario_test.testscenario.js b/test/data/sandbox/testscenario_test.testscenario.js
index 71f1cae4..43791005 100644
--- a/test/data/sandbox/testscenario_test.testscenario.js
+++ b/test/data/sandbox/testscenario_test.testscenario.js
@@ -4,13 +4,13 @@ Scenario('Simple test', () => {
   console.log('It\'s usual test');
 });
 
-Scenario('Simple async/await test', async (I) => {
+Scenario('Simple async/await test', async ({ I }) => {
   const text = await I.stringWithScenarioType('async/await');
   console.log(text);
 });
 
 // eslint-disable-next-line arrow-parens
-Scenario('Should understand async without brackets', async I => {
+Scenario('Should understand async without brackets', async ({ I }) => {
   const text = await I.stringWithScenarioType('asyncbrackets');
   console.log(text);
 });
diff --git a/test/data/sandbox/workers/base_test.workers.js b/test/data/sandbox/workers/base_test.workers.js
index ea184f92..0b1c6ae5 100644
--- a/test/data/sandbox/workers/base_test.workers.js
+++ b/test/data/sandbox/workers/base_test.workers.js
@@ -1,11 +1,11 @@
 Feature('Workers');
 
-Scenario('say something', (I) => {
+Scenario('say something', ({ I }) => {
   I.say('Hello Workers');
   I.seeThisIsWorker();
 });
 
-Scenario('glob current dir', (I) => {
+Scenario('glob current dir', ({ I }) => {
   I.amInPath('.');
   I.say('hello world');
   I.seeThisIsWorker();
@@ -13,7 +13,7 @@ Scenario('glob current dir', (I) => {
 });
 
 
-Scenario('fail a test', (I) => {
+Scenario('fail a test', ({ I }) => {
   I.amInPath('.');
   I.seeThisIsWorker();
   I.seeFile('notafile');
diff --git a/test/data/sandbox/workers/failing_test.worker.js b/test/data/sandbox/workers/failing_test.worker.js
index 233676c1..3f15495f 100644
--- a/test/data/sandbox/workers/failing_test.worker.js
+++ b/test/data/sandbox/workers/failing_test.worker.js
@@ -4,7 +4,7 @@ Before(() => {
   throw new Error('worker has failed');
 });
 
-Scenario('should not be executed', (I) => {
+Scenario('should not be executed', ({ I }) => {
   I.say('Hello Workers');
   I.seeThisIsWorker();
 });
diff --git a/test/data/sandbox/workers/test_grep.workers.js b/test/data/sandbox/workers/test_grep.workers.js
index 68d5e428..a99d3605 100644
--- a/test/data/sandbox/workers/test_grep.workers.js
+++ b/test/data/sandbox/workers/test_grep.workers.js
@@ -1,11 +1,11 @@
 Feature('@feature_grep in worker');
 
-Scenario('From worker @1_grep print message 1', (I) => {
+Scenario('From worker @1_grep print message 1', ({ I }) => {
   console.log('message 1');
   I.seeThisIsWorker();
 });
 
-Scenario('From worker @2_grep print message 2', (I) => {
+Scenario('From worker @2_grep print message 2', ({ I }) => {
   console.log('message 2');
   I.seeThisIsWorker();
 });
diff --git a/test/runner/definitions_test.js b/test/runner/definitions_test.js
index afc6d48f..cd838e9b 100644
--- a/test/runner/definitions_test.js
+++ b/test/runner/definitions_test.js
@@ -184,11 +184,11 @@ describe('Definitions', function () {
       types.should.be.valid;
 
       const definitionsFile = types.getSourceFileOrThrow(`${codecept_dir}/steps.d.ts`);
-      const CallbackOrder = definitionsFile.getNamespaceOrThrow('CodeceptJS').getInterfaceOrThrow('CallbackOrder').getStructure();
+      const CallbackOrder = definitionsFile.getNamespaceOrThrow('CodeceptJS').getInterfaceOrThrow('SupportObject').getStructure();
       CallbackOrder.properties.should.containSubset([
-        { name: '[0]', type: 'CodeceptJS.I' },
-        { name: '[1]', type: 'MyPage' },
-        { name: '[2]', type: 'SecondPage' },
+        { name: 'I', type: 'CodeceptJS.I' },
+        { name: 'MyPage', type: 'MyPage' },
+        { name: 'SecondPage', type: 'SecondPage' },
       ]);
       done();
     });
diff --git a/test/runner/interface_test.js b/test/runner/interface_test.js
index 971af7a5..95f26a1b 100644
--- a/test/runner/interface_test.js
+++ b/test/runner/interface_test.js
@@ -125,14 +125,12 @@ describe('CodeceptJS Interface', () => {
   it('should run all tests with data of array by only', (done) => {
     exec(config_run_config('codecept.addt.json'), (err, stdout) => {
       const output = stdout.replace(/in [0-9]ms/g, '').replace(/\r/g, '');
-      output.should.include(`Got array item 1
-  ✔ Should log array of strings | {"1"}`);
-
-      output.should.include(`Got array item 2
-  ✔ Should log array of strings | {"2"}`);
-
-      output.should.include(`Got array item 3
-  ✔ Should log array of strings | {"3"}`);
+      output.should.include('Got array item 1');
+      output.should.include('Should log array of strings | {"1"}');
+      output.should.include('Got array item 2');
+      output.should.include('Should log array of strings | {"2"}');
+      output.should.include('Got array item 3');
+      output.should.include('Should log array of strings | {"3"}');
       assert(!err);
       done();
     });
diff --git a/test/unit/parser_test.js b/test/unit/parser_test.js
index 6d990336..70440345 100644
--- a/test/unit/parser_test.js
+++ b/test/unit/parser_test.js
@@ -15,8 +15,15 @@ class Obj {
   async method4(locator, context) {
     return false;
   }
+
+  method5({ locator, sec }) {}
 }
-/* eslint-enable no-unused-vars */
+const fixturesDestructuredArgs = [
+  'function namedFn({locator, sec}) {}',
+  'function * namedFn({locator, sec}) {}',
+  '({locator, sec}) => {}',
+  '({locator, sec}) => {}',
+];
 
 describe('parser', () => {
   const obj = new Obj();
@@ -29,5 +36,18 @@ describe('parser', () => {
     it('should get params for async function', () => {
       expect(parser.getParamsToString(obj.method4)).to.eql('locator, context');
     });
+    fixturesDestructuredArgs.forEach(arg => {
+      it(`should get params for anonymous function with destructured args | ${arg}`, () => {
+        expect(parser.getParams(arg)).to.eql(['locator', 'sec']);
+      });
+    });
+
+    it('should get params for anonymous function with destructured args', () => {
+      expect(parser.getParams(({ locator, sec }, { first, second }) => {})).to.eql(['locator', 'sec', 'first', 'second']);
+    });
+
+    it('should get params for class method with destructured args', () => {
+      expect(parser.getParams(obj.method5)).to.eql(['locator', 'sec']);
+    });
   });
 });