wdio-mediawiki 2.4.0 → 2.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/Api.js CHANGED
@@ -21,7 +21,7 @@ module.exports = {
21
21
  const bot = new MWBot();
22
22
 
23
23
  await bot.loginGetEditToken( {
24
- apiUrl: `${baseUrl}/api.php`,
24
+ apiUrl: `${ baseUrl }/api.php`,
25
25
  username: username,
26
26
  password: password
27
27
  } );
package/BlankPage.js CHANGED
@@ -3,7 +3,9 @@
3
3
  const Page = require( './Page' );
4
4
 
5
5
  class BlankPage extends Page {
6
- get heading() { return $( '#firstHeading' ); }
6
+ get heading() {
7
+ return $( '#firstHeading' );
8
+ }
7
9
 
8
10
  async open() {
9
11
  await super.openTitle( 'Special:BlankPage', { uselang: 'en' } );
@@ -1,20 +1,64 @@
1
1
  'use strict';
2
2
 
3
3
  const Page = require( './Page' );
4
+ const Util = require( 'wdio-mediawiki/Util' );
4
5
 
5
6
  class CreateAccountPage extends Page {
6
- get username() { return $( '#wpName2' ); }
7
- get password() { return $( '#wpPassword2' ); }
8
- get confirmPassword() { return $( '#wpRetype' ); }
9
- get create() { return $( '#wpCreateaccount' ); }
10
- get heading() { return $( '#firstHeading' ); }
7
+ get username() {
8
+ return $( '#wpName2' );
9
+ }
10
+
11
+ get password() {
12
+ return $( '#wpPassword2' );
13
+ }
14
+
15
+ get confirmPassword() {
16
+ return $( '#wpRetype' );
17
+ }
18
+
19
+ get create() {
20
+ return $( '#wpCreateaccount' );
21
+ }
22
+
23
+ get heading() {
24
+ return $( '#firstHeading' );
25
+ }
26
+
27
+ get tempPasswordInput() {
28
+ return $( '#wpCreateaccountMail' );
29
+ }
30
+
31
+ get reasonInput() {
32
+ return $( '#wpReason' );
33
+ }
11
34
 
12
- open() {
13
- super.openTitle( 'Special:CreateAccount' );
35
+ async open() {
36
+ await super.openTitle( 'Special:CreateAccount' );
14
37
  }
15
38
 
39
+ /**
40
+ * Navigate to Special:CreateAccount, then fill out and submit the account creation form.
41
+ *
42
+ * @param {string} username
43
+ * @param {string} password
44
+ * @return {Promise<void>}
45
+ */
16
46
  async createAccount( username, password ) {
17
47
  await this.open();
48
+ await this.submitForm( username, password );
49
+ }
50
+
51
+ /**
52
+ * Fill out and submit the account creation form on Special:CreateAccount.
53
+ * The browser is assumed to have already navigated to this page.
54
+ *
55
+ * @param {string} username
56
+ * @param {string} password
57
+ * @return {Promise<void>}
58
+ */
59
+ async submitForm( username, password ) {
60
+ await Util.waitForModuleState( 'mediawiki.special.createaccount', 'ready', 10000 );
61
+
18
62
  await this.username.setValue( username );
19
63
  await this.password.setValue( password );
20
64
  await this.confirmPassword.setValue( password );
package/LoginPage.js CHANGED
@@ -1,15 +1,33 @@
1
+ // This file is used at Selenium/Explanation/Page object pattern
2
+ // https://www.mediawiki.org/wiki/Selenium/Explanation/Page_object_pattern
3
+
1
4
  'use strict';
2
5
 
3
6
  const Page = require( './Page' );
4
7
 
5
8
  class LoginPage extends Page {
6
- get username() { return $( '#wpName1' ); }
7
- get password() { return $( '#wpPassword1' ); }
8
- get loginButton() { return $( '#wpLoginAttempt' ); }
9
- get userPage() { return $( '#pt-userpage' ); }
9
+ get username() {
10
+ return $( '#wpName1' );
11
+ }
12
+
13
+ get password() {
14
+ return $( '#wpPassword1' );
15
+ }
16
+
17
+ get loginButton() {
18
+ return $( '#wpLoginAttempt' );
19
+ }
20
+
21
+ get userPage() {
22
+ return $( '#pt-userpage' );
23
+ }
24
+
25
+ async open() {
26
+ await super.openTitle( 'Special:UserLogin' );
27
+ }
10
28
 
11
- open() {
12
- super.openTitle( 'Special:UserLogin' );
29
+ async getActualUsername() {
30
+ return browser.execute( () => mw.config.get( 'wgUserName' ) );
13
31
  }
14
32
 
15
33
  async login( username, password ) {
@@ -17,6 +35,17 @@ class LoginPage extends Page {
17
35
  await this.username.setValue( username );
18
36
  await this.password.setValue( password );
19
37
  await this.loginButton.click();
38
+ await browser.waitUntil(
39
+ async () => await browser.execute(
40
+ ( expectedUsername ) => typeof mw !== 'undefined' &&
41
+ mw.config.get( 'wgUserName' ) === expectedUsername,
42
+ username
43
+ ),
44
+ {
45
+ timeout: 15000,
46
+ timeoutMsg: 'Cannot submit login form'
47
+ }
48
+ );
20
49
  }
21
50
 
22
51
  async loginAdmin() {
package/Page.js CHANGED
@@ -3,7 +3,7 @@
3
3
  const querystring = require( 'querystring' );
4
4
 
5
5
  /**
6
- * Based on http://webdriver.io/guide/testrunner/pageobjects.html
6
+ * Based on https://webdriver.io/docs/pageobjects
7
7
  */
8
8
  class Page {
9
9
 
@@ -11,20 +11,28 @@ class Page {
11
11
  * Navigate the browser to a given page.
12
12
  *
13
13
  * @since 1.0.0
14
- * @see <http://webdriver.io/api/protocol/url.html>
14
+ * @see <https://webdriver.io/docs/api/browser/url>
15
15
  * @param {string} title Page title
16
16
  * @param {Object} [query] Query parameter
17
17
  * @param {string} [fragment] Fragment parameter
18
- * @return {void} This method runs a browser command.
18
+ * @return {Promise<void>}
19
19
  */
20
20
  async openTitle( title, query = {}, fragment = '' ) {
21
- const config = Object.assign( browser.config || {}, browser.options || {} );
22
21
  query.title = title;
23
22
  await browser.url(
24
- config.baseUrl + '/index.php?' +
23
+ browser.config.baseUrl + '/index.php?' +
25
24
  querystring.stringify( query ) +
26
25
  ( fragment ? ( '#' + fragment ) : '' )
27
26
  );
27
+ // Wait for the page to be fully loaded. TODO: This can be replaced by the `wait` option to
28
+ // browser.url in webdriverio 9 (T363704).
29
+ await browser.waitUntil(
30
+ () => browser.execute( () => document.readyState === 'complete' ),
31
+ {
32
+ timeout: 10 * 1000,
33
+ timeoutMsg: 'Page did not load in time'
34
+ }
35
+ );
28
36
  }
29
37
  }
30
38
 
package/README.md CHANGED
@@ -1,12 +1,12 @@
1
1
  # wdio-mediawiki
2
2
 
3
- A plugin for [WebdriverIO](http://webdriver.io/) providing utilities to simplify testing of MediaWiki features.
3
+ A plugin for [WebdriverIO](https://webdriver.io) providing utilities to simplify testing of MediaWiki features.
4
4
 
5
5
  ## Getting Started
6
6
 
7
7
  ### Page
8
8
 
9
- The `Page` class is a base class for following the [Page Objects Pattern](http://webdriver.io/guide/testrunner/pageobjects.html).
9
+ The `Page` class is a base class for following the [Page Objects Pattern](https://webdriver.io/docs/pageobjects).
10
10
 
11
11
  * `openTitle( title [, Object query [, string fragment ] ] )`
12
12
 
@@ -17,7 +17,7 @@ See [BlankPage](./BlankPage.js) and [specs/BlankPage](./specs/BlankPage.js) for
17
17
 
18
18
  ### Api
19
19
 
20
- Utilities to interact with the MediaWiki API. Uses the [mwbot](https://github.com/Fannon/mwbot) library.
20
+ Utilities to interact with the MediaWiki API. Uses the [mwbot](https://github.com/gesinn-it-pub/mwbot) library.
21
21
 
22
22
  Actions are performed logged-in using `browser.config.mwUser` and `browser.config.mwPwd`,
23
23
  which typically come from `MEDIAWIKI_USER` and `MEDIAWIKI_PASSWORD` environment variables.
@@ -46,11 +46,12 @@ making assertions that depend on its outcome.
46
46
  `Util` is a collection of popular utility methods.
47
47
 
48
48
  * `getTestString([ string prefix ])`
49
+ * `isTargetNotWikitext(string target)`
49
50
  * `waitForModuleState(string moduleName [, string moduleStatus [, number timeout ] ])`
50
51
 
51
52
  ## Versioning
52
53
 
53
- This package follows [Semantic Versioning guidelines](https://semver.org/) for its releases. In
54
+ This package follows [Semantic Versioning guidelines](https://semver.org) for its releases. In
54
55
  particular, its major version must be bumped when compatibility is removed for a previous of
55
56
  MediaWiki.
56
57
 
@@ -65,11 +66,11 @@ co-exists with its deprecated equivalent for at least one release.
65
66
 
66
67
  ## Issue tracker
67
68
 
68
- Please report issues to [Phabricator](https://phabricator.wikimedia.org/tag/mediawiki-core-tests/).
69
+ Please report issues to [Phabricator](https://phabricator.wikimedia.org/tag/mediawiki-core-tests).
69
70
 
70
71
  ## Contributing
71
72
 
72
73
  This module is maintained in the MediaWiki core repository and published from there as a
73
74
  package to npmjs.org. To simplify development and to ensure changes are verified
74
75
  automatically, MediaWiki core itself uses this module directly from the working copy
75
- using [npm Local Paths](https://docs.npmjs.com/files/package.json#local-paths).
76
+ using [npm Local Paths](https://docs.npmjs.com/cli/v10/configuring-npm/package-json#local-paths).
package/RunJobs.js CHANGED
@@ -6,7 +6,7 @@ const MAINPAGE_REQUESTS_MAX_RUNS = 10; // (arbitrary) safe-guard against endless
6
6
 
7
7
  function getJobCount() {
8
8
  const bot = new MWBot( {
9
- apiUrl: `${browser.config.baseUrl}/api.php`
9
+ apiUrl: `${ browser.config.baseUrl }/api.php`
10
10
  } );
11
11
  return bot.request( {
12
12
  action: 'query',
@@ -16,27 +16,27 @@ function getJobCount() {
16
16
  }
17
17
 
18
18
  function log( message ) {
19
- process.stdout.write( `RunJobs ${message}\n` );
19
+ process.stdout.write( `RunJobs ${ message }\n` );
20
20
  }
21
21
 
22
22
  function runThroughMainPageRequests( runCount = 1 ) {
23
23
  const page = new Page();
24
- log( `through requests to the main page (run ${runCount}).` );
24
+ log( `through requests to the main page (run ${ runCount }).` );
25
25
 
26
- page.openTitle( '' );
27
-
28
- return getJobCount().then( ( jobCount ) => {
29
- if ( jobCount === 0 ) {
30
- log( 'found no more queued jobs.' );
31
- return;
32
- }
33
- log( `detected ${jobCount} more queued job(s).` );
34
- if ( runCount >= MAINPAGE_REQUESTS_MAX_RUNS ) {
35
- log( 'stopping requests to the main page due to reached limit.' );
36
- return;
37
- }
38
- return runThroughMainPageRequests( ++runCount );
39
- } );
26
+ return page.openTitle( '' ).then(
27
+ () => getJobCount().then( ( jobCount ) => {
28
+ if ( jobCount === 0 ) {
29
+ log( 'found no more queued jobs.' );
30
+ return;
31
+ }
32
+ log( `detected ${ jobCount } more queued job(s).` );
33
+ if ( runCount >= MAINPAGE_REQUESTS_MAX_RUNS ) {
34
+ log( 'stopping requests to the main page due to reached limit.' );
35
+ return;
36
+ }
37
+ return runThroughMainPageRequests( ++runCount );
38
+ } )
39
+ );
40
40
  }
41
41
 
42
42
  /**
@@ -61,9 +61,7 @@ function runThroughMainPageRequests( runCount = 1 ) {
61
61
  class RunJobs {
62
62
 
63
63
  static run() {
64
- browser.call( () => {
65
- return runThroughMainPageRequests();
66
- } );
64
+ browser.call( () => runThroughMainPageRequests() );
67
65
  }
68
66
  }
69
67
 
package/Util.js CHANGED
@@ -1,10 +1,44 @@
1
1
  'use strict';
2
2
 
3
3
  module.exports = {
4
+ /**
5
+ * Generate a random number string with some additional extended ASCII.
6
+ *
7
+ * @param {string} prefix A prefix to apply to the generated output.
8
+ * @return {string}
9
+ */
4
10
  getTestString( prefix = '' ) {
5
11
  return prefix + Math.random().toString() + '-Iñtërnâtiônàlizætiøn';
6
12
  },
7
13
 
14
+ /**
15
+ * Check if a page is (or, if it doesn't yet exist, would be by default) a wikitext content
16
+ * object, as opposed to e.g. a JSON blob or a content model provided by an extension. This
17
+ * is useful for when a target of a test requires wikitext behaviour, such as testing for
18
+ * having a talk page, being subject to redirects, being editable, or similar concerns.
19
+ *
20
+ * @param {string} target The name of the page in question.
21
+ * @return {Promise<boolean>} True if the target is not wikitext.
22
+ */
23
+ async isTargetNotWikitext( target ) {
24
+ // First, make sure that the 'mw' object should exist
25
+ await this.waitForModuleState( 'mediawiki.base' );
26
+
27
+ // Then, ask the API for the basic 'info' data about the given page
28
+ return browser.executeAsync( ( target_, done ) => {
29
+ mw.loader.using( 'mediawiki.api' ).then( () => {
30
+ const api = new mw.Api();
31
+ api.get( {
32
+ action: 'query', prop: 'info', titles: target_,
33
+ format: 'json', formatversion: 2
34
+ } ).then( ( result ) => {
35
+ // Finally, return whether said page is wikitext (or would be, if it doesn't yet exist)
36
+ done( result.query.pages[ 0 ].contentmodel !== 'wikitext' );
37
+ } );
38
+ } );
39
+ }, target );
40
+ },
41
+
8
42
  /**
9
43
  * Wait for a given module to reach a specific state
10
44
  *
@@ -13,14 +47,14 @@ module.exports = {
13
47
  * @param {number} timeout The wait time in milliseconds before the wait fails
14
48
  */
15
49
  async waitForModuleState( moduleName, moduleStatus = 'ready', timeout = 5000 ) {
16
- await browser.waitUntil( async () => {
17
- return await browser.execute( ( arg ) => {
18
- return typeof mw !== 'undefined' &&
19
- mw.loader.getState( arg.name ) === arg.status;
20
- }, { status: moduleStatus, name: moduleName } );
21
- }, {
22
- timeout: timeout,
23
- timeoutMsg: 'Failed to wait for ' + moduleName + ' to be ' + moduleStatus + ' after ' + timeout + ' ms.'
24
- } );
50
+ await browser.waitUntil(
51
+ () => browser.execute(
52
+ ( arg ) => typeof mw !== 'undefined' && mw.loader.getState( arg.name ) === arg.status,
53
+ { status: moduleStatus, name: moduleName }
54
+ ), {
55
+ timeout: timeout,
56
+ timeoutMsg: 'Failed to wait for ' + moduleName + ' to be ' + moduleStatus + ' after ' + timeout + ' ms.'
57
+ }
58
+ );
25
59
  }
26
60
  };
package/index.js CHANGED
@@ -23,11 +23,10 @@ function testTitle( title ) {
23
23
  * @since 1.1.0
24
24
  * @param {string} title Test title
25
25
  * @param {string} extension png for screenshots, mp4 for videos
26
- * @param {string} screenshotPath Optional path
27
26
  * @return {string} Full path of screenshot/video file
28
27
  */
29
- function filePath( title, extension, screenshotPath = undefined ) {
30
- return `${screenshotPath || browser.config.screenshotPath}/${testTitle( title )}-${makeFilenameDate()}.${extension}`;
28
+ function filePath( title, extension ) {
29
+ return `${ browser.config.screenshotPath }/${ testTitle( title ) }-${ makeFilenameDate() }.${ extension }`;
31
30
  }
32
31
 
33
32
  /**
@@ -35,19 +34,18 @@ function filePath( title, extension, screenshotPath = undefined ) {
35
34
  *
36
35
  * @since 1.0.0
37
36
  * @param {string} title Description (will be sanitised and used as file name)
38
- * @param {string} screenshotPath Optional path
39
37
  * @return {string} File path
40
38
  */
41
- async function saveScreenshot( title, screenshotPath = undefined ) {
39
+ async function saveScreenshot( title ) {
42
40
  // Create sensible file name for current test title
43
- const path = filePath( title, 'png', screenshotPath );
41
+ const path = filePath( title, 'png' );
44
42
  // Ensure directory exists, based on WebDriverIO#saveScreenshotSync()
45
43
  try {
46
44
  // eslint-disable-next-line security/detect-non-literal-fs-filename
47
- fs.statSync( screenshotPath || browser.config.screenshotPath );
45
+ fs.statSync( browser.config.screenshotPath );
48
46
  } catch ( err ) {
49
47
  // eslint-disable-next-line security/detect-non-literal-fs-filename
50
- fs.mkdirSync( screenshotPath || browser.config.screenshotPath );
48
+ fs.mkdirSync( browser.config.screenshotPath );
51
49
  }
52
50
  // Create and save screenshot
53
51
  await browser.saveScreenshot( path );
@@ -75,7 +73,7 @@ function startVideo( ffmpeg, title ) {
75
73
  ] );
76
74
  const logBuffer = function ( buffer, prefix ) {
77
75
  const lines = buffer.toString().trim().split( '\n' );
78
- lines.forEach( function ( line ) {
76
+ lines.forEach( ( line ) => {
79
77
  console.log( prefix + line );
80
78
  } );
81
79
  };
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "wdio-mediawiki",
3
- "version": "2.4.0",
3
+ "version": "2.6.0",
4
4
  "description": "WebdriverIO plugin for testing a MediaWiki site.",
5
- "homepage": "https://gerrit.wikimedia.org/g/mediawiki/core/+/master/tests/selenium/wdio-mediawiki/",
5
+ "homepage": "https://gerrit.wikimedia.org/g/mediawiki/core/+/master/tests/selenium/wdio-mediawiki",
6
6
  "license": "MIT",
7
7
  "keywords": [
8
8
  "mediawiki",
@@ -12,9 +12,6 @@
12
12
  "*.js",
13
13
  "specs/"
14
14
  ],
15
- "engines": {
16
- "node": ">=10.0"
17
- },
18
15
  "dependencies": {
19
16
  "mwbot": "2.1.3"
20
17
  }
@@ -1,13 +1,12 @@
1
1
  'use strict';
2
2
 
3
- const assert = require( 'assert' );
4
3
  const BlankPage = require( './../BlankPage' );
5
4
 
6
- describe( 'BlankPage', function () {
7
- it( 'should have its title @daily', async function () {
5
+ describe( 'BlankPage', () => {
6
+ it( 'should have its title @daily', async () => {
8
7
  await BlankPage.open();
9
8
 
10
9
  // check
11
- assert.strictEqual( await BlankPage.heading.getText(), 'Blank page' );
10
+ await expect( await BlankPage.heading ).toHaveText( 'Blank page' );
12
11
  } );
13
12
  } );
@@ -15,6 +15,8 @@ const fs = require( 'fs' );
15
15
  const path = require( 'path' );
16
16
  const logPath = process.env.LOG_DIR || path.join( process.cwd(), 'tests/selenium/log' );
17
17
  const { makeFilenameDate, saveScreenshot, startVideo, stopVideo } = require( 'wdio-mediawiki' );
18
+ // T355556: remove when T324766 is resolved
19
+ const dns = require( 'dns' );
18
20
 
19
21
  if ( !process.env.MW_SERVER || !process.env.MW_SCRIPT_PATH ) {
20
22
  throw new Error( 'MW_SERVER or MW_SCRIPT_PATH not defined.\nSee https://www.mediawiki.org/wiki/Selenium/How-to/Set_environment_variables\n' );
@@ -22,8 +24,8 @@ if ( !process.env.MW_SERVER || !process.env.MW_SCRIPT_PATH ) {
22
24
 
23
25
  /**
24
26
  * For more details documentation and available options:
25
- * - https://webdriver.io/docs/configurationfile/
26
- * - https://webdriver.io/docs/options/
27
+ * - https://webdriver.io/docs/configurationfile
28
+ * - https://webdriver.io/docs/configuration
27
29
  */
28
30
  exports.config = {
29
31
  // ======
@@ -54,7 +56,7 @@ exports.config = {
54
56
 
55
57
  maxInstances: 1,
56
58
  capabilities: [ {
57
- // For Chrome/Chromium https://sites.google.com/a/chromium.org/chromedriver/capabilities
59
+ // For Chrome/Chromium https://www.w3.org/TR/webdriver
58
60
  browserName: 'chrome',
59
61
  'goog:chromeOptions': {
60
62
  // If DISPLAY is set, assume developer asked non-headless or CI with Xvfb.
@@ -64,7 +66,10 @@ exports.config = {
64
66
  '--enable-automation',
65
67
  ...( process.env.DISPLAY ? [] : [ '--headless' ] ),
66
68
  // Chrome sandbox does not work in Docker
67
- ...( fs.existsSync( '/.dockerenv' ) ? [ '--no-sandbox' ] : [] )
69
+ ...( fs.existsSync( '/.dockerenv' ) ? [ '--no-sandbox' ] : [] ),
70
+ // Workaround inputs not working consistently post-navigation on Chrome 90
71
+ // https://issuetracker.google.com/issues/42322798
72
+ '--allow-pre-commit-input'
68
73
  ]
69
74
  }
70
75
  } ],
@@ -83,9 +88,9 @@ exports.config = {
83
88
  bail: 0,
84
89
  // Base for browser.url() and wdio-mediawiki/Page#openTitle()
85
90
  baseUrl: process.env.MW_SERVER + process.env.MW_SCRIPT_PATH,
86
- // See also: https://webdriver.io/docs/frameworks/
91
+ // See also: https://webdriver.io/docs/frameworks
87
92
  framework: 'mocha',
88
- // See also: https://mochajs.org/
93
+ // See also: https://mochajs.org
89
94
  // The number of times to retry the entire specfile when it fails as a whole
90
95
  specFileRetries: 1,
91
96
  // Delay in seconds between the spec file retry attempts
@@ -97,15 +102,16 @@ exports.config = {
97
102
  ui: 'bdd',
98
103
  timeout: process.env.DEBUG ? ( 60 * 60 * 1000 ) : ( 60 * 1000 )
99
104
  },
100
- // See also: https://webdriver.io/docs/dot-reporter.html
105
+ // See also: https://webdriver.io/docs/dot-reporter
101
106
  reporters: [
102
- // See also: https://webdriver.io/docs/spec-reporter/
107
+ // See also: https://webdriver.io/docs/spec-reporter
103
108
  'spec',
104
- // See also: https://webdriver.io/docs/junit-reporter/
109
+ // See also: https://webdriver.io/docs/junit-reporter
105
110
  [ 'junit', {
106
111
  outputDir: logPath,
107
112
  outputFileFormat: function () {
108
- return `WDIO.xunit-${makeFilenameDate()}.xml`;
113
+ const random = Math.random().toString( 16 ).slice( 2, 10 );
114
+ return `WDIO.xunit-${ makeFilenameDate() }-${ random }.xml`;
109
115
  }
110
116
  } ]
111
117
  ],
@@ -114,13 +120,27 @@ exports.config = {
114
120
  // Hooks
115
121
  // =====
116
122
 
123
+ /**
124
+ * Gets executed just before initializing the webdriver session and test framework.
125
+ * It allows you to manipulate configurations depending on the capability or spec.
126
+ *
127
+ * @param {Object} config wdio configuration object
128
+ * @param {Array.<Object>} capabilities list of capabilities details
129
+ * @param {Array.<string>} specs List of spec file paths that are to be run
130
+ */
131
+ // T355556: remove when T324766 is resolved
132
+ beforeSession: function () {
133
+ // eslint-disable-next-line n/no-unsupported-features/node-builtins
134
+ dns.setDefaultResultOrder( 'ipv4first' );
135
+ },
136
+
117
137
  /**
118
138
  * Executed before a Mocha test starts.
119
139
  *
120
140
  * @param {Object} test Mocha Test object
121
141
  */
122
142
  beforeTest: function ( test ) {
123
- ffmpeg = startVideo( ffmpeg, `${test.parent}-${test.title}` );
143
+ ffmpeg = startVideo( ffmpeg, `${ test.parent }-${ test.title }` );
124
144
  },
125
145
 
126
146
  /**
@@ -129,7 +149,7 @@ exports.config = {
129
149
  * @param {Object} test Mocha Test object
130
150
  */
131
151
  afterTest: async function ( test ) {
132
- await saveScreenshot( `${test.parent}-${test.title}` );
152
+ await saveScreenshot( `${ test.parent }-${ test.title }` );
133
153
  stopVideo( ffmpeg );
134
154
  }
135
155
  };