Repository: Codeception/CodeceptJS (js)

Issue:
Output dir doesn't auto create when tests run by command 'codeceptjs run-workers 5'
#### What are you trying to achieve?
I want to see my tests passed after running like `codeceptjs run-workers 5` and save screenshot 
#### What do you get instead?
instead I get error ` ENOENT: no such file or directory, open '/Users/WebstormProjects/e2e/.codeceptjs-output/test.png'`
because output dir was not created

### Details

* CodeceptJS version: 2.3.6
* Operating System: macos
* puppeteer

```js
exports.config = {
    tests: './user-scenario/**/*.test.ts',
    output: '../../.codeceptjs-output',
    plugins: {
        customLocator: {
            enabled: true,
            attribute: 'data-test',
            showActual: true,
        },
    },
    helpers: {
        Puppeteer: {
            waitForTimeout: 3000,
            waitForAction: 200,
            show: !!process.env.VISUAL,
            chrome: {
                args: ['--no-sandbox', '--disable-setuid-sandbox'],
                defaultViewport: {
                    width: 1280,
                    height: 960,
                },
                executablePath: process.env.CHROMIUM_PATH,
            },
        },
    },
    include: {
        I: './settings/steps.js',
    },
    bootstrap: null,
    mocha: {},
    name: 'e2e',
    require: ['ts-node/e2e'],
};
```


Pull request description:
(fix): no custom output folder is created with run-worker
## Motivation/Description of the PR
- Resolves #2154 

## Type of change

- [ ] Breaking changes
- [ ] New functionality
- [x] Bug fix
- [ ] Documentation changes/updates
- [ ] Hot fix
- [ ] Markdown files fix - not related to source code

## Checklist:

- [x] Tests have been added
- [ ] Documentation has been added (Run `npm run docs`)
- [x] Lint checking (Run `npm run lint`)
- [ ] Local tests are passed (Run `npm test`)


Patch:
diff --git a/.gitignore b/.gitignore
index 33ba18d1..89adfd13 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,6 +12,7 @@ docs/build
 test/data/output
 test/acceptance/output
 examples/output
+examples/selenoid-example/output
 test/data/app/db
 test/data/sandbox/steps.d.ts
 testpullfilecache*
@@ -19,4 +20,4 @@ testpullfilecache*
 package-lock.json
 yarn.lock
 /.vs
-typings/types.d.ts
\ No newline at end of file
+typings/types.d.ts
diff --git a/docs/changelog.md b/docs/changelog.md
index 7caca9bd..b34b3d0e 100644
--- a/docs/changelog.md
+++ b/docs/changelog.md
@@ -7,6 +7,10 @@ layout: Section
 
 # Releases
 
+## 2.4.1
+
+* **[Hotfix]** - Add missing lib that prevents codeceptjs from initializing.
+
 ## 2.4.0
 
 * Improved setup wizard with `npx codecept init`:
