diff --git a/config/global.ini.php b/config/global.ini.php
index a06950d8b72cf61d14ba9b77cedc1a94b6ced4ae..e11ec59618fe3b429ff0f037e85d43a52382c2ab 100644
--- a/config/global.ini.php
+++ b/config/global.ini.php
@@ -86,6 +86,11 @@ tracker_always_new_visitor = 0
 ; Allow automatic upgrades to Beta or RC releases
 allow_upgrades_to_beta = 0
 
+[Tests]
+; Whether to save fixture data in separate databases when running tests. if you run tests often, this means
+; you don't have to re-run the test fixture setup each time you re-run a test.
+persist_fixture_data = 0
+
 [General]
 ; the following settings control whether Unique Visitors will be processed for different period types.
 ; year and range periods are disabled by default, to ensure optimal performance for high traffic Piwik instances
diff --git a/core/Config.php b/core/Config.php
index cd0e11b82c7c7d86aa94764aa044e50a94dde264..67efc8a507ab378d9148d93db5a41a1d7d7744ce 100644
--- a/core/Config.php
+++ b/core/Config.php
@@ -40,6 +40,8 @@ use Exception;
  */
 class Config extends Singleton
 {
+    const RELATIVE_CONFIG_OVERRIDE_PATH = 'tmp/test.config.ini';
+
     /**
      * Contains configuration files values
      *
@@ -82,6 +84,11 @@ class Config extends Singleton
 
         if ($pathLocal) {
             $this->pathLocal = $pathLocal;
+        } else {
+            $configOverridePath = $this->getConfigOverridePath();
+            if (file_exists($configOverridePath)) {
+                $this->pathLocal = $configOverridePath;
+            }
         }
 
         if ($pathGlobal) {
@@ -587,12 +594,15 @@ class Config extends Singleton
      * @param array $configCommon
      * @param array $configCache
      * @param string $pathLocal
+     * @param bool $clear
      *
      * @throws \Exception if config file not writable
      */
-    protected function writeConfig($configLocal, $configGlobal, $configCommon, $configCache, $pathLocal)
+    protected function writeConfig($configLocal, $configGlobal, $configCommon, $configCache, $pathLocal, $clear = true)
     {
-        if ($this->isTest) {
+        if ($this->isTest
+            && $pathLocal == $this->pathLocal
+        ) {
             return;
         }
 
@@ -604,13 +614,15 @@ class Config extends Singleton
             }
         }
 
-        $this->clear();
+        if ($clear) {
+            $this->clear();
+        }
     }
 
     /**
      * Writes the current configuration to the **config.ini.php** file. Only writes options whose
      * values are different from the default.
-     * 
+     *
      * @api
      */
     public function forceSave()
@@ -618,6 +630,28 @@ class Config extends Singleton
         $this->writeConfig($this->configLocal, $this->configGlobal, $this->configCommon, $this->configCache, $this->pathLocal);
     }
 
+    /**
+     * Writes the config cache to tmp/test.config.ini.
+     *
+     * Used for testing purposes to communicate config overrides w/ other processes (such as the log
+     * importer).
+     */
+    public function saveConfigOverride()
+    {
+        $path = $this->getConfigOverridePath();
+        $this->writeConfig($this->configCache, $this->configGlobal, $this->configCommon, $this->configCache, $path, false);
+    }
+
+    /**
+     * Removes the file at tmp/test.config.ini.
+     *
+     * Used for testing purposes.
+     */
+    public function removeConfigOverride()
+    {
+        @unlink($this->getConfigOverridePath());
+    }
+
     /**
      * @throws \Exception
      */
@@ -665,4 +699,8 @@ class Config extends Singleton
         return $merged;
     }
 
+    private function getConfigOverridePath()
+    {
+        return PIWIK_INCLUDE_PATH . '/' . self::RELATIVE_CONFIG_OVERRIDE_PATH;
+    }
 }
diff --git a/core/Db/Schema/Mysql.php b/core/Db/Schema/Mysql.php
index 08b7312f659ef46da7f8fc1b5e2b377c3c219ad9..53c030c22f7d3d1884c3a68a902987e848a2bb7a 100644
--- a/core/Db/Schema/Mysql.php
+++ b/core/Db/Schema/Mysql.php
@@ -527,7 +527,7 @@ class Mysql implements SchemaInterface
         // The anonymous user is the user that is assigned by default
         // note that the token_auth value is anonymous, which is assigned by default as well in the Login plugin
         $db = Db::get();
