wdio-mediawiki 2.5.0 → 2.7.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
@@ -88,5 +88,43 @@ module.exports = {
88
88
  reason: 'browser test done',
89
89
  token: adminBot.editToken
90
90
  } );
91
+ },
92
+
93
+ /**
94
+ * Assign a new user group to the given username.
95
+ *
96
+ * @since 2.7.0
97
+ * @param {MWBot} adminBot
98
+ * @param {string} username
99
+ * @param {string} groupName
100
+ */
101
+ async addUserToGroup( adminBot, username, groupName ) {
102
+ const userGroupsResponse = await adminBot.request( {
103
+ action: 'query',
104
+ list: 'users',
105
+ usprop: 'groups',
106
+ ususers: username,
107
+ formatversion: 2
108
+ } );
109
+
110
+ if (
111
+ userGroupsResponse.query.users.length &&
112
+ userGroupsResponse.query.users[ 0 ].groups.includes( groupName )
113
+ ) {
114
+ return;
115
+ }
116
+ const tokenResponse = await adminBot.request( {
117
+ action: 'query',
118
+ meta: 'tokens',
119
+ type: 'userrights'
120
+ } );
121
+ await adminBot.request( {
122
+ action: 'userrights',
123
+ user: username,
124
+ token: tokenResponse.query.tokens.userrightstoken,
125
+ add: groupName,
126
+ reason: 'Selenium testing'
127
+ } );
128
+ // If there is an error, the above already throws.
91
129
  }
92
130
  };
