selenium: Upgrade from webdriver v4 to v5

* Options no longer needed or no longer exist in wdio v5:
  - coloredLogs: Now always on. The underlying 'chalk' library can still
    be influenced via the FORCE_COLOR environment variable.
  - screenshotPath: Removed. Was already disabled in our config.
  - deprecationWarnings: Meh.
  - 'sync: true' – On by default when `@wdio/sync` is installed.
    The wdio v5 config generator doesn't recommend setting manually.

* The selenium.sh script was removed. It existed to start and stop
  chromedriver for local use by developers. This is now done by
  the wdio-chromedriver-service. In WMF CI, Quibble starts its own
  chromedriver (as optimisation, reused across gated repos),
  which is why the 'selenium-test' entry points remains and skips
  this.

* The wdio-mediawiki package now requires wdio v5 and Node 10.
  This doesn't affect extension repos because versions are pinned.
  Upgrade may happen at the earliest convenience.

* Several WDIO methods changed names or signature. Full list at:
  <https://github.com/webdriverio/webdriverio/blob/v5.13.2/CHANGELOG.md#v500-2018-12-20>
  Highlights:
  - browser.element() is now browser.findElement() with "$()" as alias.
  - browser.localStorage replaced by browser.setLocalStorage.
  - browser.deleteCookie() requires `name` param. To delete all at once,
    there is a new method browser.deleteAllCookies().
  - Commands that return data no longer wrapped in `{ value: … }`.
    Values are now returned directly.
  - Custom config keys are now under browser.config instead of browser.options.
    Renamed our username/password keys to be mw-prefixed, to avoid clashes
    and reduce confusion with similar config keys.
  - browser.click(selector) and browser.getText(selector) no longer exist.
    Use $(selector).click() or .getText() instead.

* Fix "no such alert" warning from specs/page.js by removing the apparently
  redundant code.

Bug: T234002
Bug: T213268
Change-Id: I908997569ca8457997af30cb29e98ac41fae3b64
This commit is contained in:
Timo Tijhof 2019-09-27 04:08:00 +01:00
parent 50cd683080
commit 1955a8aa56
28 changed files with 5488 additions and 2125 deletions

7293
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -6,12 +6,21 @@
"qunit": "grunt qunit",
"doc": "jsduck",
"postdoc": "grunt copy:jsduck",
"selenium": "bash ./tests/selenium/selenium.sh",
"selenium": "wdio ./tests/selenium/wdio.conf.js",
"selenium-daily": "npm run selenium-test -- --mochaOpts.grep @daily",
"selenium-test": "wdio ./tests/selenium/wdio.conf.js"
"selenium-test": "wdio ./tests/selenium/wdio.conf.js --skip-chromedriver"
},
"devDependencies": {
"eslint-config-wikimedia": "0.13.0",
"@wdio/cli": "5.13.2",
"@wdio/devtools-service": "5.13.2",
"@wdio/dot-reporter": "5.13.2",
"@wdio/junit-reporter": "5.13.2",
"@wdio/local-runner": "5.13.2",
"@wdio/mocha-framework": "5.13.2",
"@wdio/sauce-service": "5.13.2",
"@wdio/sync": "5.13.2",
"chromedriver": "73.0.0",
"eslint-config-wikimedia": "0.14.1",
"grunt": "1.0.4",
"grunt-banana-checker": "0.7.0",
"grunt-contrib-copy": "1.0.0",
@ -29,11 +38,8 @@
"postcss-less": "2.0.0",
"qunit": "2.9.1",
"stylelint-config-wikimedia": "0.6.0",
"wdio-dot-reporter": "0.0.10",
"wdio-junit-reporter": "0.4.4",
"wdio-chromedriver-service": "5.0.2",
"wdio-mediawiki": "file:tests/selenium/wdio-mediawiki",
"wdio-mocha-framework": "0.6.4",
"wdio-sauce-service": "0.4.14",
"webdriverio": "4.14.4"
"webdriverio": "5.13.2"
}
}

