selenium: Initial version of wdio-mediawiki package

This is new package will be reusable by other repositories for
their browser tests, without having to reference the internal
selenium/pageobjects/ directory from MediaWiki core.

In addition to not requiring direct imports, it will also avoid
problems in the future by allowing the package to be versioned
and iterated upon without forcing an atomic global upgrade
(or broken master builds), everytime we change something.

See wdio-mediawiki/README for details.

Within MediaWiki core itself, the package is used using the
'file' specifier in its package.json, so that we always test
and develop using its working copy, which makes drafting and
testing changes easier.

Also misc changes to make wdio.conf easier to understand.

Bug: T193088
Change-Id: I547a7899e7a97693a93567dd763784e637433d55
This commit is contained in:
Timo Tijhof 2018-05-02 18:48:24 +01:00 committed by Zfilipin
parent e52b056322
commit 16a4d9da7b
24 changed files with 403 additions and 340 deletions

View file

@ -25,12 +25,12 @@
"karma-firefox-launcher": "1.0.1",
"karma-mocha-reporter": "2.2.5",
"karma-qunit": "2.0.1",
"mwbot": "1.0.10",
"postcss-less": "1.1.5",
"qunit": "2.5.0",
"stylelint": "9.2.0",
"stylelint-config-wikimedia": "0.4.3",
"wdio-junit-reporter": "0.2.0",
"wdio-mediawiki": "file:tests/selenium/wdio-mediawiki",
"wdio-mocha-framework": "0.5.8",
"wdio-sauce-service": "0.3.1",
"wdio-spec-reporter": "0.0.5",

View file

