diff --git a/tests/PHPUnit/Integration/ApiGetReportMetadataTest.php b/tests/PHPUnit/Integration/ApiGetReportMetadataTest.php
new file mode 100755
index 0000000000000000000000000000000000000000..0cb376dcfa54626291769ac36128864b4d908b48
--- /dev/null
+++ b/tests/PHPUnit/Integration/ApiGetReportMetadataTest.php
@@ -0,0 +1,74 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ * @version $Id$
+ */
+
+/**
+ * This tests the output of the API plugin API 
+ * It will return metadata about all API reports from all plugins
+ * as well as the data itself, pre-processed and ready to be displayed
+ */
+class Test_Piwik_Integration_ApiGetReportMetadata extends IntegrationTestCase
+{
+    protected $dateTime = '2009-01-04 00:11:42';
+    protected $idSite   = 1;
+    protected $idGoal   = 1;
+    protected $idGoal2  = 2;
+    protected $idGoal3  = 3;
+
+    public function setUp()
+    {
+        parent::setUp();
+        $this->createWebsite($this->dateTime, $ecommerce = 1);
+        Piwik_Goals_API::getInstance()->addGoal($this->idSite, 'Goal 1 - Thank you', 'title', 'Thank you', 'contains', $caseSensitive = false, $revenue = 10, $allowMultipleConversions = 1);
+        Piwik_Goals_API::getInstance()->addGoal($this->idSite, 'Goal 2 - Hello', 'url', 'hellow', 'contains', $caseSensitive = false, $revenue = 10, $allowMultipleConversions = 0);
+        Piwik_Goals_API::getInstance()->addGoal($this->idSite, 'triggered js', 'manually', '', '');
+        $this->trackVisits();
+
+        // From Piwik 1.5, we hide Goals.getConversions and other get* methods via @ignore, but we ensure that they still work
+        // This hack allows the API proxy to let us generate example URLs for the ignored functions
+        Piwik_API_Proxy::getInstance()->hideIgnoredFunctions = false;
+    }
+
+    public function getOutputPrefix()
+    {
+        return 'apiGetReportMetadata';
+    }
+
+    public function getApiForTesting()
+    {
+        return array(
+            array('API', array('idSite' => $this->idSite, 'date' => $this->dateTime))
+        );
+    }
+
+    /**
+     * @dataProvider getApiForTesting
+     * @group        Integration
+     * @group        ApiGetReportMetadata
+     */
+    public function testApi($api, $params)
+    {
+        $this->runApiTests($api, $params);
+    }
+
+    protected function trackVisits()
+    {
+        $idSite   = $this->idSite;
+        $dateTime = $this->dateTime;
+
+        $t = $this->getTracker($idSite, $dateTime, $defaultInit = true);
+
+        // Record 1st page view
+        $t->setUrl('http://example.org/index.htm');
+        $this->checkResponse($t->doTrackPageView('incredible title!'));
+
+        $t->setForceVisitDateTime(Piwik_Date::factory($dateTime)->addHour(0.3)->getDatetime());
+        $this->checkResponse($t->doTrackGoal($this->idGoal3, $revenue = 42.256));
+    }
+}
+
diff --git a/tests/PHPUnit/Integration/ApiGetReportMetadata_yearTest.php b/tests/PHPUnit/Integration/ApiGetReportMetadata_yearTest.php
new file mode 100755
index 0000000000000000000000000000000000000000..ff7b68cb02688d0e62076a89a9d75cfbc8e3239e
--- /dev/null
+++ b/tests/PHPUnit/Integration/ApiGetReportMetadata_yearTest.php
@@ -0,0 +1,55 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ * @version $Id$
+ */
+
+/**
+ * test the Yearly metadata API response, 
+ * with no visits, with custom response language 
+ */
+class Test_Piwik_Integration_ApiGetReportMetadata_Year extends IntegrationTestCase
+{
+    protected $idSite   = 1;
+    protected $dateTime = '2009-01-04 00:11:42';
+
+    public function setUp()
+    {
+        parent::setUp();
+        $this->createWebsite($this->dateTime);
+    }
+
+    public function getApiForTesting()
+    {
+        $params = array('idSite'   => $this->idSite,
+                        'date'     => $this->dateTime,
+                        'periods'  => 'year',
+                        'language' => 'fr');
+        return array(
+            array('API.getProcessedReport', $params),
+            array('API.getReportMetadata', $params),
+            array('LanguagesManager.getTranslationsForLanguage', $params),
+            array('LanguagesManager.getAvailableLanguageNames', $params),
+            array('SitesManager.getJavascriptTag', $params)
+        );
+    }
+
+    public function getOutputPrefix()
+    {
+        return 'apiGetReportMetadata_year';
+    }
+
+    /**
+     * @dataProvider getApiForTesting
+     * @group        Integration
+     * @group        ApiGetReportMetadata
+     * @group        ApiGetReportMetadata_year
+     */
+    public function testApi($api, $params)
+    {
+        $this->runApiTests($api, $params);
+    }
+}
diff --git a/tests/PHPUnit/Integration/CsvExportTest.php b/tests/PHPUnit/Integration/CsvExportTest.php
new file mode 100755
index 0000000000000000000000000000000000000000..cb3bed3dbd40cd7f1f16242f356cb4071e7341b1
--- /dev/null
+++ b/tests/PHPUnit/Integration/CsvExportTest.php
@@ -0,0 +1,65 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ * @version $Id$
+ */
+
+require_once dirname(__FILE__).'/TwoVisitsWithCustomVariablesTest.php';
+
+/**
+ * Test CSV export with Expanded rows, Translated labels, Different languages
+ */
+class Test_Piwik_Integration_CsvExport extends Test_Piwik_Integration_TwoVisitsWithCustomVariables
+{
+
+    protected $useEscapedQuotes  = false;
+    protected $doExtraQuoteTests = false;
+
+    public function getApiForTesting()
+    {
+        $apiToCall    = array('VisitsSummary.get', 'CustomVariables.getCustomVariables');
+
+        $enExtraParam = array('expanded' => 0, 'flat' => 1, 'include_aggregate_rows' => 0, 'translateColumnNames' => 1);
+
+        $deExtraParam = array('expanded' => 0, 'flat' => 1, 'include_aggregate_rows' => 1, 'translateColumnNames' => 1);
+
+        return array(
+            array($apiToCall, array('idSite'                 => $this->idSite,
+                                    'date'                   => $this->dateTime, 'format' => 'csv',
+                                    'otherRequestParameters' => array('expanded' => 0, 'flat' => 0),
+                                    'testSuffix'             => '_xp0')),
+
+            array($apiToCall, array('idSite'                 => $this->idSite,
+                                    'date'                   => $this->dateTime,
+                                    'format'                 => 'csv',
+                                    'otherRequestParameters' => $enExtraParam,
+                                    'language'               => 'en',
+                                    'testSuffix'             => '_xp1_inner0_trans-en')),
+
+            array($apiToCall, array('idSite'                 => $this->idSite,
+                                    'date'                   => $this->dateTime,
+                                    'format'                 => 'csv',
+                                    'otherRequestParameters' => $deExtraParam,
+                                    'language'               => 'de',
+                                    'testSuffix'             => '_xp1_inner1_trans-de')),
+        );
+    }
+
+    /**
+     * @dataProvider getApiForTesting
+     * @group        Integration
+     * @group        CsvExport
+     */
+    public function testApi($api, $params)
+    {
+        $this->runApiTests($api, $params);
+    }
+
+    public function getOutputPrefix()
+    {
+        return 'csvExport';
+    }
+}
diff --git a/tests/PHPUnit/Integration/TwoVisitsWithCustomVariablesTest.php b/tests/PHPUnit/Integration/TwoVisitsWithCustomVariablesTest.php
new file mode 100755
index 0000000000000000000000000000000000000000..834dc53bacb5db622ae6b4cb38332e8b117a6430
--- /dev/null
+++ b/tests/PHPUnit/Integration/TwoVisitsWithCustomVariablesTest.php
@@ -0,0 +1,163 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ * @version $Id$
+ */
+
+/**
+ * Tests w/ two visits & custom variables.
+ */
+class Test_Piwik_Integration_TwoVisitsWithCustomVariables extends IntegrationTestCase
+{
+    protected $dateTime  = '2010-01-03 11:22:33';
+    protected $width     = 1111;
+    protected $height    = 222;
+
+    protected $idSite    = 1;
+    protected $idGoal1   = 1;
+    protected $idGoal2   = 2;
+    protected $visitorId = null;
+
+    protected $useEscapedQuotes  = true;
+    protected $doExtraQuoteTests = true;
+
+    public function getApiForTesting()
+    {
+        $apiToCall = array('VisitsSummary.get', 'CustomVariables.getCustomVariables');
+
+        $return = array(
+            array($apiToCall, array('idSite'       => 'all',
+                                    'date'         => $this->dateTime,
+                                    'periods'      => array('day', 'week'),
+                                    'setDateLastN' => true)),
+        );
+
+        return $return;
+    }
+
+    /**
+     * @dataProvider getApiForTesting
+     * @group        Integration
+     * @group        TwoVisitsWithCustomVariables
+     */
+    public function testApi($api, $params)
+    {
+        $this->runApiTests($api, $params);
+    }
+
+    public function getOutputPrefix()
+    {
+        return 'twoVisitsWithCustomVariables';
+    }
+
+    public function setUp()
+    {
+        parent::setUp();
+
+        // tests run in UTC, the Tracker in UTC
+        $this->createWebsite($this->dateTime);
+        Piwik_Goals_API::getInstance()->addGoal($this->idSite, 'triggered js', 'manually', '', '');
+        Piwik_Goals_API::getInstance()->addGoal($this->idSite, 'second goal', 'manually', '', '');
+
+        $this->trackVisits();
+    }
+
+    protected function trackVisits()
+    {
+        $dateTime = $this->dateTime;
+        $idSite   = $this->idSite;
+        $idGoal   = $this->idGoal1;
+        $idGoal2  = $this->idGoal2;
+
+        ob_start();
+        $visitorA = $this->getTracker($idSite, $dateTime, $defaultInit = true);
+        // Used to test actual referer + keyword position in Live!
+        $visitorA->setUrlReferrer(urldecode('http://www.google.com/url?sa=t&source=web&cd=1&ved=0CB4QFjAA&url=http%3A%2F%2Fpiwik.org%2F&rct=j&q=this%20keyword%20should%20be%20ranked&ei=V8WfTePkKKLfiALrpZWGAw&usg=AFQjCNF_MGJRqKPvaKuUokHtZ3VvNG9ALw&sig2=BvKAdCtNixsmfNWXjsNyMw'));
+
+        // no campaign, but a search engine to attribute the goal conversion to
+        $attribution = array(
+            '',
+            '',
+            1302306504,
+            'http://www.google.com/search?q=piwik&ie=utf-8&oe=utf-8&aq=t&rls=org.mozilla:en-GB:official&client=firefox-a'
+        );
+        $visitorA->setAttributionInfo(json_encode($attribution));
+
+        $visitorA->setResolution($this->width, $this->height);
+
+        // At first, visitor custom var is set to LoggedOut
+        $visitorA->setForceVisitDateTime(Piwik_Date::factory($dateTime)->addHour(0.1)->getDatetime());
+        $visitorA->setUrl('http://example.org/homepage');
+        $visitorA->setCustomVariable($id = 1, $name = 'VisitorType', $value = 'LoggedOut');
+        $this->checkResponse($visitorA->doTrackPageView('Homepage'));
+        $this->checkResponse($visitorA->doTrackGoal($idGoal2, 0));
+
+        // After login, set to LoggedIn, should overwrite previous value
+        $visitorA->setForceVisitDateTime(Piwik_Date::factory($dateTime)->addHour(0.2)->getDatetime());
+        $visitorA->setUrl('http://example.org/user/profile');
+        $visitorA->setCustomVariable($id = 1, $name = 'VisitorType', $value = 'LoggedIn');
+        $visitorA->setCustomVariable($id = 4, $name = 'Status user', $value = 'Loggedin', $scope = 'page');
+        if ($this->useEscapedQuotes) {
+            $lookingAtProfile = 'looking at &quot;profile page&quot;';
+        } else {
+            $lookingAtProfile = 'looking at profile page';
+        }
+        $visitorA->setCustomVariable($id = 5, $name = 'Status user', $value = $lookingAtProfile, $scope = 'page');
+        $this->checkResponse($visitorA->doTrackPageView('Profile page'));
+
+        $visitorA->setCustomVariable($id = 2, $name = 'SET WITH EMPTY VALUE', $value = '');
+        $visitorA->setCustomVariable($id = 1, $name = 'Language', $value = 'FR', $scope = 'page');
+        $visitorA->setCustomVariable($id = 2, $name = 'SET WITH EMPTY VALUE PAGE SCOPE', $value = '', $scope = 'page');
+        $visitorA->setCustomVariable($id = 4, $name = 'Status user', $value = "looking at \"profile page\"", $scope = 'page');
+        $visitorA->setCustomVariable($id = 3, $name = 'Value will be VERY long and truncated', $value = 'abcdefghijklmnopqrstuvwxyz----abcdefghijklmnopqrstuvwxyz----abcdefghijklmnopqrstuvwxyz----abcdefghijklmnopqrstuvwxyz----abcdefghijklmnopqrstuvwxyz----abcdefghijklmnopqrstuvwxyz----abcdefghijklmnopqrstuvwxyz----abcdefghijklmnopqrstuvwxyz----abcdefghijklmnopqrstuvwxyz----abcdefghijklmnopqrstuvwxyz----abcdefghijklmnopqrstuvwxyz----abcdefghijklmnopqrstuvwxyz----abcdefghijklmnopqrstuvwxyz----abcdefghijklmnopqrstuvwxyz----abcdefghijklmnopqrstuvwxyz----abcdefghijklmnopqrstuvwxyz----abcdefghijklmnopqrstuvwxyz----abcdefghijklmnopqrstuvwxyz----');
+        $this->checkResponse($visitorA->doTrackPageView('Profile page for user *_)%'));
+        $this->checkResponse($visitorA->doTrackGoal($idGoal, 0));
+
+        if ($this->doExtraQuoteTests) {
+            $visitorA->setCustomVariable($id = 2, $name = 'var1', $value = 'looking at "profile page"',
+                $scope = 'page');
+            $visitorA->setCustomVariable($id = 3, $name = 'var2', $value = '\'looking at "\profile page"\'',
+                $scope = 'page');
+            $visitorA->setCustomVariable($id = 4, $name = 'var3', $value = '\\looking at "\profile page"\\',
+                $scope = 'page');
+            $this->checkResponse($visitorA->doTrackPageView('Concurrent page views'));
+        }
+
+        // -
+        // Second new visitor on Idsite 1: one page view
+        $visitorB = $this->getTracker($idSite, $dateTime, $defaultInit = true);
+        $visitorB->setUrlReferrer('');
+
+        $attribution = array(
+            ' CAMPAIGN NAME -%20YEAH! ',
+            ' CAMPAIGN%20KEYWORD - RIGHT... ',
+            1302306504,
+            'http://www.example.org/test/really?q=yes'
+        );
+        $visitorB->setAttributionInfo(json_encode($attribution));
+        $visitorB->setResolution($this->width, $this->height);
+        $visitorB->setUserAgent('Mozilla/5.0 (Windows; U; Windows NT 5.1; en-GB; rv:1.9.0.6) Gecko/2009011913 Firefox/3.0.6');
+        $visitorB->setForceVisitDateTime(Piwik_Date::factory($dateTime)->addHour(1)->getDatetime());
+        $visitorB->setCustomVariable($id = 1, $name = 'VisitorType', $value = 'LoggedOut');
+        $visitorB->setCustomVariable($id = 2, $name = 'Othercustom value which should be truncated abcdefghijklmnopqrstuvwxyz', $value = 'abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz');
+        $visitorB->setCustomVariable($id = -2, $name = 'not tracked', $value = 'not tracked');
+        $visitorB->setCustomVariable($id = 6, $name = 'not tracked', $value = 'not tracked');
+        $visitorB->setCustomVariable($id = 6, $name = array('not tracked'), $value = 'not tracked');
+        $visitorB->setUrl('http://example.org/homepage');
+        $this->checkResponse($visitorB->doTrackGoal($idGoal, 1000));
+
+        $visitorB->setForceVisitDateTime(Piwik_Date::factory($dateTime)->addHour(1.1)->getDatetime());
+        $this->checkResponse($visitorB->doTrackPageView('Homepage'));
+
+        // DIFFERENT test -
+        // testing that starting the visit with an outlink works (doesn't trigger errors)
+        $visitorB->setForceVisitDateTime(Piwik_Date::factory($dateTime)->addHour(2)->getDatetime());
+        $this->checkResponse($visitorB->doTrackAction('http://test.com', 'link'));
+
+        // hack
+        $this->visitorId = $visitorB->getVisitorId();
+    }
+}
diff --git a/tests/PHPUnit/IntegrationTestCase.php b/tests/PHPUnit/IntegrationTestCase.php
new file mode 100755
index 0000000000000000000000000000000000000000..ff7de44ae67651f6e4e4ff2bdda162c0953c493b
--- /dev/null
+++ b/tests/PHPUnit/IntegrationTestCase.php
@@ -0,0 +1,1214 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ * @version $Id$
+ */
+require_once PIWIK_INCLUDE_PATH . '/libs/PiwikTracker/PiwikTracker.php';
+
+/**
+ * Base class for Integration tests.
+ *
+ * Provides helpers to track data and then call API get* methods to check outputs automatically.
+ *
+ */
+abstract class IntegrationTestCase extends DatabaseTestCase
+{
+
+    /**
+     * Identifies the last language used in an API/Controller call.
+     *
+     * @var string
+     */
+    protected $lastLanguage;
+
+    /**
+     * Initializes the test
+     * Load english translations to ensure API response have english text
+     *
+     * @see tests/core/Test_Database#setUp()
+     */
+    public function setUp()
+    {
+        parent::setUp();
+
+        if (self::$widgetTestingLevel != self::NO_WIDGET_TESTING)
+        {
+            self::initializeControllerTesting();
+        }
+
+        Piwik::createAccessObject();
+        Piwik_PostEvent('FrontController.initAuthenticationObject');
+
+        // We need to be SU to create websites for tests
+        Piwik::setUserIsSuperUser();
+
+        // Load and install plugins
+        $pluginsManager = Piwik_PluginsManager::getInstance();
+        $plugins = Piwik_Config::getInstance()->Plugins['Plugins'];
+
+        $pluginsManager->loadPlugins( $plugins );
+        $pluginsManager->installLoadedPlugins();
+
+        $_GET = $_REQUEST = array();
+        $_SERVER['HTTP_REFERER'] = '';
+
+        // Make sure translations are loaded to check messages in English
+        Piwik_Translate::getInstance()->loadEnglishTranslation();
+
+        // List of Modules, or Module.Method that should not be called as part of the XML output compare
+        // Usually these modules either return random changing data, or are already tested in specific unit tests.
+        $this->setApiNotToCall(self::$defaultApiNotToCall);
+        $this->setApiToCall( array());
+
+        if (self::$widgetTestingLevel != self::NO_WIDGET_TESTING)
+        {
+            Piwik::setUserIsSuperUser();
+
+            // create users for controller testing
+            $usersApi = Piwik_UsersManager_API::getInstance();
+            $usersApi->addUser('anonymous', self::DEFAULT_USER_PASSWORD, 'anonymous@anonymous.com');
+            $usersApi->addUser('test_view', self::DEFAULT_USER_PASSWORD, 'view@view.com');
+            $usersApi->addUser('test_admin', self::DEFAULT_USER_PASSWORD, 'admin@admin.com');
+
+            // disable shuffling of tag cloud visualization so output is consistent
+            Piwik_Visualization_Cloud::$debugDisableShuffle = true;
+        }
+    }
+
+    public function tearDown()
+    {
+        parent::tearDown();
+        $_GET = $_REQUEST = array();
+        Piwik_Translate::getInstance()->unloadEnglishTranslation();
+
+        // re-enable tag cloud shuffling
+        Piwik_Visualization_Cloud::$debugDisableShuffle = true;
+    }
+
+    protected $apiToCall = array();
+    protected $apiNotToCall = array();
+
+    public static $defaultApiNotToCall = array(
+        'LanguagesManager',
+        'DBStats',
+        'UsersManager',
+        'SitesManager',
+        'ExampleUI',
+        'Live',
+        'SEO',
+        'ExampleAPI',
+        'PDFReports',
+        'API',
+        'ImageGraph',
+    );
+
+    /**
+     * Widget testing level constant. If self::$widgetTestingLevel is
+     * set to this, controller actions will not be tested.
+     */
+    const NO_WIDGET_TESTING = 'none';
+
+    /**
+     * Widget testing level constant. If self::$widgetTestingLevel is
+     * set to this, controller actions will be checked for non-fatal errors, but
+     * the output will be ignored.
+     */
+    const CHECK_WIDGET_ERRORS = 'check_errors';
+
+    /**
+     * Widget testing level constant. If self::$widgetTestingLevel is
+     * set to this, controller actions will be run & their output will be checked with
+     * expected output files.
+     */
+    const COMPARE_WIDGET_OUTPUT = 'compare_output';
+
+    /**
+     * Determines how much of controller actions are tested (if at all).
+     */
+    static public $widgetTestingLevel = self::NO_WIDGET_TESTING;
+
+    /**
+     * API testing level constant. If self::$apiTestingLevel is
+     * set to this, API methods will not be tested.
+     */
+    const NO_API_TESTING = 'none';
+
+    /**
+     * API testing level constant. If self::$apiTestingLevel is
+     * set to this, API methods will be run & their output will be checked with
+     * expected output files.
+     */
+    const COMPARE_API_OUTPUT = 'compare_output';
+
+    /**
+     * Determines how much testing API methods are subjected to (if any).
+     */
+    static public $apiTestingLevel = self::COMPARE_API_OUTPUT;
+
+    const DEFAULT_USER_PASSWORD = 'nopass';
+
+    /**
+     * Forces the test to only call and fetch XML for the specified plugins,
+     * or exact API methods.
+     * If not called, all default tests will be executed.
+     *
+     * @param array $apiToCall array( 'ExampleAPI', 'Plugin.getData' )
+     *
+     * @throws Exception
+     * @return void
+     */
+    protected function setApiToCall( $apiToCall )
+    {
+        if(func_num_args() != 1)
+        {
+            throw new Exception('setApiToCall expects an array');
+        }
+        if(!is_array($apiToCall))
+        {
+            $apiToCall = array($apiToCall);
+        }
+        $this->apiToCall = $apiToCall;
+    }
+
+    /**
+     * Sets a list of API methods to not call during the test
+     *
+     * @param string $apiNotToCall eg. 'ExampleAPI.getPiwikVersion'
+     *
+     * @return void
+     */
+    protected function setApiNotToCall( $apiNotToCall )
+    {
+        if(!is_array($apiNotToCall))
+        {
+            $apiNotToCall = array($apiNotToCall);
+        }
+        $this->apiNotToCall = $apiNotToCall;
+    }
+
+    /**
+     * Returns a PiwikTracker object that you can then use to track pages or goals.
+     *
+     * @param         $idSite
+     * @param         $dateTime
+     * @param boolean $defaultInit If set to true, the tracker object will have default IP, user agent, time, resolution, etc.
+     *
+     * @return PiwikTracker
+     */
+    protected function getTracker($idSite, $dateTime, $defaultInit = true )
+    {
+        $t = new PiwikTracker( $idSite, $this->getTrackerUrl());
+        $t->setForceVisitDateTime($dateTime);
+
+        if($defaultInit)
+        {
+            $t->setIp('156.5.3.2');
+
+            // Optional tracking
+            $t->setUserAgent( "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-GB; rv:1.9.2.6) Gecko/20100625 Firefox/3.6.6 (.NET CLR 3.5.30729)");
+            $t->setBrowserLanguage('fr');
+            $t->setLocalTime( '12:34:06' );
+            $t->setResolution( 1024, 768 );
+            $t->setBrowserHasCookies(true);
+            $t->setPlugins($flash = true, $java = true, $director = false);
+        }
+        return $t;
+    }
+
+    /**
+     * Creates a website, then sets its creation date to a day earlier than specified dateTime
+     * Useful to create a website now, but force data to be archived back in the past.
+     *
+     * @param string  $dateTime eg '2010-01-01 12:34:56'
+     * @param int     $ecommerce
+     * @param string  $siteName
+     *
+     * @return int    idSite of website created
+     */
+    protected function createWebsite( $dateTime, $ecommerce = 0, $siteName = 'Piwik test' )
+    {
+        $idSite = Piwik_SitesManager_API::getInstance()->addSite(
+            $siteName,
+            "http://piwik.net/",
+            $ecommerce,
+            $ips = null,
+            $excludedQueryParameters = null,
+            $timezone = null,
+            $currency = null
+        );
+
+        // Manually set the website creation date to a day earlier than the earliest day we record stats for
+        Zend_Registry::get('db')->update(Piwik_Common::prefixTable("site"),
+            array('ts_created' => Piwik_Date::factory($dateTime)->subDay(1)->getDatetime()),
+            "idsite = $idSite"
+        );
+
+        // Clear the memory Website cache
+        Piwik_Site::clearCache();
+
+        // add access to all test users if doing controller tests
+        if (self::$widgetTestingLevel != self::NO_WIDGET_TESTING)
+        {
+            $usersApi = Piwik_UsersManager_API::getInstance();
+            $usersApi->setUserAccess('anonymous', 'view', array($idSite));
+            $usersApi->setUserAccess('test_view', 'view', array($idSite));
+            $usersApi->setUserAccess('test_admin', 'admin', array($idSite));
+        }
+
+        return $idSite;
+    }
+
+    /**
+     * Checks that the response is a GIF image as expected.
+     * Will fail the test if the response is not the expected GIF
+     *
+     * @param $response
+     */
+    protected function checkResponse($response)
+    {
+        $trans_gif_64 = "R0lGODlhAQABAIAAAAAAAAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==";
+        $expectedResponse = base64_decode($trans_gif_64);
+        $this->assertEquals($expectedResponse, $response, "Expected GIF beacon, got: <br/>\n" . $response ."<br/>\n");
+    }
+
+    /**
+     * Returns URL to the proxy script, used to ensure piwik.php
+     * uses the test environment, and allows variable overwriting
+     *
+     * @return string
+     */
+    protected function getTrackerUrl()
+    {
+        $piwikUrl = Piwik_Url::getCurrentUrlWithoutFileName();
+
+        $pathBeforeRoot = 'tests';
+        // Running from a plugin
+        if(strpos($piwikUrl, 'plugins/') !== false)
+        {
+            $pathBeforeRoot = 'plugins';
+        }
+        $piwikUrl = substr($piwikUrl, 0, strpos($piwikUrl, $pathBeforeRoot.'/')) . 'tests/PHPUnit/proxy-piwik.php';
+        return $piwikUrl;
+    }
+
+    /**
+     * Initializes parts of Piwik so controller actions can be called & tested.
+     */
+    public static function initializeControllerTesting()
+    {
+        static $initialized = false;
+
+        if (!$initialized)
+        {
+            Zend_Registry::set('timer', new Piwik_Timer);
+
+            $pluginsManager = Piwik_PluginsManager::getInstance();
+            $pluginsToLoad = Piwik_Config::getInstance()->Plugins['Plugins'];
+            $pluginsManager->loadPlugins( $pluginsToLoad );
+
+            $initialized = true;
+        }
+    }
+
+    public static function processRequestArgs()
+    {
+        // set the widget testing level
+        if (isset($_GET['widgetTestingLevel']))
+        {
+            self::setWidgetTestingLevel($_GET['widgetTestingLevel']);
+        }
+
+        // set the API testing level
+        if (isset($_GET['apiTestingLevel']))
+        {
+            self::setApiTestingLevel($_GET['apiTestingLevel']);
+        }
+    }
+
+    public static function setWidgetTestingLevel($level)
+    {
+        if (!$level) return;
+
+        if ($level != self::NO_WIDGET_TESTING &&
+            $level != self::CHECK_WIDGET_ERRORS &&
+            $level != self::COMPARE_WIDGET_OUTPUT)
+        {
+            echo "<p>Invalid option for 'widgetTestingLevel', ignoring.</p>\n";
+            return;
+        }
+
+        self::$widgetTestingLevel = $level;
+    }
+
+    public function setApiTestingLevel($level)
+    {
+        if (!$level) return;
+
+        if ($level != self::NO_API_TESTING &&
+            $level != self::COMPARE_API_OUTPUT)
+        {
+            echo "<p>Invalid option for 'apiTestingLevel', ignoring.</p>";
+            return;
+        }
+
+        self::$apiTestingLevel = $level;
+    }
+
+    /**
+     * Given a list of default parameters to set, returns the URLs of APIs to call
+     * If any API was specified in setApiToCall() we ensure only these are tested.
+     * If any API is set as excluded (see list below) then it will be ignored.
+     *
+     * @param array       $parametersToSet Parameters to set in api call
+     * @param array       $formats         Array of 'format' to fetch from API
+     * @param array       $periods         Array of 'period' to query API
+     * @param bool        $setDateLastN    If set to true, the 'date' parameter will be rewritten to query instead a range of dates, rather than one period only.
+     * @param bool|string $language        2 letter language code, defaults to default piwik language
+     * @param bool|string $segment
+     *
+     * @return array of API URLs query strings
+     */
+    protected function generateUrlsApi( $parametersToSet, $formats, $periods, $setDateLastN = false, $language = false, $segment = false )
+    {
+        // Get the URLs to query against the API for all functions starting with get*
+        $skipped = $requestUrls = array();
+        $apiMetadata = new Piwik_API_DocumentationGenerator;
+        foreach(Piwik_API_Proxy::getInstance()->getMetadata() as $class => $info)
+        {
+            $moduleName = Piwik_API_Proxy::getInstance()->getModuleNameFromClassName($class);
+            foreach($info as $methodName => $infoMethod)
+            {
+                $apiId = $moduleName.'.'.$methodName;
+
+                // If Api to test were set, we only test these
+                if(!empty($this->apiToCall)
+                    && in_array($moduleName, $this->apiToCall) === false
+                    && in_array($apiId, $this->apiToCall) === false)
+                {
+                    $skipped[] = $apiId;
+                    continue;
+                }
+                // Excluded modules from test
+                elseif(
+                    (strpos($methodName, 'get') !== 0
+                        || in_array($moduleName, $this->apiNotToCall) === true
+                        || in_array($apiId, $this->apiNotToCall) === true
+                        || $methodName == 'getLogoUrl'
+                        || $methodName == 'getHeaderLogoUrl'
+                    )
+                )
+                {
+                    $skipped[] = $apiId;
+                    continue;
+                }
+
+                foreach($periods as $period)
+                {
+                    $parametersToSet['period'] = $period;
+
+                    // If date must be a date range, we process this date range by adding 6 periods to it
+                    if($setDateLastN)
+                    {
+                        if(!isset($parametersToSet['dateRewriteBackup']))
+                        {
+                            $parametersToSet['dateRewriteBackup'] = $parametersToSet['date'];
+                        }
+
+                        $lastCount = (int)$setDateLastN;
+                        if($setDateLastN === true)
+                        {
+                            $lastCount = 6;
+                        }
+                        $firstDate = $parametersToSet['dateRewriteBackup'];
+                        $secondDate = date('Y-m-d', strtotime("+$lastCount " . $period . "s", strtotime($firstDate)));
+                        $parametersToSet['date'] = $firstDate . ',' . $secondDate;
+                    }
+
+                    // Set response language
+                    if($language !== false)
+                    {
+                        $parametersToSet['language'] = $language;
+                    }
+                    // Generate for each specified format
+                    foreach($formats as $format)
+                    {
+                        $parametersToSet['format'] = $format;
+                        $parametersToSet['hideIdSubDatable'] = 1;
+                        $parametersToSet['serialize'] = 1;
+
+                        $exampleUrl = $apiMetadata->getExampleUrl($class, $methodName, $parametersToSet);
+                        if($exampleUrl === false)
+                        {
+                            $skipped[] = $apiId;
+                            continue;
+                        }
+
+                        // Remove the first ? in the query string
+                        $exampleUrl = substr($exampleUrl, 1);
+                        $apiRequestId = $apiId;
+                        if(strpos($exampleUrl, 'period=') !== false)
+                        {
+                            $apiRequestId .= '_' . $period;
+                        }
+
+                        $apiRequestId .= '.' . $format;
+
+                        $requestUrls[$apiRequestId] = $exampleUrl;
+                    }
+                }
+            }
+        }
+        return $requestUrls;
+    }
+
+    /**
+     * Will call all get* methods on authorized modules,
+     * force the archiving,
+     * record output in XML files
+     * and compare with the expected outputs.
+     *
+     * @param string            $testName       Used to write the output in a file, used as filename prefix
+     * @param string|array      $formats        String or array of formats to fetch from API
+     * @param int|bool          $idSite         Id site
+     * @param string|bool       $dateTime       Date time string of reports to request
+     * @param array|bool|string $periods        String or array of strings of periods (day, week, month, year)
+     * @param bool              $setDateLastN   When set to true, 'date' parameter passed to API request will be rewritten to query a range of dates rather than 1 date only
+     * @param string|bool       $language       2 letter language code to request data in
+     * @param string|bool       $segment        Custom Segment to query the data  for
+     * @param string|bool       $visitorId      Only used for Live! API testing
+     * @param bool              $abandonedCarts Only used in Goals API testing
+     * @param bool              $idGoal
+     * @param bool              $apiModule
+     * @param bool              $apiAction
+     * @param array             $otherRequestParameters
+     *
+     * @return void
+     */
+    protected function _callGetApiCompareOutput($testName, $formats = 'xml', $idSite = false, $dateTime = false, $periods = false,
+                                     $setDateLastN = false, $language = false, $segment = false, $visitorId = false, $abandonedCarts = false,
+                                     $idGoal = false, $apiModule = false, $apiAction = false, $otherRequestParameters = array())
+    {
+        if (self::$apiTestingLevel == self::NO_API_TESTING)
+        {
+            return;
+        }
+
+        list($pathProcessed, $pathExpected) = $this->getProcessedAndExpectedDirs();
+
+        if($periods === false)
+        {
+            $periods = 'day';
+        }
+        if(!is_array($periods))
+        {
+            $periods = array($periods);
+        }
+        if(!is_array($formats))
+        {
+            $formats = array($formats);
+        }
+        if(!is_writable($pathProcessed))
+        {
+            $this->fail('To run the tests, you need to give write permissions to the following directory (create it if it doesn\'t exist).<code><br/>mkdir '. $pathProcessed.'<br/>chmod 777 '.$pathProcessed.'</code><br/>');
+        }
+        $parametersToSet = array(
+            'idSite'           => $idSite,
+            'date'             => $periods == array('range') ? $dateTime : date('Y-m-d', strtotime($dateTime)),
+            'expanded'         => '1',
+            'piwikUrl'         => 'http://example.org/piwik/',
+            // Used in getKeywordsForPageUrl
+            'url'              => 'http://example.org/store/purchase.htm',
+
+            // Used in Actions.getPageUrl, .getDownload, etc.
+            // tied to Main.test.php doTest_oneVisitorTwoVisits
+            // will need refactoring when these same API functions are tested in a new function
+            'downloadUrl'      => urlencode('http://piwik.org/path/again/latest.zip?phpsessid=this is ignored when searching'),
+            'outlinkUrl'       => urlencode('http://dev.piwik.org/svn'),
+            'pageUrl'          => urlencode('http://example.org/index.htm?sessionid=this is also ignored by default'),
+            'pageName'         => urlencode(' Checkout / Purchasing... '),
+
+            // do not show the millisec timer in response or tests would always fail as value is changing
+            'showTimer'        => 0,
+
+            'language'         => $language ? $language : 'en',
+            'abandonedCarts'   => $abandonedCarts ? 1 : 0,
+            'idSites'          => $idSite,
+        );
+        $parametersToSet = array_merge($parametersToSet, $otherRequestParameters);
+        if(!empty($visitorId ))
+        {
+            $parametersToSet['visitorId'] = $visitorId;
+        }
+        if(!empty($apiModule ))
+        {
+            $parametersToSet['apiModule'] = $apiModule;
+        }
+        if(!empty($apiAction))
+        {
+            $parametersToSet['apiAction'] = $apiAction;
+        }
+        if(!empty($segment))
+        {
+            $parametersToSet['segment'] = $segment;
+        }
+        if($idGoal !== false)
+        {
+            $parametersToSet['idGoal'] = $idGoal;
+        }
+
+        $requestUrls = $this->generateUrlsApi($parametersToSet, $formats, $periods, $setDateLastN, $language, $segment);
+
+        foreach($requestUrls as $apiId => $requestUrl)
+        {
+            #echo "\n\n$requestUrl\n\n";
+            $isLiveMustDeleteDates = strpos($requestUrl, 'Live.getLastVisits') !== false;
+            $request = new Piwik_API_Request($requestUrl);
+
+            list($processedFilePath, $expectedFilePath) = $this->getProcessedAndExpectedPaths($testName, $apiId);
+
+            // Cast as string is important. For example when calling
+            // with format=original, objects or php arrays can be returned.
+            // we also hide errors to prevent the 'headers already sent' in the ResponseBuilder (which sends Excel headers multiple times eg.)
+            $response = (string)$request->process();
+
+            if($isLiveMustDeleteDates)
+            {
+                $response = $this->removeAllLiveDatesFromXml($response);
+            }
+
+            file_put_contents( $processedFilePath, $response );
+
+            $expected = $this->loadExpectedFile($expectedFilePath);
+            if (empty($expected))
+            {
+                continue;
+            }
+
+            // @todo This should not vary between systems AFAIK... "idsubdatatable can differ"
+            $expected = $this->removeXmlElement($expected, 'idsubdatatable',$testNotSmallAfter = false);
+            $response = $this->removeXmlElement($response, 'idsubdatatable',$testNotSmallAfter = false);
+
+            if($isLiveMustDeleteDates)
+            {
+                $expected = $this->removeAllLiveDatesFromXml($expected);
+            }
+            // If date=lastN the <prettyDate> element will change each day, we remove XML element before comparison
+            elseif(strpos($dateTime, 'last') !== false
+                || strpos($dateTime, 'today') !== false
+                || strpos($dateTime, 'now') !== false
+            )
+            {
+                if(strpos($requestUrl, 'API.getProcessedReport') !== false)
+                {
+                    $expected = $this->removePrettyDateFromXml($expected);
+                    $response = $this->removePrettyDateFromXml($response);
+                }
+                // avoid build failure when running just before midnight, generating visits in the future
+                $expected = $this->removeXmlElement($expected, 'sum_daily_nb_uniq_visitors');
+                $response = $this->removeXmlElement($response, 'sum_daily_nb_uniq_visitors');
+                $expected = $this->removeXmlElement($expected, 'nb_visits_converted');
+                $response = $this->removeXmlElement($response, 'nb_visits_converted');
+                $expected = $this->removeXmlElement($expected, 'imageGraphUrl');
+                $response = $this->removeXmlElement($response, 'imageGraphUrl');
+            }
+
+            // is there a better way to test for the current DB type in use?
+            if(Zend_Registry::get('db') instanceof Piwik_Db_Adapter_Mysqli)
+            {
+                // Do not test for TRUNCATE(SUM()) returning .00 on mysqli since this is not working
+                // http://bugs.php.net/bug.php?id=54508
+                $expected = str_replace('.00</revenue>', '</revenue>', $expected);
+                $response = str_replace('.00</revenue>', '</revenue>', $response);
+                $expected = str_replace('.1</revenue>', '</revenue>', $expected);
+                $expected = str_replace('.11</revenue>', '</revenue>', $expected);
+                $response = str_replace('.11</revenue>', '</revenue>', $response);
+                $response = str_replace('.1</revenue>', '</revenue>', $response);
+            }
+
+            if(strpos($requestUrl, 'format=xml') !== false) {
+                $this->assertXmlStringEqualsXmlString($expected, $response, "Differences with expected in: $processedFilePath %s ");
+            } else {
+                $this->assertEquals($expected, $response, "Differences with expected in: $processedFilePath %s ");
+            }
+            if(trim($response) == trim($expected))
+            {
+                file_put_contents( $processedFilePath, $response );
+            }
+        }
+    }
+
+    /**
+     * Calls a set of controller actions & either checks the result against
+     * expected output or just checks if errors occurred when called.
+     * The behavior of this function can be modified by setting
+     * self::$widgetTestingLevel (or $testingLevelOverride):
+     * <ul>
+     *   <li>If set to <b>NO_WIDGET_TESTING</b> this function simply returns.<li>
+     *   <li>If set to <b>CHECK_WIDGET_ERRORS</b> controller actions are called &
+     *       this function will just check for errors.</li>
+     *   <li>If set to <b>COMPARE_WIDGET_OUTPUT</b> controller actions are
+     *       called & the output is checked against expected output.</li>
+     * </ul>
+     *
+     * @param string $testName             Unique name of this test group. Expected/processed
+     *                                     file names use this as a prefix.
+     * @param array  $actions              Array of controller actions to call. Each element
+     *                                     must be in the following format: 'Controller.action'
+     * @param array  $requestParameters    The request parameters to set.
+     * @param array  $userTypes            The user types to test the controller with. Can contain
+     *                                     these values: 'anonymous', 'view', 'admin', 'superuser'.
+     *                                     Defaults to all four.
+     * @param int    $testingLevelOverride Overrides self::$widgetTestingLevel.
+     */
+    public function callWidgetsCompareOutput(
+        $testName, $actions, $requestParameters, $userTypes = null, $testingLevelOverride = null)
+    {
+        // deal with the testing level
+        if (self::$widgetTestingLevel == self::NO_WIDGET_TESTING)
+        {
+            return;
+        }
+
+        if (is_null($testingLevelOverride))
+        {
+            $testingLevelOverride = self::$widgetTestingLevel;
+        }
+
+        // process $userTypes argument
+        if (!$userTypes)
+        {
+            $userTypes = array('anonymous', 'view', 'admin', 'superuser');
+        }
+        else if (!is_array($userTypes))
+        {
+            $userTypes = array($userTypes);
+        }
+
+        $oldGet = $_GET;
+
+        // get all testable controller actions if necessary
+        $actionParams = array();
+        if ($actions == 'all')
+        {
+            // Goals.addWidgets requires idSite to be set
+            $_GET['idSite'] = isset($requestParameters['idSite']) ? $requestParameters['idSite'] : '0';
+
+            list($actions, $actionParams) = $this->findAllWidgets();
+
+            $_GET = $oldGet;
+        }
+        else if (!is_array($actions))
+        {
+            $actions = array($actions);
+        }
+
+        // run the tests
+        foreach ($actions as $controllerAction)
+        {
+            $customParams = isset($actionParams[$controllerAction]) ? $actionParams[$controllerAction] : array();
+            list($controllerName, $actionName) = explode('.', $controllerAction);
+
+            foreach ($userTypes as $userType)
+            {
+                $this->setUserType($userType);
+
+                try
+                {
+                    // set request parameters
+                    $_GET = array();
+                    foreach ($customParams as $key => $value)
+                    {
+                        $_GET[$key] = $value;
+                    }
+                    foreach ($requestParameters as $key => $value)
+                    {
+                        $_GET[$key] = $value;
+                    }
+
+                    $_GET['module'] = $controllerName;
+                    $_GET['action'] = $actionName;
+
+                    if ($testingLevelOverride == self::CHECK_WIDGET_ERRORS)
+                    {
+                        $this->errorsOccurredInTest = array();
+                        set_error_handler(array($this, "customErrorHandler"));
+                    }
+
+                    // call controller action
+                    $response = Piwik_FrontController::getInstance()->fetchDispatch();
+
+                    list($processedFilePath, $expectedFilePath) = $this->getProcessedAndExpectedPaths(
+                        $testName . '_' . $userType, $controllerAction, 'html');
+
+                    if ($testingLevelOverride == self::CHECK_WIDGET_ERRORS)
+                    {
+                        restore_error_handler();
+
+                        if (!empty($this->errorsOccurredInTest))
+                        {
+                            // write processed (only if there are errors)
+                            file_put_contents($processedFilePath, $response);
+
+                            $this->fail("PHP Errors occurred in calling controller action '$controllerAction':");
+                            foreach ($this->errorsOccurredInTest as $error)
+                            {
+                                echo "&nbsp;   $error<br/>\n";
+                            }
+                        }
+                    }
+                    else // check against expected
+                    {
+                        // write raw processed response
+                        file_put_contents($processedFilePath, $response);
+
+                        // load expected
+                        $expected = $this->loadExpectedFile($expectedFilePath);
+                        if (!$expected)
+                        {
+                            continue;
+                        }
+
+                        // normalize eol delimeters
+                        $expected = str_replace("\r\n", "\n", $expected);
+                        $response = str_replace("\r\n", "\n", $response);
+
+                        // check against expected
+                        $passed = $this->assertEquals(trim($expected), trim($response),
+                            "<br/>\nDifferences with expected in: $processedFilePath %s ");
+
+                        if (!$passed)
+                        {
+                            var_dump('ERROR FOR ' . $controllerAction . ' -- FETCHED RESPONSE, then EXPECTED RESPONSE - ');
+                            echo "<br/>\n";
+                            var_dump(htmlspecialchars($response));
+                            echo "<br/>\n";
+                            var_dump(htmlspecialchars($expected));
+                            echo "<br/>\n";
+                        }
+                    }
+                }
+                catch (Exception $e)
+                {
+                    $this->fail("EXCEPTION THROWN IN $controllerAction: ".$e->getTraceAsString());
+                }
+            }
+        }
+
+        // reset $_GET to old values
+        $_GET = array();
+        foreach ($oldGet as $key => $value)
+        {
+            $_GET[$key] = $value;
+        }
+
+        // set user type
+        $this->setUserType('superuser');
+    }
+
+    /**
+     * Sets the access privilegs of the current user to the specified user type.
+     *
+     * @param $userType string Can be 'superuser', 'admin', 'view' or 'anonymous'.
+     */
+    protected function setUserType( $userType )
+    {
+        if ($userType == 'superuser')
+        {
+            $code = Piwik_Auth_Result::SUCCESS_SUPERUSER_AUTH_CODE;
+            $login = 'superUserLogin';
+        }
+        else
+        {
+            $code = 0;
+
+            $login = $userType;
+            if ($login != 'anonymous')
+            {
+                $login = 'test_' . $login;
+            }
+        }
+
+        $authResultObj = new Piwik_Auth_Result($code, $login, 'dummyTokenAuth');
+        $authObj = new MockPiwik_Auth();
+        $authObj->setReturnValue('getName', 'Login');
+        $authObj->setReturnValue('authenticate', $authResultObj);
+
+        Zend_Registry::get('access')->reloadAccess($authObj);
+    }
+
+    /**
+     * Set of messages for errors that occurred during the invocation of a
+     * controller action. If not empty, there was an error in the controller.
+     */
+    private $errorsOccurredInTest = array();
+
+    /**
+     * A custom error handler used with <code>set_error_handler</code>. If
+     * an error occurs, a message describing it is saved in an array.
+     *
+     * @param int    $errno
+     * @param string $errstr
+     * @param string $errfile
+     * @param int    $errline
+     *
+     * @return void
+     */
+    public function customErrorHandler($errno, $errstr, $errfile, $errline)
+    {
+        if (strpos(strtolower($errstr), 'cannot modify header information - headers already sent')) // HACK
+        {
+            $this->errorsOccurredInTest[] = "$errfile($errline): - $errstr";
+        }
+    }
+
+    /**
+     * Returns a list of all available widgets.
+     */
+    protected function findAllWidgets()
+    {
+        $widgetList = Piwik_GetWidgetsList();
+
+        $actions = array();
+        $customParams = array();
+
+        foreach($widgetList as $widgetCategory => $widgets)
+        {
+            foreach($widgets as $widgetInfo)
+            {
+                $module = $widgetInfo['parameters']['module'];
+                $moduleAction = $widgetInfo['parameters']['action'];
+                $wholeAction = "$module.$moduleAction";
+
+                // FIXME: can't test Referers.getKeywordsForPage since it tries to make a request to
+                // localhost w/ the wrong url. Piwik_Url::getCurrentUrlWithoutFileName
+                // returns /tests/integration/?... when used within a test.
+                if ($wholeAction == "Referers.getKeywordsForPage")
+                {
+                    continue;
+                }
+
+                // rss widgets depends on feedburner URL. don't test the widget just in case
+                // feedburner is down.
+                if ($module == "ExampleRssWidget"
+                    || $module == "ExampleFeedburner")
+                {
+                    continue;
+                }
+
+                unset($widgetInfo['parameters']['module']);
+                unset($widgetInfo['parameters']['action']);
+
+                $actions[] = $wholeAction;
+                $customParams[$wholeAction] = $widgetInfo['parameters'];
+            }
+        }
+
+        return array($actions, $customParams);
+    }
+
+    protected function removeAllLiveDatesFromXml($input)
+    {
+        $toRemove = array(
+            'serverDate',
+            'firstActionTimestamp',
+            'lastActionTimestamp',
+            'lastActionDateTime',
+            'serverTimestamp',
+            'serverTimePretty',
+            'serverDatePretty',
+            'serverDatePrettyFirstAction',
+            'serverTimePrettyFirstAction',
+            'goalTimePretty',
+            'serverTimePretty',
+            'visitorId'
+        );
+        foreach($toRemove as $xml) {
+            $input = $this->removeXmlElement($input, $xml);
+        }
+        return $input;
+    }
+
+    protected function removePrettyDateFromXml($input)
+    {
+        return $this->removeXmlElement($input, 'prettyDate');
+    }
+
+    protected function removeXmlElement($input, $xmlElement, $testNotSmallAfter = true)
+    {
+        $input = preg_replace('/(<'.$xmlElement.'>.+?<\/'.$xmlElement.'>)/', '', $input);
+        //check we didn't delete the whole string
+        if($testNotSmallAfter)
+        {
+            $this->assertTrue(strlen($input) > 100);
+        }
+        return $input;
+    }
+
+    private function getProcessedAndExpectedDirs()
+    {
+        $path = $this->getPathToTestDirectory();
+        return array($path . '/processed/', $path . '/expected/');
+    }
+
+    private function getProcessedAndExpectedPaths($testName, $testId, $format = null)
+    {
+        $filename = $testName . '__' . $testId;
+        if ($format)
+        {
+            $filename .= ".$format";
+        }
+
+        list($processedDir, $expectedDir) = $this->getProcessedAndExpectedDirs();
+
+        return array($processedDir . $filename, $expectedDir . $filename);
+    }
+
+    private function loadExpectedFile($filePath)
+    {
+        $result = @file_get_contents($filePath);
+        if(empty($result))
+        {
+            $expectedDir = dirname($filePath);
+            $this->fail(" ERROR: Could not find expected API output '$filePath'. For new tests, to pass the test, you can copy files from the processed/ directory into $expectedDir  after checking that the output is valid. %s ");
+            return null;
+        }
+        return $result;
+    }
+
+
+
+
+
+
+
+    /**
+     * Returns an array describing the API methods to call & compare with
+     * expected output.
+     *
+     * The returned array must be of the following format:
+     * <code>
+     * array(
+     *     array('SomeAPI.method', array('testOption1' => 'value1', 'testOption2' => 'value2'),
+     *     array(array('SomeAPI.method', 'SomeOtherAPI.method'), array(...)),
+     *     .
+     *     .
+     *     .
+     * )
+     * </code>
+     *
+     * Valid test options:
+     * <ul>
+     *   <li><b>testSuffix</b> The suffix added to the test name. Helps determine
+     *   the filename of the expected output.</li>
+     *   <li><b>format</b> The desired format of the output. Defaults to 'xml'.</li>
+     *   <li><b>idSite</b> The id of the website to get data for.</li>
+     *   <li><b>date</b> The date to get data for.</li>
+     *   <li><b>periods</b> The period or periods to get data for. Can be an array.</li>
+     *   <li><b>setDateLastN</b> Flag describing whether to query for a set of
+     *   dates or not.</li>
+     *   <li><b>language</b> The language to use.</li>
+     *   <li><b>segment</b> The segment to use.</li>
+     *   <li><b>visitorId</b> The visitor ID to use.</li>
+     *   <li><b>abandonedCarts</b> Whether to look for abandoned carts or not.</li>
+     *   <li><b>idGoal</b> The goal ID to use.</li>
+     *   <li><b>apiModule</b> The value to use in the apiModule request parameter.</li>
+     *   <li><b>apiAction</b> The value to use in the apiAction request parameter.</li>
+     *   <li><b>otherRequestParameters</b> An array of extra request parameters to use.</li>
+     *   <li><b>disableArchiving</b> Disable archiving before running tests.</li>
+     * </ul>
+     *
+     * All test options are optional, except 'idSite' & 'date'.
+     */
+    public function getApiForTesting() {
+        return array();
+    }
+
+    /**
+     * It is possible to run another set of API tests after the first one.
+     * For example, getApiForTesting() will test and record with a specific suffix
+     * Then we will call some API that invalidates a piece of the reports
+     * Then call all reports again and check that what is expected to have changed indeed has changed!
+     *
+     * @see getApiForTesting() for returned values, same signature
+     */
+    public function getAnotherApiForTesting()
+    {
+        return array();
+    }
+
+    /**
+     * Returns an array describing the Controller actions to call & compare
+     * with expected output.
+     *
+     * The returned array must be of the following format:
+     * <code>
+     * array(
+     *     array('Controller.action', array('testOption1' => 'value1', 'testOption2' => 'value2'),
+     *     array(array('Controller.action', 'OtherController.action'), array(...)),
+     *     .
+     *     .
+     *     .
+     * )
+     * </code>
+     *
+     * Valid test options:
+     * <ul>
+     *   <li><b>UNIMPLEMENTED</b></li>
+     * </ul>
+     */
+    public function getControllerActionsForTesting() {
+        return array();
+    }
+
+    /**
+     * Gets the string prefix used in the name of the expected/processed output files.
+     */
+    public function getOutputPrefix()
+    {
+        return str_replace('Test_Piwik_Integration_', '', get_class($this));
+    }
+
+    /**
+     * Runs API tests.
+     */
+    protected function runApiTests($api, $params)
+    {
+        $testName = 'test_' . $this->getOutputPrefix();
+
+        if ($api == 'all')
+        {
+            $this->setApiToCall(array());
+            $this->setApiNotToCall(self::$defaultApiNotToCall);
+        }
+        else
+        {
+            if (!is_array($api))
+            {
+                $api = array($api);
+            }
+
+            $this->setApiToCall($api);
+            $this->setApiNotToCall(array('API.getPiwikVersion'));
+        }
+
+        if (isset($params['disableArchiving']) && $params['disableArchiving'] === true)
+        {
+            Piwik_ArchiveProcessing::$forceDisableArchiving = true;
+        }
+        else
+        {
+            Piwik_ArchiveProcessing::$forceDisableArchiving = false;
+        }
+
+        if (isset($params['language']))
+        {
+            $this->changeLanguage($params['language']);
+        }
+
+        $testSuffix = isset($params['testSuffix']) ? $params['testSuffix'] : '';
+
+        $this->_callGetApiCompareOutput(
+            $testName . $testSuffix,
+            isset($params['format']) ? $params['format'] : 'xml',
+            isset($params['idSite']) ? $params['idSite'] : false,
+            isset($params['date']) ? $params['date'] : false,
+            isset($params['periods']) ? $params['periods'] : false,
+            isset($params['setDateLastN']) ? $params['setDateLastN'] : false,
+            isset($params['language']) ? $params['language'] : false,
+            isset($params['segment']) ? $params['segment'] : false,
+            isset($params['visitorId']) ? $params['visitorId'] : false,
+            isset($params['abandonedCarts']) ? $params['abandonedCarts'] : false,
+            isset($params['idGoal']) ? $params['idGoal'] : false,
+            isset($params['apiModule']) ? $params['apiModule'] : false,
+            isset($params['apiAction']) ? $params['apiAction'] : false,
+            isset($params['otherRequestParameters']) ? $params['otherRequestParameters'] : array());
+
+        // change the language back to en
+        if ($this->lastLanguage != 'en')
+        {
+            $this->changeLanguage('en');
+        }
+    }
+
+    /**
+     * Runs controller tests.
+     */
+    protected function runControllerTests()
+    {
+        static $nonRequestParameters = array('testingLevelOverride' => null, 'userTypes' => null);
+
+        $testGroups = $this->getControllerActionsForTesting();
+        $testName = 'test_' . $this->getOutputPrefix();
+
+        foreach ($testGroups as $test)
+        {
+            list($actions, $params) = $test;
+
+            // deal w/ any language changing hacks
+            if (isset($params['language']))
+            {
+                $this->changeLanguage($params['language']);
+            }
+
+            // separate request parameters from function parameters
+            $requestParams = array();
+            foreach ($params as $key => $value)
+            {
+                if (!isset($nonRequestParameters[$key]))
+                {
+                    $requestParams[$key] = $value;
+                }
+            }
+
+            $testSuffix = isset($params['testSuffix']) ? $params['testSuffix'] : '';
+
+            $this->callWidgetsCompareOutput(
+                $testName . $testSuffix,
+                $actions,
+                $requestParams,
+                isset($params['userTypes']) ? $params['userTypes'] : false,
+                isset($params['testingLevelOverride']) ? $params['testingLevelOverride'] : false);
+
+            // change the language back to en
+            if ($this->lastLanguage != 'en')
+            {
+                $this->changeLanguage('en');
+            }
+        }
+    }
+
+    /**
+     * changing the language within one request is a bit fancy
+     * in order to keep the core clean, we need a little hack here
+     *
+     * @param string $langId
+     */
+    protected function changeLanguage( $langId )
+    {
+        if (isset($this->lastLanguage) && $this->lastLanguage != $langId)
+        {
+            $_GET['language'] = $langId;
+            Piwik_Translate::reset();
+            Piwik_Translate::getInstance()->reloadLanguage($langId);
+        }
+
+        $this->lastLanguage = $langId;
+    }
+
+    /**
+     * Path where expected/processed output files are stored. Can be overridden.
+     */
+    public function getPathToTestDirectory()
+    {
+        /**
+         * Use old path as long as files were not moved
+         * @todo move files
+         */
+        //return dirname(__FILE__).DIRECTORY_SEPARATOR.'Integration';
+        return dirname(dirname(__FILE__)).DIRECTORY_SEPARATOR.'integration';
+    }
+
+}
diff --git a/tests/PHPUnit/bootstrap.php b/tests/PHPUnit/bootstrap.php
index 82d1c7d2b38057a69d59602e8288993090d46e83..c3fbccb458c022a2e4f8bda7c02b22dfe3fa74f5 100644
--- a/tests/PHPUnit/bootstrap.php
+++ b/tests/PHPUnit/bootstrap.php
@@ -29,7 +29,7 @@ require_once PIWIK_INCLUDE_PATH .'/core/testMinimumPhpVersion.php';
 require_once PIWIK_INCLUDE_PATH .'/core/Loader.php';
 require_once PIWIK_INCLUDE_PATH .'/core/FrontController.php';
 require_once PIWIK_INCLUDE_PATH .'/tests/PHPUnit/DatabaseTestCase.php';