View file

@ -1,12 +1,15 @@
{
"root": true,
"extends": [
"wikimedia/server"
"wikimedia",
"wikimedia/node",
"wikimedia/language/es2017"
],
"env": {
"mocha": true
},
"globals": {
"$": false,
"browser": false,
"mw": false
},

View file

@ -2,11 +2,11 @@ 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 create() { return browser.element( '#wpCreateaccount' ); }
get heading() { return browser.element( '#firstHeading' ); }
get username() { return $( '#wpName2' ); }
get password() { return $( '#wpPassword2' ); }
get confirmPassword() { return $( '#wpRetype' ); }
get create() { return $( '#wpCreateaccount' ); }
get heading() { return $( '#firstHeading' ); }
open() {
super.openTitle( 'Special:CreateAccount' );

View file

@ -2,10 +2,10 @@ const Page = require( 'wdio-mediawiki/Page' ),
Api = require( 'wdio-mediawiki/Api' );
class DeletePage extends Page {
get reason() { return browser.element( '#wpReason' ); }
get watch() { return browser.element( '#wpWatch' ); }
get submit() { return browser.element( '#wpConfirmB' ); }
get displayedContent() { return browser.element( '#mw-content-text' ); }
get reason() { return $( '#wpReason' ); }
get watch() { return $( '#wpWatch' ); }
get submit() { return $( '#wpConfirmB' ); }
get displayedContent() { return $( '#mw-content-text' ); }
open( title ) {
super.openTitle( title, { action: 'delete' } );

View file

@ -2,12 +2,12 @@ const Page = require( 'wdio-mediawiki/Page' ),
Api = require( 'wdio-mediawiki/Api' );
class EditPage extends Page {
get content() { return browser.element( '#wpTextbox1' ); }
get conflictingContent() { return browser.element( '#wpTextbox2' ); }
get displayedContent() { return browser.element( '#mw-content-text .mw-parser-output' ); }
get heading() { return browser.element( '#firstHeading' ); }
get save() { return browser.element( '#wpSave' ); }
get previewButton() { return browser.element( '#wpPreview' ); }
get content() { return $( '#wpTextbox1' ); }
get conflictingContent() { return $( '#wpTextbox2' ); }
get displayedContent() { return $( '#mw-content-text .mw-parser-output' ); }
get heading() { return $( '#firstHeading' ); }
get save() { return $( '#wpSave' ); }
get previewButton() { return $( '#wpPreview' ); }
openForEditing( title ) {
super.openTitle( title, { action: 'edit' } );

View file

@ -3,16 +3,15 @@ const Page = require( 'wdio-mediawiki/Page' ),
Util = require( 'wdio-mediawiki/Util' );
class HistoryPage extends Page {
get heading() { return browser.element( '#firstHeading' ); }
get headingText() { return browser.getText( '#firstHeading' ); }
get comment() { return browser.element( '#pagehistory .comment' ); }
get rollback() { return browser.element( '.mw-rollback-link' ); }
get rollbackLink() { return browser.element( '.mw-rollback-link a' ); }
get rollbackConfirmable() { return browser.element( '.mw-rollback-link .jquery-confirmable-text' ); }
get rollbackConfirmableYes() { return browser.element( '.mw-rollback-link .jquery-confirmable-button-yes' ); }
get rollbackConfirmableNo() { return browser.element( '.mw-rollback-link .jquery-confirmable-button-no' ); }
get rollbackNonJsConfirmable() { return browser.element( '.mw-htmlform .oo-ui-fieldsetLayout-header .oo-ui-labelElement-label' ); }
get rollbackNonJsConfirmableYes() { return browser.element( '.mw-htmlform .mw-htmlform-submit-buttons button' ); }
get heading() { return $( '#firstHeading' ); }
get comment() { return $( '#pagehistory .comment' ); }
get rollback() { return $( '.mw-rollback-link' ); }
get rollbackLink() { return $( '.mw-rollback-link a' ); }
get rollbackConfirmable() { return $( '.mw-rollback-link .jquery-confirmable-text' ); }
get rollbackConfirmableYes() { return $( '.mw-rollback-link .jquery-confirmable-button-yes' ); }
get rollbackConfirmableNo() { return $( '.mw-rollback-link .jquery-confirmable-button-no' ); }
get rollbackNonJsConfirmable() { return $( '.mw-htmlform .oo-ui-fieldsetLayout-header .oo-ui-labelElement-label' ); }
get rollbackNonJsConfirmableYes() { return $( '.mw-htmlform .mw-htmlform-submit-buttons button' ); }
open( title ) {
super.openTitle( title, { action: 'history' } );
@ -29,7 +28,7 @@ class HistoryPage extends Page {
}
vandalizePage( name, content ) {
const vandalUsername = 'Evil_' + browser.options.username;
const vandalUsername = 'Evil_' + browser.config.mwUser;
browser.call( function () {
return Api.edit( name, content );
@ -37,7 +36,7 @@ class HistoryPage extends Page {
browser.call( function () {
return Api.createAccount(
vandalUsername, browser.options.password
vandalUsername, browser.config.mwPwd
);
} );

View file

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

View file

@ -1,7 +1,7 @@
const Page = require( 'wdio-mediawiki/Page' );
class RecentChangesPage extends Page {
get changesList() { return browser.element( '.mw-changeslist' ); }
get changesList() { return $( '.mw-changeslist' ); }
get titles() { return this.changesList.$$( '.mw-changeslist-title' ); }
open() {

View file

@ -1,9 +1,9 @@
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' ); }
get reason() { return $( '#wpComment' ); }
get submit() { return $( '#mw-undelete-submit' ); }
get displayedContent() { return $( '#mw-content-text' ); }
open( subject ) {
super.openTitle( 'Special:Undelete/' + subject );

View file

@ -2,7 +2,7 @@ const Page = require( 'wdio-mediawiki/Page' );
class UndoPage extends Page {
get save() { return browser.element( '#wpSave' ); }
get save() { return $( '#wpSave' ); }
undo( title, previousRev, undoRev ) {
super.openTitle( title, { action: 'edit', undoafter: previousRev, undo: undoRev } );

View file

@ -2,7 +2,7 @@ const Page = require( 'wdio-mediawiki/Page' );
class WatchablePage extends Page {
get confirmWatch() { return browser.element( '#mw-content-text button[type="submit"]' ); }
get confirmWatch() { return $( '#mw-content-text button[type="submit"]' ); }
watch( title ) {
super.openTitle( title, { action: 'watch' } );

View file

@ -2,7 +2,7 @@ const Page = require( 'wdio-mediawiki/Page' );
class WatchlistPage extends Page {
get titles() {
return browser.element( '.mw-changeslist' )
return $( '.mw-changeslist' )
.$$( '.mw-changeslist-line .mw-title' );
}

View file

@ -16,11 +16,11 @@ describe( 'Page', function () {
before( function () {
// disable VisualEditor welcome dialog
BlankPage.open();
browser.localStorage( 'POST', { key: 've-beta-welcome-dialog', value: '1' } );
browser.setLocalStorage( 've-beta-welcome-dialog', '1' );
} );
beforeEach( function () {
browser.deleteCookie();
browser.deleteAllCookies();
content = Util.getTestString( 'beforeEach-content-' );
name = Util.getTestString( 'BeforeEach-name-' );
} );
@ -30,13 +30,8 @@ describe( 'Page', function () {
assert.strictEqual( EditPage.heading.getText(), 'Creating ' + name );
assert.strictEqual( EditPage.displayedContent.getText(), content );
assert( EditPage.content.isVisible(), 'editor is still present' );
assert( !EditPage.conflictingContent.isVisible(), 'no edit conflict happened' );
// provoke and dismiss reload warning due to unsaved content
browser.url( 'data:text/html,Done' );
try {
browser.alertAccept();
} catch ( e ) {}
assert( EditPage.content.isDisplayed(), 'editor is still present' );
assert( !EditPage.conflictingContent.isDisplayed(), 'no edit conflict happened' );
} );
it( 'should be creatable', function () {
@ -81,7 +76,6 @@ describe( 'Page', function () {
// check
assert.strictEqual( EditPage.heading.getText(), name );
// eslint-disable-next-line no-restricted-syntax
assert( EditPage.displayedContent.getText().includes( editContent ) );
} );

View file

@ -10,9 +10,9 @@ describe( 'Rollback with confirmation', function () {
before( function () {
// disable VisualEditor welcome dialog
browser.deleteCookie();
browser.deleteAllCookies();
BlankPage.open();
browser.localStorage( 'POST', { key: 've-beta-welcome-dialog', value: '1' } );
browser.setLocalStorage( 've-beta-welcome-dialog', '1' );
// Enable rollback confirmation for admin user
// Requires user to log in again, handled by deleteCookie() call in beforeEach function
@ -21,7 +21,7 @@ describe( 'Rollback with confirmation', function () {
} );
beforeEach( function () {
browser.deleteCookie();
browser.deleteAllCookies();
content = Util.getTestString( 'beforeEach-content-' );
name = Util.getTestString( 'BeforeEach-name-' );
@ -45,7 +45,7 @@ describe( 'Rollback with confirmation', function () {
it.skip( 'should offer a way to cancel rollbacks', function () {
HistoryPage.rollback.click();
HistoryPage.rollbackConfirmableNo.waitForVisible( 5000 );
HistoryPage.rollbackConfirmableNo.waitForDisplayed( 5000 );
HistoryPage.rollbackConfirmableNo.click();
@ -57,7 +57,7 @@ describe( 'Rollback with confirmation', function () {
it.skip( 'should perform rollbacks after confirming intention', function () {
HistoryPage.rollback.click();
HistoryPage.rollbackConfirmableYes.waitForVisible( 5000 );
HistoryPage.rollbackConfirmableYes.waitForDisplayed( 5000 );
HistoryPage.rollbackConfirmableYes.click();
@ -90,9 +90,9 @@ describe( 'Rollback without confirmation', function () {
before( function () {
// disable VisualEditor welcome dialog
browser.deleteCookie();
browser.deleteAllCookies();
BlankPage.open();
browser.localStorage( 'POST', { key: 've-beta-welcome-dialog', value: '1' } );
browser.setLocalStorage( 've-beta-welcome-dialog', '1' );
// Disable rollback confirmation for admin user
// Requires user to log in again, handled by deleteCookie() call in beforeEach function
@ -101,7 +101,7 @@ describe( 'Rollback without confirmation', function () {
} );
beforeEach( function () {
browser.deleteCookie();
browser.deleteAllCookies();
content = Util.getTestString( 'beforeEach-content-' );
name = Util.getTestString( 'BeforeEach-name-' );
@ -117,7 +117,7 @@ describe( 'Rollback without confirmation', function () {
// waitUntil indirectly asserts that the content we are looking for is present
browser.waitUntil( function () {
return HistoryPage.headingText === 'Action complete';
return HistoryPage.heading.getText() === 'Action complete';
}, 5000, 'Expected rollback page to appear.' );
} );

View file

@ -9,7 +9,7 @@ describe( 'Special:RecentChanges', function () {
name;
beforeEach( function () {
browser.deleteCookie();
browser.deleteAllCookies();
content = Util.getTestString();
name = Util.getTestString();
} );

View file

@ -18,7 +18,7 @@ describe( 'Special:Watchlist', function () {
} );
beforeEach( function () {
browser.deleteCookie();
browser.deleteAllCookies();
LoginPage.login( username, password );
} );

View file

@ -13,11 +13,11 @@ describe( 'User', function () {
before( function () {
// disable VisualEditor welcome dialog
BlankPage.open();
browser.localStorage( 'POST', { key: 've-beta-welcome-dialog', value: '1' } );
browser.setLocalStorage( 've-beta-welcome-dialog', '1' );
} );
beforeEach( function () {
browser.deleteCookie();
browser.deleteAllCookies();
username = Util.getTestString( 'User-' );
password = Util.getTestString();
} );

View file

@ -14,9 +14,9 @@ module.exports = {
* @return {Promise<MWBot>}
*/
bot(
username = browser.options.username,
password = browser.options.password,
baseUrl = browser.options.baseUrl
username = browser.config.mwUser,
password = browser.config.mwPwd,
baseUrl = browser.config.baseUrl
) {
const bot = new MWBot();
@ -44,9 +44,9 @@ module.exports = {
*/
edit( title,
content,
username = browser.options.username,
password = browser.options.password,
baseUrl = browser.options.baseUrl
username = browser.config.mwUser,
password = browser.config.mwPwd,
baseUrl = browser.config.baseUrl
) {
return this.bot( username, password, baseUrl )
.then( function ( bot ) {
@ -84,14 +84,14 @@ module.exports = {
// Log in as admin
return bot.loginGetCreateaccountToken( {
apiUrl: `${browser.options.baseUrl}/api.php`,
username: browser.options.username,
password: browser.options.password
apiUrl: `${browser.config.baseUrl}/api.php`,
username: browser.config.mwUser,
password: browser.config.mwPwd
} ).then( function () {
// Create the new account
return bot.request( {
action: 'createaccount',
createreturnurl: browser.options.baseUrl,
createreturnurl: browser.config.baseUrl,
createtoken: bot.createaccountToken,
username: username,
password: password,
@ -115,7 +115,7 @@ module.exports = {
// block user. default = admin
return bot.request( {
action: 'block',
user: username || browser.options.username,
user: username || browser.config.mwUser,
reason: 'browser test',
token: bot.editToken,
expiry
@ -137,7 +137,7 @@ module.exports = {
// unblock user. default = admin
return bot.request( {
action: 'unblock',
user: username || browser.options.username,
user: username || browser.config.mwUser,
reason: 'browser test done',
token: bot.editToken
} );

View file

@ -1,7 +1,7 @@
const Page = require( './Page' );
class BlankPage extends Page {
get heading() { return browser.element( '#firstHeading' ); }
get heading() { return $( '#firstHeading' ); }
open() {
super.openTitle( 'Special:BlankPage', { uselang: 'en' } );

View file

@ -1,10 +1,10 @@
const Page = require( './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' ); }
get username() { return $( '#wpName1' ); }
get password() { return $( '#wpPassword1' ); }
get loginButton() { return $( '#wpLoginAttempt' ); }
get userPage() { return $( '#pt-userpage' ); }
open() {
super.openTitle( 'Special:UserLogin' );
@ -18,7 +18,7 @@ class LoginPage extends Page {
}
loginAdmin() {
this.login( browser.options.username, browser.options.password );
this.login( browser.config.mwUser, browser.config.mwPwd );
}
}

View file

@ -18,7 +18,7 @@ class Page {
openTitle( title, query = {}, fragment = '' ) {
query.title = title;
browser.url(
browser.options.baseUrl + '/index.php?' +
browser.config.baseUrl + '/index.php?' +
querystring.stringify( query ) +
( fragment ? ( '#' + fragment ) : '' )
);

View file

@ -19,7 +19,7 @@ See [BlankPage](./BlankPage.js) and [specs/BlankPage](./specs/BlankPage.js) for
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`,
Actions are performed logged-in using `browser.config.mwUser` and `browser.config.mwPwd`,
which typically come from `MEDIAWIKI_USER` and `MEDIAWIKI_PASSWORD` environment variables.
* `edit(string title, string content [, string username [, string password [, string baseUrl ] ] ])`

View file

@ -4,7 +4,7 @@ const MWBot = require( 'mwbot' ),
function getJobCount() {
const bot = new MWBot( {
apiUrl: `${browser.options.baseUrl}/api.php`
apiUrl: `${browser.config.baseUrl}/api.php`
} );
return bot.request( {
action: 'query',

View file

@ -11,11 +11,10 @@ module.exports = {
*/
waitForModuleState( moduleName, moduleStatus = 'ready', timeout = 2000 ) {
browser.waitUntil( () => {
const result = browser.execute( ( module ) => {
return browser.execute( ( arg ) => {
return typeof mw !== 'undefined' &&
mw.loader.getState( module.name ) === module.status;
mw.loader.getState( arg.name ) === arg.status;
}, { status: moduleStatus, name: moduleName } );
return result.value;
}, timeout, 'Failed to wait for ' + moduleName + ' to be ' + moduleStatus + ' after ' + timeout + ' ms.' );
}
};

View file

@ -12,12 +12,12 @@ module.exports = {
var filename, filePath;
// Create sane file name for current test title
filename = encodeURIComponent( title.replace( /\s+/g, '-' ) );
filePath = `${browser.options.screenshotPath}/${filename}.png`;
filePath = `${browser.config.screenshotPath}/${filename}.png`;
// Ensure directory exists, based on WebDriverIO#saveScreenshotSync()
try {
fs.statSync( browser.options.screenshotPath );
fs.statSync( browser.config.screenshotPath );
} catch ( err ) {
fs.mkdirSync( browser.options.screenshotPath );
fs.mkdirSync( browser.config.screenshotPath );
}
// Create and save screenshot
browser.saveScreenshot( filePath );

View file

@ -13,7 +13,7 @@
"specs/"
],
"engines": {
"node" : ">=6.0"
"node" : ">=10.0"
},
"dependencies": {
"mwbot": "1.0.10"

View file

@ -1,5 +1,6 @@
const fs = require( 'fs' ),
path = require( 'path' ),
startChromedriver = !process.argv.includes( '--skip-chromedriver' ),
logPath = process.env.LOG_DIR || path.join( __dirname, '/log' );
let ffmpeg;
@ -14,33 +15,34 @@ function filePath( test, screenshotPath, extension ) {
return path.join( screenshotPath, `${fileName( test.parent )}-${fileName( test.title )}.${extension}` );
}
// relative path
function relPath( foo ) {
return path.resolve( __dirname, '../..', foo );
}
/**
* For more details documentation and available options,
* see <https://webdriver.io/docs/configurationfile.html>
* and <https://webdriver.io/docs/options.html>.
*/
exports.config = {
// ======
// Custom WDIO config specific to MediaWiki
// Custom conf keys for MediaWiki
//
// Access via `browser.config.<key>`.
// Defaults are for MediaWiki-Vagrant
// ======
// Use in a test as `browser.options.<key>`.
// Defaults are for convenience with MediaWiki-Vagrant
mwUser: process.env.MEDIAWIKI_USER || 'Admin',
mwPwd: process.env.MEDIAWIKI_PASSWORD || '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'
),
// ==================
// Runner Configuration
// ==================
runner: 'local',
// The standalone chromedriver (also used by WMF CI) uses "/wd/hub".
// The one provided by wdio uses "/".
path: startChromedriver ? '/' : '/wd/hub',
// ======
// Sauce Labs
// ======
// See http://webdriver.io/guide/services/sauce.html
// and https://github.com/bermi/sauce-connect-launcher#advanced-usage
services: process.env.SAUCE_ACCESS_KEY ? [ 'sauce' ] : [],
user: process.env.SAUCE_USERNAME,
key: process.env.SAUCE_ACCESS_KEY,
sauceConnect: true,
@ -49,25 +51,21 @@ exports.config = {
// Test Files
// ==================
specs: [
relPath( './tests/selenium/wdio-mediawiki/specs/*.js' ),
relPath( './tests/selenium/specs/**/*.js' )
'./tests/selenium/wdio-mediawiki/specs/*.js',
'./tests/selenium/specs/**/*.js'
],
// ============
// Capabilities
// Define the different browser configurations to use ("capabilities") here.
// ============
// How many instances of the same capability (browser) may be started at the same time.
maxInstances: 1,
capabilities: [ {
// For Chrome/Chromium https://sites.google.com/a/chromium.org/chromedriver/capabilities
browserName: 'chrome',
maxInstances: 1,
chromeOptions: {
'goog:chromeOptions': {
// 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
// Otherwise, use --headless.
args: [
...( process.env.DISPLAY ? [] : [ '--headless' ] ),
// Chrome sandbox does not work in Docker
@ -78,53 +76,31 @@ exports.config = {
// ===================
// 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.
//
// 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
// Level of logging verbosity: trace | debug | info | warn | error | silent
logLevel: 'error',
// Enables colors for log output.
coloredLogs: true,
// Warns when a deprecated command is used
deprecationWarnings: true,
// Stop the tests once a certain number of failed tests have been recorded.
// Default is 0 - don't bail, run all tests.
// Stop after this many failures, or 0 to run all tests before reporting failures.
bail: 0,
// Setting this enables automatic screenshots for when a browser command fails
// It is also used by afterTest for capturig failed assertions.
// We disable it since we have our screenshot handler in the afterTest hook.
screenshotPath: null,
// Default timeout for each waitFor* command.
waitforTimeout: 10 * 1000,
// Framework you want to run your specs with.
// See also: http://webdriver.io/guide/testrunner/frameworks.html
// Base for browser.url() and wdio-mediawiki/Page#openTitle()
baseUrl: ( process.env.MW_SERVER || 'http://127.0.0.1:8080' ) + (
process.env.MW_SCRIPT_PATH || '/w'
),
services: [
...( startChromedriver ? [ 'chromedriver' ] : [] ),
...( process.env.SAUCE_ACCESS_KEY ? [ 'sauce' ] : [] )
],
// See also: https://webdriver.io/docs/frameworks.html
framework: 'mocha',
// Test reporter for stdout.
// See also: http://webdriver.io/guide/testrunner/reporters.html
reporters: [ 'dot', 'junit' ],
reporterOptions: {
junit: {
// See also: https://webdriver.io/docs/dot-reporter.html
reporters: [
'dot',
// See also: https://webdriver.io/docs/junit-reporter.html#configuration
[ 'junit', {
outputDir: logPath
}
},
// Options to be passed to Mocha.
// See the full list at http://mochajs.org/
} ]
],
// See also: http://mochajs.org/
mochaOpts: {
ui: 'bdd',
timeout: 60 * 1000
@ -133,15 +109,12 @@ exports.config = {
// =====
// Hooks
// =====
// See also: http://webdriver.io/guide/testrunner/configurationfile.html
/**
* Function to be executed before a test (in Mocha/Jasmine) or a step (in Cucumber) starts.
* @param {Object} test test details
*/
* Executed before a Mocha test starts.
* @param {Object} test Mocha Test object
*/
beforeTest: function ( test ) {
if ( process.env.DISPLAY && process.env.DISPLAY.startsWith( ':' ) ) {
var logBuffer;
const videoPath = filePath( test, logPath, 'mp4' );
const { spawn } = require( 'child_process' );
ffmpeg = spawn( 'ffmpeg', [
@ -154,7 +127,7 @@ exports.config = {
videoPath // output file
] );
logBuffer = function ( buffer, prefix ) {
const logBuffer = function ( buffer, prefix ) {
const lines = buffer.toString().trim().split( '\n' );
lines.forEach( function ( line ) {
console.log( prefix + line );
@ -180,10 +153,8 @@ exports.config = {
} );
}
},
/**
* Save a screenshot when test fails.
*
* Executed after a Mocha test ends.
* @param {Object} test Mocha Test object
*/
afterTest: function ( test ) {