Skip to content
Extraits de code Groupes Projets
UITest.php 12,84 Kio
<?php
/**
 * Piwik - Open source web analytics
 *
 * @link http://piwik.org
 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
 */
use Piwik\Access;
use Piwik\AssetManager;
use Piwik\Date;
use Piwik\Db;
use Piwik\DbHelper;
use Piwik\Plugins\VisitsSummary\API;
use Piwik\ArchiveProcessor\Rules;

abstract class UITest extends IntegrationTestCase
{
    const IMAGE_TYPE = 'png';
    const CAPTURE_PROGRAM = 'phantomjs';
    const SCREENSHOT_GROUP_SIZE = 12;
    const DEBUG_IMAGE_MAGICK_COMPARE = true;
    
    private static $recursiveProxyLinkNames = array('libs', 'plugins', 'tests');
    private static $imageMagickAvailable = false;

    public static $failureScreenshotNames = array();

    public static function createAccessInstance()
    {
        Access::setSingletonInstance($access = new Test_Access_OverrideLogin());
        \Piwik\Piwik::postEvent('Request.initAuthenticationObject');
    }
    
    public static function setUpBeforeClass()
    {
        if (self::CAPTURE_PROGRAM == 'slimerjs'
            && !self::isSlimerJsAvailable()
        ) {
            self::markTestSkipped("slimerjs is not available, skipping UI integration tests. "
                                . "(install by downloading http://slimerjs.org/download.html)");
        } else if (self::CAPTURE_PROGRAM == 'phantomjs'
                   && !self::isPhantomJsAvailable()
        ) {
            self::markTestSkipped("phantomjs is not available, skipping UI integration tests. "
                                . "(install by downloading http://phantomjs.org/download.html)");
        }
        
        parent::setUpBeforeClass();

        DbHelper::createAnonymousUser();
        
        AssetManager::getInstance()->removeMergedAssets();
        
        // launch archiving so tests don't run out of time
        Rules::$purgeDisabledByTests = true;
        $date = Date::factory(static::$fixture->dateTime)->toString();
        API::getInstance()->get(static::$fixture->idSite, 'year', $date);

        // make sure processed & expected dirs exist
        self::makeDirsAndLinks();

        // run slimerjs/phantomjs w/ all urls so we only invoke it once per 25 entries (travis needs
        // there to be output)
        $urlsToTest = static::getUrlsForTesting();

        reset($urlsToTest);
        for ($i = 0; $i < count($urlsToTest); $i += self::SCREENSHOT_GROUP_SIZE) {
            $urls = array();
            echo "Generating screenshots...";
            for ($j = $i; $j != $i + self::SCREENSHOT_GROUP_SIZE && $j < count($urlsToTest); ++$j) {
                $currentTest = current($urlsToTest);

                if (count($currentTest) == 2) {
                    list($name, $urlQuery) = $currentTest;
                    $jsToTest = false;
                } else {
                    list($name, $urlQuery, $jsToTest) = $currentTest;
                }

                $testUrl = self::getProxyUrl() . $urlQuery;

                // Screenshot Zeitgeist
                list($processedScreenshotPath, $expectedScreenshotPath) = self::getProcessedAndExpectedScreenshotPaths($name);
                $urls[] = array($processedScreenshotPath, $testUrl, $jsToTest);

                // Screenshot Morpheus
                list($processedScreenshotPath, $expectedScreenshotPath) = self::getProcessedAndExpectedScreenshotPaths($name, "Morpheus/");
                $enableMorpheus = "&morpheus=1";
                // Add the parameter to the query string, not the hash
                if(($hash = strpos($testUrl, '#')) !== false) {
                    $testUrl = substr($testUrl, 0, $hash) . $enableMorpheus . substr($testUrl, $hash);
                } else {
                    $testUrl .= $enableMorpheus;
                }

                $urls[] = array($processedScreenshotPath, $testUrl, $jsToTest);

                next($urlsToTest);
                echo ".";
            }
            echo "\n";

            self::runCaptureProgram($urls);
        }

        // check if image magick available
        self::$imageMagickAvailable = self::checkImageMagickAvailable();

        // remove existing diffs
        self::removeExistingDiffs();
    }

    public static function removeExistingDiffs()
    {
        $files = glob(dirname(__FILE__) . '/UI/screenshot-diffs/*.png');
        foreach ($files as $file) {
            unlink($file);
        }
    }
    