diff --git a/docs/translation.md b/docs/translation.md
index bfdc595c..e0d85655 100644
--- a/docs/translation.md
+++ b/docs/translation.md
@@ -75,7 +75,7 @@ Add to config
 Now you can write test like this:
 
 ```js
-Scenario('Effettuare il Login su GitHub', (io) => {
+Caratteristica('Effettuare il Login su GitHub', (io) => {
     io.sono_sulla_pagina('https://github.com/login');
     io.compilo_il_campo("Username or email address", "giuseppe-santoro");
     io.compilo_il_campo("Password", "*********");
@@ -177,6 +177,14 @@ Create translation file like this:
 ```js
 module.exports = {
   I: '',
+  contexts: {
+    Feature: 'Feature',
+    Scenario: 'Szenario',
+    Before: 'Vor',
+    After: 'Nach',
+    BeforeSuite: 'vor_der_suite',
+    AfterSuite: 'nach_der_suite',
+  },
   actions: {
     click: 'Klicken',
     wait: 'Wartenn',
diff --git a/docs/videos.md b/docs/videos.md
index f747539c..1aaf7b2e 100644
--- a/docs/videos.md
+++ b/docs/videos.md
@@ -7,9 +7,13 @@ editLink: false
 ---
 
 > Add your own videos to our [Wiki Page](https://github.com/Codeception/CodeceptJS/wiki/Videos)
-## [CodeceptJS Official YouTube Channel](https://www.youtube.com/channel/UCEs4030bmtonyDhTHEXa_2g)
+[![](http://i3.ytimg.com/vi/BRMWstiOTks/maxresdefault.jpg)](https://www.youtube.com/watch?v=BRMWstiOTks)
 
-## [Introductory Videos](https://www.youtube.com/watch?v=FPFG1rBNJ64&list=PLcFXthgti9Lt4SjSvL1ALDg6dOeTC0TvT)
+* ## [An Introduction, Getting started and working with CodeceptJS & Puppeteer (EAWeekend)](https://www.youtube.com/watch?v=BRMWstiOTks)
+
+* ## [CodeceptJS Official YouTube Channel](https://www.youtube.com/channel/UCEs4030bmtonyDhTHEXa_2g)
+
+* ## [Introductory Videos](https://www.youtube.com/watch?v=FPFG1rBNJ64&list=PLcFXthgti9Lt4SjSvL1ALDg6dOeTC0TvT)
 
 Free educational videos provided by our community member **[@ontytoom](http://github.com/ontytoom)**.
 
diff --git a/examples/selenoid-example/browsers.json b/examples/selenoid-example/browsers.json
new file mode 100644
index 00000000..d715f44c
--- /dev/null
+++ b/examples/selenoid-example/browsers.json
@@ -0,0 +1,22 @@
+{
+    "chrome": {
+        "default": "latest",
+        "versions": {
+            "latest": {
+                "image": "selenoid/chrome:latest",
+                "port": "4444",
+                "path": "/"
+            }
+        }
+    },
+    "firefox": {
+        "default": "latest",
+        "versions": {
+            "latest": {
+                "image": "selenoid/firefox:latest",
+                "port": "4444",
+                "path": "/wd/hub"
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/examples/selenoid-example/codecept.conf.js b/examples/selenoid-example/codecept.conf.js
new file mode 100644
index 00000000..541100a0
--- /dev/null
+++ b/examples/selenoid-example/codecept.conf.js
@@ -0,0 +1,26 @@
+exports.config = {
+  tests: './*_test.js',
+  output: './output',
+  helpers: {
+    WebDriver: {
+      url: 'http://localhost',
+      browser: 'chrome',
+    },
+  },
+
+  plugins: {
+    selenoid: {
+      enabled: true,
+      deletePassed: true,
+      autoCreate: true,
+      autoStart: true,
+      sessionTimeout: '30m',
+      enableVideo: true,
+      enableLog: true,
+    },
+  },
+  include: {},
+  bootstrap: null,
+  mocha: {},
+  name: 'example',
+};
diff --git a/lib/command/run-workers.js b/lib/command/run-workers.js
index 938eb6b4..d95c3430 100644
--- a/lib/command/run-workers.js
+++ b/lib/command/run-workers.js
@@ -2,11 +2,12 @@
 
 const { Suite, Test, reporters: { Base } } = require('mocha');
 const path = require('path');
+const mkdirp = require('mkdirp');
 
 const { satisfyNodeVersion, getConfig, getTestRoot } = require('./utils');
 const Codecept = require('../codecept');
 const Container = require('../container');
-const { tryOrDefault } = require('../utils');
+const { tryOrDefault, fileExists } = require('../utils');
 const output = require('../output');
 const event = require('../event');
 const runHook = require('../hooks');
@@ -59,6 +60,13 @@ module.exports = function (workers, options) {
   output.print(`Running tests in ${output.styles.bold(workers)} workers...`);
   output.print();
 
+  const outputDir = path.isAbsolute(config.output) ? config.output : path.join(testRoot, config.output);
+
+  if (!fileExists(outputDir)) {
+    output.print(`creating output directory: ${outputDir}`);
+    mkdirp.sync(outputDir);
+  }
+
   // run bootstrap all
   runHook(config.bootstrapAll, () => {
     const workerList = createWorkers(groups, options, testRoot);
diff --git a/lib/helper/Puppeteer.js b/lib/helper/Puppeteer.js
index e489a2a7..c7feb452 100644
--- a/lib/helper/Puppeteer.js
+++ b/lib/helper/Puppeteer.js
@@ -2147,7 +2147,7 @@ async function proceedSee(assertType, text, context, strict = false) {
     const locator = new Locator(context, 'css');
     description = `element ${locator.toString()}`;
     const els = await this._locate(locator);
-    assertElementExists(els, 'context element');
+    assertElementExists(els, locator.toString());
     allText = await Promise.all(els.map(el => el.getProperty('innerText').then(p => p.jsonValue())));
   }
 
diff --git a/lib/plugin/allure.js b/lib/plugin/allure.js
index b036e80d..93d497f3 100644
--- a/lib/plugin/allure.js
+++ b/lib/plugin/allure.js
@@ -244,6 +244,13 @@ module.exports = (config) => {
     }
   });
 
+  event.dispatcher.on(event.step.comment, (step) => {
+    reporter.startStep(step.toString());
+    currentStep = step;
+    reporter.endStep('passed');
+    currentStep = null;
+  });
+
   event.dispatcher.on(event.step.passed, (step) => {
     if (currentStep === step) {
       reporter.endStep('passed');
diff --git a/lib/plugin/screenshotOnFail.js b/lib/plugin/screenshotOnFail.js
index a7e0a2ce..294feec7 100644
--- a/lib/plugin/screenshotOnFail.js
+++ b/lib/plugin/screenshotOnFail.js
@@ -90,7 +90,7 @@ module.exports = function (config) {
       }
       if (test.ctx && test.ctx.test && test.ctx.test.type === 'hook') fileName = clearString(`${test.title}_${test.ctx.test.title}`);
       if (options.uniqueScreenshotNames && test) {
-        const uuid = test.uuid || test.ctx.test.uuid || Math.floor(new Date().getTime() / 1000);
+        const uuid = _getUUID(test);
         fileName = `${fileName.substring(0, 10)}_${uuid}.failed.png`;
       } else {
         fileName += '.failed.png';
@@ -131,4 +131,16 @@ module.exports = function (config) {
       }
     }, true);
   });
+
+  function _getUUID(test) {
+    if (test.uuid) {
+      return test.uuid;
+    }
+
+    if (test.ctx) {
+      return test.ctx.test.uuid;
+    }
+
+    return Math.floor(new Date().getTime() / 1000);
+  }
 };
diff --git a/lib/plugin/selenoid.js b/lib/plugin/selenoid.js
new file mode 100644
index 00000000..3f2514a0
--- /dev/null
+++ b/lib/plugin/selenoid.js
@@ -0,0 +1,290 @@
+const util = require('util');
+const path = require('path');
+const fs = require('fs');
+const axios = require('axios').default;
+const exec = util.promisify(require('child_process').exec);
+const {
+  container, event, recorder, output,
+} = require('../index');
+
+const defaultBrowserConfig = {
+  chrome: {
+    default: 'latest',
+    versions: {
+      latest: {
+        image: 'selenoid/chrome:latest',
+        port: '4444',
+        path: '/',
+      },
+    },
+  },
+  firefox: {
+    default: 'latest',
+    versions: {
+      latest: {
+        image: 'selenoid/firefox:latest',
+        port: '4444',
+        path: '/wd/hub',
+      },
+    },
+  },
+};
+
+const dockerCreateScriptArr = [
+  'docker create --rm --name $name$',
+  '-p $port$:4444',
+  '-v /var/run/docker.sock:/var/run/docker.sock',
+  `-v ${global.codecept_dir}/:/etc/selenoid/:ro`,
+  `-v ${global.output_dir}/video/:/opt/selenoid/video/`,
+  `-v ${global.output_dir}/logs/:/opt/selenoid/logs/`,
+  `-e OVERRIDE_VIDEO_OUTPUT_DIR=${global.output_dir}/video/`,
+  '$additionalParams$',
+  'aerokube/selenoid:latest-release -log-output-dir /opt/selenoid/logs',
+];
+
+const dockerImageCheckScript = [
+  'docker images',
+  '--filter reference=\'selenoid/video-recorder\'',
+  '--filter reference=\'selenoid/chrome:latest\'',
+  '--filter reference=\'selenoid/firefox:latest\'',
+].join(' ');
+
+let dockerCreateScript = dockerCreateScriptArr.join(' ');
+let dockerStartScript = 'docker start $name$';
+let dockerStopScript = 'docker stop $name$';
+let seleniumUrl = 'http://localhost:$port$';
+
+const supportedHelpers = ['WebDriver'];
+const SELENOID_START_TIMEOUT = 2000;
+const SELENOID_STOP_TIMEOUT = 10000;
+
+const createSelenoidConfig = () => {
+  const configPath = path.join(global.codecept_dir, 'browsers.json');
+  return new Promise((res, rej) => {
+    try {
+      if (fs.existsSync(configPath)) {
+        res(true);
+      } else {
+        const data = new Uint8Array(Buffer.from(JSON.stringify(defaultBrowserConfig)));
+        fs.writeFileSync(configPath, data);
+        res(true);
+      }
+    } catch (err) {
+      rej(err.stack);
+    }
+  });
+};
+
+const createSelenoid = () => exec(dockerCreateScript);
+
+const startSelenoid = () => exec(dockerStartScript);
+
+const stopSelenoid = () => exec(dockerStopScript);
+
+const checkDockerImage = () => exec(dockerImageCheckScript);
+
+const pullImage = async () => {
+  const { stdout } = await checkDockerImage();
+  let resultPromise = Promise.resolve();
+
+  if (!stdout.includes('selenoid/video-recorder')) {
+    resultPromise = resultPromise.then(() => exec('docker pull selenoid/video-recorder:latest-release'));
+  }
+  if (!stdout.includes('selenoid/chrome')) {
+    resultPromise = resultPromise.then(() => exec('docker pull selenoid/chrome:latest'));
+  }
+  if (!stdout.includes('selenoid/firefox')) {
+    resultPromise = resultPromise.then(() => exec('docker pull selenoid/firefox:latest'));
+  }
+
+  return resultPromise;
+};
+
+const wait = time => new Promise((res) => setTimeout(() => res(), time));
+
+const createAndStart = (autoCreate) => {
+  const selenoidCreated = autoCreate ? createSelenoidConfig().then(createSelenoid).then(pullImage) : Promise.resolve();
+  return selenoidCreated.then(startSelenoid).then(() => wait(SELENOID_START_TIMEOUT));
+};
+
+const deletePassedTests = (passedTests) => {
+  const deleteVideoPromiseList = passedTests.map(test => axios.delete(`${seleniumUrl}/video/${test}.mp4`));
+  const deleteLogPromiseList = passedTests.map(test => axios.delete(`${seleniumUrl}/logs/${test}.log`));
+
+  return Promise.all(deleteVideoPromiseList.concat(deleteLogPromiseList))
+    .then(() => output.debug('Deleted videos and logs for all passed tests'))
+    .catch(err => output.error('Error while deleting video and log files', err.stack));
+};
+
+const setOptionsForWebdriver = (config) => {
+  const WebDriver = container.helpers('WebDriver');
+  WebDriver._setConfig(Object.assign(WebDriver.options, {
+    capabilities: { 'selenoid:options': config },
+  }));
+};
+
+const setTestConfigForWebdriver = (test) => {
+  const WebDriver = container.helpers('WebDriver');
+  const { options } = WebDriver;
+  recorder.add('setting selenoid capabilities', () => {
+    options.capabilities['selenoid:options'].name = test.title;
+    options.capabilities['selenoid:options'].videoName = `${test.title}.mp4`;
+    options.capabilities['selenoid:options'].logName = `${test.title}.log`;
+    WebDriver._setConfig(options);
+  });
+};
+
+const replaceScriptConfig = (config) => {
+  for (const key of Object.keys(config)) {
+    dockerCreateScript = dockerCreateScript.replace(new RegExp(`\\$${key}\\$`, 'g'), config[key]);
+  }
+  dockerStartScript = dockerStartScript.replace('$name$', config.name);
+  dockerStopScript = dockerStopScript.replace('$name$', config.name);
+  seleniumUrl = seleniumUrl.replace('$port$', config.port);
+};
+
+
+/**
+ * # Selenoid
+ *  Selenoid plugin with for recording video recording and logs
+ *  ## Prerequisite
+ *   - Docker
+ *  ## How to ?
+ *
+ *  Selenoid plugin can be enabled in two ways.
+ *  1. **Automatic** - Codecept will create and manage selenoid container for you.
+ *  2. **Manual** - You can create the conatainer and configure the details in codecept
+ *
+ *  ### 1. Automatic
+ *
+ *  Add plugin configuration in codecept conf and run codecept.
+ * ```js
+ * plugins: {
+ *     selenoid: {
+ *       enabled: true,
+ *       deletePassed: true,
+ *       autoCreate: true,
+ *       autoStart: true,
+ *       sessionTimeout: '30m',
+ *       enableVideo: true,
+ *       enableLog: true,
+ *     },
+ *   }
+ * ```
+ *
+ *  ### 2. Manual
+ * Simplest way to to this is enabling `autoCreate` and it will be taken care by codecept itself.
+ * **If you are using windows machine or if `autoCreate` does not work properly, create container manually**
+ *
+ * #### 1. Add browsers.json parallel to codecept conf location. [Refer here](https://aerokube.com/selenoid/latest/#_prepare_configuration) to know more about browsers.json.
+ *
+ *  *Sample browsers.json*
+ * ```
+ * {
+ *  "chrome": {
+ *    "default": "latest",
+ *    "versions": {
+ *      "latest": {
+ *        "image": "selenoid/chrome:latest",
+ *        "port": "4444",
+ *        "path": "/"
+ *      }
+ *    }
+ *  }
+ * }
+ * ```
+ * **Note** : *You can download the sample json from example.*
+ *
+ * #### Options:
+ * | Param | Description |
+ * |--|--|
+ * | name | Name of the container (default : selenoid) |
+ * | port | Port of selenium server (default : 4444) |
+ * | deletePassed | Delete video and logs of passed tests (default : true)|
+ * | autoCreate | Will automatically create container (Linux only) (default : true)|
+ * | autoStart | If disabled start the container manually before running tests (default : true)|
+ * | enableVideo | Enable video recording (`video` folder of output)|
+ * | enableLog | Enable video recording (`logs` folder of output)|
+ * | additionalParams | example: `additionalParams: '--env TEST=test'` [Refer here](https://docs.docker.com/engine/reference/commandline/create/) to know more |
+ *  #### 2. Create selenoid container
+ *  Run the following command to create one. To know more [refer here](https://aerokube.com/selenoid/latest/#_option_2_start_selenoid_container)
+ * ```
+ * docker create                                    \
+ * --name selenoid                                  \
+ * -p 4444:4444                                     \
+ * -v /var/run/docker.sock:/var/run/docker.sock     \
+ * -v `pwd`/:/etc/selenoid/:ro                      \
+ * -v `pwd`/output/video/:/opt/selenoid/video/      \
+ * -e OVERRIDE_VIDEO_OUTPUT_DIR=`pwd`/output/video/ \
+ * aerokube/selenoid:latest-release
+ * ```
+ */
+
+const selenoid = (config) => {
+  const helpers = container.helpers();
+  let helperName;
+
+  for (const name of supportedHelpers) {
+    if (Object.keys(helpers).indexOf(name) > -1) {
+      helperName = name;
+    }
+  }
+
+  if (!helperName) {
+    output.print(`Selenoid plugin supported only for : ${supportedHelpers.toString()}`);
+    return; // no helpers for Selenoid
+  }
+
+  const {
+    autoStart,
+    name = 'selenoid',
+    deletePassed = true,
+    additionalParams = '',
+    autoCreate = true,
+    port = 4444,
+  } = config;
+  const passedTests = [];
+
+  recorder.startUnlessRunning();
+  replaceScriptConfig({ name, additionalParams, port });
+
+  if (autoStart) {
+    event.dispatcher.on(event.all.before, () => {
+      recorder.add('Starting selenoid', () => {
+        return createAndStart(autoCreate)
+          .then(() => output.debug('Selenoid started'))
+          .catch((err) => { throw new Error(err.stack); });
+      });
+    });
+
+    event.dispatcher.on(event.all.after, () => {
+      recorder.add('Stopping selenoid', () => {
+        return wait(SELENOID_STOP_TIMEOUT)
+          .then(() => deletePassedTests(passedTests))
+          .then(stopSelenoid)
+          .then(() => output.debug('Selenoid stopped'))
+          .catch((err) => { throw new Error(err.stack); });
+      });
+    });
+  }
+
+  event.dispatcher.on(event.all.before, () => {
+    switch (helperName) {
+      case 'WebDriver': setOptionsForWebdriver(config); break;
+    }
+  });
+
+  event.dispatcher.on(event.test.before, (test) => {
+    switch (helperName) {
+      case 'WebDriver': setTestConfigForWebdriver(test); break;
+    }
+  });
+
+  if (deletePassed) {
+    event.dispatcher.on(event.test.passed, (test) => {
+      passedTests.push(test.title);
+    });
+  }
+};
+
+module.exports = selenoid;
diff --git a/lib/ui.js b/lib/ui.js
index 45c3c735..54ae1734 100644
--- a/lib/ui.js
+++ b/lib/ui.js
@@ -6,6 +6,20 @@ const scenario = require('./scenario');
 const ScenarioConfig = require('./interfaces/scenarioConfig');
 const FeatureConfig = require('./interfaces/featureConfig');
 const addDataContext = require('./data/context');
+const container = require('./container');
+
+
+const setContextTranslation = (context) => {
+  const contexts = container.translation().value('contexts');
+
+  if (contexts) {
+    for (const key of Object.keys(contexts)) {
+      if (context[key]) {
+        context[contexts[key]] = context[key];
+      }
+    }
+  }
+};
 
 /**
  * Codecept-style interface:
@@ -167,6 +181,12 @@ module.exports = function (suite) {
       return context.Scenario(title, { ...opts, skipInfo });
     };
 
+    /**
+     * For translation
+     */
+
+    setContextTranslation(context);
+
     addDataContext(context);
   });
 
diff --git a/package.json b/package.json
index 896aed90..3194c589 100644
--- a/package.json
+++ b/package.json
@@ -93,7 +93,7 @@
     "chai": "^3.4.1",
     "chai-as-promised": "^5.2.0",
     "chai-subset": "^1.6.0",
-    "documentation": "^8.1.2",
+    "documentation": "^12.1.4",
     "eslint": "^6.8.0",
     "eslint-config-airbnb-base": "^14.0.0",
     "eslint-plugin-import": "^2.19.1",
@@ -111,7 +111,7 @@
     "puppeteer": "^1.20.0",
     "qrcode-terminal": "^0.12.0",
     "rosie": "^1.6.0",
-    "runio.js": "^1.0.19",
+    "runio.js": "^1.0.20",
     "sinon": "^1.17.2",
     "sinon-chai": "^2.14.0",
     "testcafe": "^1.7.1",
diff --git a/translations/it-IT.js b/translations/it-IT.js
index 5619406f..ab769e96 100644
--- a/translations/it-IT.js
+++ b/translations/it-IT.js
@@ -1,5 +1,13 @@
 module.exports = {
   I: 'io',
+  contexts: {
+    Feature: 'Caratteristica',
+    Scenario: 'lo_scenario',
+    Before: 'Prima',
+    After: 'Dopo',
+    BeforeSuite: 'Prima_della_suite',
+    AfterSuite: 'Dopo_la_suite',
+  },
   actions: {
     amOutsideAngularApp: 'sono_fuori_dalla_applicazione_angular',
     amInsideAngularApp: 'sono_dentro_la_applicazione_angular',


Test patch:
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
new file mode 100644
index 00000000..5818c4c9
--- /dev/null
+++ b/.github/workflows/test.yml
@@ -0,0 +1,21 @@
+name: Run Unit tests
+
+on: [push]
+
+jobs:
+  build:
+
+    runs-on: ubuntu-18.04
+
+    strategy:
+      matrix:
+        node-version: [10.x, 12.x]
+
+    steps:
+    - uses: actions/checkout@v1
+    - name: Use Node.js ${{ matrix.node-version }}
+      uses: actions/setup-node@v1
+      with:
+        node-version: ${{ matrix.node-version }}
+    - run: npm install
+    - run: npm test
diff --git a/examples/selenoid-example/git_test.js b/examples/selenoid-example/git_test.js
new file mode 100644
index 00000000..f9272a59
--- /dev/null
+++ b/examples/selenoid-example/git_test.js
@@ -0,0 +1,16 @@
+Feature('Git');
+
+Scenario('Demo Test Github', (I) => {
+  I.amOnPage('https://github.com/login');
+  I.see('GitHub');
+  I.fillField('login', 'randomuser_kmk');
+  I.fillField('password', 'randomuser_kmk');
+  I.click('Sign in');
+  I.see('Repositories');
+});
+
+Scenario('Demo Test GitLab', (I) => {
+  I.amOnPage('https://gitlab.com');
+  I.dontSee('GitHub');
+  I.see('GitLab');
+});
diff --git a/test/acceptance/mock_test.js b/test/acceptance/mock_test.js
index f5ade635..71d6481b 100644
--- a/test/acceptance/mock_test.js
+++ b/test/acceptance/mock_test.js
@@ -58,7 +58,7 @@ Scenario(
     I.stopMocking();
 
     I.click('GET COMMENTS');
-    I.waitForText('laudantium');
+    I.waitForText('laudantium', 3);
     I.dontSee('_uniqueId_u4805sd23', '#data');
   },
 );
diff --git a/test/data/sandbox/codecept.workers-custom-output-folder-name.conf.js b/test/data/sandbox/codecept.workers-custom-output-folder-name.conf.js
new file mode 100644
index 00000000..a89ec673
--- /dev/null
+++ b/test/data/sandbox/codecept.workers-custom-output-folder-name.conf.js
@@ -0,0 +1,15 @@
+exports.config = {
+  tests: './workers/*.js',
+  timeout: 10000,
+  output: './thisIsCustomOutputFolderName',
+  helpers: {
+    FileSystem: {},
+    Workers: {
+      require: './workers_helper',
+    },
+  },
+  include: {},
+  bootstrap: {},
+  mocha: {},
+  name: 'sandbox',
+};
diff --git a/test/data/sandbox/configs/translation/codecept.conf.js b/test/data/sandbox/configs/translation/codecept.conf.js
new file mode 100644
index 00000000..020f9ea7
--- /dev/null
+++ b/test/data/sandbox/configs/translation/codecept.conf.js
@@ -0,0 +1,9 @@
+exports.config = {
+  tests: './*_test.js',
+  output: './output',
+  include: {},
+  bootstrap: null,
+  mocha: {},
+  translation: 'it-IT',
+  name: 'translation-test',
+};
diff --git a/test/data/sandbox/configs/translation/translation_test.js b/test/data/sandbox/configs/translation/translation_test.js
new file mode 100644
index 00000000..b26651fd
--- /dev/null
+++ b/test/data/sandbox/configs/translation/translation_test.js
@@ -0,0 +1,18 @@
+Caratteristica('DevTo');
+
+Prima((I) => {
+  console.log('Before');
+});
+
+
+lo_scenario('Simple translation test', (io) => {
+  console.log('Simple test');
+});
+
+Scenario('Simple translation test 2', (I) => {
+  console.log('Simple test 2');
+});
+
+Dopo((I) => {
+  console.log('After');
+});
diff --git a/test/runner/run_workers_test.js b/test/runner/run_workers_test.js
index 1cd05074..4479a417 100644
--- a/test/runner/run_workers_test.js
+++ b/test/runner/run_workers_test.js
@@ -8,7 +8,6 @@ const codecept_dir = path.join(__dirname, '/../data/sandbox');
 const codecept_run = `${runner} run-workers --config ${codecept_dir}/codecept.workers.conf.js `;
 const codecept_run_glob = config => `${runner} run-workers --config ${codecept_dir}/${config} `;
 
-
 describe('CodeceptJS Workers Runner', function () {
   this.timeout(40000);
 
@@ -111,4 +110,27 @@ describe('CodeceptJS Workers Runner', function () {
       done();
     });
   });
+
+  it('should create output folder with custom name', function (done) {
+    const fs = require('fs');
+    const customName = 'thisIsCustomOutputFolderName';
+    const outputDir = `${codecept_dir}/${customName}`;
+    let createdOutput = false;
+
+    if (fs.existsSync(outputDir)) {
+      fs.rmdirSync(outputDir, { recursive: true });
+    }
+
+    if (!semver.satisfies(process.version, '>=11.7.0')) this.skip('not for node version');
+    const configFileName = 'codecept.workers-custom-output-folder-name.conf.js';
+    exec(`${codecept_run_glob(configFileName)} 2 --grep "grep" --debug`, (err, stdout, stderr) => {
+      stdout.should.include(customName);
+      if (fs.existsSync(outputDir)) {
+        createdOutput = true;
+      }
+      assert(createdOutput, 'The output folder is not created');
+      assert(!err);
+      done();
+    });
+  });
 });
diff --git a/test/runner/translation_test.js b/test/runner/translation_test.js
new file mode 100644
index 00000000..bd847332
--- /dev/null
+++ b/test/runner/translation_test.js
@@ -0,0 +1,16 @@
+const path = require('path');
+const exec = require('child_process').exec;
+const assert = require('assert');
+
+const runner = path.join(__dirname, '/../../bin/codecept.js');
+const codecept_dir = path.join(__dirname, '/../data/sandbox/configs/translation');
+const codecept_run = `${runner} run --config ${codecept_dir}/codecept.conf.js `;
+
+describe('Translation', () => {
+  it('Should run translated test file', (done) => {
+    exec(`${codecept_run}`, (err, stdout) => {
+      assert(!err);
+      done();
+    });
+  });
+});
diff --git a/test/unit/container_test.js b/test/unit/container_test.js
index 5daf3741..95d98689 100644
--- a/test/unit/container_test.js
+++ b/test/unit/container_test.js
@@ -38,6 +38,13 @@ describe('Container', () => {
       container.translation().I.should.eql('Я');
       container.translation().actionAliasFor('see').should.eql('вижу');
     });
+    it('should have translations for context', () => {
+      container.create({ translation: 'it-IT' });
+      container.translation().should.be.instanceOf(Translation);
+      container.translation().loaded.should.be.true;
+      container.translation().I.should.eql('io');
+      container.translation().value('contexts').Feature.should.eql('Caratteristica');
+    });
   });
 
   describe('#helpers', () => {