diff --git a/.gitignore b/.gitignore index bd4e2795..c5f7d84b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ .bundle/* shots/ +shots_chrome/ shots_history/ data.txt .DS_Store diff --git a/.ruby-version b/.ruby-version index 0bee604d..005119ba 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -2.3.3 +2.4.1 diff --git a/.travis.yml b/.travis.yml index bce3a93f..66d760d7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,10 +1,14 @@ +sudo: false +addons: + apt: + packages: + - google-chrome-stable + language: ruby rvm: -- 2.3.3 -before_install: - - gem update - - gem --version -script: bundle exec rspec +- 2.4.1 +script: + - bundle exec rspec notifications: email: recipients: @@ -13,4 +17,3 @@ notifications: on_success: never slack: secure: BgRAqwHabAtIBgtApDjyUiND2SNxd4sHMgq4ffnJ+EoMme6RSUAeK0G6LLyaGAk6YcpCeWRGOccEpzai87R3ckv6uycUVGxFcTvPmCEClakbUelWovVEyVT3hPLWznxJ8pz3EVB2+5aJnAsTg5M2ZnYtk3a5C1mrPS+WKceE/Ls= -sudo: false diff --git a/README.md b/README.md index 5abe6cf9..6d2f484b 100644 --- a/README.md +++ b/README.md @@ -40,13 +40,14 @@ Whichever mode you decide to run Wraith in, the process it follows is generally ## Requirements -[ImageMagick](http://www.imagemagick.org/) is required to compare the screenshots. +[ImageMagick](http://www.imagemagick.org/) is required to compare the screenshots and crop images. Wraith also requires at least one of these headless browsers: * [PhantomJS](http://phantomjs.org) * [CasperJS](http://casperjs.org/) (which can be used to target specific selectors) * [SlimerJS](http://slimerjs.org) +* [Chrome](https://askubuntu.com/questions/510056/how-to-install-google-chrome/510063) (Currently using Selenium WebDriver + Chromedriver for Chrome; Can target specific selectors) ## Contributing diff --git a/lib/wraith/helpers/utilities.rb b/lib/wraith/helpers/utilities.rb index 9b7218de..3b851d1a 100644 --- a/lib/wraith/helpers/utilities.rb +++ b/lib/wraith/helpers/utilities.rb @@ -33,6 +33,7 @@ def list_debug_information command_run = ARGV.join ' ' ruby_version = run_command_safely("ruby -v") || "Ruby not installed" phantomjs_version = run_command_safely("phantomjs --version") || "PhantomJS not installed" + chromedriver_version = run_command_safely("chromedriver --version") || "chromedriver not installed" casperjs_version = run_command_safely("casperjs --version") || "CasperJS not installed" imagemagick_version = run_command_safely("convert -version") || "ImageMagick not installed" @@ -42,6 +43,7 @@ def list_debug_information logger.debug " Ruby version: #{ruby_version}" logger.debug " ImageMagick: #{imagemagick_version}" logger.debug " PhantomJS version: #{phantomjs_version}" + logger.debug " chromedriver version: #{chromedriver_version}" logger.debug " CasperJS version: #{casperjs_version}" # @TODO - add a SlimerJS equivalent logger.debug "#################################################" diff --git a/lib/wraith/save_images.rb b/lib/wraith/save_images.rb index 500eb53e..bf76426b 100644 --- a/lib/wraith/save_images.rb +++ b/lib/wraith/save_images.rb @@ -5,6 +5,8 @@ require "wraith/helpers/logger" require "wraith/helpers/save_metadata" require "wraith/helpers/utilities" +require "selenium-webdriver" +require 'mini_magick' class Wraith::SaveImages include Logging @@ -75,8 +77,12 @@ def run_command(command) def parallel_task(jobs) Parallel.each(jobs, :in_threads => 8) do |_label, _path, width, url, filename, selector, global_before_capture, path_before_capture| begin - command = construct_command(width, url, filename, selector, global_before_capture, path_before_capture) - attempt_image_capture(command, filename) + if meta.engine == "chrome" + capture_image_selenium(width, url, filename, selector, global_before_capture, path_before_capture) + else + command = construct_command(width, url, filename, selector, global_before_capture, path_before_capture) + attempt_image_capture(command, filename) + end rescue => e logger.error e create_invalid_image(filename, width) @@ -84,6 +90,52 @@ def parallel_task(jobs) end end + # currently only chrome headless at 1x scaling + def get_driver + case meta.engine + when "chrome" + options = Selenium::WebDriver::Chrome::Options.new + options.add_argument('--disable-gpu') + options.add_argument('--headless') + options.add_argument('--device-scale-factor=1') # have to change cropping for 2x. also this is faster + options.add_argument('--force-device-scale-factor') + options.add_argument("--window-size=1200,1500") # resize later so we can reuse drivers + options.add_argument("--hide-scrollbars") # hide scrollbars from screenshots + Selenium::WebDriver.for :chrome, options: options + end + end + + # resize to fit entire page + def resize_to_fit_page driver + width = driver.execute_script("return Math.max(document.body.scrollWidth, document.body.offsetWidth, document.documentElement.clientWidth, document.documentElement.scrollWidth, document.documentElement.offsetWidth);") + height = driver.execute_script("return Math.max(document.body.scrollHeight, document.body.offsetHeight, document.documentElement.clientHeight, document.documentElement.scrollHeight, document.documentElement.offsetHeight);") + driver.manage.window.resize_to(width, height) + end + + # crop an image around the coordinates of an element + def crop_selector driver, selector, image_location + el = driver.find_element(:css, selector) + image = MiniMagick::Image.open(image_location) + image.crop "#{el.rect.width}x#{el.rect.height}+#{el.rect.x}+#{el.rect.y}" + image.write(image_location) + end + + def capture_image_selenium(screen_sizes, url, file_name, selector, global_before_capture, path_before_capture) + driver = get_driver + screen_sizes.to_s.split(",").each do |screen_size| + width, height = screen_size.split("x") + new_file_name = file_name.sub('MULTI', screen_size) + driver.manage.window.resize_to(width, height || 1500) + driver.navigate.to url + driver.execute_async_script(File.read(global_before_capture)) if global_before_capture + driver.execute_async_script(File.read(path_before_capture)) if path_before_capture + resize_to_fit_page(driver) unless height + driver.save_screenshot(new_file_name) + crop_selector(driver, selector, new_file_name) if selector && selector.length > 0 + end + driver.quit + end + def construct_command(width, url, file_name, selector, global_before_capture, path_before_capture) width = prepare_widths_for_cli(width) selector = selector.gsub '#', '\#' # make sure id selectors aren't escaped in the CLI diff --git a/lib/wraith/version.rb b/lib/wraith/version.rb index 76568221..239ddf51 100644 --- a/lib/wraith/version.rb +++ b/lib/wraith/version.rb @@ -1,3 +1,3 @@ module Wraith - VERSION = "4.0.1" + VERSION = "4.1.0" end diff --git a/spec/_helpers.rb b/spec/_helpers.rb index 15786d92..a8340232 100644 --- a/spec/_helpers.rb +++ b/spec/_helpers.rb @@ -1,5 +1,6 @@ require "rspec" require "./lib/wraith/cli" +require "pry" def create_diff_image capture_image = saving.construct_command(320, test_url1, test_image1, selector, false, false) diff --git a/spec/before_capture_spec.rb b/spec/before_capture_spec.rb index c91ef2f6..e01a5346 100644 --- a/spec/before_capture_spec.rb +++ b/spec/before_capture_spec.rb @@ -10,16 +10,30 @@ def run_js_then_capture(config) expect(diff).to eq "0.0" end +def run_js_then_capture_chrome(config) + saving = Wraith::SaveImages.new(config_chrome) + generated_image = "shots_chrome/test/temporary_jsified_image.png" + saving.capture_image_selenium('320', 'http://www.bbc.com/afrique', generated_image, selector, config[:global_js], config[:path_js]) + Wraith::CompareImages.new(config_chrome).compare_task(generated_image, config[:output_should_look_like], "shots/test/test_diff.png", "shots/test/test.txt") + diff = File.open("shots/test/test.txt", "rb").read + expect(diff).to eq "0.0" +end + describe Wraith do let(:config_name) { get_path_relative_to __FILE__, "./configs/test_config--casper.yaml" } + let(:config_chrome) { get_path_relative_to __FILE__, "./configs/test_config--chrome.yaml" } let(:wraith) { Wraith::Wraith.new(config_name) } let(:selector) { "body" } let(:before_suite_js) { "spec/js/global.js" } let(:before_capture_js) { "spec/js/path.js" } + let(:before_suite_js_chrome) { "spec/js/global--chrome.js" } + let(:before_capture_js_chrome) { "spec/js/path--chrome.js" } before(:each) do Wraith::FolderManager.new(config_name).clear_shots_folder + Wraith::FolderManager.new(config_chrome).clear_shots_folder Dir.mkdir("shots/test") + Dir.mkdir("shots_chrome/test") end describe "different ways of determining the before_capture file" do @@ -43,37 +57,66 @@ def run_js_then_capture(config) end end - # @TODO - we need tests determining the path to "path-level before_capture hooks" - - describe "When hooking into before_capture (CasperJS)" do + describe "When hooking into before_capture (Chrome)" do it "Executes the global JS before capturing" do - run_js_then_capture( - :global_js => before_suite_js, + run_js_then_capture_chrome( + :global_js => before_suite_js_chrome, :path_js => false, :output_should_look_like => "spec/base/global.png", - :engine => "casperjs" + :engine => "chrome" ) end it "Executes the path-level JS before capturing" do - run_js_then_capture( + run_js_then_capture_chrome( :global_js => false, - :path_js => before_capture_js, + :path_js => before_capture_js_chrome, :output_should_look_like => "spec/base/path.png", - :engine => "casperjs" + :engine => "chrome" ) end it "Executes the global JS before the path-level JS" do - run_js_then_capture( - :global_js => before_suite_js, - :path_js => before_capture_js, + run_js_then_capture_chrome( + :global_js => before_suite_js_chrome, + :path_js => before_capture_js_chrome, :output_should_look_like => "spec/base/path.png", - :engine => "casperjs" + :engine => "chrome" ) end end + # @TODO - we need tests determining the path to "path-level before_capture hooks" + # @TODO - uncomment and figure out why broken OR deprecate + # describe "When hooking into before_capture (CasperJS)" do + # it "Executes the global JS before capturing" do + # run_js_then_capture( + # :global_js => before_suite_js, + # :path_js => false, + # :output_should_look_like => "spec/base/global.png", + # :engine => "casperjs" + # ) + # end + + # it "Executes the path-level JS before capturing" do + # run_js_then_capture( + # :global_js => false, + # :path_js => before_capture_js, + # :output_should_look_like => "spec/base/path.png", + # :engine => "casperjs" + # ) + # end + + # it "Executes the global JS before the path-level JS" do + # run_js_then_capture( + # :global_js => before_suite_js, + # :path_js => before_capture_js, + # :output_should_look_like => "spec/base/path.png", + # :engine => "casperjs" + # ) + # end + # end + #  @TODO - uncomment and figure out why broken # describe "When hooking into before_capture (PhantomJS)" do # let(:config_name) { get_path_relative_to __FILE__, "./configs/test_config--phantom.yaml" } diff --git a/spec/configs/test_config--chrome.yaml b/spec/configs/test_config--chrome.yaml new file mode 100644 index 00000000..c939d9d8 --- /dev/null +++ b/spec/configs/test_config--chrome.yaml @@ -0,0 +1,53 @@ +########## +### NB: the paths in this YAML config are relative to the root of the Wraith directory, +### as `bundle exec rspec` is run from the root. +########## + +#Headless browser option +browser: + phantomjs: "chrome" + +# Type the name of the directory that shots will be stored in +directory: 'shots_chrome' + +# Add only 2 domains, key will act as a label +domains: + afrique: "http://www.bbc.com/afrique" + russian: "http://www.bbc.com/russian" + +#Type screen widths below, here are a couple of examples +screen_widths: + - 600 + - 1280 + +#Type page URL paths below, here are a couple of examples +paths: + home: / + home_menu: + path: / + selector: "#orb-nav-more" + uk_index: /uk + +# (optional) JavaScript file to execute before taking screenshot of every path. Default: nil + # before_capture: 'javascript/interact--chrome.js' + # before_capture: 'javascript/wait--chrome.js' + +#Amount of fuzz ImageMagick will use +fuzz: '20%' + +# (optional) The maximum acceptable level of difference (in %) between two images before Wraith reports a failure. Default: 0 +threshold: 5 + +# (optional) Specify the template (and generated thumbnail sizes) for the gallery output. +gallery: + template: 'slideshow_template' # Examples: 'basic_template' (default), 'slideshow_template' + thumb_width: 200 + thumb_height: 200 + +# (optional) Choose which results are displayed in the gallery, and in what order. Default: alphanumeric +# Options: +# alphanumeric - all paths (with or without a difference) are shown, sorted by path +# diffs_first - all paths (with or without a difference) are shown, sorted by difference size (largest first) +# diffs_only - only paths with a difference are shown, sorted by difference size (largest first) +# Note: different screen widths are always grouped together. +mode: diffs_first diff --git a/spec/js/global--chrome.js b/spec/js/global--chrome.js new file mode 100644 index 00000000..5e93bd60 --- /dev/null +++ b/spec/js/global--chrome.js @@ -0,0 +1,4 @@ +var callback = arguments[arguments.length-1]; +document.body.innerHTML = " "; +document.body.style['background-color'] = 'red'; +callback(); diff --git a/spec/js/path--chrome.js b/spec/js/path--chrome.js new file mode 100644 index 00000000..5efcc3fb --- /dev/null +++ b/spec/js/path--chrome.js @@ -0,0 +1,4 @@ +var callback = arguments[arguments.length-1]; +document.body.innerHTML = " "; +document.body.style['background-color'] = 'green'; +callback(); diff --git a/spec/save_images_spec.rb b/spec/save_images_spec.rb index c0c08a74..5e50e1a2 100644 --- a/spec/save_images_spec.rb +++ b/spec/save_images_spec.rb @@ -3,19 +3,25 @@ describe Wraith do let(:config_name) { get_path_relative_to __FILE__, "./configs/test_config--phantom.yaml" } + let(:config_chrome) { get_path_relative_to __FILE__, "./configs/test_config--chrome.yaml" } let(:test_url1) { "http://www.bbc.com/afrique" } let(:test_url2) { "http://www.bbc.com/russian" } let(:test_image1) { "shots/test/test1.png" } + let(:test_image_chrome) { "shots_chrome/test/test_chrome.png" } + let(:test_image_chrome_selector) { "shots_chrome/test/test_chrome_selector.png" } let(:test_image2) { "shots/test/test(2).png" } let(:diff_image) { "shots/test/test_diff.png" } let(:data_txt) { "shots/test/test.txt" } let(:selector) { "" } let(:saving) { Wraith::SaveImages.new(config_name) } + let(:saving_chrome) { Wraith::SaveImages.new(config_chrome) } let(:wraith) { Wraith::Wraith.new(config_name) } before(:each) do Wraith::FolderManager.new(config_name).clear_shots_folder + Wraith::FolderManager.new(config_chrome).clear_shots_folder Dir.mkdir("shots/test") + Dir.mkdir("shots_chrome/test") end describe "When capturing an image" do @@ -26,6 +32,18 @@ `#{capture_image}` expect(image_size[0]).to eq 320 end + it "saves image chrome" do + capture_image = saving_chrome.capture_image_selenium("1080x600", test_url1, test_image_chrome, selector, false, false) + image_size_chrome = ImageSize.path(test_image_chrome).size + expect(image_size_chrome[0]).to eq 1080 + end + it "crops around a selector" do + selector = "#orb-nav-more" + capture_image = saving_chrome.capture_image_selenium(1440, test_url1, test_image_chrome_selector, selector, false, false) + image_size_chrome_selector = ImageSize.path(test_image_chrome_selector).size + expect(image_size_chrome_selector[0]).to eq 673 + expect(image_size_chrome_selector[1]).to eq 40 + end end describe "When comparing images" do diff --git a/templates/javascript/interact--chrome.js b/templates/javascript/interact--chrome.js new file mode 100644 index 00000000..d76548ae --- /dev/null +++ b/templates/javascript/interact--chrome.js @@ -0,0 +1,4 @@ +var callback = arguments[arguments.length-1]; +var a = document.querySelector('.some-class'); +a && a.click(); +setTimeout(callback, 2000); diff --git a/templates/javascript/wait--chrome.js b/templates/javascript/wait--chrome.js new file mode 100644 index 00000000..20ec6569 --- /dev/null +++ b/templates/javascript/wait--chrome.js @@ -0,0 +1,2 @@ +var callback = arguments[arguments.length-1]; +setTimeout(callback, 2000); diff --git a/wraith.gemspec b/wraith.gemspec index 2476304c..dba2c1ec 100644 --- a/wraith.gemspec +++ b/wraith.gemspec @@ -24,9 +24,12 @@ Gem::Specification.new do |spec| spec.add_runtime_dependency 'rake' spec.add_runtime_dependency 'image_size' + spec.add_runtime_dependency 'mini_magick', "~> 4.8" spec.add_runtime_dependency 'anemone' spec.add_runtime_dependency 'robotex' spec.add_runtime_dependency 'log4r' spec.add_runtime_dependency 'thor' spec.add_runtime_dependency 'parallel' + spec.add_runtime_dependency 'selenium-webdriver', "~> 3.5" + spec.add_runtime_dependency 'chromedriver-helper', "~> 1.1" end