mirror of
https://gerrit.wikimedia.org/r/mediawiki/skins/MinervaNeue
synced 2024-11-24 06:13:54 +00:00
Migrate editor_wikitext_saving.feature from Ruby to Node
This migrates the first of the browser tests which had a @login step from Ruby to Node.js Bug: T219920 Change-Id: I84e217e2a781aab9eb10e7d873c527d578ec8fd4
This commit is contained in:
parent
b2448e0d23
commit
7f2b69ac14
|
@ -40,3 +40,76 @@ To run only test(s) which name contains string TEST-NAME, run this from mediawik
|
|||
./node_modules/.bin/wdio tests/selenium/wdio.conf.js --spec extensions/EXTENSION-NAME/tests/selenium/specs/FILE-NAME.js --mochaOpts.grep TEST-NAME
|
||||
|
||||
Make sure Chromedriver is running when executing the above command.
|
||||
|
||||
# Migrating a test from Ruby to Node.js
|
||||
|
||||
Currently we are in the midst of porting our Ruby tests to Node.js.
|
||||
When the tests/browser/features folder is empty, we are done and the whole tests/browser folder can be removed.
|
||||
|
||||
This is a slow process (porting a single test can take an entire afternoon).
|
||||
|
||||
## Step 1 - move feature file
|
||||
Move the feature you want to port to Node.js
|
||||
```
|
||||
mv tests/browser/features/<name>.feature tests/selenium/features/
|
||||
```
|
||||
Example: https://gerrit.wikimedia.org/r/#/c/mediawiki/skins/MinervaNeue/+/501792/1/tests/selenium/features/editor_wikitext_saving.feature
|
||||
|
||||
## Step 2 - add boilerplate for missing steps
|
||||
Run the feature in cucumber
|
||||
```
|
||||
./node_modules/.bin/wdio tests/selenium/wdio.conf.cucumber.js --spec tests/selenium/features/<name>.feature
|
||||
```
|
||||
|
||||
You will get warnings such as:
|
||||
```
|
||||
Step "I go to a page that has languages" is not defined. You can ignore this error by setting cucumberOpts.ignoreUndefinedDefinitions as true.
|
||||
```
|
||||
|
||||
For each missing step define them as one liners inside selenium/features/step_definitions/index.js
|
||||
|
||||
Create functions with empty bodies for each step.
|
||||
|
||||
Function anmes should be camel case without space, for example, `I go to a page that has languages` becomes `iGoToAPageThatHasLanguages`. Each function should be imported from a step file inside the features/step_definitions folder.
|
||||
|
||||
Re-reun the test. If done correctly this should now pass.
|
||||
|
||||
Example: https://gerrit.wikimedia.org/r/#/c/mediawiki/skins/MinervaNeue/+/501792/1..2
|
||||
|
||||
## Step 3 - copy across Ruby function bodies
|
||||
|
||||
Copy across the body of the Ruby equivalent inside each function body in tests/browser/features/step_definitions as comments.
|
||||
|
||||
Example: https://gerrit.wikimedia.org/r/#/c/mediawiki/skins/MinervaNeue/+/501792/2..3
|
||||
|
||||
## Step 4 - rewrite Ruby to Node.js
|
||||
|
||||
Sadly there is no shortcut here. Reuse as much as you can. Work with the knowledge that the parts you are adding will aid the next browser test migration.
|
||||
|
||||
The docs are your friend: http://v4.webdriver.io/api/utility/waitForVisible.html
|
||||
|
||||
Example: https://gerrit.wikimedia.org/r/#/c/mediawiki/skins/MinervaNeue/+/501792/2..4
|
||||
|
||||
## Step 5 - Make it work without Cucumber
|
||||
|
||||
Now the tests should be passing when run the browser tests using wdio.conf.cucumber.js or `npm run selenium-test-cucumber`
|
||||
|
||||
The final step involves making these run with
|
||||
`npm run selenium-test`
|
||||
|
||||
This is relatively straightforward and mechanical.
|
||||
|
||||
1) Copy the feature file to the specs folder
|
||||
```
|
||||
cp tests/selenium/features/editor_wikitext_saving.feature tests/selenium/specs/editor_wikitext_saving.js
|
||||
```
|
||||
2) Convert indents to tabs
|
||||
3) Add `//` before any tags
|
||||
4) Replace `Scenario: <name>` with `it( '<name>', () => {`
|
||||
5) Add closing braces for all scenarios: ` } );`
|
||||
6) Replace `Feature: <feature>` with `describe('<feature>)', () => {` and add closing brace.
|
||||
7) Replace `Background:` with `beforeEach( () => {` and add closing brace.
|
||||
8) Find and replace `Given `, `When `, `And `, `Then ` with empy strings.
|
||||
9) At top of file copy and paste imports from `selenium/features/step_definitions/index.js` to top of your new file and rewrite their paths.
|
||||
10) Relying on autocomplete (VisualStudio Code works great) replace all the lines with the imports
|
||||
11) Drop unused imports from the file.
|
||||
|
|
|
@ -1,5 +1,27 @@
|
|||
const assert = require( 'assert' ),
|
||||
{ ArticlePage, UserLoginPage } = require( '../support/world' );
|
||||
Api = require( 'wdio-mediawiki/Api' ),
|
||||
{ ArticlePage, UserLoginPage, api } = require( '../support/world.js' );
|
||||
|
||||
const login = () => {
|
||||
return api.loginGetEditToken( {
|
||||
username: browser.options.username,
|
||||
password: browser.options.password,
|
||||
apiUrl: `${browser.options.baseUrl}/api.php`
|
||||
} );
|
||||
};
|
||||
|
||||
const createPages = ( pages ) => {
|
||||
const summary = 'edit by selenium test';
|
||||
return login().then( () =>
|
||||
api.batch(
|
||||
pages.map( ( page ) => [ 'create' ].concat( page ).concat( [ summary ] ) )
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
const createPage = ( title, wikitext ) => {
|
||||
return login().then( () => Api.edit( title, wikitext ) );
|
||||
};
|
||||
|
||||
const iAmUsingTheMobileSite = () => {
|
||||
ArticlePage.setMobileMode();
|
||||
|
@ -11,6 +33,8 @@ const iAmInBetaMode = () => {
|
|||
|
||||
const iAmOnPage = ( article ) => {
|
||||
ArticlePage.open( article );
|
||||
// Make sure the article opened and JS loaded.
|
||||
ArticlePage.waitUntilResourceLoaderModuleReady( 'skins.minerva.scripts' );
|
||||
};
|
||||
|
||||
const iAmLoggedIn = () => {
|
||||
|
@ -24,7 +48,25 @@ const iAmLoggedIntoTheMobileWebsite = () => {
|
|||
iAmLoggedIn();
|
||||
};
|
||||
|
||||
const pageExists = ( title ) => {
|
||||
return createPage( title, 'Page created by Selenium browser test.' ).then( () => {
|
||||
const d = new Date();
|
||||
// wait 2 seconds so the change can propogate.
|
||||
browser.waitUntil( () => new Date() - d > 2000 );
|
||||
} );
|
||||
};
|
||||
|
||||
const iAmOnAPageThatDoesNotExist = () => {
|
||||
return iAmOnPage( `NewPage ${new Date()}` );
|
||||
};
|
||||
|
||||
const iShouldSeeAToastNotification = () => {
|
||||
ArticlePage.notification_element.waitForVisible();
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
createPage, createPages,
|
||||
pageExists, iAmOnAPageThatDoesNotExist, iShouldSeeAToastNotification,
|
||||
iAmLoggedIntoTheMobileWebsite,
|
||||
iAmUsingTheMobileSite,
|
||||
iAmLoggedIn, iAmOnPage, iAmInBetaMode
|
||||
|
|
|
@ -1,13 +1,10 @@
|
|||
const { api, ArticlePage } = require( '../support/world' ),
|
||||
Api = require( 'wdio-mediawiki/Api' );
|
||||
|
||||
const login = () => {
|
||||
return api.loginGetEditToken( {
|
||||
username: browser.options.username,
|
||||
password: browser.options.password,
|
||||
apiUrl: `${browser.options.baseUrl}/api.php`
|
||||
} );
|
||||
};
|
||||
const { api, ArticlePage } = require( '../support/world' );
|
||||
const Api = require( 'wdio-mediawiki/Api' );
|
||||
const {
|
||||
iAmOnPage,
|
||||
createPages,
|
||||
createPage
|
||||
} = require( './common_steps' );
|
||||
|
||||
const waitForPropagation = ( timeMs ) => {
|
||||
// wait 2 seconds so the change can propogate.
|
||||
|
@ -17,7 +14,6 @@ const waitForPropagation = ( timeMs ) => {
|
|||
|
||||
const iAmInAWikiThatHasCategories = ( title ) => {
|
||||
const msg = 'This page is used by Selenium to test category related features.',
|
||||
summary = 'edit by selenium test',
|
||||
wikitext = `
|
||||
${msg}
|
||||
|
||||
|
@ -26,11 +22,11 @@ const iAmInAWikiThatHasCategories = ( title ) => {
|
|||
[[Category:Selenium hidden category]]
|
||||
`;
|
||||
|
||||
login().then( () => api.batch( [
|
||||
[ 'create', 'Category:Selenium artifacts', msg, summary ],
|
||||
[ 'create', 'Category:Test category', msg, summary ],
|
||||
[ 'create', 'Category:Selenium hidden category', '__HIDDENCAT__', summary ]
|
||||
] ) )
|
||||
createPages( [
|
||||
[ 'create', 'Category:Selenium artifacts', msg ],
|
||||
[ 'create', 'Category:Test category', msg ],
|
||||
[ 'create', 'Category:Selenium hidden category', '__HIDDENCAT__' ]
|
||||
] )
|
||||
.catch( ( err ) => {
|
||||
if ( err.code === 'articleexists' ) {
|
||||
return;
|
||||
|
@ -63,8 +59,20 @@ const iAmOnAPageThatHasTheFollowingEdits = function ( table ) {
|
|||
waitForPropagation( 5000 );
|
||||
};
|
||||
|
||||
const iGoToAPageThatHasLanguages = () => {
|
||||
const wikitext = `This page is used by Selenium to test language related features.
|
||||
|
||||
[[es:Selenium language test page]]
|
||||
`;
|
||||
|
||||
return createPage( 'Selenium language test page', wikitext ).then( () => {
|
||||
iAmOnPage( 'Selenium language test page' );
|
||||
} );
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
waitForPropagation,
|
||||
iAmOnAPageThatHasTheFollowingEdits,
|
||||
iAmInAWikiThatHasCategories
|
||||
iAmInAWikiThatHasCategories,
|
||||
iGoToAPageThatHasLanguages
|
||||
};
|
||||
|
|
70
tests/selenium/features/step_definitions/editor_steps.js
Normal file
70
tests/selenium/features/step_definitions/editor_steps.js
Normal file
|
@ -0,0 +1,70 @@
|
|||
const assert = require( 'assert' );
|
||||
const { ArticlePageWithEditorOverlay, ArticlePage } = require( '../support/world.js' );
|
||||
|
||||
const iClickTheEditButton = () => {
|
||||
ArticlePage.edit_link_element.waitForVisible();
|
||||
ArticlePage.edit_link_element.click();
|
||||
};
|
||||
const iSeeTheWikitextEditorOverlay = () => {
|
||||
ArticlePageWithEditorOverlay.editor_overlay_element.waitForVisible();
|
||||
ArticlePageWithEditorOverlay.editor_textarea_element.waitForExist();
|
||||
};
|
||||
const iClearTheEditor = () => {
|
||||
ArticlePageWithEditorOverlay.editor_textarea_element.setValue( '' );
|
||||
};
|
||||
const iDoNotSeeTheWikitextEditorOverlay = () => {
|
||||
browser.waitUntil( () => {
|
||||
return ArticlePageWithEditorOverlay.editor_overlay_element.isVisible() === false;
|
||||
}, 10000 );
|
||||
};
|
||||
const iTypeIntoTheEditor = ( text ) => {
|
||||
ArticlePageWithEditorOverlay.editor_overlay_element.waitForExist();
|
||||
ArticlePageWithEditorOverlay.editor_textarea_element.waitForExist();
|
||||
ArticlePageWithEditorOverlay.editor_textarea_element.waitForVisible();
|
||||
ArticlePageWithEditorOverlay.editor_textarea_element.addValue( text );
|
||||
browser.waitUntil( () => {
|
||||
return !ArticlePageWithEditorOverlay
|
||||
.continue_element.getAttribute( 'disabled' );
|
||||
} );
|
||||
};
|
||||
const iClickContinue = () => {
|
||||
ArticlePageWithEditorOverlay.continue_element.waitForExist();
|
||||
ArticlePageWithEditorOverlay.continue_element.click();
|
||||
};
|
||||
const iClickSubmit = () => {
|
||||
ArticlePageWithEditorOverlay.submit_element.waitForExist();
|
||||
ArticlePageWithEditorOverlay.submit_element.click();
|
||||
};
|
||||
const iSayOkayInTheConfirmDialog = () => {
|
||||
browser.waitUntil( () => {
|
||||
try {
|
||||
const text = browser.alertText;
|
||||
return text && true;
|
||||
} catch ( e ) {
|
||||
return false;
|
||||
}
|
||||
}, 2000 );
|
||||
browser.alertAccept();
|
||||
};
|
||||
const theTextOfTheFirstHeadingShouldBe = ( title ) => {
|
||||
ArticlePage.first_heading_element.waitForVisible();
|
||||
assert.strictEqual(
|
||||
ArticlePage.first_heading_element.getText(),
|
||||
title
|
||||
);
|
||||
};
|
||||
const thereShouldBeARedLinkWithText = ( text ) => {
|
||||
ArticlePage.red_link_element.waitForExist();
|
||||
assert.strictEqual(
|
||||
ArticlePage.red_link_element.getText(),
|
||||
text
|
||||
);
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
iClickTheEditButton, iSeeTheWikitextEditorOverlay, iClearTheEditor,
|
||||
thereShouldBeARedLinkWithText,
|
||||
iDoNotSeeTheWikitextEditorOverlay,
|
||||
iTypeIntoTheEditor, iClickContinue, iClickSubmit, iSayOkayInTheConfirmDialog,
|
||||
theTextOfTheFirstHeadingShouldBe
|
||||
};
|
|
@ -3,8 +3,10 @@ const { defineSupportCode } = require( 'cucumber' ),
|
|||
iShouldSeeTheCategoriesOverlay, iShouldSeeAListOfCategories
|
||||
} = require( './category_steps' ),
|
||||
{ iAmInAWikiThatHasCategories,
|
||||
iAmOnAPageThatHasTheFollowingEdits } = require( './create_page_api_steps' ),
|
||||
iAmOnAPageThatHasTheFollowingEdits,
|
||||
iGoToAPageThatHasLanguages } = require( './create_page_api_steps' ),
|
||||
{
|
||||
pageExists, iAmOnAPageThatDoesNotExist, iShouldSeeAToastNotification,
|
||||
iAmUsingTheMobileSite,
|
||||
iAmLoggedIntoTheMobileWebsite,
|
||||
iAmOnPage, iAmInBetaMode
|
||||
|
@ -14,11 +16,29 @@ const { defineSupportCode } = require( 'cucumber' ),
|
|||
} = require( './diff_steps' ),
|
||||
{
|
||||
iOpenTheLatestDiff,
|
||||
iClickTheEditButton, iSeeTheWikitextEditorOverlay, iClearTheEditor,
|
||||
iDoNotSeeTheWikitextEditorOverlay,
|
||||
iTypeIntoTheEditor, iClickContinue, iClickSubmit, iSayOkayInTheConfirmDialog,
|
||||
theTextOfTheFirstHeadingShouldBe, thereShouldBeARedLinkWithText
|
||||
} = require( './editor_steps' ),
|
||||
{
|
||||
iClickOnTheHistoryLinkInTheLastModifiedBar
|
||||
} = require( './history_steps' );
|
||||
|
||||
defineSupportCode( function ( { Then, When, Given } ) {
|
||||
|
||||
// Editor steps
|
||||
Given( /^I click the edit button$/, iClickTheEditButton );
|
||||
Then( /^I see the wikitext editor overlay$/, iSeeTheWikitextEditorOverlay );
|
||||
When( /^I clear the editor$/, iClearTheEditor );
|
||||
When( /^I type "(.+)" into the editor$/, iTypeIntoTheEditor );
|
||||
When( /^I click continue$/, iClickContinue );
|
||||
When( /^I click submit$/, iClickSubmit );
|
||||
When( /^I say OK in the confirm dialog$/, iSayOkayInTheConfirmDialog );
|
||||
Then( /^I do not see the wikitext editor overlay$/, iDoNotSeeTheWikitextEditorOverlay );
|
||||
Then( /^the text of the first heading should be "(.+)"$/, theTextOfTheFirstHeadingShouldBe );
|
||||
Then( /^there should be a red link with text "(.+)"$/, thereShouldBeARedLinkWithText );
|
||||
|
||||
// common steps
|
||||
Given( /^I am using the mobile site$/, iAmUsingTheMobileSite );
|
||||
|
||||
|
@ -27,12 +47,16 @@ defineSupportCode( function ( { Then, When, Given } ) {
|
|||
Given( /^I am on the "(.+)" page$/, iAmOnPage );
|
||||
|
||||
Given( /^I am logged into the mobile website$/, iAmLoggedIntoTheMobileWebsite );
|
||||
Then( /^I should see a toast notification$/, iShouldSeeAToastNotification );
|
||||
|
||||
// Page steps
|
||||
Given( /^I am in a wiki that has categories$/, () => {
|
||||
iAmInAWikiThatHasCategories( 'Selenium categories test page' );
|
||||
} );
|
||||
Given( /^I am on a page that has the following edits:$/, iAmOnAPageThatHasTheFollowingEdits );
|
||||
Given( /^I am on a page that does not exist$/, iAmOnAPageThatDoesNotExist );
|
||||
Given( /^I go to a page that has languages$/, iGoToAPageThatHasLanguages );
|
||||
Given( /^the page "(.+)" exists$/, pageExists );
|
||||
|
||||
// history steps
|
||||
When( /^I open the latest diff$/, iOpenTheLatestDiff );
|
||||
|
|
|
@ -11,8 +11,12 @@ const MinervaPage = require( './minerva_page' );
|
|||
class ArticlePage extends MinervaPage {
|
||||
|
||||
get category_element() { return $( '.category-button' ); }
|
||||
get edit_link_element() { return $( '#ca-edit' ); }
|
||||
get first_heading_element() { return $( '#section_0' ); }
|
||||
get notification_element() { return $( '.mw-notification-area .mw-notification' ); }
|
||||
get overlay_heading_element() { return $( '.overlay-title h2' ); }
|
||||
get overlay_category_topic_item_element() { return $( '.topic-title-list li' ); }
|
||||
get red_link_element() { return $( 'a.new' ); }
|
||||
get is_authenticated_element() { return $( 'body.is-authenticated' ); }
|
||||
get last_modified_bar_history_link_element() { return $( '.last-modifier-tagline a[href*=\'Special:History\']' ); }
|
||||
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
/**
|
||||
* Represents a generic article page with the editor overlay open
|
||||
*
|
||||
* @class ArticlePageWithEditorOverlay
|
||||
* @extends MinervaPage
|
||||
* @example
|
||||
* https://en.m.wikipedia.org/wiki/Barack_Obama#/editor/0
|
||||
*/
|
||||
|
||||
const MinervaPage = require( './minerva_page' );
|
||||
|
||||
class ArticlePageWithEditorOverlay extends MinervaPage {
|
||||
get editor_overlay_element() { return $( '.editor-overlay' ); }
|
||||
|
||||
// overlay components
|
||||
get editor_textarea_element() { return $( '.editor-overlay .wikitext-editor' ); }
|
||||
get continue_element() { return $( '.editor-overlay .continue' ); }
|
||||
get submit_element() { return $( '.editor-overlay .submit' ); }
|
||||
}
|
||||
|
||||
module.exports = new ArticlePageWithEditorOverlay();
|
|
@ -62,6 +62,14 @@ class MinervaPage extends Page {
|
|||
this.setCookie( 'optin', 'beta' );
|
||||
}
|
||||
|
||||
waitUntilResourceLoaderModuleReady( moduleName ) {
|
||||
browser.waitUntil( () => {
|
||||
const state = browser.execute( ( m ) => {
|
||||
return mw.loader.getState( m );
|
||||
}, moduleName );
|
||||
return state.value === 'ready';
|
||||
} );
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = MinervaPage;
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
*/
|
||||
module.exports = {
|
||||
ArticlePage: require( './article_page' ),
|
||||
ArticlePageWithEditorOverlay: require( './article_page_with_editor_overlay' ),
|
||||
SpecialHistoryPage: require( './special_history_page' ),
|
||||
SpecialMobileDiffPage: require( './special_mobilediff_page' ),
|
||||
DiffPage: require( './diff_page' )
|
||||
|
|
61
tests/selenium/specs/editor_wikitext_saving.js
Normal file
61
tests/selenium/specs/editor_wikitext_saving.js
Normal file
|
@ -0,0 +1,61 @@
|
|||
const { iGoToAPageThatHasLanguages } = require( './../features/step_definitions/create_page_api_steps' ),
|
||||
{
|
||||
pageExists, iAmOnAPageThatDoesNotExist, iShouldSeeAToastNotification,
|
||||
iAmLoggedIntoTheMobileWebsite
|
||||
} = require( './../features/step_definitions/common_steps' ),
|
||||
{
|
||||
iClickTheEditButton, iSeeTheWikitextEditorOverlay, iClearTheEditor,
|
||||
iDoNotSeeTheWikitextEditorOverlay,
|
||||
iTypeIntoTheEditor, iClickContinue, iClickSubmit, iSayOkayInTheConfirmDialog,
|
||||
theTextOfTheFirstHeadingShouldBe, thereShouldBeARedLinkWithText
|
||||
} = require( './../features/step_definitions/editor_steps' );
|
||||
|
||||
// @test2.m.wikipedia.org @login
|
||||
describe( 'Wikitext Editor (Makes actual saves)', () => {
|
||||
|
||||
beforeEach( () => {
|
||||
iAmLoggedIntoTheMobileWebsite();
|
||||
} );
|
||||
|
||||
// @editing
|
||||
it( 'It is possible to edit', () => {
|
||||
iGoToAPageThatHasLanguages();
|
||||
iClickTheEditButton();
|
||||
iSeeTheWikitextEditorOverlay();
|
||||
iTypeIntoTheEditor( 'ABC GHI' );
|
||||
iClickContinue();
|
||||
iClickSubmit();
|
||||
iDoNotSeeTheWikitextEditorOverlay();
|
||||
iShouldSeeAToastNotification();
|
||||
} );
|
||||
|
||||
// @editing @en.m.wikipedia.beta.wmflabs.org
|
||||
it( 'Redirects', () => {
|
||||
const title = 'Selenium wikitext editor test ' + Math.random();
|
||||
pageExists( title );
|
||||
iAmOnAPageThatDoesNotExist();
|
||||
iClickTheEditButton();
|
||||
iSeeTheWikitextEditorOverlay();
|
||||
iClearTheEditor();
|
||||
iTypeIntoTheEditor( `#REDIRECT [[${title}]]` );
|
||||
iClickContinue();
|
||||
iClickSubmit();
|
||||
iSayOkayInTheConfirmDialog();
|
||||
iDoNotSeeTheWikitextEditorOverlay();
|
||||
theTextOfTheFirstHeadingShouldBe( title );
|
||||
} );
|
||||
|
||||
// @editing @en.m.wikipedia.beta.wmflabs.org
|
||||
it( 'Broken redirects', () => {
|
||||
iAmOnAPageThatDoesNotExist();
|
||||
iClickTheEditButton();
|
||||
iSeeTheWikitextEditorOverlay();
|
||||
iClearTheEditor();
|
||||
iTypeIntoTheEditor( '#REDIRECT [[AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA]]' );
|
||||
iClickContinue();
|
||||
iClickSubmit();
|
||||
iSayOkayInTheConfirmDialog();
|
||||
iDoNotSeeTheWikitextEditorOverlay();
|
||||
thereShouldBeARedLinkWithText( 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' );
|
||||
} );
|
||||
} );
|
Loading…
Reference in a new issue