diff --git a/.gitignore b/.gitignore index c422267..ee05c83 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,12 @@ composer.phar -/vendor/ +vendor # Commit your application's lock file http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file # You may choose to ignore a library lock file http://getcomposer.org/doc/02-libraries.md#lock-file # composer.lock + +# Ignore testing directories +bats +bin +libexec +share diff --git a/LICENSE b/LICENSE index bf7bebf..ec10696 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ -MIT License +The MIT License (MIT) -Copyright (c) 2017 Terminus Plugin Project +Copyright (c) 2016 Pantheon Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md new file mode 100644 index 0000000..164553c --- /dev/null +++ b/README.md @@ -0,0 +1,46 @@ +# Terminus Wraith Plugin + +Wraith - A Terminus plugin to assist with visual regression testing of Pantheon site environments. + +## Usage: +``` +$ terminus wraith [--sites site-name.test,site-name.prod --paths home=/,about=/about,news=/news,... --config --spider] +``` + +## Examples: +Use the previous configuration or prompt for the environments and pages to compare and then generate snapshots: +``` +$ terminus wraith +``` +Prompt for the environments and pages to compare and then generate snapshots: +``` +$ terminus wraith --config +``` +Use the previous configuration or prompt for new pages to compare the my-site.test and my-site.prod environments and then generate snapshots: +``` +$ terminus wraith --sites=my-site.test,my-site.prod +``` +Use the previous configuration or prompt for new environments to compare the /, /about and /news pages and then generate snapshots: +``` +$ terminus wraith --pages=home=/,about=/about,news=/news +``` +Use the previous configuration or prompt for new environments, craw for pages and then generate snapshots: +``` +$ terminus wraith --spider +``` + +## Installation: + +For installation help, see [Extend with Plugins](https://pantheon.io/docs/terminus/plugins/). + +``` +mkdir -p ~/.terminus/plugins +composer create-project -d ~/.terminus/plugins terminus-plugin-project/terminus-wraith-plugin:~1 +``` + +## Configuration: + +Install [Wraith](http://bbc-news.github.io/wraith/) for your [operating system](http://bbc-news.github.io/wraith/os-install.html). See [http://bbc-news.github.io/wraith/os-install.html](http://bbc-news.github.io/wraith/os-install.html). + +## Help: +Run `terminus help wraith` for help. diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..93efea4 --- /dev/null +++ b/composer.json @@ -0,0 +1,37 @@ +{ + "name": "terminus-plugin-project/terminus-wraith-plugin", + "description": "Wraith - A Terminus plugin to assist with visual regression testing of Pantheon site environments.", + "homepage": "https://github.com/terminus-plugin-project/terminus-wraith-plugin", + "authors": [ + { + "name": "Ed Reel", + "email": "edreel@gmail.com", + "homepage": "https://github.com/uberhacker", + "role": "Developer" + } + ], + "type": "terminus-plugin", + "keywords": [ + "pantheon", + "terminus", + "regression", + "testing", + "wraith", + "plugin" + ], + "support": { + "issues": "https://github.com/terminus-plugin-project/terminus-wraith-plugin/issues" + }, + "license": "MIT", + "require": { + "php": "^5.5 || ^7.0" + }, + "autoload": { + "psr-4": { "TerminusPluginProject\\TerminusWraith\\": "src" } + }, + "extra": { + "terminus": { + "compatible-version": "^1" + } + } +} diff --git a/src/Commands/WraithCommand.php b/src/Commands/WraithCommand.php new file mode 100644 index 0000000..c8371e5 --- /dev/null +++ b/src/Commands/WraithCommand.php @@ -0,0 +1,217 @@ + [], 'paths' => [], 'config' => false, 'spider' => false]) { + + $this->checkRequirements(); + + // Determine if capture configuration exists. + $capture_file = __DIR__ . '/configs/capture.yaml'; + if (!file_exists($capture_file)) { + $options['config'] = true; + exec('wraith setup', $messages, $return_var); + if (!empty($messages)) { + foreach ($messages as $message) { + $this->io()->writeln($message); + } + } + } + + // Override --config option if --no-interaction is set. + if ($options['no-interaction']) { + $options['config'] = false; + } + + // Configuration requested. + if ($options['config']) { + // Extract capture data. + $capture_data = $this->getYaml($capture_file); + + // Get the sites to compare. + if (empty($options['sites'])) { + $creds = array(); + $site_envs = array(); + $site_envs[] = $this->io()->ask('Enter the source in `site-name.env` format'); + $login = $this->io()->confirm('Does this environment require a login?'); + if (!$login) { + $creds[$site_envs[0]] = ''; + } else { + $user = $this->io()->ask('Enter the username'); + $pass = $this->io()->askHidden('Enter the password'); + $creds[$site_envs[0]] = "${user}:${pass}@"; + } + $site_envs[] = $this->io()->ask('Enter the target in `site-name.env` format'); + $login = $this->io()->confirm('Does this environment require a login?'); + if (!$login) { + $creds[$site_envs[1]] = ''; + } else { + $user = $this->io()->ask('Enter the username'); + $pass = $this->io()->askHidden('Enter the password'); + $creds[$site_envs[1]] = "${user}:${pass}@"; + } + } else { + $site_envs = explode(',', $options['sites'][0]); + } + + // Get the site domains. + $domains = array(); + foreach ($site_envs as $site_env) { + list(, $env) = $this->getSiteEnv($site_env); + $domain_info = $env->getDomains()->serialize(); + foreach ($domain_info as $info) { + $domains[$info['environment']] = 'http://' . $creds[$site_env] . $info['domain']; + } + } + $capture_data['domains'] = $domains; + + // Get the site paths. + if (!$options['spider']) { + $paths = array(); + if (empty($options['paths'])) { + $path = $options['yes'] ? '' : '/'; + while ($path) { + if ($name = $this->io()->ask('Enter the name for a relative path or `0` to cancel', 0)) { + if ($path = $this->io()->ask('Enter the url of the relative path or `0` to cancel', 0)) { + $paths[$name] = $path; + } + } else { + $path = 0; + } + } + // Need at least one page to compare. + if (empty($paths)) { + $paths['home'] = '/'; + } + } else { + $pairs = explode(',', $options['paths'][0]); + foreach ($pairs as $pair) { + $values = explode('=', $pair); + $paths[$values[0]] = $values[1]; + } + } + if (isset($capture_data['imports'])) { + unset($capture_data['imports']); + } + $capture_data['paths'] = $paths; + } + + // Output the new configuration. + $this->putYaml($capture_file, $capture_data); + } + + // Craw to detect pages. + if ($options['spider']) { + $capture_data = $this->getYaml($capture_file); + if (isset($capture_data['paths'])) { + unset($capture_data['paths']); + } + $capture_data['imports'] = 'spider_paths.yaml'; + $this->putYaml($capture_file, $capture_data); + exec('wraith spider ' . $capture_file, $messages, $return_var); + if (!empty($messages)) { + foreach ($messages as $message) { + $this->io()->writeln($message); + } + } + } + + // Generate snapshots. + exec('wraith capture ' . $capture_file, $messages, $return_var); + if (!empty($messages)) { + foreach ($messages as $message) { + $this->io()->writeln($message); + } + } + + } + + /** + * Platform independent check whether a command exists. + * + * @param string $command Command to check + * @return bool True if exists, false otherwise + */ + protected function commandExists($command) + { + $windows = (php_uname('s') == 'Windows NT'); + $test_command = $windows ? 'where' : 'command -v'; + $file = popen("$test_command $command", 'r'); + $result = fgets($file, 255); + return $windows ? !preg_match('#Could not find files#', $result) : !empty($result); + } + + /** + * Check for plugin requirements. + */ + protected function checkRequirements() + { + if (!$this->commandExists('wraith')) { + $message = 'Please install Wraith to enable visual regression testing. See http://bbc-news.github.io/wraith/os-install.html.'; + throw new TerminusNotFoundException($message); + } + } + + /** + * Extract YAML data. + * + * @param string $yaml_file The YAML file + * @return array An array of YAML compatible data + */ + protected function getYaml($yaml_file) + { + if ($yaml_data = @file_get_contents($yaml_file)) { + return Yaml::parse($yaml_data); + } + return array(); + } + + /** + * Save YAML data. + * + * @param string $yaml_file The YAML file + * @param array $yaml_data An array of YAML compatible data + */ + protected function putYaml($yaml_file, $yaml_data) + { + if (!$yaml_formatted_data = Yaml::dump($yaml_data)) { + throw new TerminusNotFoundException('Unable to save configuration. Invalid YAML data.'); + } + try { + $handle = fopen($yaml_file, 'w'); + fwrite($handle, $yaml_formatted_data); + fclose($handle); + } catch (Exception $e) { + throw new TerminusNotFoundException($e->getMessage()); + } + } +}