Merge "mediawiki.UI: Remove deprecated `.mw-ui-constructive` class"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Fri, 7 Jul 2017 18:13:04 +0000 (18:13 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Fri, 7 Jul 2017 18:13:04 +0000 (18:13 +0000)
20 files changed:
includes/api/ApiBase.php
includes/api/ApiStashEdit.php
includes/deferred/DeferredUpdates.php
includes/libs/rdbms/database/IDatabase.php
includes/objectcache/SqlBagOStuff.php
includes/parser/Parser.php
includes/specials/SpecialNewimages.php
package.json
resources/Resources.php
resources/src/mediawiki.special/mediawiki.special.newFiles.js [deleted file]
tests/parser/ParserTestRunner.php
tests/parser/parserTests.txt
tests/phpunit/includes/deferred/DeferredUpdatesTest.php
tests/selenium/.eslintrc.json
tests/selenium/README.md
tests/selenium/pageobjects/createaccount.page.js
tests/selenium/pageobjects/edit.page.js
tests/selenium/pageobjects/userlogout.page.js [deleted file]
tests/selenium/specs/page.js
tests/selenium/specs/user.js

index 2dcece1..bc3def8 100644 (file)
@@ -894,7 +894,7 @@ abstract class ApiBase extends ContextSource {
         * Get a WikiPage object from a title or pageid param, if possible.
         * Can die, if no param is set or if the title or page id is not valid.
         *
-        * @param array $params
+        * @param array $params User provided set of parameters, as from $this->extractRequestParams()
         * @param bool|string $load Whether load the object's state from the database:
         *        - false: don't load (if the pageid is given, it will still be loaded)
         *        - 'fromdb': load from a replica DB
@@ -935,7 +935,7 @@ abstract class ApiBase extends ContextSource {
         * Can die, if no param is set or if the title or page id is not valid.
         *
         * @since 1.29
-        * @param array $params
+        * @param array $params User provided set of parameters, as from $this->extractRequestParams()
         * @return Title
         */
        public function getTitleFromTitleOrPageId( $params ) {
index c7a00c6..d03fca8 100644 (file)
@@ -44,6 +44,7 @@ class ApiStashEdit extends ApiBase {
 
        const PRESUME_FRESH_TTL_SEC = 30;
        const MAX_CACHE_TTL = 300; // 5 minutes
+       const MAX_SIGNATURE_TTL = 60;
 
        public function execute() {
                $user = $this->getUser();
@@ -391,6 +392,12 @@ class ApiStashEdit extends ApiBase {
                // Put an upper limit on the TTL for sanity to avoid extreme template/file staleness.
                $since = time() - wfTimestamp( TS_UNIX, $parserOutput->getTimestamp() );
                $ttl = min( $parserOutput->getCacheExpiry() - $since, self::MAX_CACHE_TTL );
+
+               // Avoid extremely stale user signature timestamps (T84843)
+               if ( $parserOutput->getFlag( 'user-signature' ) ) {
+                       $ttl = min( $ttl, self::MAX_SIGNATURE_TTL );
+               }
+
                if ( $ttl <= 0 ) {
                        return [ null, 0, 'no_ttl' ];
                }
index a3a37f6..e8f27ef 100644 (file)
@@ -76,9 +76,12 @@ class DeferredUpdates {
        public static function addUpdate( DeferrableUpdate $update, $stage = self::POSTSEND ) {
                global $wgCommandLineMode;
 
-               // This is a sub-DeferredUpdate, run it right after its parent update
                if ( self::$executeContext && self::$executeContext['stage'] >= $stage ) {
+                       // This is a sub-DeferredUpdate; run it right after its parent update.
+                       // Also, while post-send updates are running, push any "pre-send" jobs to the
+                       // active post-send queue to make sure they get run this round (or at all).
                        self::$executeContext['subqueue'][] = $update;
+
                        return;
                }
 
@@ -183,16 +186,6 @@ class DeferredUpdates {
                while ( $updates ) {
                        $queue = []; // clear the queue
 
-                       if ( $mode === 'enqueue' ) {
-                               try {
-                                       // Push enqueuable updates to the job queue and get the rest
-                                       $updates = self::enqueueUpdates( $updates );
-                               } catch ( Exception $e ) {
-                                       // Let other updates have a chance to run if this failed
-                                       MWExceptionHandler::rollbackMasterChangesAndLog( $e );
-                               }
-                       }
-
                        // Order will be DataUpdate followed by generic DeferrableUpdate tasks
                        $updatesByType = [ 'data' => [], 'generic' => [] ];
                        foreach ( $updates as $du ) {
@@ -212,13 +205,9 @@ class DeferredUpdates {
                        // Execute all remaining tasks...
                        foreach ( $updatesByType as $updatesForType ) {
                                foreach ( $updatesForType as $update ) {
-                                       self::$executeContext = [
-                                               'update' => $update,
-                                               'stage' => $stage,
-                                               'subqueue' => []
-                                       ];
+                                       self::$executeContext = [ 'stage' => $stage, 'subqueue' => [] ];
                                        /** @var DeferrableUpdate $update */
-                                       $guiError = self::runUpdate( $update, $lbFactory, $stage );
+                                       $guiError = self::runUpdate( $update, $lbFactory, $mode, $stage );
                                        $reportableError = $reportableError ?: $guiError;
                                        // Do the subqueue updates for $update until there are none
                                        while ( self::$executeContext['subqueue'] ) {
@@ -230,7 +219,7 @@ class DeferredUpdates {
                                                        $subUpdate->setTransactionTicket( $ticket );
                                                }
 
-                                               $guiError = self::runUpdate( $subUpdate, $lbFactory, $stage );
+                                               $guiError = self::runUpdate( $subUpdate, $lbFactory, $mode, $stage );
                                                $reportableError = $reportableError ?: $guiError;
                                        }
                                        self::$executeContext = null;
@@ -248,16 +237,26 @@ class DeferredUpdates {
        /**
         * @param DeferrableUpdate $update
         * @param LBFactory $lbFactory
+        * @param string $mode
         * @param integer $stage
         * @return ErrorPageError|null
         */
-       private static function runUpdate( DeferrableUpdate $update, LBFactory $lbFactory, $stage ) {
+       private static function runUpdate(
+               DeferrableUpdate $update, LBFactory $lbFactory, $mode, $stage
+       ) {
                $guiError = null;
                try {
-                       $fnameTrxOwner = get_class( $update ) . '::doUpdate';
-                       $lbFactory->beginMasterChanges( $fnameTrxOwner );
-                       $update->doUpdate();
-                       $lbFactory->commitMasterChanges( $fnameTrxOwner );
+                       if ( $mode === 'enqueue' && $update instanceof EnqueueableDataUpdate ) {
+                               // Run only the job enqueue logic to complete the update later
+                               $spec = $update->getAsJobSpecification();
+                               JobQueueGroup::singleton( $spec['wiki'] )->push( $spec['job'] );
+                       } else {
+                               // Run the bulk of the update now
+                               $fnameTrxOwner = get_class( $update ) . '::doUpdate';
+                               $lbFactory->beginMasterChanges( $fnameTrxOwner );
+                               $update->doUpdate();
+                               $lbFactory->commitMasterChanges( $fnameTrxOwner );
+                       }
                } catch ( Exception $e ) {
                        // Reporting GUI exceptions does not work post-send
                        if ( $e instanceof ErrorPageError && $stage === self::PRESEND ) {
index 7c6413c..e463ea3 100644 (file)
@@ -1252,7 +1252,7 @@ interface IDatabase {
         * @param array $selectJoinConds Join conditions for the SELECT part of the query, see
         *    IDatabase::select() for details.
         *
-        * @return IResultWrapper
+        * @return bool
         */
        public function insertSelect( $destTable, $srcTable, $varMap, $conds,
                $fname = __METHOD__,
index 6c10301..70795ec 100644 (file)
@@ -148,7 +148,7 @@ class SqlBagOStuff extends BagOStuff {
        protected function getSeparateMainLB() {
                global $wgDBtype;
 
-               if ( $wgDBtype === 'mysql' && $this->usesMainDB() ) {
+               if ( $this->usesMainDB() && $wgDBtype !== 'sqlite' ) {
                        if ( !$this->separateMainLB ) {
                                // We must keep a separate connection to MySQL in order to avoid deadlocks
                                $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
@@ -156,8 +156,7 @@ class SqlBagOStuff extends BagOStuff {
                        }
                        return $this->separateMainLB;
                } else {
-                       // However, SQLite has an opposite behavior. And PostgreSQL needs to know
-                       // if we are in transaction or not (@TODO: find some PostgreSQL work-around).
+                       // However, SQLite has an opposite behavior due to DB-level locking
                        return null;
                }
        }
index 9ea65e0..4a78ff8 100644 (file)
@@ -4502,12 +4502,16 @@ class Parser {
                # which may corrupt this parser instance via its wfMessage()->text() call-
 
                # Signatures
-               $sigText = $this->getUserSig( $user );
-               $text = strtr( $text, [
-                       '~~~~~' => $d,
-                       '~~~~' => "$sigText $d",
-                       '~~~' => $sigText
-               ] );
+               if ( strpos( $text, '~~~' ) !== false ) {
+                       $sigText = $this->getUserSig( $user );
+                       $text = strtr( $text, [
+                               '~~~~~' => $d,
+                               '~~~~' => "$sigText $d",
+                               '~~~' => $sigText
+                       ] );
+                       # The main two signature forms used above are time-sensitive
+                       $this->mOutput->setFlag( 'user-signature' );
+               }
 
                # Context links ("pipe tricks"): [[|name]] and [[name (context)|]]
                $tc = '[' . Title::legalChars() . ']';
index 069dd0b..0a653e7 100644 (file)
@@ -33,6 +33,8 @@ class SpecialNewFiles extends IncludableSpecialPage {
        }
 
        public function execute( $par ) {
+               $context = new DerivativeContext( $this->getContext() );
+
                $this->setHeaders();
                $this->outputHeader();
                $mimeAnalyzer = MediaWiki\MediaWikiServices::getInstance()->getMimeAnalyzer();
@@ -71,6 +73,15 @@ class SpecialNewFiles extends IncludableSpecialPage {
 
                        $opts->setValue( 'start', $start, true );
                        $opts->setValue( 'end', $end, true );
+
+                       // also swap values in request object, which is used by HTMLForm
+                       // to pre-populate the fields with the previous input
+                       $request = $context->getRequest();
+                       $context->setRequest( new DerivativeRequest(
+                               $request,
+                               [ 'start' => $start, 'end' => $end ] + $request->getValues(),
+                               $request->wasPosted()
+                       ) );
                }
 
                // if all media types have been selected, wipe out the array to prevent
@@ -87,10 +98,10 @@ class SpecialNewFiles extends IncludableSpecialPage {
 
                if ( !$this->including() ) {
                        $this->setTopText();
-                       $this->buildForm();
+                       $this->buildForm( $context );
                }
 
-               $pager = new NewFilesPager( $this->getContext(), $opts );
+               $pager = new NewFilesPager( $context, $opts );
 
                $out->addHTML( $pager->getBody() );
                if ( !$this->including() ) {
@@ -98,7 +109,7 @@ class SpecialNewFiles extends IncludableSpecialPage {
                }
        }
 
-       protected function buildForm() {
+       protected function buildForm( IContextSource $context ) {
                $mediaTypesText = array_map( function ( $type ) {
                        // mediastatistics-header-unknown, mediastatistics-header-bitmap,
                        // mediastatistics-header-drawing, mediastatistics-header-audio,
@@ -185,7 +196,7 @@ class SpecialNewFiles extends IncludableSpecialPage {
                        unset( $formDescriptor['hidepatrolled'] );
                }
 
-               HTMLForm::factory( 'ooui', $formDescriptor, $this->getContext() )
+               HTMLForm::factory( 'ooui', $formDescriptor, $context )
                        // For the 'multiselect' field values to be preserved on submit
                        ->setFormIdentifier( 'specialnewimages' )
                        ->setWrapperLegendMsg( 'newimages-legend' )
@@ -193,8 +204,6 @@ class SpecialNewFiles extends IncludableSpecialPage {
                        ->setMethod( 'get' )
                        ->prepareForm()
                        ->displayForm( false );
-
-               $this->getOutput()->addModules( 'mediawiki.special.newFiles' );
        }
 
        protected function getGroupName() {
index 66c13cd..fe3c910 100644 (file)
@@ -24,6 +24,7 @@
     "karma-firefox-launcher": "1.0.1",
     "karma-mocha-reporter": "2.2.3",
     "karma-qunit": "1.0.0",
+    "nodemw": "0.10.1",
     "qunitjs": "1.23.1",
     "stylelint-config-wikimedia": "0.4.1",
     "wdio-junit-reporter": "0.2.0",
index 12f482f..8001243 100644 (file)
@@ -2030,12 +2030,6 @@ return [
        'mediawiki.special.movePage.styles' => [
                'styles' => 'resources/src/mediawiki.special/mediawiki.special.movePage.css',
        ],
-       'mediawiki.special.newFiles' => [
-               'scripts' => 'resources/src/mediawiki.special/mediawiki.special.newFiles.js',
-               'dependencies' => [
-                       'mediawiki.widgets.datetime',
-               ],
-       ],
        'mediawiki.special.pageLanguage' => [
                'scripts' => 'resources/src/mediawiki.special/mediawiki.special.pageLanguage.js',
                'dependencies' => [
diff --git a/resources/src/mediawiki.special/mediawiki.special.newFiles.js b/resources/src/mediawiki.special/mediawiki.special.newFiles.js
deleted file mode 100644 (file)
index 5e86eaa..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-/*!
- * JavaScript for Special:NewFiles
- */
-( function ( mw, $ ) {
-       $( function () {
-               var start = mw.widgets.datetime.DateTimeInputWidget.static.infuse( 'mw-input-start' ),
-                       end = mw.widgets.datetime.DateTimeInputWidget.static.infuse( 'mw-input-end' ),
-                       temp;
-
-               // If the start date comes after the end date, swap the two values.
-               // This swap is already done internally when the form is submitted with a start date that
-               // comes after the end date, but this swap makes the change visible in the HTMLForm.
-               if ( start.getValue() !== '' &&
-                       end.getValue() !== '' &&
-                       start.getValue() > end.getValue() ) {
-                       temp = start.getValue();
-                       start.setValue( end.getValue() );
-                       end.setValue( temp );
-               }
-       } );
-}( mediaWiki, jQuery ) );
index a373142..9dce73f 100644 (file)
@@ -266,7 +266,10 @@ class ParserTestRunner {
                $setup['wgSVGConverters'] = [ 'null' => 'echo "1">$output' ];
 
                // Fake constant timestamp
-               Hooks::register( 'ParserGetVariableValueTs', 'ParserTestRunner::getFakeTimestamp' );
+               Hooks::register( 'ParserGetVariableValueTs', function ( &$parser, &$ts ) {
+                       $ts = $this->getFakeTimestamp();
+                       return true;
+               } );
                $teardown[] = function () {
                        Hooks::clear( 'ParserGetVariableValueTs' );
                };
@@ -747,6 +750,7 @@ class ParserTestRunner {
                $context = RequestContext::getMain();
                $user = $context->getUser();
                $options = ParserOptions::newFromContext( $context );
+               $options->setTimestamp( $this->getFakeTimestamp() );
 
                if ( !isset( $opts['wrap'] ) ) {
                        $options->setWrapOutputClass( false );
@@ -774,6 +778,7 @@ class ParserTestRunner {
 
                if ( isset( $opts['pst'] ) ) {
                        $out = $parser->preSaveTransform( $test['input'], $title, $user, $options );
+                       $output = $parser->getOutput();
                } elseif ( isset( $opts['msg'] ) ) {
                        $out = $parser->transformMsg( $test['input'], $options, $title );
                } elseif ( isset( $opts['section'] ) ) {
@@ -824,6 +829,12 @@ class ParserTestRunner {
                        }
                }
 
+               if ( isset( $output ) && isset( $opts['showflags'] ) ) {
+                       $actualFlags = array_keys( TestingAccessWrapper::newFromObject( $output )->mFlags );
+                       sort( $actualFlags );
+                       $out .= "\nflags=" . join( ', ', $actualFlags );
+               }
+
                ScopedCallback::consume( $teardownGuard );
 
                $expected = $test['result'];
@@ -1598,11 +1609,14 @@ class ParserTestRunner {
        }
 
        /**
-        * The ParserGetVariableValueTs hook, used to make sure time-related parser
+        * Fake constant timestamp to make sure time-related parser
         * functions give a persistent value.
+        *
+        * - Parser::getVariableValue (via ParserGetVariableValueTs hook)
+        * - Parser::preSaveTransform (via ParserOptions)
         */
-       static function getFakeTimestamp( &$parser, &$ts ) {
-               $ts = 123; // parsed as '1970-01-01T00:02:03Z'
-               return true;
+       private function getFakeTimestamp() {
+               // parsed as '1970-01-01T00:02:03Z'
+               return 123;
        }
 }
index 44bcdff..041b40f 100644 (file)
@@ -10421,11 +10421,13 @@ parsoid={ "modes": ["wt2html","wt2wt"], "normalizePhp": true }
 Magic Word: {{REVISIONID}}
 !! options
 parsoid={ "modes": ["wt2html","wt2wt"], "normalizePhp": true }
+showflags
 !! wikitext
 {{REVISIONID}}
 !! html/*
 <p>1337
 </p>
+flags=vary-revision-id
 !! end
 
 !! test
@@ -13619,17 +13621,34 @@ pre-save transform: Signature expansion
 pst
 !! wikitext
 * ~~~
+* ~~~~
+* ~~~~~
 * <noinclude>~~~</noinclude>
 * <includeonly>~~~</includeonly>
 * <onlyinclude>~~~</onlyinclude>
 !! html/php
 * [[Special:Contributions/127.0.0.1|127.0.0.1]]
+* [[Special:Contributions/127.0.0.1|127.0.0.1]] 00:02, 1 January 1970 (UTC)
+* 00:02, 1 January 1970 (UTC)
 * <noinclude>[[Special:Contributions/127.0.0.1|127.0.0.1]]</noinclude>
 * <includeonly>[[Special:Contributions/127.0.0.1|127.0.0.1]]</includeonly>
 * <onlyinclude>[[Special:Contributions/127.0.0.1|127.0.0.1]]</onlyinclude>
 !! end
 
 
+!! test
+ParserOutput flags from signature expansion (T84843)
+!! options
+pst
+showflags
+!! wikitext
+~~~~
+!! html/php
+[[Special:Contributions/127.0.0.1|127.0.0.1]] 00:02, 1 January 1970 (UTC)
+flags=user-signature
+!! end
+
+
 !! test
 pre-save transform: Signature expansion in nowiki tags (T2093)
 !! options
index 2c199bc..3b42356 100644 (file)
@@ -192,4 +192,30 @@ class DeferredUpdatesTest extends MediaWikiTestCase {
 
                DeferredUpdates::doUpdates();
        }
+
+       public function testPresendAddOnPostsendRun() {
+               $this->setMwGlobals( 'wgCommandLineMode', true );
+
+               $x = false;
+               $y = false;
+               wfGetLBFactory()->commitMasterChanges( __METHOD__ ); // clear anything
+
+               DeferredUpdates::addCallableUpdate(
+                       function () use ( &$x, &$y ) {
+                               $x = true;
+                               DeferredUpdates::addCallableUpdate(
+                                       function () use ( &$y ) {
+                                               $y = true;
+                                       },
+                                       DeferredUpdates::PRESEND
+                               );
+                       },
+                       DeferredUpdates::POSTSEND
+               );
+
+               DeferredUpdates::doUpdates();
+
+               $this->assertTrue( $x, "Outer POSTSEND update ran" );
+               $this->assertTrue( $y, "Nested PRESEND update ran" );
+       }
 }
index d64ada9..db736b7 100644 (file)
@@ -10,5 +10,8 @@
        },
        "globals": {
                "browser": false
+       },
+       "rules":{
+               "no-console":0
        }
 }
index 479b958..a14cccb 100644 (file)
@@ -27,8 +27,8 @@ To run only one file (for example page.js), you first need to spawn the chromedr
 
 Then in another terminal:
 
-    cd mediawiki/tests/selenium
-    ../../node_modules/.bin/wdio --spec page.js
+    cd tests/selenium
+    ../../node_modules/.bin/wdio --spec specs/page.js
 
 The runner reads the config file `wdio.conf.js` and runs the spec listed in
 `page.js`.
index a0b9490..f54e31c 100644 (file)
@@ -21,5 +21,57 @@ class CreateAccountPage extends Page {
                this.create.click();
        }
 
+       apiCreateAccount( username, password ) {
+               const url = require( 'url' ), // https://nodejs.org/docs/latest/api/url.html
+                       baseUrl = url.parse( browser.options.baseUrl ), // http://webdriver.io/guide/testrunner/browserobject.html
+                       Bot = require( 'nodemw' ), // https://github.com/macbre/nodemw
+                       client = new Bot( {
+                               protocol: baseUrl.protocol,
+                               server: baseUrl.hostname,
+                               port: baseUrl.port,
+                               path: baseUrl.path,
+                               debug: false
+                       } );
+
+               return new Promise( ( resolve, reject ) => {
+                       client.api.call(
+                               {
+                                       action: 'query',
+                                       meta: 'tokens',
+                                       type: 'createaccount'
+                               },
+                               /**
+                                * @param {Error|null} err
+                                * @param {Object} info Processed query result
+                                * @param {Object} next More results?
+                                * @param {Object} data Raw data
+                                */
+                               function ( err, info, next, data ) {
+                                       if ( err ) {
+                                               reject( err );
+                                               return;
+                                       }
+                                       client.api.call( {
+                                               action: 'createaccount',
+                                               createreturnurl: browser.options.baseUrl,
+                                               createtoken: data.query.tokens.createaccounttoken,
+                                               username: username,
+                                               password: password,
+                                               retype: password
+                                       }, function ( err ) {
+                                               if ( err ) {
+                                                       reject( err );
+                                                       return;
+                                               }
+                                               resolve();
+                                       }, 'POST' );
+                               },
+                               'POST'
+                       );
+
+               } );
+
+       }
+
 }
 module.exports = new CreateAccountPage();
index 819c546..25da8cb 100644 (file)
@@ -8,15 +8,37 @@ class EditPage extends Page {
        get heading() { return browser.element( '#firstHeading' ); }
        get save() { return browser.element( '#wpSave' ); }
 
-       open( name ) {
+       openForEditing( name ) {
                super.open( name + '&action=edit' );
        }
 
        edit( name, content ) {
-               this.open( name );
+               this.openForEditing( name );
                this.content.setValue( content );
                this.save.click();
        }
 
+       apiEdit( name, content ) {
+               const url = require( 'url' ), // https://nodejs.org/docs/latest/api/url.html
+                       baseUrl = url.parse( browser.options.baseUrl ), // http://webdriver.io/guide/testrunner/browserobject.html
+                       Bot = require( 'nodemw' ), // https://github.com/macbre/nodemw
+                       client = new Bot( {
+                               protocol: baseUrl.protocol,
+                               server: baseUrl.hostname,
+                               port: baseUrl.port,
+                               path: baseUrl.path,
+                               debug: false
+                       } );
+
+               return new Promise( ( resolve, reject ) => {
+                       client.edit( name, content, `Created page with "${content}"`, function ( err ) {
+                               if ( err ) {
+                                       return reject( err );
+                               }
+                               resolve();
+                       } );
+               } );
+       }
+
 }
 module.exports = new EditPage();
diff --git a/tests/selenium/pageobjects/userlogout.page.js b/tests/selenium/pageobjects/userlogout.page.js
deleted file mode 100644 (file)
index e355fd5..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-'use strict';
-const Page = require( './page' );
-
-class UserLogoutPage extends Page {
-
-       open() {
-               super.open( 'Special:UserLogout' );
-       }
-
-}
-module.exports = new UserLogoutPage();
index da80aaf..06d3d60 100644 (file)
@@ -1,14 +1,22 @@
 'use strict';
 const assert = require( 'assert' ),
+       EditPage = require( '../pageobjects/edit.page' ),
        HistoryPage = require( '../pageobjects/history.page' ),
-       EditPage = require( '../pageobjects/edit.page' );
+       UserLoginPage = require( '../pageobjects/userlogin.page' );
 
 describe( 'Page', function () {
 
        var content,
                name;
 
+       before( function () {
+               // disable VisualEditor welcome dialog
+               UserLoginPage.open();
+               browser.localStorage( 'POST', { key: 've-beta-welcome-dialog', value: '1' } );
+       } );
+
        beforeEach( function () {
+               browser.deleteCookie();
                content = Math.random().toString();
                name = Math.random().toString();
        } );
@@ -29,12 +37,14 @@ describe( 'Page', function () {
                var content2 = Math.random().toString();
 
                // create
-               EditPage.edit( name, content );
+               browser.call( function () {
+                       return EditPage.apiEdit( name, content );
+               } );
 
                // edit
                EditPage.edit( name, content2 );
 
-               // check content
+               // check
                assert.equal( EditPage.heading.getText(), name );
                assert.equal( EditPage.displayedContent.getText(), content2 );
 
@@ -43,7 +53,9 @@ describe( 'Page', function () {
        it( 'should have history', function () {
 
                // create
-               EditPage.edit( name, content );
+               browser.call( function () {
+                       return EditPage.apiEdit( name, content );
+               } );
 
                // check
                HistoryPage.open( name );
index 6746c5b..3f3872d 100644 (file)
@@ -1,16 +1,22 @@
 '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' );
+       PreferencesPage = require( '../pageobjects/preferences.page' ),
+       UserLoginPage = require( '../pageobjects/userlogin.page' );
 
 describe( 'User', function () {
 
        var password,
                username;
 
+       before( function () {
+               // disable VisualEditor welcome dialog
+               UserLoginPage.open();
+               browser.localStorage( 'POST', { key: 've-beta-welcome-dialog', value: '1' } );
+       } );
+
        beforeEach( function () {
+               browser.deleteCookie();
                username = `User-${Math.random().toString()}`;
                password = Math.random().toString();
        } );
@@ -28,10 +34,9 @@ describe( 'User', function () {
        it( 'should be able to log in', function () {
 
                // create
-               CreateAccountPage.createAccount( username, password );
-
-               // logout
-               UserLogoutPage.open();
+               browser.call( function () {
+                       return CreateAccountPage.apiCreateAccount( username, password );
+               } );
 
                // log in
                UserLoginPage.login( username, password );
@@ -46,9 +51,14 @@ describe( 'User', function () {
                var realName = Math.random().toString();
 
                // create
-               CreateAccountPage.createAccount( username, password );
+               browser.call( function () {
+                       return CreateAccountPage.apiCreateAccount( username, password );
+               } );
+
+               // log in
+               UserLoginPage.login( username, password );
 
-               // change real name
+               // change
                PreferencesPage.changeRealName( realName );
 
                // check