-        $db->query("INSERT INTO " . Common::prefixTable("user") . "
+        $db->query("INSERT IGNORE INTO " . Common::prefixTable("user") . "
 					VALUES ( 'anonymous', '', 'anonymous', 'anonymous@example.org', 'anonymous', 0, '" . Date::factory('now')->getDatetime() . "' );");
     }
 
diff --git a/core/Plugin/Manager.php b/core/Plugin/Manager.php
index ee9092156546b9d7de762f41f9207092f41e6bc3..87eb628eb8eaa9a0fc79eced30fec51a23b18d96 100644
--- a/core/Plugin/Manager.php
+++ b/core/Plugin/Manager.php
@@ -922,7 +922,6 @@ class Manager extends Singleton
     private function installPluginIfNecessary(Plugin $plugin)
     {
         $pluginName = $plugin->getPluginName();
-
         $saveConfig = false;
 
         // is the plugin already installed or is it the first time we activate it?
diff --git a/tests/PHPUnit/Fixture.php b/tests/PHPUnit/Fixture.php
index 54e054587b112e87d22d8fa9f72c86dfae4b4dd4..03252556bd3a3364cd7a1907906a4c81d84a9e91 100644
--- a/tests/PHPUnit/Fixture.php
+++ b/tests/PHPUnit/Fixture.php
@@ -43,7 +43,6 @@ use Piwik\DataAccess\ArchiveTableCreator;
  * Related TODO: we should try and reduce the amount of existing fixtures by
  *                merging some together.
  */
-// TODO: rename to Fixture
 class Fixture extends PHPUnit_Framework_Assert
 {
     const IMAGES_GENERATED_ONLY_FOR_OS = 'linux';
@@ -51,15 +50,16 @@ class Fixture extends PHPUnit_Framework_Assert
     const IMAGES_GENERATED_FOR_GD = '2.1.1';
     const DEFAULT_SITE_NAME = 'Piwik test';
 
-    const ADMIN_USER_LOGIN = 'admin';
+    const ADMIN_USER_LOGIN = 'superUserLogin';
     const ADMIN_USER_PASSWORD = '098f6bcd4621d373cade4e832627b4f6';
 
     public $dbName = false;
-    public $createEmptyDatabase = true;
     public $createConfig = true;
+    public $dropDatabaseInSetUp = true;
     public $dropDatabaseInTearDown = true;
     public $loadTranslations = true;
     public $createSuperUser = true;
+    public $overwriteExisting = true;
 
     /** Adds data to Piwik. Creates sites, tracks visits, imports log files, etc. */
     public function setUp()
@@ -73,7 +73,27 @@ class Fixture extends PHPUnit_Framework_Assert
         // empty
     }
 
-    public function setUpEnvironment()
+    private function handleConfiguration()
+    {
+        Config::getInstance()->removeConfigOverride();
+
+        $testsConfig = Config::getInstance()->Tests;
+        if (!empty($testsConfig['persist_fixture_data'])) {
+            $this->dbName = get_class($this);
+            $this->dropDatabaseInSetUp = false;
+            $this->dropDatabaseInTearDown = false;
+            $this->overwriteExisting = false;
+
+            Config::getInstance()->database_tests['dbname'] = $this->dbName;
+            Config::getInstance()->saveConfigOverride();
+        }
+
+        if ($this->dbName === false) { // must be after test config is created
+            $this->dbName = Config::getInstance()->database['dbname'];
+        }
+    }
+
+    public function performSetUp()
     {
         try {
             \Piwik\SettingsPiwik::$piwikUrlCache = '';
@@ -82,14 +102,14 @@ class Fixture extends PHPUnit_Framework_Assert
                 Config::getInstance()->setTestEnvironment();
             }
 
-            if ($this->dbName === false) { // must be after test config is created
-                $this->dbName = Config::getInstance()->database['dbname'];
-            }
+            $this->handleConfiguration();
 
             static::connectWithoutDatabase();
-            if ($this->createEmptyDatabase) {
+
+            if ($this->dropDatabaseInSetUp) {
                 DbHelper::dropDatabase();
             }
+
             DbHelper::createDatabase($this->dbName);
             DbHelper::disconnectDatabase();
 
@@ -141,10 +161,32 @@ class Fixture extends PHPUnit_Framework_Assert
         if ($this->createSuperUser) {
             self::createSuperUser();
         }
+
+        if ($this->overwriteExisting
+            || !$this->isFixtureSetUp()
+        ) {
+            $this->setUp();
+
+            $this->markFixtureSetUp();
+        }
+    }
+
+    public function isFixtureSetUp()
+    {
+        $optionName = get_class($this) . '.setUpFlag';
+        return Option::get($optionName) !== false;
     }
 
-    public function tearDownEnvironment()
+    public function markFixtureSetUp()
     {
+        $optionName = get_class($this) . '.setUpFlag';
+        Option::set($optionName, 1);
+    }
+
+    public function performTearDown()
+    {
+        $this->tearDown();
+
         \Piwik\SettingsPiwik::$piwikUrlCache = null;
         self::unloadAllPlugins();
 
diff --git a/tests/PHPUnit/Integration/Core/TrackerTest.php b/tests/PHPUnit/Integration/Core/TrackerTest.php
index 3fd6a34d286a0b8d021f66d77c6b1f8af1ed667c..a8585bbd1950414f58ac88a1b1e1b4eeb097011d 100644
--- a/tests/PHPUnit/Integration/Core/TrackerTest.php
+++ b/tests/PHPUnit/Integration/Core/TrackerTest.php
@@ -18,6 +18,7 @@ class Core_TrackerTest extends DatabaseTestCase
         Fixture::createWebsite('2014-02-04');
         Fixture::createSuperUser();
     }
+
     /**
      * Test the Bulk tracking API as documented in: http://developer.piwik.org/api-reference/tracking-api#bulk-tracking
      *
diff --git a/tests/PHPUnit/IntegrationTestCase.php b/tests/PHPUnit/IntegrationTestCase.php
index f3bd65923c51cbd328d594bf54dfbabf18a3f52c..4c9e4daa0c5835a23a21ab663e1f8f754506e505 100755
--- a/tests/PHPUnit/IntegrationTestCase.php
+++ b/tests/PHPUnit/IntegrationTestCase.php
@@ -85,8 +85,7 @@ abstract class IntegrationTestCase extends PHPUnit_Framework_TestCase
         }
 
         try {
-            $fixture->setUpEnvironment();
-            $fixture->setUp();
+            $fixture->performSetUp();
         } catch (Exception $e) {
             static::fail("Failed to setup fixture: " . $e->getMessage() . "\n" . $e->getTraceAsString());
         }
@@ -100,8 +99,7 @@ abstract class IntegrationTestCase extends PHPUnit_Framework_TestCase
             $fixture = static::$fixture;
         }
 
-        $fixture->tearDown();
-        $fixture->tearDownEnvironment();
+        $fixture->performTearDown();
     }
 
     public function setUp()
diff --git a/tests/PHPUnit/TestingEnvironment.php b/tests/PHPUnit/TestingEnvironment.php
index 3bd0ad9d91dc455322b0dc38555149941001bc7e..5dcfa1fa25a6f4d314c4d2e35baeb11478be469a 100644
--- a/tests/PHPUnit/TestingEnvironment.php
+++ b/tests/PHPUnit/TestingEnvironment.php
@@ -1,5 +1,7 @@
 <?php
 
+use Piwik\Piwik;
+
 if (!defined('PIWIK_TEST_MODE')) {
     define('PIWIK_TEST_MODE', true);
 }
@@ -37,11 +39,11 @@ class Piwik_TestingEnvironment
 {
     public static function addHooks()
     {
-        \Piwik\Piwik::addAction('Access.createAccessSingleton', function($access) {
+        Piwik::addAction('Access.createAccessSingleton', function($access) {
             $access = new Piwik_MockAccess($access);
             \Piwik\Access::setSingletonInstance($access);
         });
-        \Piwik\Piwik::addAction('Config.createConfigSingleton', function($config) {
+        Piwik::addAction('Config.createConfigSingleton', function($config) {
             \Piwik\CacheFile::$invalidateOpCacheBeforeRead = true;
 
             $config->setTestEnvironment();
@@ -54,15 +56,15 @@ class Piwik_TestingEnvironment
             );
             $config->Plugins_Tracker = array('Plugins_Tracker' => $trackerPluginsToLoad);
         });
-        \Piwik\Piwik::addAction('Request.dispatch', function() {
+        Piwik::addAction('Request.dispatch', function() {
             \Piwik\Plugins\CoreVisualizations\Visualizations\Cloud::$debugDisableShuffle = true;
             \Piwik\Visualization\Sparkline::$enableSparklineImages = false;
             \Piwik\Plugins\ExampleUI\API::$disableRandomness = true;
         });
-        \Piwik\Piwik::addAction('AssetManager.getStylesheetFiles', function(&$stylesheets) {
+        Piwik::addAction('AssetManager.getStylesheetFiles', function(&$stylesheets) {
             $stylesheets[] = 'tests/resources/screenshot-override/override.css';
         });
-        \Piwik\Piwik::addAction('AssetManager.getJavaScriptFiles', function(&$jsFiles) {
+        Piwik::addAction('AssetManager.getJavaScriptFiles', function(&$jsFiles) {
             $jsFiles[] = 'tests/resources/screenshot-override/jquery.waitforimages.js';
             $jsFiles[] = 'tests/resources/screenshot-override/override.js';
         });
diff --git a/tests/PHPUnit/config.ini.travis.php b/tests/PHPUnit/config.ini.travis.php
index 048ac9432c5db6e6cd381df36b3acb3b00c6ce70..5ee6c488723fdbf171553578fb0998a032608ded 100644
--- a/tests/PHPUnit/config.ini.travis.php
+++ b/tests/PHPUnit/config.ini.travis.php
@@ -21,4 +21,7 @@ tables_prefix = piwiktests_
 
 [log]
 log_writers[] = file
-log_level = debug
\ No newline at end of file
+log_level = debug
+
+[Tests]
+persist_fixture_data = 1
\ No newline at end of file
diff --git a/tests/resources/screenshot-capture/capture.js b/tests/resources/screenshot-capture/capture.js
index 57c3643a408798da2b654a26d1e8842fdc5ea336..8ab57f6900d225a45cca84b1cf0c580adb17b03a 100644
--- a/tests/resources/screenshot-capture/capture.js
+++ b/tests/resources/screenshot-capture/capture.js
@@ -1,3 +1,4 @@
+// TODO: this is a mess, need to refactor
 var fs = require('fs');
 var app = typeof slimer === 'undefined' ? phantom : slimer;
 var readFileSync = fs.readFileSync || fs.read;
@@ -7,27 +8,69 @@ var PAGE_LOAD_TIMEOUT = 120;
 
 var PageFacade = function (webpage) {
     this.webpage = webpage;
+    this.events = [];
+    this.impl = {
+        click: function (selector) {
+            var position = this._getPosition(selector);
+            this.webpage.sendEvent('click', position.x, position.y);
+        },
+
+        keypress: function (keys) {
+            this.webpage.sendEvent('keypress', keys);
+        },
+
+        mousemove: function (selector) {
+            var position = this._getPosition(selector);
+            this.webpage.sendEvent('mousemove', position.x, position.y);
+        }
+    };
 };
 
 PageFacade.prototype = {
-    click: function (selector) {
-        var elementPosition = this._getPosition(selector);
-        this._clickImpl(elementPosition);
+    click: function (selector, waitTime) {
+        this.events.push(['click', waitTime || 1000, selector]);
+    },
+
+    sendKeys: function (selector, keys, waitTime) {
+        this.events.push(['click', 100, selector]);
+        this.events.push(['keypress', waitTime || 1000, keys]);
     },
 
-    sendKeys: function (selector, keys) {
-        var elementPosition = this._getPosition(selector);
-        this._clickImpl(elementPosition);
-        this.webpage.sendEvent('keypress', keys);
+    mouseMove: function (selector, waitTime) {
+        this.events.push(['mousemove', waitTime || 1000, selector]);
     },
 
-    mouseMove: function (selector) {
-        var position = this._getPosition(selector);
-        this.webpage.sendEvent('mousemove', position.x, position.y);
+    executeEvents: function (callback, i) {
+        i = i || 0;
+
+        var evt = this.events[i];
+        if (!evt) {
+            callback();
+            return;
+        }
+
+        var type = evt.shift(),
+            waitTime = evt.shift();
+
+        this.impl[type].apply(this, evt);
+        this._waitForNextEvent(callback, i, waitTime);
     },
 
-    _clickImpl: function (position) {
-        this.webpage.sendEvent('click', position.x, position.y);
+    getAjaxRequestCount: function () {
+        return this.webpage.evaluate(function () {
+            return globalAjaxQueue.active;
+        });
+    },
+
+    _waitForNextEvent: function (callback, i, waitTime) {
+        var self = this;
+        setTimeout(function () {
+            if (self.getAjaxRequestCount() == 0) {
+                self.executeEvents(callback, i + 1);
+            } else {
+                self._waitForNextEvent(callback, i, waitTime);
+            }
+        }, waitTime);
     },
 
     _getPosition: function (selector) {
@@ -145,9 +188,10 @@ PageRenderer.prototype = {
         }, Math.max(1000 * 15 * this.screenshotCount, 1000 * 60 * 10));
     },
 
-    _executeScreenJs: function (js) {
+    _executeScreenJs: function (js, callback) {
         var page = new PageFacade(this.webpage);
         eval(js);
+        page.executeEvents(callback || function () {});
     }
 };
 
@@ -266,10 +310,8 @@ UnitTestRenderer.prototype._saveCurrentScreen = function () {
 
     console.log("SAVING " + outputPath + " at " + this._getElapsedExecutionTime());
 
-    this._executeScreenJs(screenJs);
-
     var self = this;
-    setTimeout(function () {
+    this._executeScreenJs(screenJs, function () {
         try {
             self._setCorrectViewportSize();
             self.webpage.render(outputPath);
@@ -279,7 +321,7 @@ UnitTestRenderer.prototype._saveCurrentScreen = function () {
             console.log("ERROR: " + e.message);
             app.exit(1);
         }
-    }, 5 * 1000);
+    });
 };
 
 UnitTestRenderer.prototype._renderNextUrl = function () {