    public static function tearDownAfterClass()
    {
        if (file_exists("C:\\nppdf32Log\\debuglog.txt")) { // remove slimerjs oddity
            unlink("C:\\nppdf32Log\\debuglog.txt");
        }

        self::removeRecursiveLinks();

        Db::createDatabaseObject();

        self::outputDiffViewerHtmlFile();
        
        parent::tearDownAfterClass();
    }

    public function setUp()
    {
        parent::setUp();
        
        if (!Db::get()) {
            DbHelper::createDatabaseObject();
        }
    }
    
    public function tearDown()
    {
        parent::tearDown();
        
        Db::get()->closeConnection();
    }
    
    private static function runCaptureProgram($urlInfo)
    {
        file_put_contents(PIWIK_INCLUDE_PATH . '/tmp/urls.txt', json_encode($urlInfo));
        $cmd = self::CAPTURE_PROGRAM . " \"" . PIWIK_INCLUDE_PATH . "/tests/resources/screenshot-capture/capture.js\" 2>&1";
        
        exec($cmd, $output, $result);
        $output = implode("\n", $output);
        if ($result !== 0
            || strpos($output, "ERROR") !== false
        ) {
            echo self::CAPTURE_PROGRAM . " failed: " . $output . "\n\ncommand used: $cmd\n";
        }
        return $output;
    }

    protected function compareScreenshots($testsInfo)
    {
        $failures = array();
        foreach ($testsInfo as $info) {
            list($name, $urlQuery) = $info;

            // compare processed w/ expected
            try {
                $this->compareScreenshot($name, $urlQuery);
            } catch (Exception $ex) {
                $failures[] = $ex;
            }
        }
    }
    
    protected function compareScreenshot($name, $urlQuery)
    {
        list($processedPath, $expectedPath) = self::getProcessedAndExpectedScreenshotPaths($name);

        $this->compareScreenshotAgainstExpected($name, $urlQuery, $processedPath, $expectedPath);


    }

    private function saveImageDiff($expectedPath, $processedPath, $diffPath)
    {
        $cmd = "compare \"$expectedPath\" \"$processedPath\" \"$diffPath\" 2>&1";
        exec($cmd, $output, $result);

        if (self::DEBUG_IMAGE_MAGICK_COMPARE
            && $result !== 0
        ) {
            echo "Could not save image diff: " . implode("\n", $output) . "\n";
        }
    }

    private static function checkImageMagickAvailable()
    {
        return self::isProgramAvailable('compare');
    }
    
    private static function isSlimerJsAvailable()
    {
        return self::isProgramAvailable('slimerjs');
    }

    public static function isPhantomJsAvailable()
    {
        return self::isProgramAvailable('phantomjs');
    }
    
    private static function isProgramAvailable($name)
    {
        exec($name . ' --help 2>&1', $output, $result);
        return $result === 0 || $result === 1;
    }

    private static function getProcessedAndExpectedScreenshotPaths($name, $pathSuffix = '')
    {
        list($processedDir, $expectedDir) = self::getProcessedAndExpectedDirs();

        $outputPrefix = static::getOutputPrefix();

        $processedScreenshotPath = $processedDir . $pathSuffix . $outputPrefix . '_' . "$name." . self::IMAGE_TYPE;
        $expectedScreenshotPath = $expectedDir . $pathSuffix . $outputPrefix . '_' . "$name." . self::IMAGE_TYPE;

        return array($processedScreenshotPath, $expectedScreenshotPath);
    }
    
    protected static function getProcessedAndExpectedDirs()
    {
        $path = self::getPathToTestDirectory() . '/../UI';
        return array($path . '/processed-ui-screenshots/', $path . '/expected-ui-screenshots/');
    }
    
    public static function getProxyUrl()
    {
        return Test_Piwik_BaseFixture::getRootUrl() . 'tests/PHPUnit/proxy/index.php';
    }

    private static function makeDirsAndLinks()
    {
        $dirs = array_merge(self::getProcessedAndExpectedDirs(), array(
            PIWIK_INCLUDE_PATH . '/tmp/sessions', self::getScreenshotDiffDir()
        ));
        foreach ($dirs as $dir) {
            if (!is_dir($dir)) {
                mkdir($dir);
            }
        }

        self::createProxySymlinks();
    }

    private static function createProxySymlinks()
    {
        foreach (self::$recursiveProxyLinkNames as $linkName) {
            $linkPath = PIWIK_INCLUDE_PATH . '/tests/PHPUnit/proxy/' . $linkName;
            if (!file_exists($linkPath)) {
                symlink(PIWIK_INCLUDE_PATH . '/' . $linkName, $linkPath);
            }
        }
    }

