var wgServer = process.env.MW_SERVER,
wgScriptPath = process.env.MW_SCRIPT_PATH,
+ WebdriverIOconfigFile,
karmaProxy = {};
grunt.loadNpmTasks( 'grunt-banana-checker' );
grunt.loadNpmTasks( 'grunt-jsonlint' );
grunt.loadNpmTasks( 'grunt-karma' );
grunt.loadNpmTasks( 'grunt-stylelint' );
+ grunt.loadNpmTasks( 'grunt-webdriver' );
karmaProxy[ wgScriptPath ] = {
target: wgServer + wgScriptPath,
changeOrigin: true
};
+ if ( process.env.JENKINS_HOME ) {
+ WebdriverIOconfigFile = './tests/selenium/wdio.conf.jenkins.js';
+ } else {
+ WebdriverIOconfigFile = './tests/selenium/wdio.conf.js';
+ }
+
grunt.initConfig( {
eslint: {
all: [
return require( 'path' ).join( dest, src.replace( 'resources/', '' ) );
}
}
+ },
+
+ // Configure WebdriverIO task
+ webdriver: {
+ test: {
+ configFile: WebdriverIOconfigFile
+ }
}
+
} );
grunt.registerTask( 'assert-mw-env', function () {
* (T27187) Search suggestions based on jquery.suggestions will now correctly only
highlight prefix matches in the results.
* (T157035) "new mw.Uri()" was ignoring options when using default URI.
+* Special:Allpages can no longer be filtered by redirect in miser mode.
=== Action API changes in 1.29 ===
* Submitting sensitive authentication request parameters to action=login,
* action=purge now requires a POST.
* There is a new `languagevariants` siprop for action=query&meta=siteinfo,
which returns a list of languages with active LanguageConverter instances.
+* action=query&query=allpages will no longer filter redirects using a database
+ query in miser mode. This may result in less results being returned than were
+ requested.
=== Action API internal changes in 1.29 ===
* New methods were added to ApiBase to handle errors and warnings using i18n
$this->addWhere( "page_title $op= $cont_from" );
}
- if ( $params['filterredir'] == 'redirects' ) {
- $this->addWhereFld( 'page_is_redirect', 1 );
- } elseif ( $params['filterredir'] == 'nonredirects' ) {
- $this->addWhereFld( 'page_is_redirect', 0 );
+ $miserMode = $this->getConfig()->get( 'MiserMode' );
+ if ( !$miserMode ) {
+ if ( $params['filterredir'] == 'redirects' ) {
+ $this->addWhereFld( 'page_is_redirect', 1 );
+ } elseif ( $params['filterredir'] == 'nonredirects' ) {
+ $this->addWhereFld( 'page_is_redirect', 0 );
+ }
}
$this->addWhereFld( 'page_namespace', $params['namespace'] );
$selectFields = $resultPageSet->getPageTableFields();
}
+ $miserModeFilterRedirValue = null;
+ $miserModeFilterRedir = $miserMode && $params['filterredir'] !== 'all';
+ if ( $miserModeFilterRedir ) {
+ $selectFields[] = 'page_is_redirect';
+
+ if ( $params['filterredir'] == 'redirects' ) {
+ $miserModeFilterRedirValue = 1;
+ } elseif ( $params['filterredir'] == 'nonredirects' ) {
+ $miserModeFilterRedirValue = 0;
+ }
+ }
+
$this->addFields( $selectFields );
$forceNameTitleIndex = true;
if ( isset( $params['minsize'] ) ) {
break;
}
+ if ( $miserModeFilterRedir && (int)$row->page_is_redirect !== $miserModeFilterRedirValue ) {
+ // Filter implemented in PHP due to being in Miser Mode
+ continue;
+ }
+
if ( is_null( $resultPageSet ) ) {
$title = Title::makeTitle( $row->page_namespace, $row->page_title );
$vals = [
}
public function getAllowedParams() {
- return [
+ $ret = [
'from' => null,
'continue' => [
ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
ApiBase::PARAM_DFLT => 'all'
],
];
+
+ if ( $this->getConfig()->get( 'MiserMode' ) ) {
+ $ret['filterredir'][ApiBase::PARAM_HELP_MSG_APPEND] = [ 'api-help-param-limited-in-miser-mode' ];
+ }
+
+ return $ret;
}
protected function getExamplesMessages() {
* to the order in which the handles where given.
*
* @param FileBackendStoreOpHandle[] $fileOpHandles
- *
- * @throws FileBackendError
* @return StatusValue[] Map of StatusValue objects
+ * @throws FileBackendError
*/
final public function executeOpHandlesInternal( array $fileOpHandles ) {
$ps = $this->scopedProfileSection( __METHOD__ . "-{$this->name}" );
foreach ( $fileOpHandles as $fileOpHandle ) {
if ( !( $fileOpHandle instanceof FileBackendStoreOpHandle ) ) {
- throw new InvalidArgumentException( "Got a non-FileBackendStoreOpHandle object." );
+ throw new InvalidArgumentException( "Expected FileBackendStoreOpHandle object." );
} elseif ( $fileOpHandle->backend->getName() !== $this->getName() ) {
- throw new InvalidArgumentException(
- "Got a FileBackendStoreOpHandle for the wrong backend." );
+ throw new InvalidArgumentException( "Expected handle for this file backend." );
}
}
+
$res = $this->doExecuteOpHandlesInternal( $fileOpHandles );
foreach ( $fileOpHandles as $fileOpHandle ) {
$fileOpHandle->closeResources();
if ( !empty( $params['async'] ) ) { // deferred
$status->value = $opHandle;
} else { // actually write the object in Swift
- $status->merge( current( $this->doExecuteOpHandlesInternal( [ $opHandle ] ) ) );
+ $status->merge( current( $this->executeOpHandlesInternal( [ $opHandle ] ) ) );
}
return $status;
if ( !empty( $params['async'] ) ) { // deferred
$status->value = $opHandle;
} else { // actually write the object in Swift
- $status->merge( current( $this->doExecuteOpHandlesInternal( [ $opHandle ] ) ) );
+ $status->merge( current( $this->executeOpHandlesInternal( [ $opHandle ] ) ) );
}
return $status;
if ( !empty( $params['async'] ) ) { // deferred
$status->value = $opHandle;
} else { // actually write the object in Swift
- $status->merge( current( $this->doExecuteOpHandlesInternal( [ $opHandle ] ) ) );
+ $status->merge( current( $this->executeOpHandlesInternal( [ $opHandle ] ) ) );
}
return $status;
if ( !empty( $params['async'] ) ) { // deferred
$status->value = $opHandle;
} else { // actually move the object in Swift
- $status->merge( current( $this->doExecuteOpHandlesInternal( [ $opHandle ] ) ) );
+ $status->merge( current( $this->executeOpHandlesInternal( [ $opHandle ] ) ) );
}
return $status;
if ( !empty( $params['async'] ) ) { // deferred
$status->value = $opHandle;
} else { // actually delete the object in Swift
- $status->merge( current( $this->doExecuteOpHandlesInternal( [ $opHandle ] ) ) );
+ $status->merge( current( $this->executeOpHandlesInternal( [ $opHandle ] ) ) );
}
return $status;
if ( !empty( $params['async'] ) ) { // deferred
$status->value = $opHandle;
} else { // actually change the object in Swift
- $status->merge( current( $this->doExecuteOpHandlesInternal( [ $opHandle ] ) ) );
+ $status->merge( current( $this->executeOpHandlesInternal( [ $opHandle ] ) ) );
}
return $status;
$from = $request->getVal( 'from', null );
$to = $request->getVal( 'to', null );
$namespace = $request->getInt( 'namespace' );
- $hideredirects = $request->getBool( 'hideredirects', false );
+
+ $miserMode = (bool)$this->getConfig()->get( 'MiserMode' );
+
+ // Redirects filter is disabled in MiserMode
+ $hideredirects = $request->getBool( 'hideredirects', false ) && !$miserMode;
$namespaces = $this->getLanguage()->getNamespaces();
protected function outputHTMLForm( $namespace = NS_MAIN,
$from = '', $to = '', $hideRedirects = false
) {
+ $miserMode = (bool)$this->getConfig()->get( 'MiserMode' );
$fields = [
'from' => [
'type' => 'text',
'value' => $hideRedirects,
],
];
+
+ if ( $miserMode ) {
+ unset ( $fields['hideredirects'] );
+ }
+
$form = HTMLForm::factory( 'table', $fields, $this->getContext() );
$form->setMethod( 'get' )
->setWrapperLegendMsg( 'allpages' )
"recentchanges-legend": "Recent changes options",
"recentchanges-summary": "Track the most recent changes to the wiki on this page.",
"recentchangestext": "-",
- "recentchanges-noresult": "No changes during the given period matching these criteria.",
+ "recentchanges-noresult": "No changes during the given period match these criteria.",
"recentchanges-feed-description": "Track the most recent changes to the wiki in this feed.",
"recentchanges-label-newpage": "This edit created a new page",
"recentchanges-label-minor": "This is a minor edit",
"rcfilters-activefilters": "Active filters",
"rcfilters-restore-default-filters": "Restore default filters",
"rcfilters-clear-all-filters": "Clear all filters",
+ "rcfilters-clear-filters-to-defaults": "Set filters to defaults",
"rcfilters-search-placeholder": "Filter recent changes (browse or start typing)",
"rcfilters-invalid-filter": "Invalid filter",
"rcfilters-empty-filter": "No active filters. All contributions are shown.",
"rcfilters-activefilters": "Title for the filters selection showing the active filters.",
"rcfilters-restore-default-filters": "Label for the button that resets filters to defaults",
"rcfilters-clear-all-filters": "Title for the button that clears all filters",
+ "rcfilters-clear-filters-to-defaults": "Title for the button that sets filters to default",
"rcfilters-search-placeholder": "Placeholder for the filter search input.",
"rcfilters-invalid-filter": "A label for an invalid filter.",
"rcfilters-empty-filter": "Placeholder for the filter list when no filters were chosen.",
"scripts": {
"test": "grunt test",
"doc": "jsduck",
- "postdoc": "grunt copy:jsduck"
+ "postdoc": "grunt copy:jsduck",
+ "selenium": "killall -0 chromedriver 2>/dev/null || chromedriver --url-base=/wd/hub --port=4444 & grunt webdriver:test; killall chromedriver"
},
"devDependencies": {
+ "deepmerge": "1.3.2",
"eslint": "3.12.2",
"eslint-config-wikimedia": "0.3.0",
"grunt": "1.0.1",
"grunt-jsonlint": "1.1.0",
"grunt-karma": "2.0.0",
"grunt-stylelint": "0.7.0",
+ "grunt-webdriver": "2.0.3",
"karma": "1.1.0",
"karma-chrome-launcher": "2.0.0",
"karma-firefox-launcher": "1.0.0",
"karma-qunit": "1.0.0",
"qunitjs": "1.22.0",
- "stylelint-config-wikimedia": "0.4.1"
+ "stylelint-config-wikimedia": "0.4.1",
+ "wdio-junit-reporter": "0.2.0",
+ "wdio-mocha-framework": "0.5.8",
+ "wdio-spec-reporter": "0.0.5",
+ "webdriverio": "4.6.2"
}
}
'rcfilters-activefilters',
'rcfilters-restore-default-filters',
'rcfilters-clear-all-filters',
+ 'rcfilters-clear-filters-to-defaults',
'rcfilters-search-placeholder',
'rcfilters-invalid-filter',
'rcfilters-empty-filter',
this.resetButton.setLabel(
currFiltersAreEmpty ? mw.msg( 'rcfilters-restore-default-filters' ) : ''
);
+ this.resetButton.setTitle(
+ currFiltersAreEmpty ?
+ mw.msg( 'rcfilters-clear-filters-to-defaults' ) :
+ mw.msg( 'rcfilters-clear-all-filters' )
+ );
this.resetButton.toggle( !hideResetButton );
this.emptyFilterMessage.toggle( currFiltersAreEmpty );
}
} else {
this.scrollToTop( this.capsule.$element, 10 );
+ if ( !this.filterPopup.getSelectedFilter() ) {
+ // No selection, scroll the popup list to top
+ setTimeout( function () { this.capsule.popup.$body.scrollTop( 0 ); }.bind( this ), 0 );
+ }
}
};
--- /dev/null
+{
+ "extends": "../../.eslintrc.json",
+ "env": {
+ "es6": true,
+ "mocha": true,
+ "node": true
+ },
+ "globals": {
+ "browser": false
+ }
+}
--- /dev/null
+# Selenium tests
+
+## Prerequisites
+
+- [Chrome](https://www.google.com/chrome/)
+- [ChromeDriver](https://sites.google.com/a/chromium.org/chromedriver/)
+- [Node.js](https://nodejs.org/en/)
+- [MediaWiki-Vagrant](https://www.mediawiki.org/wiki/MediaWiki-Vagrant)
+
+Set up MediaWiki-Vagrant:
+
+ cd mediawiki/vagrant
+ vagrant up
+
+## Installation
+
+ cd mediawiki
+ npm install
+
+## Usage
+
+ npm run selenium
+
+To run only one file (for example page.js), you first need to spawn the chromedriver:
+
+ chromedriver --url-base=/wd/hub --port=4444
+
+Then in another terminal:
+
+ cd mediawiki/tests/selenium
+ ../../node_modules/.bin/wdio --spec page.js
+
+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 targetting a MediaWiki-Vagrant
+installation on installation on http://127.0.0.1:8080 with a user Admin and
+password 'vagrant'. Those settings can be overriden 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
+
+Example:
+
+ MW_SERVER=http://example.org MW_SCRIPT_PATH=/dev/w npm run selenium
+
+## Links
+
+- [Selenium/Node.js](https://www.mediawiki.org/wiki/Selenium/Node.js)
--- /dev/null
+'use strict';
+const Page = require( './page' );
+
+class CreateAccountPage extends Page {
+
+ get username() { return browser.element( '#wpName2' ); }
+ get password() { return browser.element( '#wpPassword2' ); }
+ get confirmPassword() { return browser.element( '#wpRetype' ); }
+ get create() { return browser.element( '#wpCreateaccount' ); }
+ get heading() { return browser.element( '#firstHeading' ); }
+
+ open() {
+ super.open( 'Special:CreateAccount' );
+ }
+
+ createAccount( username, password ) {
+ this.open();
+ this.username.setValue( username );
+ this.password.setValue( password );
+ this.confirmPassword.setValue( password );
+ this.create.click();
+ }
+
+}
+module.exports = new CreateAccountPage();
--- /dev/null
+'use strict';
+const Page = require( './page' );
+
+class EditPage extends Page {
+
+ get content() { return browser.element( '#wpTextbox1' ); }
+ get displayedContent() { return browser.element( '#mw-content-text' ); }
+ get heading() { return browser.element( '#firstHeading' ); }
+ get save() { return browser.element( '#wpSave' ); }
+
+ open( name ) {
+ super.open( name + '&action=edit' );
+ }
+
+ edit( name, content ) {
+ this.open( name );
+ this.content.setValue( content );
+ this.save.click();
+ }
+
+}
+module.exports = new EditPage();
--- /dev/null
+'use strict';
+const Page = require( './page' );
+
+class HistoryPage extends Page {
+
+ get comment() { return browser.element( '#pagehistory .comment' ); }
+
+ open( name ) {
+ super.open( name + '&action=history' );
+ }
+
+}
+module.exports = new HistoryPage();
--- /dev/null
+// From http://webdriver.io/guide/testrunner/pageobjects.html
+'use strict';
+class Page {
+ constructor() {
+ this.title = 'My Page';
+ }
+ open( path ) {
+ browser.url( '/index.php?title=' + path );
+ }
+}
+module.exports = Page;
--- /dev/null
+'use strict';
+const Page = require( './page' );
+
+class PreferencesPage extends Page {
+
+ get realName() { return browser.element( '#mw-input-wprealname' ); }
+ get save() { return browser.element( '#prefcontrol' ); }
+
+ open() {
+ super.open( 'Special:Preferences' );
+ }
+
+ changeRealName( realName ) {
+ this.open();
+ this.realName.setValue( realName );
+ this.save.click();
+ }
+
+}
+module.exports = new PreferencesPage();
--- /dev/null
+'use strict';
+const Page = require( './page' );
+
+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();
+ }
+
+}
+module.exports = new UserLoginPage();
--- /dev/null
+'use strict';
+const Page = require( './page' );
+
+class UserLogoutPage extends Page {
+
+ open() {
+ super.open( 'Special:UserLogout' );
+ }
+
+}
+module.exports = new UserLogoutPage();
--- /dev/null
+'use strict';
+const assert = require( 'assert' ),
+ HistoryPage = require( '../pageobjects/history.page' ),
+ EditPage = require( '../pageobjects/edit.page' );
+
+describe( 'Page', function () {
+
+ var content,
+ name;
+
+ beforeEach( function () {
+ content = Math.random().toString();
+ name = Math.random().toString();
+ } );
+
+ it( 'should be creatable', function () {
+
+ // create
+ EditPage.edit( name, content );
+
+ // check
+ assert.equal( EditPage.heading.getText(), name );
+ assert.equal( EditPage.displayedContent.getText(), content );
+
+ } );
+
+ it( 'should be editable', function () {
+
+ var content2 = Math.random().toString();
+
+ // create
+ EditPage.edit( name, content );
+
+ // edit
+ EditPage.edit( name, content2 );
+
+ // check content
+ assert.equal( EditPage.heading.getText(), name );
+ assert.equal( EditPage.displayedContent.getText(), content2 );
+
+ } );
+
+ it( 'should have history', function () {
+
+ // create
+ EditPage.edit( name, content );
+
+ // check
+ HistoryPage.open( name );
+ assert.equal( HistoryPage.comment.getText(), `(Created page with "${content}")` );
+
+ } );
+
+} );
--- /dev/null
+'use strict';
+const assert = require( 'assert' ),
+ CreateAccountPage = require( '../pageobjects/createaccount.page' ),
+ UserLoginPage = require( '../pageobjects/userlogin.page' ),
+ UserLogoutPage = require( '../pageobjects/userlogout.page' ),
+ PreferencesPage = require( '../pageobjects/preferences.page' );
+
+describe( 'User', function () {
+
+ var password,
+ username;
+
+ beforeEach( function () {
+ username = `User-${Math.random().toString()}`;
+ password = Math.random().toString();
+ } );
+
+ it( 'should be able to create account', function () {
+
+ // create
+ CreateAccountPage.createAccount( username, password );
+
+ // check
+ assert.equal( CreateAccountPage.heading.getText(), `Welcome, ${username}!` );
+
+ } );
+
+ it( 'should be able to log in', function () {
+
+ // create
+ CreateAccountPage.createAccount( username, password );
+
+ // logout
+ UserLogoutPage.open();
+
+ // log in
+ UserLoginPage.login( username, password );
+
+ // check
+ assert.equal( UserLoginPage.userPage.getText(), username );
+
+ } );
+
+ it( 'should be able to change preferences', function () {
+
+ var realName = Math.random().toString();
+
+ // create
+ CreateAccountPage.createAccount( username, password );
+
+ // change real name
+ PreferencesPage.changeRealName( realName );
+
+ // check
+ assert.equal( PreferencesPage.realName.getValue(), realName );
+
+ } );
+
+} );
--- /dev/null
+/* eslint no-undef: "error"*/
+/* eslint-env node*/
+'use strict';
+var merge = require( 'deepmerge' ),
+ wdioConf = require( './wdio.conf.js' );
+
+// Overwrite default settings
+exports.config = merge( wdioConf.config, {
+ username: 'WikiAdmin',
+ password: 'testpass',
+ screenshotPath: '../log/',
+ baseUrl: process.env.MW_SERVER + process.env.MW_SCRIPT_PATH,
+
+ reporters: [ 'spec', 'junit' ],
+ reporterOptions: {
+ junit: {
+ outputDir: '../log/'
+ }
+ }
+} );
--- /dev/null
+/* eslint comma-dangle: 0 */
+/* eslint no-undef: "error"*/
+/* eslint no-console: 0 */
+/* eslint-env node*/
+'use strict';
+
+const path = require( 'path' );
+
+function relPath( foo ) {
+ return path.resolve( __dirname, '../..', foo );
+}
+
+exports.config = {
+
+ //
+ // ======
+ //
+ // ======
+ // Custom
+ // ======
+ // Define any custom variables.
+ // Example:
+ // username: 'Admin',
+ // Use if from tests with:
+ // browser.options.username
+ username: process.env.MEDIAWIKI_USER === undefined ?
+ 'Admin' :
+ process.env.MEDIAWIKI_USER,
+ password: process.env.MEDIAWIKI_PASSWORD === undefined ?
+ 'vagrant' :
+ process.env.MEDIAWIKI_PASSWORD,
+ //
+ // ==================
+ // Specify 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/specs/**/*.js' ),
+ relPath( './extensions/*/tests/selenium/specs/**/*.js' ),
+ relPath( './extensions/VisualEditor/modules/ve-mw/tests/selenium/specs/**/*.js' )
+ ],
+ // Patterns to exclude.
+ exclude: [
+ // 'path/to/excluded/files'
+ ],
+ //
+ // ============
+ // 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.
+ //
+ 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,
+ //
+ browserName: 'chrome',
+ // Since Chrome v57 https://bugs.chromium.org/p/chromedriver/issues/detail?id=1625
+ chromeOptions: {
+ args: [ '--enable-automation' ]
+ }
+ } ],
+ //
+ // ===================
+ // Test Configurations
+ // ===================
+ // Define all options that are relevant for the WebdriverIO instance here
+ //
+ // 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.
+ sync: true,
+ //
+ // Level of logging verbosity: silent | verbose | command | data | result | error
+ logLevel: 'error',
+ //
+ // Enables colors for log output.
+ coloredLogs: true,
+ //
+ // Saves a screenshot to a given path if a command fails.
+ screenshotPath: './log/',
+ //
+ // Set a base URL in order to shorten url command calls. If your url parameter starts
+ // with "/", then the base url gets prepended.
+ baseUrl: (
+ process.env.MW_SERVER === undefined ?
+ 'http://127.0.0.1:8080' :
+ process.env.MW_SERVER
+ ) + (
+ process.env.MW_SCRIPT_PATH === undefined ?
+ '/w' :
+ process.env.MW_SCRIPT_PATH
+ ),
+ //
+ // Default timeout for all waitFor* commands.
+ waitforTimeout: 20000,
+ //
+ // 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.
+ framework: 'mocha',
+
+ // Test reporter for stdout.
+ // The only one supported by default is 'dot'
+ // see also: http://webdriver.io/guide/testrunner/reporters.html
+ reporters: [ 'spec' ],
+ //
+ // Options to be passed to Mocha.
+ // See the full list at http://mochajs.org/
+ mochaOpts: {
+ ui: 'bdd',
+ timeout: 20000
+ },
+ //
+ // =====
+ // 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.
+ // onPrepare: function ( config, capabilities ) {
+ // }
+ //
+ // Gets executed before test execution begins. At this point you can access all global
+ // variables, such as `browser`. It is the perfect place to define custom commands.
+ // before: function (capabilities, specs) {
+ // },
+ //
+ // Hook that gets executed before the suite starts
+ // beforeSuite: function (suite) {
+ // },
+ //
+ // 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 starts (e.g. runs after calling
+ // afterEach in Mocha)
+ //
+ // Function to be executed before a test (in Mocha/Jasmine) or a step (in Cucumber) starts.
+ // beforeTest: function (test) {
+ // },
+ //
+ // Runs before a WebdriverIO command gets executed.
+ // beforeCommand: function (commandName, args) {
+ // },
+ //
+ // Runs after a WebdriverIO command gets executed
+ // afterCommand: function (commandName, args, result, error) {
+ // },
+ //
+ // Function to be executed after a test (in Mocha/Jasmine) or a step (in Cucumber) starts.
+ // afterTest: function (test) {
+ // },
+ //
+ // Hook that gets executed after the suite has ended
+ // afterSuite: function (suite) {
+ // },
+ //
+ // Gets executed after all tests are done. You still have access to all global variables from
+ // the test.
+ // after: function (result, capabilities, specs) {
+ // },
+ //
+ // Gets executed after all workers got shut down and the process is about to exit. It is not
+ // possible to defer the end of the process using a promise.
+ // onComplete: function(exitCode) {
+ // }
+};