@@ -1,6 +1,7 @@
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
7
  get username() {
@@ -23,12 +24,41 @@ class CreateAccountPage extends Page {
23
24
  return $( '#firstHeading' );
24
25
  }
25
26
 
26
- open() {
27
- super.openTitle( 'Special:CreateAccount' );
27
+ get tempPasswordInput() {
28
+ return $( '#wpCreateaccountMail' );
28
29
  }
29
30
 
31
+ get reasonInput() {
32
+ return $( '#wpReason' );
33
+ }
34
+
35
+ async open() {
36
+ await super.openTitle( 'Special:CreateAccount' );
37
+ }
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
+ */
30
46
  async createAccount( username, password ) {
31
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
+
32
62
  await this.username.setValue( username );
33
63
  await this.password.setValue( password );
34
64
  await this.confirmPassword.setValue( password );
package/LoginPage.js CHANGED
@@ -1,3 +1,6 @@
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' );
@@ -19,8 +22,12 @@ class LoginPage extends Page {
19
22
  return $( '#pt-userpage' );
20
23
  }
21
24
 
22
- open() {
23
- super.openTitle( 'Special:UserLogin' );
25
+ async open() {
26
+ await super.openTitle( 'Special:UserLogin' );
27
+ }
28
+
29
+ async getActualUsername() {
30
+ return browser.execute( () => mw.config.get( 'wgUserName' ) );
24
31
  }
25
32
 
26
33
  async login( username, password ) {
@@ -28,6 +35,17 @@ class LoginPage extends Page {
28
35
  await this.username.setValue( username );
29
36
  await this.password.setValue( password );
30
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
+ );
31
49
  }
32
50
 
33
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,11 +11,11 @@ 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
21
  query.title = title;
@@ -24,6 +24,15 @@ class Page {
24
24
  querystring.stringify( query ) +
25
25
  ( fragment ? ( '#' + fragment ) : '' )
26
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
+ );
27
36
  }
28
37
  }
29
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.
@@ -26,6 +26,7 @@ which typically come from `MEDIAWIKI_USER` and `MEDIAWIKI_PASSWORD` environment
26
26
  * `createAccount(MWBot bot, string username, string password)`
27
27
  * `blockUser(MWBot bot, [ string username [, string expiry ] ])`
28
28
  * `unblockUser(MWBot bot, [ string username ])`
29
+ * `addUserToGroup(MWBot bot, string username, string groupName)`
29
30
 
30
31
  Example:
31
32
 
@@ -46,11 +47,12 @@ making assertions that depend on its outcome.
46
47
  `Util` is a collection of popular utility methods.
47
48
 
48
49
  * `getTestString([ string prefix ])`
50
+ * `isTargetNotWikitext(string target)`
49
51
  * `waitForModuleState(string moduleName [, string moduleStatus [, number timeout ] ])`
50
52
 
51
53
  ## Versioning
52
54
 
53
- This package follows [Semantic Versioning guidelines](https://semver.org/) for its releases. In
55
+ This package follows [Semantic Versioning guidelines](https://semver.org) for its releases. In
54
56
  particular, its major version must be bumped when compatibility is removed for a previous of
55
57
  MediaWiki.
56
58
 
@@ -65,11 +67,11 @@ co-exists with its deprecated equivalent for at least one release.
65
67
 
66
68
  ## Issue tracker
67
69
 
68
- Please report issues to [Phabricator](https://phabricator.wikimedia.org/tag/mediawiki-core-tests/).
70
+ Please report issues to [Phabricator](https://phabricator.wikimedia.org/tag/mediawiki-core-tests).
69
71
 
70
72
  ## Contributing
71
73
 
72
74
  This module is maintained in the MediaWiki core repository and published from there as a
73
75
  package to npmjs.org. To simplify development and to ensure changes are verified
74
76
  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).
77
+ using [npm Local Paths](https://docs.npmjs.com/cli/v10/configuring-npm/package-json#local-paths).
package/RunJobs.js CHANGED
@@ -23,20 +23,20 @@ function runThroughMainPageRequests( runCount = 1 ) {
23
23
  const page = new Page();
24
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
@@ -73,7 +73,7 @@ function startVideo( ffmpeg, title ) {
73
73
  ] );
74
74
  const logBuffer = function ( buffer, prefix ) {
75
75
  const lines = buffer.toString().trim().split( '\n' );
76
- lines.forEach( function ( line ) {
76
+ lines.forEach( ( line ) => {
77
77
  console.log( prefix + line );
78
78
  } );
79
79
  };
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "wdio-mediawiki",
3
- "version": "2.5.0",
3
+ "version": "2.7.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,10 +12,6 @@
12
12
  "*.js",
13
13
  "specs/"
14
14
  ],
15
- "engines": {
16
- "node": ">=18.17.0",
17
- "npm": ">=9.6.7"
18
- },
19
15
  "dependencies": {
20
16
  "mwbot": "2.1.3"
21
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
  } );
@@ -24,8 +24,8 @@ if ( !process.env.MW_SERVER || !process.env.MW_SCRIPT_PATH ) {
24
24
 
25
25
  /**
26
26
  * For more details documentation and available options:
27
- * - https://webdriver.io/docs/configurationfile/
28
- * - https://webdriver.io/docs/options/
27
+ * - https://webdriver.io/docs/configurationfile
28
+ * - https://webdriver.io/docs/configuration
29
29
  */
30
30
  exports.config = {
31
31
  // ======
@@ -56,7 +56,7 @@ exports.config = {
56
56
 
57
57
  maxInstances: 1,
58
58
  capabilities: [ {
59
- // For Chrome/Chromium https://sites.google.com/a/chromium.org/chromedriver/capabilities
59
+ // For Chrome/Chromium https://www.w3.org/TR/webdriver
60
60
  browserName: 'chrome',
61
61
  'goog:chromeOptions': {
62
62
  // If DISPLAY is set, assume developer asked non-headless or CI with Xvfb.
@@ -66,7 +66,10 @@ exports.config = {
66
66
  '--enable-automation',
67
67
  ...( process.env.DISPLAY ? [] : [ '--headless' ] ),
68
68
  // Chrome sandbox does not work in Docker
69
- ...( 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'
70
73
  ]
71
74
  }
72
75
  } ],
@@ -85,9 +88,9 @@ exports.config = {
85
88
  bail: 0,
86
89
  // Base for browser.url() and wdio-mediawiki/Page#openTitle()
87
90
  baseUrl: process.env.MW_SERVER + process.env.MW_SCRIPT_PATH,
88
- // See also: https://webdriver.io/docs/frameworks/
91
+ // See also: https://webdriver.io/docs/frameworks
89
92
  framework: 'mocha',
90
- // See also: https://mochajs.org/
93
+ // See also: https://mochajs.org
91
94
  // The number of times to retry the entire specfile when it fails as a whole
92
95
  specFileRetries: 1,
93
96
  // Delay in seconds between the spec file retry attempts
@@ -99,15 +102,16 @@ exports.config = {
99
102
  ui: 'bdd',
100
103
  timeout: process.env.DEBUG ? ( 60 * 60 * 1000 ) : ( 60 * 1000 )
101
104
  },
102
- // See also: https://webdriver.io/docs/dot-reporter.html
105
+ // See also: https://webdriver.io/docs/dot-reporter
103
106
  reporters: [
104
- // See also: https://webdriver.io/docs/spec-reporter/
107
+ // See also: https://webdriver.io/docs/spec-reporter
105
108
  'spec',
106
- // See also: https://webdriver.io/docs/junit-reporter/
109
+ // See also: https://webdriver.io/docs/junit-reporter
107
110
  [ 'junit', {
108
111
  outputDir: logPath,
109
112
  outputFileFormat: function () {
110
- return `WDIO.xunit-${ makeFilenameDate() }.xml`;
113
+ const random = Math.random().toString( 16 ).slice( 2, 10 );
114
+ return `WDIO.xunit-${ makeFilenameDate() }-${ random }.xml`;
111
115
  }
112
116
  } ]
113
117
  ],
@@ -119,12 +123,14 @@ exports.config = {
119
123
  /**
120
124
  * Gets executed just before initializing the webdriver session and test framework.
121
125
  * It allows you to manipulate configurations depending on the capability or spec.
126
+ *
122
127
  * @param {Object} config wdio configuration object
123
128
  * @param {Array.<Object>} capabilities list of capabilities details
124
129
  * @param {Array.<string>} specs List of spec file paths that are to be run
125
130
  */
126
131
  // T355556: remove when T324766 is resolved
127
132
  beforeSession: function () {
133
+ // eslint-disable-next-line n/no-unsupported-features/node-builtins
128
134
  dns.setDefaultResultOrder( 'ipv4first' );
129
135
  },
130
136