Merge "Browser tests for CodeMirror (wikitext 2010 editor)"

This commit is contained in:
jenkins-bot 2021-01-08 11:22:16 +00:00 committed by Gerrit Code Review
commit 217c17257a
10 changed files with 7086 additions and 371 deletions

1
.gitignore vendored
View file

@ -3,6 +3,7 @@
.*.swp
.directory
/node_modules/
/tests/selenium/log/
/vendor/
/composer.lock
.eslintcache

6930
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,14 +1,26 @@
{
"private": true,
"scripts": {
"test": "grunt test"
"test": "grunt test",
"selenium-test": "wdio tests/selenium/wdio.conf.js"
},
"devDependencies": {
"@wdio/cli": "6.1.12",
"@wdio/devtools-service": "6.4.0",
"@wdio/junit-reporter": "6.1.9",
"@wdio/local-runner": "6.10.13",
"@wdio/mocha-framework": "6.10.11",
"@wdio/spec-reporter": "6.10.6",
"@wdio/sync": "6.10.11",
"dotenv": "8.2.0",
"eslint-config-wikimedia": "0.17.0",
"grunt": "1.3.0",
"grunt-banana-checker": "0.9.0",
"grunt-eslint": "23.0.0",
"grunt-stylelint": "0.16.0",
"stylelint-config-wikimedia": "0.10.3"
"stylelint-config-wikimedia": "0.10.3",
"wdio-mediawiki": "1.0.0",
"wdio-video-reporter": "3.0.0",
"webdriverio": "6.1.12"
}
}

View file

@ -0,0 +1,9 @@
{
"root": true,
"extends": [
"wikimedia/selenium"
],
"globals": {
"mw": "readonly"
}
}

View file

@ -0,0 +1,23 @@
'use strict';
const Api = require( 'wdio-mediawiki/Api' );
const Util = require( 'wdio-mediawiki/Util' );
const fixture1 = '[]{{template}}';
class FixtureContent {
/**
* Create a new fixture for testing syntax highlighting.
*
* @return {string} Page title
*/
createFixturePage() {
const title = Util.getTestString( 'CodeMirror-fixture1-' );
browser.call( () => Api.bot().then( ( bot ) => bot.edit( title, fixture1 ) ) );
return title;
}
}
module.exports = new FixtureContent();

View file

@ -0,0 +1,14 @@
'use strict';
// Temporary, can be removed along with the feature flag.
class HighlightingFeatureFlag {
enable() {
browser.setCookies( {
name: 'mw-codemirror-bracket-matching-test',
value: '1'
} );
}
}
module.exports = new HighlightingFeatureFlag();

View file

@ -0,0 +1,41 @@
'use strict';
const Page = require( 'wdio-mediawiki/Page' );
// Copied from mediawiki-core edit.page.js
class EditPage extends Page {
openForEditing( title ) {
super.openTitle( title, { action: 'edit', vehidebetadialog: 1, hidewelcomedialog: 1 } );
}
get legacyTextInput() { return $( '#wpTextbox1' ); }
clickText() {
if ( this.legacyTextInput.isDisplayed() ) {
this.legacyTextInput.click();
} else {
// Click the container, if using WikiEditor etc.
this.legacyTextInput.parentElement().click();
}
}
cursorToPosition( index ) {
this.clickText();
// Second "Control" deactivates the modifier.
const keys = [ 'Control', 'Home', 'Control' ];
for ( let i = 0; i < index; i++ ) {
keys.push( 'ArrowRight' );
}
browser.keys( keys );
}
getHighlightedMatchingBrackets() {
const elements = $$( '.CodeMirror-line .CodeMirror-matchingbracket' );
const matchingTexts = elements.map( function ( el ) {
return el.getText();
} );
return matchingTexts.join( '' );
}
}
module.exports = new EditPage();

View file

