Repository: codeceptjs/CodeceptJS (js)

Issue:
Video name
#### What are you trying to achieve?

Having a video for a failed test with the scenario name, as for screenshots.

#### What do you get instead?

File names with ids similar to "2f6507fa202d0d3e5d8cc2a55850a285.webm"

### Details

* CodeceptJS version: 3.3.5 
* NodeJS Version: 16.10.0
* Operating System: Win 10 Pro
* playwright
* Configuration file:

```js
require('ts-node/register')
const { setHeadlessWhen } = require('@codeceptjs/configure')

// turn on headless mode when running with HEADLESS=true environment variable
// HEADLESS=true npx codecept run
setHeadlessWhen(process.env.TEST_HEADLESS === 'true')

exports.config = {
	tests: './tests/*/**_test.ts',
	output: './tests/output',
	timeout: 240, // limit all tests in all suites to 240 secs
	helpers: {
		Playwright: {
			browser: 'chromium',
			show: true,
			url:'http://localhost:3030',
			video: true,
			trace: false,
			// keepTraceForPassedTests: true,
			waitForAction: 2000,
			waitForTimeout: 30000,
			windowSize: '1800x920',
		},
	},
	bootstrapAll: require('./presettings.ts').bootstrapAll,
	bootstrap: require('./presettings.ts').bootstrap,
	teardownAll: require('./presettings').teardownAll,
	teardown: require('./presettings').teardown,
	plugins: {
		subtitles: {
			enabled: true
		},
		pauseOnFail: {},
		retryFailedStep: {
			enabled: true,
		},
		tryTo: {
			enabled: true,
		},
		screenshotOnFail: {
			enabled: true,
		},
		// coverage: {
		// 	enabled: true,
		// },
	},
}

```