-#require_once PIWIK_INCLUDE_PATH .'/tests/PHPUnit/IntegrationTestCase.php';
+require_once PIWIK_INCLUDE_PATH .'/tests/PHPUnit/IntegrationTestCase.php';
 require_once PIWIK_INCLUDE_PATH .'/tests/PHPUnit/FakeAccess.php';
 
 // required to build code coverage for uncovered files
diff --git a/tests/PHPUnit/phpunit.xml b/tests/PHPUnit/phpunit.xml
index 70cc777b20dc11701a6a02b379b9811a556c248d..1c96b0551bc7cdece6eaf16e3987769a7d5b2758 100644
--- a/tests/PHPUnit/phpunit.xml
+++ b/tests/PHPUnit/phpunit.xml
@@ -45,7 +45,6 @@
 
 <php>
     <server name="HTTP_HOST" value="local"/>
-    <server name="PATH_INFO" value=""/>
     <server name="REQUEST_URI" value="/piwik/tests/all_tests.php"/>
     <server name="REMOTE_ADDR" value="127.0.0.1"/>
 </php>
diff --git a/tests/PHPUnit/proxy-piwik.php b/tests/PHPUnit/proxy-piwik.php
new file mode 100755
index 0000000000000000000000000000000000000000..eac5a26dd952c4a14cdc8fb381a3c556852218a2
--- /dev/null
+++ b/tests/PHPUnit/proxy-piwik.php
@@ -0,0 +1,32 @@
+<?php
+/**
+ *  Proxy to normal piwik.php, but in testing mode
+ *  
+ *  - Use the tests database to record Tracking data
+ *  - Allows to overwrite the Visitor IP, and Server datetime 
+ *  
+ * @see Main.test.php
+ * 
+ */
+// Wrapping the request inside ob_start() calls to ensure that the Test
+// calling us waits for the full request to process before unblocking
+ob_start();
+
+define('PIWIK_INCLUDE_PATH', '../..');
+define('PIWIK_USER_PATH', PIWIK_INCLUDE_PATH);
+
+require_once PIWIK_INCLUDE_PATH .'/libs/upgradephp/upgrade.php';
+require_once PIWIK_INCLUDE_PATH .'/core/Loader.php';
+
+// Config files forced to use the test database
+// Note that this also provides security for Piwik installs containing tests files: 
+// this proxy will not record any data in the production database.
+Piwik::createConfigObject();
+Piwik_Config::getInstance()->setTestEnvironment();
+Piwik_Config::getInstance()->PluginsInstalled['PluginsInstalled'] = array();
+
+Piwik_Tracker::setTestEnvironment();
+Piwik_Common::deleteTrackerCache();
+
+include '../../piwik.php';
+ob_flush();
diff --git a/tests/config_test.php b/tests/config_test.php
index b107a43b2a559ad6fa3592c330ce7787934a3f15..c85175017990f7adeec0b22639feaae5a865281e 100644
--- a/tests/config_test.php
+++ b/tests/config_test.php
@@ -49,9 +49,11 @@ function dump($var)
 	print("</pre>");
 }
 
-function printDebug($text)
-{
-	return;
+if(!function_exists('printDebug')) {
+    function printDebug($text)
+    {
+        return;
+    }
 }
 
 require_once PIWIK_INCLUDE_PATH .'/core/Loader.php';