diff --git a/comparison b/comparison
index 9385d64..ee16f65 100755
--- a/comparison
+++ b/comparison
@@ -3,9 +3,11 @@
require __DIR__ . '/vendor/autoload.php';
+use Codappix\WebsiteComparison\Command\CompareCommand;
use Codappix\WebsiteComparison\Command\CreateBaseCommand;
use Symfony\Component\Console\Application;
$application = new Application();
$application->add(new CreateBaseCommand());
+$application->add(new CompareCommand());
$application->run();
diff --git a/composer.json b/composer.json
index c8bc4bb..950de22 100644
--- a/composer.json
+++ b/composer.json
@@ -15,6 +15,8 @@
}
},
"require": {
+ "php": "^7.2",
+ "php-imagick": "*",
"facebook/webdriver": "^1.6",
"symfony/console": "^4.1",
"symfony/process": "^4.1",
diff --git a/src/Command/CompareCommand.php b/src/Command/CompareCommand.php
new file mode 100644
index 0000000..a5a7699
--- /dev/null
+++ b/src/Command/CompareCommand.php
@@ -0,0 +1,119 @@
+
+ *
+ * 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\Service\ScreenshotCrawlerService;
+use Facebook\WebDriver\Chrome\ChromeDriver;
+use Facebook\WebDriver\Chrome\ChromeDriverService;
+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;
+
+class CompareCommand extends Command
+{
+ /**
+ * @var Process
+ */
+ protected $chromeProcess;
+
+ protected function configure()
+ {
+ $this
+ ->setName('comparison:comparetobase')
+ ->setDescription('Compare curent state against saved base.')
+ ->setHelp('Crawls and screenshots the original website, as a base for future comparison.')
+
+ ->addOption(
+ 'screenshotDir',
+ null,
+ InputOption::VALUE_OPTIONAL,
+ 'Define the sub directory containing original Screenshots for comparison.',
+ 'output/base'
+ )
+ ->addOption(
+ 'compareDir',
+ null,
+ InputOption::VALUE_OPTIONAL,
+ 'Define the sub directory to use for storing created Screenshots.',
+ 'output/compare'
+ )
+ ->addOption(
+ 'diffResultDir',
+ null,
+ InputOption::VALUE_OPTIONAL,
+ 'Define the sub directory to use for storing created diffs.',
+ 'output/diffResult'
+ )
+ ->addOption(
+ 'screenshotWidth',
+ null,
+ InputOption::VALUE_OPTIONAL,
+ 'The width for screen resolution and screenshots.',
+ 3840
+ )
+
+ ->addArgument(
+ 'baseUrl',
+ InputArgument::REQUIRED,
+ 'E.g. https://typo3.org/ the base url of the website to crawl.'
+ )
+ ;
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output)
+ {
+ $screenshotCrawler = new ScreenshotCrawlerService(
+ $output,
+ $this->getDriver(),
+ $input->getArgument('baseUrl'),
+ $input->getOption('compareDir'),
+ $input->getOption('screenshotWidth')
+ );
+ $hasDifferences = $screenshotCrawler->compare(
+ $input->getOption('screenshotDir'),
+ $input->getOption('diffResultDir')
+ );
+
+ if ($hasDifferences) {
+ return 255;
+ }
+ }
+
+ protected function getDriver(): ChromeDriver
+ {
+ $chromeDriverService = new ChromeDriverService(
+ '/usr/lib/chromium-browser/chromedriver',
+ 9515,
+ [
+ '--port=9515',
+ '--headless',
+ ]
+ );
+ $driver = ChromeDriver::start(null, $chromeDriverService);
+
+ return $driver;
+ }
+}
diff --git a/src/Command/CreateBaseCommand.php b/src/Command/CreateBaseCommand.php
index b56da72..9a054de 100644
--- a/src/Command/CreateBaseCommand.php
+++ b/src/Command/CreateBaseCommand.php
@@ -32,9 +32,6 @@ use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Process\Exception\ProcessFailedException;
use Symfony\Component\Process\Process;
-/**
- *
- */
class CreateBaseCommand extends Command
{
/**
@@ -54,7 +51,7 @@ class CreateBaseCommand extends Command
null,
InputOption::VALUE_OPTIONAL,
'Define the sub directory to use for storing created Screenshots.',
- 'output'
+ 'output/base'
)
->addOption(
'screenshotWidth',
diff --git a/src/Service/ScreenshotCrawlerService.php b/src/Service/ScreenshotCrawlerService.php
index c72b931..3cd5c88 100644
--- a/src/Service/ScreenshotCrawlerService.php
+++ b/src/Service/ScreenshotCrawlerService.php
@@ -59,6 +59,21 @@ class ScreenshotCrawlerService
*/
protected $screenshotWidth = 3840;
+ /**
+ * @var string
+ */
+ protected $compareDirectory = '';
+
+ /**
+ * @var string
+ */
+ protected $diffResultDir = '';
+
+ /**
+ * @var bool
+ */
+ protected $hasDifferences = false;
+
public function __construct(
OutputInterface $output,
RemoteWebDriver $driver,
@@ -69,16 +84,13 @@ class ScreenshotCrawlerService
$this->output = $output;
$this->driver = $driver;
$this->baseUrl = rtrim($baseUrl, '/') . '/';
- $this->screenshotDir = implode(DIRECTORY_SEPARATOR, [
- dirname(dirname(dirname(__FILE__))),
- rtrim($screenshotDir, '/')
- ]) . DIRECTORY_SEPARATOR;
+ $this->screenshotDir = $this->convertReltiveFolder($screenshotDir);
$this->screenshotWidth = $screenshotWidth;
}
public function crawl()
{
- $this->createScreenshotDirIfNecessary();
+ $this->createDir($this->screenshotDir);
$linkList = new UrlListDto();
$linkList->addUrl($this->baseUrl);
@@ -88,7 +100,11 @@ class ScreenshotCrawlerService
$screenshotHeight = $this->driver->findElement(WebDriverBy::cssSelector('body'))
->getSize()
->getHeight();
- $this->createScreenshot($this->driver->getCurrentURL(), $screenshotHeight);
+ $createdScreenshot = $this->createScreenshot($this->driver->getCurrentURL(), $screenshotHeight);
+
+ if ($this->compareDirectory !== '') {
+ $this->compareScreenshot($createdScreenshot);
+ }
$linkList->markUrlAsFinished($url);
array_map([$linkList, 'addUrl'], $this->fetchFurtherLinks(
@@ -97,35 +113,44 @@ class ScreenshotCrawlerService
}
}
+ 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 createScreenshotDirIfNecessary(string $subPath = '')
+ protected function createDir(string $dir)
{
- $dir = $this->screenshotDir;
- if ($subPath !== '') {
- $dir = $dir . DIRECTORY_SEPARATOR . trim($subPath, DIRECTORY_SEPARATOR);
- }
if (!is_dir($dir)) {
mkdir($dir, 0777, true);
}
- if (!is_dir($this->screenshotDir)) {
- throw new \Exception('Could not create screenshot dir: "' . $dir . '".', 1535528875);
+ if (!is_dir($dir)) {
+ throw new \Exception('Could not create directory: "' . $dir . '".', 1535528875);
}
}
- protected function createScreenshot(string $url, int $height)
+ 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->createScreenshotDirIfNecessary(dirname($screenshotTarget));
+ $this->createDir($this->screenshotDir . dirname($screenshotTarget));
+ $completeScreenshotTarget = $this->screenshotDir . $screenshotTarget;
$screenshotProcess = new Process([
'chromium-browser',
'--headless',
'--disable-gpu',
'--window-size=' . $this->screenshotWidth . ',' . $height,
- '--screenshot=' . $this->screenshotDir . $screenshotTarget,
+ '--screenshot=' . $completeScreenshotTarget,
$url
]);
// TODO: Check for success
@@ -134,10 +159,76 @@ class ScreenshotCrawlerService
if ($this->output->isVerbose()) {
$this->output->writeln(sprintf(
'Created screenshot "%s" for url "%s".',
- $this->screenshotDir . $screenshotTarget,
+ $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)
@@ -204,4 +295,17 @@ class ScreenshotCrawlerService
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);
+ }
}