@ -25,36 +25,32 @@ environment variable to any value:
DISPLAY=1 npm run selenium
To run only one file (for example page.js), you first need to spawn the chromedriver:
To run only one test (for example specs/page.js), you first need to start Chromedriver:
chromedriver --url-base=wd/hub --port=4444
Then in another terminal:
Then, in another terminal:
cd tests/selenium
../../node_modules/.bin/wdio --spec specs/page.js
npm run selenium-test -- --spec tests/selenium/specs/page.js
To run only one test (name contains string 'preferences'):
You can also filter specific cases, for ones that contain the string 'preferences':
../../node_modules/.bin/wdio --spec specs/user.js --mochaOpts.grep preferences
npm run selenium-test -- tests/selenium/specs/user.js --mochaOpts.grep preferences
The runner reads the config file `wdio.conf.js` and runs the spec listed in
`page.js`.
The defaults in the configuration files aim are targeting a MediaWiki-Vagrant
installation on http://127.0.0.1:8080 with a user Admin and
password 'vagrant'. Those settings can be overridden using environment
The runner reads the configuration from `wdio.conf.js`. The defaults target
a MediaWiki-Vagrant installation on `http://127.0.0.1:8080` with a user "Admin"
and password "vagrant". Those settings can be overridden using environment
variables:
`MW_SERVER`: to be set to the value of your $wgServer
`MW_SCRIPT_PATH`: ditto with $wgScriptPath
`MEDIAWIKI_USER`: username of an account that can create users on the wiki
`MEDIAWIKI_PASSWORD`: password for above user
- `MW_SERVER`: to be set to the value of your $wgServer
- `MW_SCRIPT_PATH`: ditto with $wgScriptPath
- `MEDIAWIKI_USER`: username of an account that can create users on the wiki
- `MEDIAWIKI_PASSWORD`: password for above user
Example:
MW_SERVER=http://example.org MW_SCRIPT_PATH=/dev/w npm run selenium
## Links
## Further reading
- [Selenium/Node.js](https://www.mediawiki.org/wiki/Selenium/Node.js)

View file

@ -1,9 +1,7 @@
const Page = require( './page' ),
// https://github.com/Fannon/mwbot
MWBot = require( 'mwbot' );
const Page = require( 'wdio-mediawiki/Page' ),
Api = require( 'wdio-mediawiki/Api' );
class CreateAccountPage extends Page {
get username() { return browser.element( '#wpName2' ); }
get password() { return browser.element( '#wpPassword2' ); }
get confirmPassword() { return browser.element( '#wpRetype' ); }
@ -11,7 +9,7 @@ class CreateAccountPage extends Page {
get heading() { return browser.element( '#firstHeading' ); }
open() {
super.open( 'Special:CreateAccount' );
super.openTitle( 'Special:CreateAccount' );
}
createAccount( username, password ) {
@ -22,23 +20,9 @@ class CreateAccountPage extends Page {
this.create.click();
}
// @deprecated Use wdio-mediawiki/Api#createAccount() instead.
apiCreateAccount( username, password ) {
let bot = new MWBot();
return bot.loginGetCreateaccountToken( {
apiUrl: `${browser.options.baseUrl}/api.php`,
username: browser.options.username,
password: browser.options.password
} ).then( function () {
return bot.request( {
action: 'createaccount',
createreturnurl: browser.options.baseUrl,
createtoken: bot.createaccountToken,
username: username,
password: password,
retype: password
} );
} );
return Api.createAccount( username, password );
}
}

View file

@ -1,6 +1,5 @@
const Page = require( './page' ),
// https://github.com/Fannon/mwbot
MWBot = require( 'mwbot' );
const Page = require( 'wdio-mediawiki/Page' ),
Api = require( 'wdio-mediawiki/Api' );
class DeletePage extends Page {
get reason() { return browser.element( '#wpReason' ); }
@ -8,26 +7,19 @@ class DeletePage extends Page {
get submit() { return browser.element( '#wpConfirmB' ); }
get displayedContent() { return browser.element( '#mw-content-text' ); }
open( name ) {
super.open( name + '&action=delete' );
open( title ) {
super.openTitle( title, { action: 'delete' } );
}
delete( name, reason ) {
this.open( name );
delete( title, reason ) {
this.open( title );
this.reason.setValue( reason );
this.submit.click();
}
// @deprecated Use wdio-mediawiki/Api#delete() instead.
apiDelete( name, reason ) {
let bot = new MWBot();
return bot.loginGetEditToken( {
apiUrl: `${browser.options.baseUrl}/api.php`,
username: browser.options.username,
password: browser.options.password
} ).then( function () {
return bot.delete( name, reason );
} );
return Api.delete( name, reason );
}
}

View file

@ -1,6 +1,5 @@
const Page = require( './page' ),
// https://github.com/Fannon/mwbot
MWBot = require( 'mwbot' );
const Page = require( 'wdio-mediawiki/Page' ),
Api = require( 'wdio-mediawiki/Api' );
class EditPage extends Page {
get content() { return browser.element( '#wpTextbox1' ); }
@ -8,8 +7,8 @@ class EditPage extends Page {
get heading() { return browser.element( '#firstHeading' ); }
get save() { return browser.element( '#wpSave' ); }
openForEditing( name ) {
super.open( name + '&action=edit' );
openForEditing( title ) {
super.openTitle( title, { action: 'edit' } );
}
edit( name, content ) {
@ -18,16 +17,9 @@ class EditPage extends Page {
this.save.click();
}
// @deprecated Use wdio-mediawiki/Api#edit() instead.
apiEdit( name, content ) {
let bot = new MWBot();
return bot.loginGetEditToken( {
apiUrl: `${browser.options.baseUrl}/api.php`,
username: browser.options.username,
password: browser.options.password
} ).then( function () {
return bot.edit( name, content, `Created page with "${content}"` );
} );
return Api.edit( name, content );
}
}

View file

@ -1,10 +1,10 @@
const Page = require( './page' );
const Page = require( 'wdio-mediawiki/Page' );
class HistoryPage extends Page {
get comment() { return browser.element( '#pagehistory .comment' ); }
open( name ) {
super.open( name + '&action=history' );
open( title ) {
super.openTitle( title, { action: 'history' } );
}
}

View file

@ -1,11 +1,12 @@
/**
* Based on http://webdriver.io/guide/testrunner/pageobjects.html
*/
const Page = require( 'wdio-mediawiki/Page' );
class Page {
/**
* @deprecated Use wdio-mediawiki/Page and openTitle() instead.
*/
class LegacyPage extends Page {
open( path ) {
browser.url( browser.options.baseUrl + '/index.php?title=' + path );
}
}
module.exports = Page;
module.exports = LegacyPage;

View file

@ -1,11 +1,11 @@
const Page = require( './page' );
const Page = require( 'wdio-mediawiki/Page' );
class PreferencesPage extends Page {
get realName() { return browser.element( '#mw-input-wprealname' ); }
get save() { return browser.element( '#prefcontrol' ); }
open() {
super.open( 'Special:Preferences' );
super.openTitle( 'Special:Preferences' );
}
changeRealName( realName ) {

View file

@ -1,17 +1,16 @@
const Page = require( './page' );
const Page = require( 'wdio-mediawiki/Page' );
class RestorePage extends Page {
get reason() { return browser.element( '#wpComment' ); }
get submit() { return browser.element( '#mw-undelete-submit' ); }
get displayedContent() { return browser.element( '#mw-content-text' ); }
open( name ) {
super.open( 'Special:Undelete/' + name );
open( subject ) {
super.openTitle( 'Special:Undelete/' + subject );
}
restore( name, reason ) {
this.open( name );
restore( subject, reason ) {
this.open( subject );
this.reason.setValue( reason );
this.submit.click();
}

View file

@ -1,25 +1,6 @@
const Page = require( './page' );
const LoginPage = require( 'wdio-mediawiki/LoginPage' );
class UserLoginPage extends Page {
get username() { return browser.element( '#wpName1' ); }
get password() { return browser.element( '#wpPassword1' ); }
get loginButton() { return browser.element( '#wpLoginAttempt' ); }
get userPage() { return browser.element( '#pt-userpage' ); }
open() {
super.open( 'Special:UserLogin' );
}
login( username, password ) {
this.open();
this.username.setValue( username );
this.password.setValue( password );
this.loginButton.click();
}
loginAdmin() {
this.login( browser.options.username, browser.options.password );
}
}
module.exports = new UserLoginPage();
/**
* @deprecated Use wdio-mediawiki/LoginPage instead.
*/
module.exports = LoginPage;

View file

@ -1,4 +1,5 @@
const assert = require( 'assert' ),
Api = require( 'wdio-mediawiki/Api' ),
DeletePage = require( '../pageobjects/delete.page' ),
RestorePage = require( '../pageobjects/restore.page' ),
EditPage = require( '../pageobjects/edit.page' ),
@ -39,12 +40,12 @@ describe( 'Page', function () {
// create
browser.call( function () {
return EditPage.apiEdit( name, initialContent );
return Api.edit( name, initialContent );
} );
// delete
browser.call( function () {
return DeletePage.apiDelete( name, 'delete prior to recreate' );
return Api.delete( name, 'delete prior to recreate' );
} );
// create
@ -53,13 +54,12 @@ describe( 'Page', function () {
// check
assert.equal( EditPage.heading.getText(), name );
assert.equal( EditPage.displayedContent.getText(), content );
} );
it( 'should be editable', function () {
// create
browser.call( function () {
return EditPage.apiEdit( name, content );
return Api.edit( name, content );
} );
// edit
@ -73,7 +73,7 @@ describe( 'Page', function () {
it( 'should have history', function () {
// create
browser.call( function () {
return EditPage.apiEdit( name, content );
return Api.edit( name, content );
} );
// check
@ -87,7 +87,7 @@ describe( 'Page', function () {
// create
browser.call( function () {
return EditPage.apiEdit( name, content );
return Api.edit( name, content );
} );
// delete
@ -106,12 +106,12 @@ describe( 'Page', function () {
// create
browser.call( function () {
return EditPage.apiEdit( name, content );
return Api.edit( name, content );
} );
// delete
browser.call( function () {
return DeletePage.apiDelete( name, content + '-deletereason' );
return Api.delete( name, content + '-deletereason' );
} );
// restore

View file

@ -1,7 +1,8 @@
const assert = require( 'assert' ),
CreateAccountPage = require( '../pageobjects/createaccount.page' ),
PreferencesPage = require( '../pageobjects/preferences.page' ),
UserLoginPage = require( '../pageobjects/userlogin.page' );
UserLoginPage = require( 'wdio-mediawiki/LoginPage' ),
Api = require( 'wdio-mediawiki/Api' );
describe( 'User', function () {
var password,
@ -30,7 +31,7 @@ describe( 'User', function () {
it( 'should be able to log in', function () {
// create
browser.call( function () {
return CreateAccountPage.apiCreateAccount( username, password );
return Api.createAccount( username, password );
} );
// log in
@ -45,7 +46,7 @@ describe( 'User', function () {
// create
browser.call( function () {
return CreateAccountPage.apiCreateAccount( username, password );
return Api.createAccount( username, password );
} );
// log in

View file

@ -0,0 +1,10 @@
{
"extends": "wikimedia",
"env": {
"es6": true,
"node": true
},
"globals": {
"browser": false
}
}

View file

@ -0,0 +1,77 @@
const MWBot = require( 'mwbot' );
// TODO: Once we require Node 7 or later, we can use async-await.
module.exports = {
/**
* Shortcut for `MWBot#edit( .. )`.
*
* @since 1.0.0
* @see <https://www.mediawiki.org/wiki/API:Edit>
* @param {string} title
* @param {string} content
* @return {Object} Promise for API action=edit response data.
*/
edit( title, content ) {
let bot = new MWBot();
return bot.loginGetEditToken( {
apiUrl: `${browser.options.baseUrl}/api.php`,
username: browser.options.username,
password: browser.options.password
} ).then( function () {
return bot.edit( title, content, `Created page with "${content}"` );
} );
},
/**
* Shortcut for `MWBot#delete( .. )`.
*
* @since 1.0.0
* @see <https://www.mediawiki.org/wiki/API:Delete>
* @param {string} title
* @param {string} reason
* @return {Object} Promise for API action=delete response data.
*/
delete( title, reason ) {
let bot = new MWBot();
return bot.loginGetEditToken( {
apiUrl: `${browser.options.baseUrl}/api.php`,
username: browser.options.username,
password: browser.options.password
} ).then( function () {
return bot.delete( title, reason );
} );
},
/**
* Shortcut for `MWBot#request( { acount: 'createaccount', .. } )`.
*
* @since 1.0.0
* @see <https://www.mediawiki.org/wiki/API:Account_creation>
* @param {string} username
* @param {string} password
* @return {Object} Promise for API action=createaccount response data.
*/
createAccount( username, password ) {
let bot = new MWBot();
// Log in as admin
return bot.loginGetCreateaccountToken( {
apiUrl: `${browser.options.baseUrl}/api.php`,
username: browser.options.username,
password: browser.options.password
} ).then( function () {
// Create the new account
return bot.request( {
action: 'createaccount',
createreturnurl: browser.options.baseUrl,
createtoken: bot.createaccountToken,
username: username,
password: password,
retype: password
} );
} );
}
};

View file

@ -0,0 +1,11 @@
const Page = require( 'wdio-mediawiki/Page' );
class BlankPage extends Page {
get heading() { return browser.element( '#firstHeading' ); }
open() {
super.openTitle( 'Special:BlankPage', { uselang: 'en' } );
}
}
module.exports = new BlankPage();

View file

@ -0,0 +1,8 @@
# Notable changes
## [Unreleased]
* Api: Added initial version.
* Page: Added initial version.
* BlankPage: Added initial version.
* LoginPage: Added initial version.

View file

@ -0,0 +1,21 @@
Copyright 2018 Željko Filipin
Copyright 2018 Timo Tijhof
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View file

@ -0,0 +1,25 @@
const Page = require( 'wdio-mediawiki/Page' );
class LoginPage extends Page {
get username() { return browser.element( '#wpName1' ); }
get password() { return browser.element( '#wpPassword1' ); }
get loginButton() { return browser.element( '#wpLoginAttempt' ); }
get userPage() { return browser.element( '#pt-userpage' ); }
open() {
super.openTitle( 'Special:UserLogin' );
}
login( username, password ) {
this.open();
this.username.setValue( username );
this.password.setValue( password );
this.loginButton.click();
}
loginAdmin() {
this.login( browser.options.username, browser.options.password );
}
}
module.exports = new LoginPage();

View file

@ -0,0 +1,23 @@
const querystring = require( 'querystring' );
/**
* Based on http://webdriver.io/guide/testrunner/pageobjects.html
*/
class Page {
/**
* Navigate the browser to a given page.
*
* @since 1.0.0
* @see <http://webdriver.io/api/protocol/url.html>
* @param {string} title Page title
* @param {Object} [query] Query parameter
* @return {void} This method runs a browser command.
*/
openTitle( title, query = {} ) {
query.title = title;
browser.url( browser.options.baseUrl + '/index.php?' + querystring.stringify( query ) );
}
}
module.exports = Page;

View file

@ -0,0 +1,53 @@
# wdio-mediawiki
A plugin for [WebdriverIO](http://webdriver.io/) providing utilities to simplify testing of MediaWiki features.
## Getting Started
### Page
The `Page` class is a base class for following the [Page Objects Pattern](http://webdriver.io/guide/testrunner/pageobjects.html).
* `openTitle( title [, Object query ] )`
The convention is for implementations to extend this class and provide an `open()` method
that calls `super.openTitle()`, as well as add various getters for elements on the page.
See [BlankPage](./BlankPage.js) and [specs/BlankPage](./specs/BlankPage.js) for an example.
### Api
Utilities to interact with the MediaWiki API. Uses the [mwbot](https://github.com/Fannon/mwbot) library.
Actions are performed logged-in using `browser.options.username` and `browser.options.password`,
which typically come from `MEDIAWIKI_USER` and `MEDIAWIKI_PASSWORD` environment variables.
* `edit(title, content)`
* `delete(title, reason)`
* `createAccount(username, password)`
## Versioning
This package follows [Semantic Versioning guidelines](https://semver.org/) for its releases. In
particular, its major version must be bumped when compatibility is removed for a previous of
MediaWiki.
It is the expectation that this module will only support a single version of MediaWiki at any
given time, and that tests in older branches of MediaWiki-related projects naturally use the older
release line of this package.
In order to allow for smooth and decentralised upgrades, it is recommended that the only type of
breaking change made to this package is a change that removes something. Thus, in order to change
something, it must either be backwards-compatible, or must be introduced as a new method that
co-exists with its deprecated equivalent for at least one release.
## Issue tracker
Please report issues to [Phabricator](https://phabricator.wikimedia.org/tag/mediawiki-core-tests/).
## Contributing
This module is maintained in the MediaWiki core repository and published from there as a
package to npmjs.org. To simplify development and to ensure changes are verified
automatically, MediaWiki core itself uses this module directly from the working copy
using [npm Local Paths](https://docs.npmjs.com/files/package.json#local-paths).

View file

@ -0,0 +1,26 @@
const fs = require( 'fs' );
module.exports = {
/**
* Based on <https://github.com/webdriverio/webdriverio/issues/269#issuecomment-306342170>
*
* @since 1.0.0
* @param {string} title Description (will be sanitised and used as file name)
* @return {string} File path
*/
saveScreenshot( title ) {
var filename, filePath;
// Create sane file name for current test title
filename = encodeURIComponent( title.replace( /\s+/g, '-' ) );
filePath = `${browser.options.screenshotPath}/${filename}.png`;
// Ensure directory exists, based on WebDriverIO#saveScreenshotSync()
try {
fs.statSync( browser.options.screenshotPath );
} catch ( err ) {
fs.mkdirSync( browser.options.screenshotPath );
}
// Create and save screenshot
browser.saveScreenshot( filePath );
return filePath;
}
};

View file

@ -0,0 +1,21 @@
{
"name": "wdio-mediawiki",
"version": "0.1.0",
"description": "WebdriverIO plugin for testing a MediaWiki site.",
"homepage": "https://gerrit.wikimedia.org/g/mediawiki/core/+/master/tests/selenium/wdio-mediawiki/",
"license": "MIT",
"keywords": [
"mediawiki",
"wdio-plugin"
],
"files": [
"*.js",
"specs/"
],
"engines": {
"node" : ">=6.0"
},
"dependencies": {
"mwbot": "1.0.10"
}
}

View file

@ -0,0 +1,11 @@
const assert = require( 'assert' ),
BlankPage = require( 'wdio-mediawiki/BlankPage' );
describe( 'BlankPage', function () {
it( 'should have its title', function () {
BlankPage.open();
// check
assert.equal( BlankPage.heading.getText(), 'Blank page' );
} );
} );

View file

@ -1,5 +1,6 @@
const fs = require( 'fs' ),
path = require( 'path' ),
saveScreenshot = require( 'wdio-mediawiki' ).saveScreenshot,
logPath = process.env.LOG_DIR || __dirname + '/log';
function relPath( foo ) {
@ -11,33 +12,43 @@ exports.config = {
// Custom WDIO config specific to MediaWiki
// ======
// Use in a test as `browser.options.<key>`.
// Configure wiki admin user/pass via env
// Defaults are for convenience with MediaWiki-Vagrant
// Wiki admin
username: process.env.MEDIAWIKI_USER || 'Admin',
password: process.env.MEDIAWIKI_PASSWORD || 'vagrant',
// Base for browser.url() and Page#openTitle()
baseUrl: ( process.env.MW_SERVER || 'http://127.0.0.1:8080' ) + (
process.env.MW_SCRIPT_PATH || '/w'
),
// ======
// Sauce Labs
// ======
// See http://webdriver.io/guide/services/sauce.html
// and https://docs.saucelabs.com/reference/platforms-configurator
services: [ 'sauce' ],
user: process.env.SAUCE_USERNAME,
key: process.env.SAUCE_ACCESS_KEY,
// Default timeout in milliseconds for Selenium Grid requests
connectionRetryTimeout: 90 * 1000,
// Default request retries count
connectionRetryCount: 3,
// ==================
// Specify Test Files
// Test Files
// ==================
// Define which test specs should run. The pattern is relative to the directory
// from which `wdio` was called. Notice that, if you are calling `wdio` from an
// NPM script (see https://docs.npmjs.com/cli/run-script) then the current working
// directory is where your package.json resides, so `wdio` will be called from there.
specs: [
relPath( './tests/selenium/wdio-mediawiki/specs/*.js' ),
relPath( './tests/selenium/specs/**/*.js' ),
relPath( './extensions/*/tests/selenium/specs/**/*.js' ),
relPath( './extensions/VisualEditor/modules/ve-mw/tests/selenium/specs/**/*.js' ),
relPath( './skins/*/tests/selenium/specs/**/*.js' )
],
// Patterns to exclude.
// Patterns to exclude
exclude: [
relPath( './extensions/CirrusSearch/tests/selenium/specs/**/*.js' )
],
@ -45,51 +56,37 @@ exports.config = {
// ============
// Capabilities
// ============
// Define your capabilities here. WebdriverIO can run multiple capabilities at the same
// time. Depending on the number of capabilities, WebdriverIO launches several test
// sessions. Within your capabilities you can overwrite the spec and exclude options in
// order to group specific specs to a specific capability.
// First, you can define how many instances should be started at the same time. Let's
// say you have 3 different capabilities (Chrome, Firefox, and Safari) and you have
// set maxInstances to 1; wdio will spawn 3 processes. Therefore, if you have 10 spec
// files and you set maxInstances to 10, all spec files will get tested at the same time
// and 30 processes will get spawned. The property handles how many capabilities
// from the same test should run tests.
// How many instances of the same capability (browser) may be started at the same time.
maxInstances: 1,
// If you have trouble getting all important capabilities together, check out the
// Sauce Labs platform configurator - a great tool to configure your capabilities:
// https://docs.saucelabs.com/reference/platforms-configurator
//
// For Chrome/Chromium https://sites.google.com/a/chromium.org/chromedriver/capabilities
capabilities: [ {
// maxInstances can get overwritten per capability. So if you have an in-house Selenium
// grid with only 5 firefox instances available you can make sure that not more than
// 5 instances get started at a time.
maxInstances: 1,
// For Chrome/Chromium https://sites.google.com/a/chromium.org/chromedriver/capabilities
browserName: 'chrome',
maxInstances: 1,
chromeOptions: {
// If DISPLAY is set, assume running from developer machine and/or with Xvfb.
// If DISPLAY is set, assume developer asked non-headless or CI with Xvfb.
// Otherwise, use --headless (added in Chrome 59)
// https://chromium.googlesource.com/chromium/src/+/59.0.3030.0/headless/README.md
args: (
process.env.DISPLAY ? [] : [ '--headless' ]
).concat(
args: [
...( process.env.DISPLAY ? [] : [ '--headless' ] ),
// Chrome sandbox does not work in Docker
fs.existsSync( '/.dockerenv' ) ? [ '--no-sandbox' ] : []
)
...( fs.existsSync( '/.dockerenv' ) ? [ '--no-sandbox' ] : [] )
]
}
} ],
// ===================
// Test Configurations
// ===================
// Define all options that are relevant for the WebdriverIO instance here
// Enabling synchronous mode (via the wdio-sync package), means specs don't have to
// use Promise#then() or await for browser commands, such as like `brower.element()`.
// Instead, it will automatically pause JavaScript execution until th command finishes.
//
// By default WebdriverIO commands are executed in a synchronous way using
// the wdio-sync package. If you still want to run your tests in an async way
// e.g. using promises you can set the sync option to false.
// For non-browser commands (such as MWBot and other promises), this means you
// have to use `browser.call()` to make sure WDIO waits for it before the next
// browser command.
sync: true,
// Level of logging verbosity: silent | verbose | command | data | result | error
@ -101,67 +98,23 @@ exports.config = {
// Warns when a deprecated command is used
deprecationWarnings: true,
// If you only want to run your tests until a specific amount of tests have failed use
// bail (default is 0 - don't bail, run all tests).
// Stop the tests once a certain number of failed tests have been recorded.
// Default is 0 - don't bail, run all tests.
bail: 0,
// Saves a screenshot to a given path if a command fails.
// Setting this enables automatic screenshots for when a browser command fails
// It is also used by afterTest for capturig failed assertions.
screenshotPath: logPath,
// Set a base URL in order to shorten url command calls. If your `url` parameter starts
// with `/`, the base url gets prepended, not including the path portion of your baseUrl.
// If your `url` parameter starts without a scheme or `/` (like `some/path`), the base url
// gets prepended directly.
baseUrl: (
process.env.MW_SERVER || 'http://127.0.0.1:8080'
) + (
process.env.MW_SCRIPT_PATH || '/w'
),
// Default timeout for each waitFor* command.
waitforTimeout: 10 * 1000,
// Default timeout for all waitFor* commands.
waitforTimeout: 10000,
// Default timeout in milliseconds for request
// if Selenium Grid doesn't send response
connectionRetryTimeout: 90000,
// Default request retries count
connectionRetryCount: 3,
// Initialize the browser instance with a WebdriverIO plugin. The object should have the
// plugin name as key and the desired plugin options as properties. Make sure you have
// the plugin installed before running any tests. The following plugins are currently
// available:
// WebdriverCSS: https://github.com/webdriverio/webdrivercss
// WebdriverRTC: https://github.com/webdriverio/webdriverrtc
// Browserevent: https://github.com/webdriverio/browserevent
// plugins: {
// webdrivercss: {
// screenshotRoot: 'my-shots',
// failedComparisonsRoot: 'diffs',
// misMatchTolerance: 0.05,
// screenWidth: [320,480,640,1024]
// },
// webdriverrtc: {},
// browserevent: {}
// },
//
// Test runner services
// Services take over a specific job you don't want to take care of. They enhance
// your test setup with almost no effort. Unlike plugins, they don't add new
// commands. Instead, they hook themselves up into the test process.
// services: [],//
// Framework you want to run your specs with.
// The following are supported: Mocha, Jasmine, and Cucumber
// see also: http://webdriver.io/guide/testrunner/frameworks.html
//
// Make sure you have the wdio adapter package for the specific framework installed
// before running any tests.
// See also: http://webdriver.io/guide/testrunner/frameworks.html
framework: 'mocha',
// Test reporter for stdout.
// The only one supported by default is 'dot'
// see also: http://webdriver.io/guide/testrunner/reporters.html
// See also: http://webdriver.io/guide/testrunner/reporters.html
reporters: [ 'spec', 'junit' ],
reporterOptions: {
junit: {
@ -173,146 +126,24 @@ exports.config = {
// See the full list at http://mochajs.org/
mochaOpts: {
ui: 'bdd',
timeout: 60000
timeout: 60 * 1000
},
// =====
// Hooks
// =====
// WebdriverIO provides several hooks you can use to interfere with the test process in order to enhance
// it and to build services around it. You can either apply a single function or an array of
// methods to it. If one of them returns with a promise, WebdriverIO will wait until that promise got
// resolved to continue.
// See also: http://webdriver.io/guide/testrunner/configurationfile.html
/**
* Gets executed once before all workers get launched.
* @param {Object} config wdio configuration object
* @param {Array.<Object>} capabilities list of capabilities details
* Save a screenshot when test fails.
*
* @param {Object} test Mocha Test object
*/
// onPrepare: function (config, capabilities) {
// },
/**
* Gets executed just before initialising the webdriver session and test framework. It allows you
* to manipulate configurations depending on the capability or spec.
* @param {Object} config wdio configuration object
* @param {Array.<Object>} capabilities list of capabilities details
* @param {Array.<String>} specs List of spec file paths that are to be run
*/
// beforeSession: function (config, capabilities, specs) {
// },
/**
* Gets executed before test execution begins. At this point you can access to all global
* variables like `browser`. It is the perfect place to define custom commands.
* @param {Array.<Object>} capabilities list of capabilities details
* @param {Array.<String>} specs List of spec file paths that are to be run
*/
// before: function (capabilities, specs) {
// },
/**
* Runs before a WebdriverIO command gets executed.
* @param {String} commandName hook command name
* @param {Array} args arguments that command would receive
*/
// beforeCommand: function (commandName, args) {
// },
/**
* Hook that gets executed before the suite starts
* @param {Object} suite suite details
*/
// beforeSuite: function (suite) {
// },
/**
* Function to be executed before a test (in Mocha/Jasmine) or a step (in Cucumber) starts.
* @param {Object} test test details
*/
// beforeTest: function (test) {
// },
/**
* Hook that gets executed _before_ a hook within the suite starts (e.g. runs before calling
* beforeEach in Mocha)
*/
// beforeHook: function () {
// },
/**
* Hook that gets executed _after_ a hook within the suite ends (e.g. runs after calling
* afterEach in Mocha)
*/
// afterHook: function () {
// },
/**
* Function to be executed after a test (in Mocha/Jasmine) or a step (in Cucumber) ends.
* @param {Object} test test details
*/
// from https://github.com/webdriverio/webdriverio/issues/269#issuecomment-306342170
afterTest: function ( test ) {
var filename, filePath;
// if test passed, ignore, else take and save screenshot
if ( test.passed ) {
return;
var filePath;
if ( !test.passed ) {
filePath = saveScreenshot( test.title );
console.log( '\n\tScreenshot: ' + filePath + '\n' );
}
// Create sane file name for current test title
filename = encodeURIComponent( test.title.replace( /\s+/g, '-' ) );
filePath = `${browser.options.screenshotPath}/${filename}.png`;
// Ensure directory exists, based on WebDriverIO#saveScreenshotSync()
try {
fs.statSync( browser.options.screenshotPath );
} catch ( err ) {
fs.mkdirSync( browser.options.screenshotPath );
}
// Create and save screenshot
browser.saveScreenshot( filePath );
console.log( '\n\tScreenshot location:', filePath, '\n' );
}
/**
* Hook that gets executed after the suite has ended
* @param {Object} suite suite details
*/
// afterSuite: function (suite) {
// },
/**
* Runs after a WebdriverIO command gets executed
* @param {String} commandName hook command name
* @param {Array} args arguments that command would receive
* @param {Number} result 0 - command success, 1 - command error
* @param {Object} error error object if any
*/
// afterCommand: function (commandName, args, result, error) {
// },
/**
* Gets executed after all tests are done. You still have access to all global variables from
* the test.
* @param {Number} result 0 - test pass, 1 - test fail
* @param {Array.<Object>} capabilities list of capabilities details
* @param {Array.<String>} specs List of spec file paths that ran
*/
// after: function (result, capabilities, specs) {
// },
/**
* Gets executed right after terminating the webdriver session.
* @param {Object} config wdio configuration object
* @param {Array.<Object>} capabilities list of capabilities details
* @param {Array.<String>} specs List of spec file paths that ran
*/
// afterSession: function (config, capabilities, specs) {
// },
/**
* Gets executed after all workers got shut down and the process is about to exit.
* @param {Object} exitCode 0 - success, 1 - fail
* @param {Object} config wdio configuration object
* @param {Array.<Object>} capabilities list of capabilities details
*/
// onComplete: function(exitCode, config, capabilities) {
// }
};