diff --git a/comparison b/comparison index ee16f65..ae55bbb 100755 --- a/comparison +++ b/comparison @@ -5,9 +5,31 @@ require __DIR__ . '/vendor/autoload.php'; use Codappix\WebsiteComparison\Command\CompareCommand; use Codappix\WebsiteComparison\Command\CreateBaseCommand; +use Facebook\WebDriver\Chrome\ChromeDriver; +use Facebook\WebDriver\Chrome\ChromeDriverService; use Symfony\Component\Console\Application; +use Symfony\Component\EventDispatcher\EventDispatcher; + +$eventDispatcher = new EventDispatcher(); + +$chromeDriver = (function () { + $chromeDriverService = new ChromeDriverService( + '/usr/lib/chromium-browser/chromedriver', + 9515, + [ + '--port=9515', + '--headless', + ] + ); + + return ChromeDriver::start(null, $chromeDriverService); +})(); $application = new Application(); -$application->add(new CreateBaseCommand()); -$application->add(new CompareCommand()); +$application->setDispatcher($eventDispatcher); + +// TODO: Use factory for commands, which injects event dispatcher and chrome driver? +$application->add(new CreateBaseCommand($eventDispatcher, $chromeDriver)); +$application->add(new CompareCommand($eventDispatcher, $chromeDriver)); + $application->run(); diff --git a/composer.json b/composer.json index 950de22..69810b8 100644 --- a/composer.json +++ b/composer.json @@ -16,10 +16,11 @@ }, "require": { "php": "^7.2", - "php-imagick": "*", + "ext-imagick": "*", "facebook/webdriver": "^1.6", "symfony/console": "^4.1", "symfony/process": "^4.1", - "guzzlehttp/psr7": "^1.4" + "guzzlehttp/psr7": "^1.4", + "symfony/event-dispatcher": "^4.1" } } diff --git a/src/Command/CompareCommand.php b/src/Command/CompareCommand.php index a5a7699..69638a5 100644 --- a/src/Command/CompareCommand.php +++ b/src/Command/CompareCommand.php @@ -21,23 +21,39 @@ namespace Codappix\WebsiteComparison\Command; * 02110-1301, USA. */ -use Codappix\WebsiteComparison\Service\ScreenshotCrawlerService; -use Facebook\WebDriver\Chrome\ChromeDriver; -use Facebook\WebDriver\Chrome\ChromeDriverService; +use Codappix\WebsiteComparison\Service\Screenshot\CompareService; +use Codappix\WebsiteComparison\Service\Screenshot\CrawlerService; +use Codappix\WebsiteComparison\Service\Screenshot\Service; +use Facebook\WebDriver\Remote\RemoteWebDriver; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; -use Symfony\Component\Process\Exception\ProcessFailedException; -use Symfony\Component\Process\Process; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\EventDispatcher\GenericEvent; class CompareCommand extends Command { /** - * @var Process + * @var EventDispatcherInterface */ - protected $chromeProcess; + protected $eventDispatcher; + + /** + * @var RemoteWebDriver + */ + protected $webDriver; + + public function __construct( + EventDispatcherInterface $eventDispatcher, + RemoteWebDriver $webDriver + ) { + parent::__construct(null); + + $this->eventDispatcher = $eventDispatcher; + $this->webDriver = $webDriver; + } protected function configure() { @@ -85,35 +101,80 @@ class CompareCommand extends Command protected function execute(InputInterface $input, OutputInterface $output) { - $screenshotCrawler = new ScreenshotCrawlerService( - $output, - $this->getDriver(), - $input->getArgument('baseUrl'), + + $screenshotService = new Service( + $this->eventDispatcher, $input->getOption('compareDir'), $input->getOption('screenshotWidth') ); - $hasDifferences = $screenshotCrawler->compare( + + $screenshotCrawler = new CrawlerService( + $this->webDriver, + $screenshotService, + $input->getArgument('baseUrl') + ); + + $compareService = new CompareService( + $this->eventDispatcher, + $screenshotService, $input->getOption('screenshotDir'), $input->getOption('diffResultDir') ); + $this->registerEvents($output, $compareService); - if ($hasDifferences) { + $screenshotCrawler->crawl(); + + if ($compareService->hasDifferences()) { return 255; } } - protected function getDriver(): ChromeDriver + protected function registerEvents(OutputInterface $output, CompareService $compareService) { - $chromeDriverService = new ChromeDriverService( - '/usr/lib/chromium-browser/chromedriver', - 9515, - [ - '--port=9515', - '--headless', - ] + $this->eventDispatcher->addListener( + 'service.screenshot.created', + function (GenericEvent $event) use ($output, $compareService) { + $output->writeln(sprintf( + 'Comparing Screenshot for url "%s".', + $event->getArgument('url') + )); + $compareService->compareScreenshot( + $event->getArgument('screenshot') + ); + } ); - $driver = ChromeDriver::start(null, $chromeDriverService); - return $driver; + if ($output->isVerbose()) { + $this->eventDispatcher->addListener( + 'service.screenshot.isSame', + function (GenericEvent $event) use ($output) { + $output->writeln(sprintf( + 'Screenshot "%s" is as expected.', + $event->getArgument('screenshot') + )); + } + ); + } + + $this->eventDispatcher->addListener( + 'service.screenshot.isDifferent', + function (GenericEvent $event) use ($output) { + $output->writeln(sprintf( + 'Screenshot "%s" is different, created diff at "%s".', + $event->getArgument('screenshot'), + $event->getArgument('diff') + )); + } + ); + + $this->eventDispatcher->addListener( + 'service.screenshot.error', + function (GenericEvent $event) use ($output) { + $output->writeln(sprintf( + '"%s"', + $event->getArgument('e')->getMessage() + )); + } + ); } } diff --git a/src/Command/CreateBaseCommand.php b/src/Command/CreateBaseCommand.php index 9a054de..4bf4d57 100644 --- a/src/Command/CreateBaseCommand.php +++ b/src/Command/CreateBaseCommand.php @@ -21,23 +21,38 @@ namespace Codappix\WebsiteComparison\Command; * 02110-1301, USA. */ -use Codappix\WebsiteComparison\Service\ScreenshotCrawlerService; -use Facebook\WebDriver\Chrome\ChromeDriver; -use Facebook\WebDriver\Chrome\ChromeDriverService; +use Codappix\WebsiteComparison\Service\Screenshot\CrawlerService; +use Codappix\WebsiteComparison\Service\Screenshot\Service; +use Facebook\WebDriver\Remote\RemoteWebDriver; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; -use Symfony\Component\Process\Exception\ProcessFailedException; -use Symfony\Component\Process\Process; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\EventDispatcher\GenericEvent; class CreateBaseCommand extends Command { /** - * @var Process + * @var EventDispatcherInterface */ - protected $chromeProcess; + protected $eventDispatcher; + + /** + * @var RemoteWebDriver + */ + protected $webDriver; + + public function __construct( + EventDispatcherInterface $eventDispatcher, + RemoteWebDriver $webDriver + ) { + parent::__construct(null); + + $this->eventDispatcher = $eventDispatcher; + $this->webDriver = $webDriver; + } protected function configure() { @@ -71,28 +86,45 @@ class CreateBaseCommand extends Command protected function execute(InputInterface $input, OutputInterface $output) { - $screenshotCrawler = new ScreenshotCrawlerService( - $output, - $this->getDriver(), - $input->getArgument('baseUrl'), + $this->registerEvents($output); + + $screenshotService = new Service( + $this->eventDispatcher, $input->getOption('screenshotDir'), $input->getOption('screenshotWidth') ); + + $screenshotCrawler = new CrawlerService( + $this->webDriver, + $screenshotService, + $input->getArgument('baseUrl') + ); $screenshotCrawler->crawl(); } - protected function getDriver(): ChromeDriver + protected function registerEvents(OutputInterface $output) { - $chromeDriverService = new ChromeDriverService( - '/usr/lib/chromium-browser/chromedriver', - 9515, - [ - '--port=9515', - '--headless', - ] - ); - $driver = ChromeDriver::start(null, $chromeDriverService); + if ($output->isVerbose()) { + $this->eventDispatcher->addListener( + 'service.screenshot.created', + function (GenericEvent $event) use ($output) { + $output->writeln(sprintf( + 'Created screenshot "%s" for url "%s".', + $event->getArgument('screenshot'), + $event->getArgument('url') + )); + } + ); + } - return $driver; + $this->eventDispatcher->addListener( + 'screenshot.service.error', + function (GenericEvent $event) use ($output) { + $output->writeln(sprintf( + '"%s"', + $event->getArgument('e')->getMessage() + )); + } + ); } } diff --git a/src/Service/Screenshot/CompareService.php b/src/Service/Screenshot/CompareService.php new file mode 100644 index 0000000..77af4e8 --- /dev/null +++ b/src/Service/Screenshot/CompareService.php @@ -0,0 +1,152 @@ + + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\EventDispatcher\GenericEvent; + +class CompareService +{ + /** + * @var EventDispatcherInterface + */ + protected $eventDispatcher; + + /** + * @var Service + */ + protected $screenshotService; + + /** + * @var string + */ + protected $compareDirectory = ''; + + /** + * @var string + */ + protected $diffResultDir = ''; + + /** + * @var bool + */ + protected $hasDifferences = false; + + public function __construct( + EventDispatcherInterface $eventDispatcher, + Service $screenshotService, + string $compareDirectory, + string $diffResultDir + ) { + $this->eventDispatcher = $eventDispatcher; + $this->screenshotService = $screenshotService; + $this->compareDirectory = $this->screenshotService->convertReltiveFolder($compareDirectory); + $this->diffResultDir = $this->screenshotService->convertReltiveFolder($diffResultDir); + } + + public function hasDifferences(): bool + { + return $this->hasDifferences; + } + + public function compareScreenshot(string $screenshot) + { + try { + if ($this->doScreenshotsDiffer($screenshot)) { + $this->eventDispatcher->dispatch( + 'service.screenshot.isDifferent', + new GenericEvent('New Screenshot is different then base version.', [ + 'screenshot' => $screenshot, + 'diff' => $this->getDiffFileName($screenshot) + ]) + ); + return; + } + } catch (\ImagickException $e) { + $this->eventDispatcher->dispatch( + 'service.screenshot.error', + new GenericEvent($e->getMessage(), [$e]) + ); + return; + } + + $this->eventDispatcher->dispatch( + 'service.screenshot.isSame', + new GenericEvent('New Screenshot is the same as base version.', [ + 'screenshot' => $screenshot, + ]) + ); + } + + protected function doScreenshotsDiffer(string $screenshot): bool + { + $actualScreenshot = new \Imagick($screenshot); + $actualGeometry = $actualScreenshot->getImageGeometry(); + $compareScreenshot = new \Imagick($this->getBaseScreenshot($screenshot)); + $compareGeometry = $compareScreenshot->getImageGeometry(); + + if ($actualGeometry !== $compareGeometry) { + throw new \ImagickException(sprintf( + "Screenshots don't have an equal geometry. Should be %sx%s but is %sx%s", + $compareGeometry['width'], + $compareGeometry['height'], + $actualGeometry['width'], + $actualGeometry['height'] + )); + } + + $result = $actualScreenshot->compareImages( + $compareScreenshot, + \Imagick::METRIC_ROOTMEANSQUAREDERROR + ); + if ($result[1] > 0) { + /** @var \Imagick $diffScreenshot */ + $diffScreenshot = $result[0]; + $diffScreenshot->setImageFormat('png'); + $fileName = $this->getDiffFileName($screenshot); + $this->screenshotService->createDir(dirname($fileName)); + file_put_contents($fileName, $diffScreenshot); + + return true; + } + + return false; + } + + protected function getBaseScreenshot(string $compareScreenshot): string + { + return str_replace( + $this->screenshotService->getScreenshotDir(), + $this->compareDirectory, + $compareScreenshot + ); + } + + protected function getDiffFileName(string $screenshot): string + { + return str_replace( + $this->screenshotService->getScreenshotDir(), + $this->diffResultDir, + $screenshot + ); + } +} diff --git a/src/Service/Screenshot/CrawlerService.php b/src/Service/Screenshot/CrawlerService.php new file mode 100644 index 0000000..629ae08 --- /dev/null +++ b/src/Service/Screenshot/CrawlerService.php @@ -0,0 +1,123 @@ + + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use Codappix\WebsiteComparison\Model\UrlListDto; +use Facebook\WebDriver\Remote\RemoteWebDriver; +use Facebook\WebDriver\Remote\RemoteWebElement; +use Facebook\WebDriver\WebDriverBy; +use GuzzleHttp\Psr7\Uri; + +class CrawlerService +{ + /** + * @var RemoteWebDriver + */ + protected $driver; + + /** + * @var Service + */ + protected $screenshotService; + + /** + * @var string + */ + protected $baseUrl = ''; + + public function __construct( + RemoteWebDriver $driver, + Service $screenshotService, + string $baseUrl + ) { + $this->driver = $driver; + $this->screenshotService = $screenshotService; + $this->baseUrl = rtrim($baseUrl, '/') . '/'; + } + + public function crawl() + { + $linkList = new UrlListDto(); + $linkList->addUrl($this->baseUrl); + + while ($url = $linkList->getNextUrl()) { + $this->driver->get($url); + $screenshotHeight = $this->driver->findElement(WebDriverBy::cssSelector('body')) + ->getSize() + ->getHeight(); + $this->screenshotService->createScreenshot( + $this->driver->getCurrentURL(), + $screenshotHeight + ); + + $linkList->markUrlAsFinished($url); + array_map([$linkList, 'addUrl'], $this->fetchFurtherLinks( + $this->driver->findElements(WebDriverBy::cssSelector('a')) + )); + } + } + + protected function fetchFurtherLinks(array $webElements): array + { + $links = []; + foreach ($webElements as $webElement) { + try { + $link = $this->fetchLinkFromElement($webElement); + } catch (\Exception $e) { + continue; + } + + $links[] = $link; + } + + return $links; + } + + protected function fetchLinkFromElement(RemoteWebElement $element): string + { + $uri = null; + $href = $element->getAttribute('href'); + if (is_string($href)) { + $uri = new Uri($href); + } + + if ($uri === null) { + throw new \Exception('Did not get a Uri for element.', 1535530859); + } + + if ($this->isInternalLink($uri)) { + return (string) $uri; + } + + throw new \Exception('Was external link.', 1535639056); + } + + protected function isInternalLink(Uri $uri): bool + { + $validHosts = [ + '', + (new Uri($this->baseUrl))->getHost(), + ]; + + return in_array($uri->getHost(), $validHosts); + } +} diff --git a/src/Service/Screenshot/Service.php b/src/Service/Screenshot/Service.php new file mode 100644 index 0000000..e8d1f28 --- /dev/null +++ b/src/Service/Screenshot/Service.php @@ -0,0 +1,132 @@ + + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use GuzzleHttp\Psr7\Uri; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\EventDispatcher\GenericEvent; +use Symfony\Component\Process\Process; + +class Service +{ + /** + * @var EventDispatcherInterface + */ + protected $eventDispatcher; + + /** + * @var string + */ + protected $screenshotDir = ''; + + /** + * @var int + */ + protected $screenshotWidth = 3840; + + public function __construct( + EventDispatcherInterface $eventDispatcher, + string $screenshotDir, + int $screenshotWidth + ) { + $this->eventDispatcher = $eventDispatcher; + $this->screenshotDir = $this->convertReltiveFolder($screenshotDir); + $this->screenshotWidth = $screenshotWidth; + } + + public function createScreenshot(string $url, int $height): string + { + // TODO: Include width in screenshot dir. + // This enables to compare different resolutions + $screenshotTarget = $this->getScreenshotTarget($url); + $this->createDir($this->screenshotDir . dirname($screenshotTarget)); + $completeScreenshotTarget = $this->screenshotDir . $screenshotTarget; + + $screenshotProcess = new Process([ + 'chromium-browser', + '--headless', + '--disable-gpu', + '--window-size=' . $this->screenshotWidth . ',' . $height, + '--screenshot=' . $completeScreenshotTarget, + $url + ]); + // TODO: Check for success + $screenshotProcess->run(); + + $this->eventDispatcher->dispatch( + 'service.screenshot.created', + new GenericEvent('Created Screenshot', [ + 'screenshot' => $completeScreenshotTarget, + 'url' => $url, + ]) + ); + + return $completeScreenshotTarget; + } + + protected function getScreenshotTarget(string $url): string + { + $uri = new Uri($url); + + return implode( + DIRECTORY_SEPARATOR, + array_filter( + [ + $uri->getScheme(), + $uri->getHost(), + trim($uri->getPath(), '/'), + $uri->getQuery(), + ], + function (string $string) { + return trim($string, ' /') !== ''; + } + ) + ) . '.png'; + } + + public function getScreenshotDir(): string + { + return $this->screenshotDir; + } + + /** + * @throws \Exception If folder could not be created. + */ + public function createDir(string $dir) + { + if (!is_dir($dir)) { + mkdir($dir, 0777, true); + } + + if (!is_dir($dir)) { + throw new \Exception('Could not create directory: "' . $dir . '".', 1535528875); + } + } + + public function convertReltiveFolder(string $folder): string + { + return implode(DIRECTORY_SEPARATOR, [ + dirname(dirname(dirname(dirname(__FILE__)))), + rtrim($folder, '/'), + ]) . DIRECTORY_SEPARATOR; + } +} diff --git a/src/Service/ScreenshotCrawlerService.php b/src/Service/ScreenshotCrawlerService.php deleted file mode 100644 index 3cd5c88..0000000 --- a/src/Service/ScreenshotCrawlerService.php +++ /dev/null @@ -1,311 +0,0 @@ - - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. - */ - -use Codappix\WebsiteComparison\Model\UrlListDto; -use Facebook\WebDriver\Remote\RemoteWebDriver; -use Facebook\WebDriver\Remote\RemoteWebElement; -use Facebook\WebDriver\WebDriverBy; -use GuzzleHttp\Psr7\Uri; -use Symfony\Component\Console\Output\OutputInterface; -use Symfony\Component\Process\Process; - -/** - * - */ -class ScreenshotCrawlerService -{ - /** - * @var OutputInterface - */ - protected $output; - - /** - * @var RemoteWebDriver - */ - protected $driver; - - /** - * @var string - */ - protected $baseUrl = ''; - - /** - * @var string - */ - protected $screenshotDir = ''; - - /** - * @var int - */ - protected $screenshotWidth = 3840; - - /** - * @var string - */ - protected $compareDirectory = ''; - - /** - * @var string - */ - protected $diffResultDir = ''; - - /** - * @var bool - */ - protected $hasDifferences = false; - - public function __construct( - OutputInterface $output, - RemoteWebDriver $driver, - string $baseUrl, - string $screenshotDir = 'output/', - int $screenshotWidth = 3840 - ) { - $this->output = $output; - $this->driver = $driver; - $this->baseUrl = rtrim($baseUrl, '/') . '/'; - $this->screenshotDir = $this->convertReltiveFolder($screenshotDir); - $this->screenshotWidth = $screenshotWidth; - } - - public function crawl() - { - $this->createDir($this->screenshotDir); - - $linkList = new UrlListDto(); - $linkList->addUrl($this->baseUrl); - - while ($url = $linkList->getNextUrl()) { - $this->driver->get($url); - $screenshotHeight = $this->driver->findElement(WebDriverBy::cssSelector('body')) - ->getSize() - ->getHeight(); - $createdScreenshot = $this->createScreenshot($this->driver->getCurrentURL(), $screenshotHeight); - - if ($this->compareDirectory !== '') { - $this->compareScreenshot($createdScreenshot); - } - - $linkList->markUrlAsFinished($url); - array_map([$linkList, 'addUrl'], $this->fetchFurtherLinks( - $this->driver->findElements(WebDriverBy::cssSelector('a')) - )); - } - } - - public function compare(string $compareDirectory, string $diffResultDir): bool - { - // TODO: Check for existence of directory - $this->compareDirectory = $this->convertReltiveFolder($compareDirectory); - $this->diffResultDir = $this->convertReltiveFolder($diffResultDir); - $this->crawl(); - - return $this->hasDifferences; - } - - /** - * @throws \Exception If folder could not be created. - */ - protected function createDir(string $dir) - { - if (!is_dir($dir)) { - mkdir($dir, 0777, true); - } - - if (!is_dir($dir)) { - throw new \Exception('Could not create directory: "' . $dir . '".', 1535528875); - } - } - - protected function createScreenshot(string $url, int $height): string - { - // TODO: Include width in screenshot dir. - // This enables to compare different resolutions - $screenshotTarget = $this->getScreenshotTarget($url); - $this->createDir($this->screenshotDir . dirname($screenshotTarget)); - $completeScreenshotTarget = $this->screenshotDir . $screenshotTarget; - - $screenshotProcess = new Process([ - 'chromium-browser', - '--headless', - '--disable-gpu', - '--window-size=' . $this->screenshotWidth . ',' . $height, - '--screenshot=' . $completeScreenshotTarget, - $url - ]); - // TODO: Check for success - $screenshotProcess->run(); - - if ($this->output->isVerbose()) { - $this->output->writeln(sprintf( - 'Created screenshot "%s" for url "%s".', - $completeScreenshotTarget, - $url - )); - } - - return $completeScreenshotTarget; - } - - protected function compareScreenshot(string $screenshot) - { - try { - if ($this->doScreenshotsDiffer($screenshot)) { - $this->hasDifferences = true; - $this->output->writeln(sprintf( - 'Screenshot "%s" is different then "%s". Diff was written to "%s".', - $screenshot, - $this->getBaseScreenshot($screenshot), - $this->getDiffFileName($screenshot) - )); - return; - } - } catch (\ImagickException $e) { - $this->output->writeln('' . $e->getMessage() . ''); - return; - } - - if ($this->output->isVerbose()) { - $this->output->writeln('Screenshot is same.'); - } - } - - protected function doScreenshotsDiffer(string $screenshot): bool - { - $actualScreenshot = new \Imagick($screenshot); - $actualGeometry = $actualScreenshot->getImageGeometry(); - $compareScreenshot = new \Imagick($this->getBaseScreenshot($screenshot)); - $compareGeometry = $compareScreenshot->getImageGeometry(); - - if ($actualGeometry !== $compareGeometry) { - throw new \ImagickException(sprintf( - "Screenshots don't have an equal geometry. Should be %sx%s but is %sx%s", - $compareGeometry['width'], - $compareGeometry['height'], - $actualGeometry['width'], - $actualGeometry['height'] - )); - } - - $result = $actualScreenshot->compareImages($compareScreenshot, \Imagick::METRIC_ROOTMEANSQUAREDERROR); - if ($result[1] > 0) { - /** @var \Imagick $diffScreenshot */ - $diffScreenshot = $result[0]; - $diffScreenshot->setImageFormat('png'); - $fileName = $this->getDiffFileName($screenshot); - $this->createDir(dirname($fileName)); - file_put_contents($fileName, $diffScreenshot); - - return true; - } - - return false; - } - - protected function getBaseScreenshot(string $compareScreenshot): string - { - return str_replace( - $this->screenshotDir, - $this->compareDirectory, - $compareScreenshot - ); - } - - protected function getScreenshotTarget(string $url) - { - $uri = new Uri($url); - - return implode( - DIRECTORY_SEPARATOR, - array_filter( - [ - $uri->getScheme(), - $uri->getHost(), - trim($uri->getPath(), '/'), - $uri->getQuery(), - ], - function (string $string) { - return trim($string, ' /') !== ''; - } - ) - ) . '.png'; - } - - protected function fetchFurtherLinks(array $webElements): array - { - $links = []; - foreach ($webElements as $webElement) { - try { - $link = $this->fetchLinkFromElement($webElement); - } catch (\Exception $e) { - continue; - } - - $links[] = $link; - } - - return $links; - } - - protected function fetchLinkFromElement(RemoteWebElement $element): string - { - $uri = null; - $href = $element->getAttribute('href'); - if (is_string($href)) { - $uri = new Uri($href); - } - - if ($uri === null) { - throw new \Exception('Did not get a Uri for element.', 1535530859); - } - - if ($this->isInternalLink($uri)) { - return (string) $uri; - } - - throw new \Exception('Was external link.', 1535639056); - } - - protected function isInternalLink(Uri $uri): bool - { - $validHosts = [ - '', - (new Uri($this->baseUrl))->getHost(), - ]; - - return in_array($uri->getHost(), $validHosts); - } - - protected function convertReltiveFolder(string $folder): string - { - return implode(DIRECTORY_SEPARATOR, [ - dirname(dirname(dirname(__FILE__))), - rtrim($folder, '/'), - ]) . DIRECTORY_SEPARATOR; - } - - protected function getDiffFileName(string $screenshot): string - { - return str_replace($this->screenshotDir, $this->diffResultDir, $screenshot); - } -}