    private static function removeRecursiveLinks()
    {
        foreach (self::$recursiveProxyLinkNames as $linkName) {
            $wholePath = PIWIK_INCLUDE_PATH . '/tests/PHPUnit/proxy/' . $linkName;
            if (file_exists($wholePath)) {
                unlink($wholePath);
            }
        }
    }
    private static function getScreenshotDiffPath($name)
    {
        $outputPrefix = static::getOutputPrefix();
        $diffDir = self::getScreenshotDiffDir();
        return $diffDir . "/" . $outputPrefix . '_' . $name . '.' . self::IMAGE_TYPE;
    }

    private static function getScreenshotDiffDir()
    {
        return dirname(__FILE__) . "/UI/screenshot-diffs";
    }

    private static function outputDiffViewerHtmlFile()
    {
        if (!empty(self::$failureScreenshotNames)) {
            $diffViewerPath = self::getScreenshotDiffDir() . '/diffviewer.html';
            echo "\nFailures encountered. View all diffs at:
$diffViewerPath

If processed screenshots are correct, you can copy the generated screenshots to the expected screenshot folder.

*** IMPORTANT *** In your commit message, explain the cause of the difference in rendering so other Piwik developers will be aware of it.";
        }

        list($processedDir, $expectedDir) = self::getProcessedAndExpectedDirs();
        $diffDir = self::getScreenshotDiffDir();

        $diffViewerEntries = array();
        foreach (self::$failureScreenshotNames as $name) {
            $name = static::getOutputPrefix() . '_' . $name;
            $file = $name . '.png';

            $diffFileOrError = $file;
            if (!is_file($diffDir . '/' . $file)) {
                $diffFileOrError = false;
            }

            $diffViewerEntries[] = array(
                'name' => $name,
                'expectedUrl' => 'https://raw.github.com/piwik/piwik-ui-tests/master/expected-ui-screenshots/' . $file,
                'processedUrl' => '../processed-ui-screenshots/' . $file,
                'diffUrl' => $diffFileOrError
            );
        }

        $diffViewerHtml = '<html>
<head></head>
<body>
<h1>Screenshot Test Failures</h1>
<table>
    <tr>
        <th>Name</th>
        <th>Expected</th>
        <th>Processed</th>
        <th>Difference</th>
    </tr>';
        foreach ($diffViewerEntries as $fileInfo) {
            $diffViewerHtml .= '
    <tr>
        <td>' . $fileInfo['name'] . '</td>
        <td><a href="' . $fileInfo['expectedUrl'] . '">Expected</a></td>
        <td><a href="' . $fileInfo['processedUrl'] . '">Processed</a></td>
        <td>';
            if (!empty($fileInfo['diffUrl'])) {
                $diffViewerHtml .= '<a href="' . $fileInfo['diffUrl'] . '">Difference</a>';
            } else {
                $diffViewerHtml .= "<em>Could not create diff.</em>";
            }
            $diffViewerHtml .= '
        </td>
    </tr>';
        }
        $diffViewerHtml .= '
</table>
</body>
</html>';
        
        file_put_contents($diffDir . '/diffviewer.html', $diffViewerHtml);
    }

    /**
     * @param $name
     * @param $urlQuery
     * @param $processedPath
     * @param $expectedPath
     */
    protected function compareScreenshotAgainstExpected($name, $urlQuery, $processedPath, $expectedPath)
    {
        if (!file_exists($processedPath)) {
            $this->fail("failed to generate screenshot for '$name'.");
            return;
        }

        $processed = file_get_contents($processedPath);

        if (!file_exists($expectedPath)) {
            $this->fail("expected screenshot for '$name' test is missing.
Generated screenshot: $processedPath");
            return;
        }

        $expected = file_get_contents($expectedPath);
        if ($expected != $processed) {
            self::$failureScreenshotNames[] = $name;

            $diffPath = self::getScreenshotDiffPath($name);

            echo "\nFail: generated screenshot does not match expected for '$name'.
Url to reproduce: $urlQuery
Generated screenshot: $processedPath
Expected screenshot: $expectedPath
Screenshot diff: $diffPath\n";

            $this->saveImageDiff($expectedPath, $processedPath, $diffPath);
        }

        $this->assertTrue($expected == $processed, "screenshot compare failed for '$processedPath'");
    }
}