"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",
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)
-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' ); }
get heading() { return browser.element( '#firstHeading' ); }
open() {
- super.open( 'Special:CreateAccount' );
+ super.openTitle( 'Special:CreateAccount' );
}
createAccount( username, password ) {
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 );
}
}
-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' ); }
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 );
}
}
-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' ); }
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 ) {
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 );
}
}
-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' } );
}
}
+const Page = require( 'wdio-mediawiki/Page' );
+
/**
- * Based on http://webdriver.io/guide/testrunner/pageobjects.html
+ * @deprecated Use wdio-mediawiki/Page and openTitle() instead.
*/
-
-class Page {
+class LegacyPage extends Page {
open( path ) {
browser.url( browser.options.baseUrl + '/index.php?title=' + path );
}
}
-module.exports = Page;
+module.exports = LegacyPage;
-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 ) {
-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();
}
-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;
const assert = require( 'assert' ),
+ Api = require( 'wdio-mediawiki/Api' ),
DeletePage = require( '../pageobjects/delete.page' ),
RestorePage = require( '../pageobjects/restore.page' ),
EditPage = require( '../pageobjects/edit.page' ),
// 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
// 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
it( 'should have history', function () {
// create
browser.call( function () {
- return EditPage.apiEdit( name, content );
+ return Api.edit( name, content );
} );
// check
// create
browser.call( function () {
- return EditPage.apiEdit( name, content );
+ return Api.edit( name, content );
} );
// delete
// 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
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,
it( 'should be able to log in', function () {
// create
browser.call( function () {
- return CreateAccountPage.apiCreateAccount( username, password );
+ return Api.createAccount( username, password );
} );
// log in
// create
browser.call( function () {
- return CreateAccountPage.apiCreateAccount( username, password );
+ return Api.createAccount( username, password );
} );
// log in
--- /dev/null
+{
+ "extends": "wikimedia",
+ "env": {
+ "es6": true,
+ "node": true
+ },
+ "globals": {
+ "browser": false
+ }
+}
--- /dev/null
+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
+ } );
+ } );
+ }
+};
--- /dev/null
+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();
--- /dev/null
+# Notable changes
+
+## [Unreleased]
+
+* Api: Added initial version.
+* Page: Added initial version.
+* BlankPage: Added initial version.
+* LoginPage: Added initial version.
--- /dev/null
+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.
--- /dev/null
+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();
--- /dev/null
+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;
--- /dev/null
+# 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).
--- /dev/null
+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;
+ }
+};
--- /dev/null
+{
+ "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"
+ }
+}
--- /dev/null
+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' );
+ } );
+} );
const fs = require( 'fs' ),
path = require( 'path' ),
+ saveScreenshot = require( 'wdio-mediawiki' ).saveScreenshot,
logPath = process.env.LOG_DIR || __dirname + '/log';
function relPath( foo ) {
// 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' )
],
// ============
// 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
// 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 all waitFor* commands.
- waitforTimeout: 10000,
+ // Default timeout for each waitFor* command.
+ waitforTimeout: 10 * 1000,
- // 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: {
// 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.
-
- /**
- * Gets executed once before all workers get launched.
- * @param {Object} config wdio configuration object
- * @param {Array.<Object>} capabilities list of capabilities details
- */
- // 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) {
- // },
+ // See also: http://webdriver.io/guide/testrunner/configurationfile.html
/**
- * Runs before a WebdriverIO command gets executed.
- * @param {String} commandName hook command name
- * @param {Array} args arguments that command would receive
+ * Save a screenshot when test fails.
+ *
+ * @param {Object} test Mocha Test object
*/
- // 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;
- }
- // 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 );
+ var filePath;
+ if ( !test.passed ) {
+ filePath = saveScreenshot( test.title );
+ console.log( '\n\tScreenshot: ' + filePath + '\n' );
}
- // 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) {
- // }
};