@ -0,0 +1,55 @@
'use strict';
const assert = require( 'assert' ),
EditPage = require( '../pageobjects/edit.page' ),
FeatureFlag = require( '../highlightingfeatureflag' ),
FixtureContent = require( '../fixturecontent' ),
LoginPage = require( 'wdio-mediawiki/LoginPage' ),
UserPreferences = require( '../userpreferences' );
describe( 'CodeMirror bracket match default', function () {
before( function () {
LoginPage.loginAdmin();
this.title = FixtureContent.createFixturePage();
UserPreferences.enableWikitext2010EditorWithCodeMirror();
// FIXME: Unknown conflict between this test and the FeatureFlag.enable cases.
} );
it( 'is disabled by default', function () {
EditPage.openForEditing( this.title );
EditPage.clickText();
EditPage.cursorToPosition( 0 );
assert.strictEqual( EditPage.getHighlightedMatchingBrackets(), '' );
} );
after( function () {
browser.reloadSession();
} );
} );
describe( 'CodeMirror bracket match highlighting for the wikitext 2010 editor', function () {
before( function () {
LoginPage.loginAdmin();
this.title = FixtureContent.createFixturePage();
UserPreferences.enableWikitext2010EditorWithCodeMirror();
FeatureFlag.enable();
} );
beforeEach( function () {
EditPage.openForEditing( this.title );
EditPage.clickText();
} );
it( 'highlighted on initial load', function () {
EditPage.cursorToPosition( 0 );
assert.strictEqual( EditPage.getHighlightedMatchingBrackets(), '[]' );
} );
it( 'bracket match highlighting moves along with the cursor', function () {
EditPage.cursorToPosition( 3 );
// FIXME: wait for hook to fire
browser.pause( 100 );
assert.strictEqual( EditPage.getHighlightedMatchingBrackets(), '{}' );
} );
} );

View file

@ -0,0 +1,36 @@
'use strict';
const BlankPage = require( 'wdio-mediawiki/BlankPage' );
const Util = require( 'wdio-mediawiki/Util' );
class UserPreferences {
setPreferences( preferences ) {
BlankPage.open();
Util.waitForModuleState( 'mediawiki.base' );
return browser.execute( function ( prefs ) {
return mw.loader.using( 'mediawiki.api' ).then( function () {
return new mw.Api().saveOptions( prefs );
} );
}, preferences );
}
enableWikitext2010EditorWithCodeMirror() {
this.setPreferences( {
usebetatoolbar: '1',
usecodemirror: '1',
'visualeditor-enable': '0',
'visualeditor-newwikitext': '0'
} );
}
enableWikitext2017EditorWithCodeMirror() {
this.setPreferences( {
usecodemirror: '1',
'visualeditor-enable': '1',
'visualeditor-newwikitext': '1'
} );
}
}
module.exports = new UserPreferences();

332
tests/selenium/wdio.conf.js Normal file
View file