Pull request:
fix(playwright) - video name and missing type
## Motivation/Description of the PR
- Resolves #3402 
- Adding missing type `keepVideoForPassedTests`
![Screen Shot 2022-09-29 at 11 19 49 AM](https://user-images.githubusercontent.com/7845001/192993377-07b33546-b6a7-4663-887d-028e6a669490.png)


Applicable helpers:
- [x] Playwright

## Type of change
- [x] :bug: Bug fix

## Checklist:

- [ ] 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/docs/helpers/Playwright.md b/docs/helpers/Playwright.md
index cc25fa63..fc2e2239 100644
--- a/docs/helpers/Playwright.md
+++ b/docs/helpers/Playwright.md
@@ -49,6 +49,7 @@ Type: [object][5]
 -   `disableScreenshots` **[boolean][25]?** don't save screenshot on failure.
 -   `emulate` **any?** browser in device emulation mode.
 -   `video` **[boolean][25]?** enables video recording for failed tests; videos are saved into `output/videos` folder
+-   `keepVideoForPassedTests` **[boolean][25]?** save videos for passed tests; videos are saved into `output/videos` folder
 -   `trace` **[boolean][25]?** record [tracing information][34] with screenshots and snapshots.
 -   `fullPageScreenshots` **[boolean][25]?** make full page screenshots on failure.
 -   `uniqueScreenshotNames` **[boolean][25]?** option to prevent screenshot override if you have scenarios with the same name in different suites.
@@ -66,7 +67,6 @@ Type: [object][5]
 -   `locale` **[string][7]?** locale string. Example: 'en-GB', 'de-DE', 'fr-FR', ...
 -   `manualStart` **[boolean][25]?** do not start browser before a test, start it manually inside a helper with `this.helpers["Playwright"]._startBrowser()`.
 -   `chromium` **[object][5]?** pass additional chromium options
--   `firefox` **[object][5]?** pass additional firefox options
 -   `electron` **[object][5]?** (pass additional electron options
 -   `channel` **any?** (While Playwright can operate against the stock Google Chrome and Microsoft Edge browsers available on the machine. In particular, current Playwright version will support Stable and Beta channels of these browsers. See [Google Chrome & Microsoft Edge][36].
 
diff --git a/docs/helpers/WebDriver.md b/docs/helpers/WebDriver.md
index 0ae7ed8a..77f68b52 100644
--- a/docs/helpers/WebDriver.md
+++ b/docs/helpers/WebDriver.md
@@ -30,11 +30,11 @@ Type: [object][16]
 -   `browser` **[string][17]** browser in which to perform testing.
 -   `basicAuth` **[string][17]?** (optional) the basic authentication to pass to base url. Example: {username: 'username', password: 'password'}
 -   `host` **[string][17]?** WebDriver host to connect.
--   `port` **[string][17]?** WebDriver port to connect.
+-   `port` **[number][20]?** WebDriver port to connect.
 -   `protocol` **[string][17]?** protocol for WebDriver server.
 -   `path` **[string][17]?** path to WebDriver server,
 -   `restart` **[boolean][29]?** restart browser between tests.
--   `smartWait` **[boolean][29]?** **enables [SmartWait][33]**; wait for additional milliseconds for element to appear. Enable for 5 secs: "smartWait": 5000.
+-   `smartWait` **([boolean][29] | [number][20])?** **enables [SmartWait][33]**; wait for additional milliseconds for element to appear. Enable for 5 secs: "smartWait": 5000.
 -   `disableScreenshots` **[boolean][29]?** don't save screenshots on failure.
 -   `fullPageScreenshots` **[boolean][29]?** (optional - make full page screenshots on failure.
 -   `uniqueScreenshotNames` **[boolean][29]?** option to prevent screenshot override if you have scenarios with the same name in different suites.
diff --git a/lib/data/context.js b/lib/data/context.js
index f41d89cc..8b26b76f 100644
--- a/lib/data/context.js
+++ b/lib/data/context.js
@@ -1,6 +1,7 @@
 const { isGenerator } = require('../utils');
 const DataTable = require('./table');
 const DataScenarioConfig = require('./dataScenarioConfig');
+const Secret = require('../secret');
 
 module.exports = function (context) {
   context.Data = function (dataTable) {
@@ -70,6 +71,12 @@ function replaceTitle(title, dataRow) {
   // it should be printed
   if (Object.prototype.toString.call(dataRow.data) === (Object()).toString()
     && dataRow.data.toString() !== (Object()).toString()) {
+    Object.entries(dataRow.data).forEach(entry => {
+      const [key, value] = entry;
+      if (value instanceof Secret) {
+        dataRow.data[key] = value.getMasked();
+      }
+    });
     return `${title} | ${dataRow.data}`;
   }
 
diff --git a/lib/helper/Playwright.js b/lib/helper/Playwright.js
index 17f6a592..297f07f0 100644
--- a/lib/helper/Playwright.js
+++ b/lib/helper/Playwright.js
@@ -62,6 +62,7 @@ const { createValueEngine, createDisabledEngine } = require('./extras/Playwright
  * @prop {boolean} [disableScreenshots=false] - don't save screenshot on failure.
  * @prop {any} [emulate] - browser in device emulation mode.
  * @prop {boolean} [video=false] - enables video recording for failed tests; videos are saved into `output/videos` folder
+ * @prop {boolean} [keepVideoForPassedTests=false] - save videos for passed tests; videos are saved into `output/videos` folder
  * @prop {boolean} [trace=false] - record [tracing information](https://playwright.dev/docs/trace-viewer) with screenshots and snapshots.
  * @prop {boolean} [fullPageScreenshots=false] - make full page screenshots on failure.
  * @prop {boolean} [uniqueScreenshotNames=false] - option to prevent screenshot override if you have scenarios with the same name in different suites.
@@ -79,7 +80,6 @@ const { createValueEngine, createDisabledEngine } = require('./extras/Playwright
  * @prop {string} [locale] - locale string. Example: 'en-GB', 'de-DE', 'fr-FR', ...
  * @prop {boolean} [manualStart] - do not start browser before a test, start it manually inside a helper with `this.helpers["Playwright"]._startBrowser()`.
  * @prop {object} [chromium] - pass additional chromium options
- * @prop {object} [firefox] - pass additional firefox options
  * @prop {object} [electron] - (pass additional electron options
  * @prop {any} [channel] - (While Playwright can operate against the stock Google Chrome and Microsoft Edge browsers available on the machine. In particular, current Playwright version will support Stable and Beta channels of these browsers. See [Google Chrome & Microsoft Edge](https://playwright.dev/docs/browsers/#google-chrome--microsoft-edge).
  */
@@ -549,22 +549,22 @@ class Playwright extends Helper {
   }
 
   /**
-  * Use Playwright API inside a test.
-  *
-  * First argument is a description of an action.
-  * Second argument is async function that gets this helper as parameter.
-  *
-  * { [`page`](https://github.com/microsoft/playwright/blob/main/docs/src/api/class-page.md), [`browserContext`](https://github.com/microsoft/playwright/blob/main/docs/src/api/class-browsercontext.md) [`browser`](https://github.com/microsoft/playwright/blob/main/docs/src/api/class-browser.md) } objects from Playwright API are available.
-  *
-  * ```js
-  * I.usePlaywrightTo('emulate offline mode', async ({ browserContext }) => {
-  *   await browserContext.setOffline(true);
-  * });
-  * ```
-  *
-  * @param {string} description used to show in logs.
-  * @param {function} fn async function that executed with Playwright helper as argumen
-  */
+   * Use Playwright API inside a test.
+   *
+   * First argument is a description of an action.
+   * Second argument is async function that gets this helper as parameter.
+   *
+   * { [`page`](https://github.com/microsoft/playwright/blob/main/docs/src/api/class-page.md), [`browserContext`](https://github.com/microsoft/playwright/blob/main/docs/src/api/class-browsercontext.md) [`browser`](https://github.com/microsoft/playwright/blob/main/docs/src/api/class-browser.md) } objects from Playwright API are available.
+   *
+   * ```js
+   * I.usePlaywrightTo('emulate offline mode', async ({ browserContext }) => {
+   *   await browserContext.setOffline(true);
+   * });
+   * ```
+   *
+   * @param {string} description used to show in logs.
+   * @param {function} fn async function that executed with Playwright helper as argumen
+   */
   usePlaywrightTo(description, fn) {
     return this._useTo(...arguments);
   }
@@ -2030,7 +2030,11 @@ class Playwright extends Helper {
     }
 
     if (this.options.recordVideo && this.page && this.page.video()) {
+      const videoPath = `${global.output_dir}/videos/${clearString(test.title)}.failed.webm`;
       test.artifacts.video = await this.page.video().path();
+      fs.rename(test.artifacts.video, videoPath, (() => {
+        test.artifacts.video = videoPath;
+      }));
     }
 
     if (this.options.trace) {
@@ -2042,8 +2046,13 @@ class Playwright extends Helper {
 
   async _passed(test) {
     if (this.options.recordVideo && this.page && this.page.video()) {
+      const videoPath = `${global.output_dir}/videos/${clearString(test.title)}.passed.webm`;
+
       if (this.options.keepVideoForPassedTests) {
         test.artifacts.video = await this.page.video().path();
+        fs.rename(test.artifacts.video, videoPath, (() => {
+          test.artifacts.video = videoPath;
+        }));
       } else {
         this.page.video().delete().catch(e => {});
       }
@@ -2457,32 +2466,32 @@ class Playwright extends Helper {
   }
 
   /**
-  * Mocks network request using [`browserContext.route`](https://playwright.dev/docs/api/class-browsercontext#browser-context-route) of Playwright
-  *
-  * ```js
-  * I.mockRoute(/(\.png$)|(\.jpg$)/, route => route.abort());
-  * ```
-  * This method allows intercepting and mocking requests & responses. [Learn more about it](https://playwright.dev/docs/network#handle-requests)
-  *
-  * @param {string|RegExp} [url] URL, regex or pattern for to match URL
-  * @param {function} [handler] a function to process reques
-  */
+   * Mocks network request using [`browserContext.route`](https://playwright.dev/docs/api/class-browsercontext#browser-context-route) of Playwright
+   *
+   * ```js
+   * I.mockRoute(/(\.png$)|(\.jpg$)/, route => route.abort());
+   * ```
+   * This method allows intercepting and mocking requests & responses. [Learn more about it](https://playwright.dev/docs/network#handle-requests)
+   *
+   * @param {string|RegExp} [url] URL, regex or pattern for to match URL
+   * @param {function} [handler] a function to process reques
+   */
   async mockRoute(url, handler) {
     return this.browserContext.route(...arguments);
   }
 
   /**
-  * Stops network mocking created by `mockRoute`.
-  *
-  * ```js
-  * I.stopMockingRoute(/(\.png$)|(\.jpg$)/);
-  * I.stopMockingRoute(/(\.png$)|(\.jpg$)/, previouslySetHandler);
-  * ```
-  * If no handler is passed, all mock requests for the rote are disabled.
-  *
-  * @param {string|RegExp} [url] URL, regex or pattern for to match URL
-  * @param {function} [handler] a function to process reques
-  */
+   * Stops network mocking created by `mockRoute`.
+   *
+   * ```js
+   * I.stopMockingRoute(/(\.png$)|(\.jpg$)/);
+   * I.stopMockingRoute(/(\.png$)|(\.jpg$)/, previouslySetHandler);
+   * ```
+   * If no handler is passed, all mock requests for the rote are disabled.
+   *
+   * @param {string|RegExp} [url] URL, regex or pattern for to match URL
+   * @param {function} [handler] a function to process reques
+   */
   async stopMockingRoute(url, handler) {
     return this.browserContext.unroute(...arguments);
   }
diff --git a/lib/helper/WebDriver.js b/lib/helper/WebDriver.js
index 4c9e4bc7..828c55da 100644
--- a/lib/helper/WebDriver.js
+++ b/lib/helper/WebDriver.js
@@ -48,7 +48,7 @@ let version;
  * @prop {string} [protocol=http] - protocol for WebDriver server.
  * @prop {string} [path=/wd/hub] - path to WebDriver server,
  * @prop {boolean} [restart=true] - restart browser between tests.
- * @prop {boolean} [smartWait=false] - **enables [SmartWait](http://codecept.io/acceptance/#smartwait)**; wait for additional milliseconds for element to appear. Enable for 5 secs: "smartWait": 5000.
+ * @prop {boolean|number} [smartWait=false] - **enables [SmartWait](http://codecept.io/acceptance/#smartwait)**; wait for additional milliseconds for element to appear. Enable for 5 secs: "smartWait": 5000.
  * @prop {boolean} [disableScreenshots=false] - don't save screenshots on failure.
  * @prop {boolean} [fullPageScreenshots=false] (optional - make full page screenshots on failure.
  * @prop {boolean} [uniqueScreenshotNames=false] - option to prevent screenshot override if you have scenarios with the same name in different suites.


Test patch:
diff --git a/test/unit/data/ui_test.js b/test/unit/data/ui_test.js
index 9e8052c2..a95d4960 100644
--- a/test/unit/data/ui_test.js
+++ b/test/unit/data/ui_test.js
@@ -5,6 +5,7 @@ const Suite = require('mocha/lib/suite');
 const makeUI = require('../../../lib/ui');
 const addData = require('../../../lib/data/context');
 const DataTable = require('../../../lib/data/table');
+const Secret = require('../../../lib/secret');
 
 describe('ui', () => {
   let suite;
@@ -13,20 +14,23 @@ describe('ui', () => {
 
   beforeEach(() => {
     context = {};
-    suite = new Suite('empty');
+    suite = new Suite('empty', null);
     makeUI(suite);
     suite.emit('pre-require', context, {}, new Mocha());
     addData(context);
 
-    dataTable = new DataTable(['login', 'password']);
+    dataTable = new DataTable(['username', 'password']);
     dataTable.add(['jon', 'snow']);
     dataTable.xadd(['tyrion', 'lannister']);
     dataTable.add(['jaime', 'lannister']);
+    dataTable.add(['Username', new Secret('theSecretPassword')]);
   });
 
   describe('Data', () => {
     it('can add a tag to all scenarios', () => {
-      dataScenarioConfig = context.Data(dataTable).Scenario('scenario', () => {});
+      const dataScenarioConfig = context.Data(dataTable)
+        .Scenario('scenario', () => {
+        });
 
       dataScenarioConfig.tag('@user');
 
@@ -36,7 +40,7 @@ describe('ui', () => {
     });
 
     it('can add a timout to all scenarios', () => {
-      dataScenarioConfig = context.Data(dataTable).Scenario('scenario', () => {});
+      const dataScenarioConfig = context.Data(dataTable).Scenario('scenario', () => {});
 
       dataScenarioConfig.timeout(3);
 
@@ -44,7 +48,7 @@ describe('ui', () => {
     });
 
     it('can add retries to all scenarios', () => {
-      dataScenarioConfig = context.Data(dataTable).Scenario('scenario', () => {});
+      const dataScenarioConfig = context.Data(dataTable).Scenario('scenario', () => {});
 
       dataScenarioConfig.retry(3);
 
@@ -52,7 +56,7 @@ describe('ui', () => {
     });
 
     it('can expect failure for all scenarios', () => {
-      dataScenarioConfig = context.Data(dataTable).Scenario('scenario', () => {});
+      const dataScenarioConfig = context.Data(dataTable).Scenario('scenario', () => {});
 
       dataScenarioConfig.fails();
 
@@ -62,7 +66,7 @@ describe('ui', () => {
     it('can expect a specific error for all scenarios', () => {
       const err = new Error();
 
-      dataScenarioConfig = context.Data(dataTable).Scenario('scenario', () => {});
+      const dataScenarioConfig = context.Data(dataTable).Scenario('scenario', () => {});
 
       dataScenarioConfig.throws(err);
 
@@ -73,27 +77,28 @@ describe('ui', () => {
       const helperName = 'myHelper';
       const helper = {};
 
-      dataScenarioConfig = context.Data(dataTable).Scenario('scenario', () => {});
+      const dataScenarioConfig = context.Data(dataTable).Scenario('scenario', () => {});
 
       dataScenarioConfig.config(helperName, helper);
 
       dataScenarioConfig.scenarios.forEach(scenario => expect(helper).to.equal(scenario.test.config[helperName]));
     });
 
-    it("should shows object's toString() method in each scenario's name if the toString() method is overrided", () => {
-      const data = [
-        {
-          toString: () => 'test case title',
-        },
-      ];
-      const dataScenarioConfig = context.Data(data).Scenario('scenario', () => {});
+    it("should shows object's toString() method in each scenario's name if the toString() method is overridden", () => {
+      const data = [{ toString: () => 'test case title' }];
+      const dataScenarioConfig = context.Data(data).Scenario('scenario', () => { });
       expect('scenario | test case title').to.equal(dataScenarioConfig.scenarios[0].test.title);
     });
 
-    it("should shows JSON.stringify() in each scenario's name if the toString() method isn't overrided", () => {
+    it("should shows JSON.stringify() in each scenario's name if the toString() method isn't overridden", () => {
       const data = [{ name: 'John Do' }];
       const dataScenarioConfig = context.Data(data).Scenario('scenario', () => {});
       expect(`scenario | ${JSON.stringify(data[0])}`).to.equal(dataScenarioConfig.scenarios[0].test.title);
     });
+
+    it('should shows secret value as *****', () => {
+      const dataScenarioConfig = context.Data(dataTable).Scenario('scenario', () => {});
+      expect('scenario | {"username":"Username","password":"*****"}').to.equal(dataScenarioConfig.scenarios[2].test.title);
+    });
   });
 });