@ -0,0 +1,332 @@
'use strict';
require( 'dotenv' ).config();
const fs = require( 'fs' );
const path = require( 'path' );
const video = require( 'wdio-video-reporter' );
const logPath = process.env.LOG_DIR || path.join( __dirname, '/log' );
// get current test title and clean it, to use it as file name
function fileName( title ) {
return encodeURIComponent( title.replace( /\s+/g, '-' ) );
}
// build file path
function filePath( test, screenshotPath, extension ) {
return path.join( screenshotPath, `${fileName( test.parent )}-${fileName( test.title )}.${extension}` );
}
/**
* For more details documentation and available options,
* see <https://webdriver.io/docs/configurationfile.html>
* and <https://webdriver.io/docs/options.html>.
*/
exports.config = {
// ======
// Custom conf keys for MediaWiki
//
// Access via `browser.config.<key>`.
// Defaults are for MediaWiki-Vagrant
// ======
mwUser: process.env.MEDIAWIKI_USER,
mwPwd: process.env.MEDIAWIKI_PASSWORD,
//
// ====================
// Runner Configuration
// ====================
//
// WebdriverIO allows it to run your tests in arbitrary locations (e.g. locally or
// on a remote machine).
runner: 'local',
// The standalone chromedriver uses "/wd/hub".
path: '/wd/hub',
//
// ==================
// 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: [
__dirname + '/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
//
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',
// If outputDir is provided WebdriverIO can capture driver session logs
// it is possible to configure which logTypes to include/exclude.
// excludeDriverLogs: ['*'], // pass '*' to exclude all driver session logs
// excludeDriverLogs: ['bugreport', 'server'],
'goog:chromeOptions': {
// If DISPLAY is set, assume developer asked non-headless or CI with Xvfb.
// Otherwise, use --headless.
args: [
...( process.env.DISPLAY ? [] : [ '--headless' ] ),
// Chrome sandbox does not work in Docker
...( fs.existsSync( '/.dockerenv' ) ? [ '--no-sandbox' ] : [] )
]
}
} ],
//
// ===================
// Test Configurations
// ===================
// Define all options that are relevant for the WebdriverIO instance here
//
// Level of logging verbosity: trace | debug | info | warn | error | silent
logLevel: 'error',
//
// Set specific log levels per logger
// loggers:
// - webdriver, webdriverio
// - @wdio/applitools-service, @wdio/browserstack-service, @wdio/devtools-service, @wdio/sauce-service
// - @wdio/mocha-framework, @wdio/jasmine-framework
// - @wdio/local-runner, @wdio/lambda-runner
// - @wdio/sumologic-reporter
// - @wdio/cli, @wdio/config, @wdio/sync, @wdio/utils
// Level of logging verbosity: trace | debug | info | warn | error | silent
// logLevels: {
// webdriver: 'info',
// '@wdio/applitools-service': 'info'
// },
//
// 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).
bail: 0,
//
// 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 + process.env.MW_SCRIPT_PATH,
//
// Default timeout for all waitFor* commands.
waitforTimeout: 10000,
//
// Default timeout in milliseconds for request
// if browser driver or grid doesn't send response
connectionRetryTimeout: 120000,
//
// Default request retries count
connectionRetryCount: 3,
//
// 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: ['chromedriver'],
// Framework you want to run your specs with.
// The following are supported: Mocha, Jasmine, and Cucumber
// see also: https://webdriver.io/docs/frameworks.html
//
// Make sure you have the wdio adapter package for the specific framework installed
// before running any tests.
framework: 'mocha',
//
// The number of times to retry the entire specfile when it fails as a whole
// specFileRetries: 1,
//
// Whether or not retried specfiles should be retried immediately or deferred to the end of the queue
// specFileRetriesDeferred: false,
//
// Test reporter for stdout.
// The only one supported by default is 'dot'
// see also: https://webdriver.io/docs/dot-reporter.html
reporters: [
'spec',
// See also: https://webdriver.io/docs/junit-reporter.html#configuration
[ 'junit', {
outputDir: logPath
} ],
[
video, {
saveAllVideos: true,
outputDir: logPath
}
]
],
//
// Options to be passed to Mocha.
// See the full list at http://mochajs.org/
mochaOpts: {
ui: 'bdd',
timeout: 60000
},
//
// =====
// 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 before a worker process is spawned and can be used to initialise specific service
* for that worker as well as modify runtime environments in an async fashion.
* @param {String} cid capability id (e.g 0-0)
* @param {[type]} caps object containing capabilities for session that will be spawn in the worker
* @param {[type]} specs specs to be run in the worker process
* @param {[type]} args object that will be merged with the main configuration once worker is initialised
* @param {[type]} execArgv list of string arguments passed to the worker process
*/
// onWorkerStart: function (cid, caps, specs, args, execArgv) {
// },
/**
* 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) starts.
*/
// beforeTest: function (test, context) {
// },
/**
* Hook that gets executed _before_ a hook within the suite starts (e.g. runs before calling
* beforeEach in Mocha)
*/
// beforeHook: function (test, context) {
// },
/**
* Hook that gets executed _after_ a hook within the suite starts (e.g. runs after calling
* afterEach in Mocha)
*/
// afterHook: function (test, context, { error, result, duration, passed, retries }) {
// },
/**
* Function to be executed after a test (in Mocha/Jasmine).
*
* afterTest: function(test, context, { error, result, duration, passed, retries }) {
*
* @param {Object} test Mocha Test object
*/
afterTest: function ( test ) {
// if test passed, ignore, else take and save screenshot
if ( test.passed ) {
return;
}
// save screenshot
const screenshotfile = filePath( test, logPath, 'png' );
browser.saveScreenshot( screenshotfile );
console.log( '\n\tScreenshot location:', screenshotfile, '\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. An error
* thrown in the onComplete hook will result in the test run failing.
* @param {Object} exitCode 0 - success, 1 - fail
* @param {Object} config wdio configuration object
* @param {Array.<Object>} capabilities list of capabilities details
* @param {<Object>} results object containing test results
*/
// onComplete: function(exitCode, config, capabilities, results) {
// },
/**
* Gets executed when a refresh happens.
* @param {String} oldSessionId session ID of the old session
* @param {String} newSessionId session ID of the new session
*/
// onReload: function(oldSessionId, newSessionId) {
// }
};