janus-cli 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.rspec +1 -1
  4. data/CHANGELOG.md +7 -0
  5. data/Guardfile +2 -2
  6. data/README.md +11 -20
  7. data/Rakefile +11 -1
  8. data/bin/janus +1 -1
  9. data/janus.gemspec +3 -1
  10. data/lib/janus.rb +0 -1
  11. data/lib/janus/browser.rb +19 -0
  12. data/lib/janus/command/record.rb +35 -4
  13. data/lib/janus/command/validate.rb +39 -5
  14. data/lib/janus/configuration.rb +21 -2
  15. data/lib/janus/core/engine.rb +26 -0
  16. data/lib/janus/core/error.rb +6 -0
  17. data/lib/janus/core/rule.rb +46 -0
  18. data/lib/janus/io/directory.rb +40 -0
  19. data/lib/janus/io/selenium.rb +43 -0
  20. data/lib/janus/screenshot.rb +7 -27
  21. data/lib/janus/version.rb +1 -1
  22. data/spec/integration/engine_spec.rb +50 -0
  23. data/spec/support/square-base.png +0 -0
  24. data/spec/support/square-big.png +0 -0
  25. data/spec/support/square-ten.png +0 -0
  26. data/spec/support/square-thirty.png +0 -0
  27. data/spec/support/square-twenty.png +0 -0
  28. data/spec/unit/janus/browser_spec.rb +57 -0
  29. data/spec/{janus → unit/janus}/command/initialize_spec.rb +1 -1
  30. data/spec/unit/janus/command/record_spec.rb +93 -0
  31. data/spec/unit/janus/command/validate_spec.rb +105 -0
  32. data/spec/{janus → unit/janus}/configuration_spec.rb +42 -0
  33. data/spec/unit/janus/core/engine_spec.rb +36 -0
  34. data/spec/unit/janus/core/error_spec.rb +4 -0
  35. data/spec/unit/janus/core/rule_spec.rb +65 -0
  36. data/spec/unit/janus/io/directory_spec.rb +83 -0
  37. data/spec/unit/janus/io/selenium_spec.rb +78 -0
  38. data/spec/unit/janus/screenshot_spec.rb +26 -0
  39. data/spec/{janus → unit/janus}/test_spec.rb +0 -0
  40. data/spec/{spec_helper.rb → unit/spec_helper.rb} +0 -0
  41. metadata +78 -19
  42. data/spec/janus/command/record_spec.rb +0 -36
  43. data/spec/janus/command/validate_spec.rb +0 -44
  44. data/spec/janus/screenshot_spec.rb +0 -79
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 48d541d4807829c7904dd94f3c490782ac384a8e
4
- data.tar.gz: e6a8dee5095f4f7bfb37534e9e51c21015db496e
3
+ metadata.gz: 937b8709825ebcdb797849676eb8828953b18ce3
4
+ data.tar.gz: 2359bd29a0b4a314918215693a98b68d4682212b
5
5
  SHA512:
6
- metadata.gz: dad2010d9f66f0e98abeeb55cf025b36151c187367b26498a556b834271e3c2d509b63fa06658033f3f506f797348f28224b52b7bd54d4bd3bcdfe04a70d9fc1
7
- data.tar.gz: 1393f2a3b1bd0349d6c300ad5b7dd31f3ecda5f3a2a3c4ed3c18af21528a6f30a7756dec9e711f503fa9fef9dca2c39b2a32caf5fa8e05be59eb87560f6c661a
6
+ metadata.gz: 32799bec8eab0f1460b418a14b20c0a1f08ba10594d0a461c9964b9545b367e8c27de752f534c26cc132cf33552d10767cb419e5b0b3fa7bcafd1c8444a6d504
7
+ data.tar.gz: a9a8fc7030531eac97a34c327c9cb206790ec1c2fc6891a827bc9827ab8bd65a3acb7a1f993c44731341ce3afece9dcaf68e87d587f2e44a693cbb9903f75b38
data/.gitignore CHANGED
@@ -1,3 +1,4 @@
1
1
  /coverage
2
2
  /pkg
3
3
  Gemfile.lock
4
+ sauce_connect.log
data/.rspec CHANGED
@@ -1,3 +1,3 @@
1
1
  --color
2
2
  --format progress
3
- --require spec_helper
3
+ --require unit/spec_helper
data/CHANGELOG.md ADDED
@@ -0,0 +1,7 @@
1
+ ## v0.2.0 (March 19, 2014)
2
+ - Add console reporting.
3
+ - Add multi-browser support.
4
+ - Add threshold-based comparison.
5
+
6
+ ## v0.1.0 (March 8, 2014)
7
+ - Initial Release
data/Guardfile CHANGED
@@ -1,6 +1,6 @@
1
1
  guard :rspec do
2
2
  watch(%r{^spec/.+_spec\.rb$})
3
- watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
4
- watch('spec/spec_helper.rb') { "spec" }
3
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/unit/#{m[1]}_spec.rb" }
4
+ watch('spec/unit/spec_helper.rb') { "spec/unit" }
5
5
  end
6
6
 
data/README.md CHANGED
@@ -49,23 +49,10 @@ directory for an example of Janus in action.
49
49
 
50
50
  ## Configuring Janus
51
51
 
52
- **directory** (required)
53
-
54
- The location where Janus screenshots are stored.
55
-
56
- **url** (optional)
57
-
58
- The base URL for all screenshots. All test URLs will be relative to this URL.
59
-
60
- **threshold** (optional)
61
-
62
- The percentage of change that is allowed in a screenshot. This value can be
63
- overridden for each individual test.
64
-
65
- **resolution** (optional)
52
+ **tests** (required)
66
53
 
67
- The browser resolution at which screenshots should be taken. This value is limited
68
- by the options available on Sauce Labs.
54
+ An array of screenshots to take. Each entry in the array contains a name and
55
+ URL. All URLs are relative to the specified base URL.
69
56
 
70
57
  **browsers** (required)
71
58
 
@@ -73,11 +60,15 @@ An array of browsers to run your tests against. Each entry in the array contains
73
60
  a platform, browser, and optional version. See the [Sauce Labs platform documentation](https://saucelabs.com/docs/platforms)
74
61
  for more information on the valid browsers.
75
62
 
76
- **tests** (required)
63
+ **directory** (required)
64
+
65
+ The location where Janus screenshots are stored.
66
+
67
+ **threshold** (optional)
68
+
69
+ The percentage of change that is allowed in a screenshot. This value can be
70
+ overridden for each individual test. *Defaults to 0*.
77
71
 
78
- An array of screenshots to take. Each entry in the array contains a name, URL,
79
- and optional threshold. All URLs are relative to the specified base URL. If a
80
- threshold is defined, it will override the global theshold.
81
72
 
82
73
  ## Best Practices
83
74
 
data/Rakefile CHANGED
@@ -1,6 +1,16 @@
1
1
  require 'bundler/gem_tasks'
2
2
  require 'rspec/core/rake_task'
3
3
 
4
- RSpec::Core::RakeTask.new(:spec)
4
+ task :spec => ['spec:unit', 'spec:integration']
5
+
6
+ namespace :spec do
7
+ RSpec::Core::RakeTask.new(:unit) do |t|
8
+ t.pattern = 'spec/unit/**/*_spec.rb'
9
+ end
10
+
11
+ RSpec::Core::RakeTask.new(:integration) do |t|
12
+ t.pattern = 'spec/integration/**/*_spec.rb'
13
+ end
14
+ end
5
15
 
6
16
  task :default => :spec
data/bin/janus CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require 'colored'
3
+ require 'colorize'
4
4
  require 'gli'
5
5
  require 'janus/version'
6
6
  require 'janus/command/initialize'
data/janus.gemspec CHANGED
@@ -16,8 +16,10 @@ Gem::Specification.new do |spec|
16
16
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
17
  spec.require_paths = ['lib']
18
18
 
19
- spec.add_dependency('colored', '~> 1.2')
19
+ spec.add_dependency('colorize', '~> 0.7.0')
20
20
  spec.add_dependency('gli', '~> 2.8')
21
+ spec.add_dependency('oily_png', '~> 1.1')
22
+ spec.add_dependency('sauce-connect', '~> 3.3.1')
21
23
  spec.add_dependency('selenium-webdriver', '~> 2.37')
22
24
 
23
25
  spec.add_development_dependency('bundler', '~> 1.3')
data/lib/janus.rb CHANGED
@@ -1,3 +1,2 @@
1
1
  module Janus
2
-
3
2
  end
@@ -0,0 +1,19 @@
1
+ module Janus
2
+ class Browser
3
+ attr_reader :platform, :name, :version
4
+
5
+ def initialize(attributes = {})
6
+ @platform = attributes['platform']
7
+ @name = attributes['name']
8
+ @version = attributes['version']
9
+ end
10
+
11
+ def eql?(other)
12
+ platform == other.platform && name == other.name && version == other.version
13
+ end
14
+
15
+ def to_s
16
+ "#{platform}, #{name} #{version}".strip
17
+ end
18
+ end
19
+ end
@@ -1,3 +1,9 @@
1
+ # coding: utf-8
2
+
3
+ require 'colorize'
4
+ require 'sauce/connect'
5
+ require 'janus/io/directory'
6
+ require 'janus/io/selenium'
1
7
  require 'janus/screenshot'
2
8
 
3
9
  module Janus
@@ -8,14 +14,39 @@ module Janus
8
14
  end
9
15
 
10
16
  def execute
17
+ Sauce::Connect.connect!(quiet: true) if @configuration.tunnel?
18
+
19
+ puts 'Recording screenshots...'
20
+ puts ''
21
+
22
+ @configuration.browsers.each do |browser|
23
+ record_screenshots_for_browser(browser)
24
+ end
25
+ end
26
+
27
+ def record_screenshots_for_browser(browser)
28
+ puts "#{browser}"
29
+ puts ''
30
+
11
31
  @configuration.tests.each do |test|
12
- record_screenshot(test)
32
+ record_screenshot(browser, test)
13
33
  end
34
+
35
+ puts ''
14
36
  end
15
37
 
16
- def record_screenshot(test)
17
- screenshot = Janus::Screenshot.capture(test, username: @configuration.username, access_key: @configuration.access_key)
18
- screenshot.save('output')
38
+ def record_screenshot(browser, test)
39
+ selenium = Janus::IO::Selenium.new(@configuration.username, @configuration.access_key, browser)
40
+ screenshot = selenium.read(test)
41
+
42
+ directory = Janus::IO::Directory.new(@configuration.directory, browser)
43
+ directory.write(test, screenshot)
44
+
45
+ print '✔ '.green
46
+ rescue
47
+ print '✖ '.red
48
+ ensure
49
+ puts test.name
19
50
  end
20
51
  end
21
52
  end
@@ -1,3 +1,11 @@
1
+ # coding: utf-8
2
+
3
+ require 'colorize'
4
+ require 'sauce/connect'
5
+ require 'janus/core/engine'
6
+ require 'janus/io/directory'
7
+ require 'janus/io/selenium'
8
+
1
9
  module Janus
2
10
  module Command
3
11
  class Validate
@@ -6,16 +14,42 @@ module Janus
6
14
  end
7
15
 
8
16
  def execute
17
+ Sauce::Connect.connect!(quiet: true) if @configuration.tunnel?
18
+
19
+ puts 'Validating screenshots...'
20
+ puts ''
21
+
22
+ @configuration.browsers.each do |browser|
23
+ validate_screenshots_for_browser(browser)
24
+ end
25
+ end
26
+
27
+ def validate_screenshots_for_browser(browser)
28
+ puts "#{browser}"
29
+ puts ''
30
+
9
31
  @configuration.tests.each do |test|
10
- validate_screenshot(test)
32
+ validate_screenshot(browser, test)
11
33
  end
34
+
35
+ puts ''
12
36
  end
13
37
 
14
- def validate_screenshot(test)
15
- original = Janus::Screenshot.load(test, path: 'output')
16
- fresh = Janus::Screenshot.capture(test, username: @configuration.username, access_key: @configuration.access_key)
38
+ def validate_screenshot(browser, test)
39
+ selenium = Janus::IO::Selenium.new(@configuration.username, @configuration.access_key, browser)
40
+ fresh = selenium.read(test)
41
+
42
+ directory = Janus::IO::Directory.new(@configuration.directory, browser)
43
+ original = directory.read(test)
44
+
45
+ engine = Janus::Core::Engine.create(@configuration)
46
+ engine.execute(original, fresh)
17
47
 
18
- raise "#{test.name}: Screenshots did not match!" unless original.image == fresh.image
48
+ print '✔ '.green
49
+ rescue
50
+ print '✖ '.red
51
+ ensure
52
+ puts test.name
19
53
  end
20
54
  end
21
55
  end
@@ -1,4 +1,5 @@
1
1
  require 'yaml'
2
+ require 'janus/browser'
2
3
  require 'janus/test'
3
4
 
4
5
  module Janus
@@ -12,7 +13,7 @@ module Janus
12
13
  Janus::Configuration.new(options)
13
14
  end
14
15
 
15
- def initialize(options)
16
+ def initialize(options = {})
16
17
  @options = options
17
18
  end
18
19
 
@@ -24,6 +25,24 @@ module Janus
24
25
  @options['access_key']
25
26
  end
26
27
 
28
+ def tunnel?
29
+ @options['tunnel']
30
+ end
31
+
32
+ def directory
33
+ @options['directory']
34
+ end
35
+
36
+ def threshold
37
+ @options['threshold'] || 0
38
+ end
39
+
40
+ def browsers
41
+ @options['browsers'].map do |browser|
42
+ Janus::Browser.new(browser)
43
+ end
44
+ end
45
+
27
46
  def tests
28
47
  @options['tests'].map do |test|
29
48
  Janus::Test.new(test)
@@ -34,7 +53,7 @@ module Janus
34
53
 
35
54
  def self.load_configuration_file
36
55
  if File.exists?('Janusfile')
37
- YAML.load(IO.read('Janusfile'))
56
+ YAML.load(::IO.read('Janusfile'))
38
57
  else
39
58
  raise 'Could not find Janus configuration file!'
40
59
  end
@@ -0,0 +1,26 @@
1
+ require 'janus/core/rule'
2
+
3
+ module Janus
4
+ module Core
5
+ class Engine
6
+ def self.create(configuration)
7
+ engine = Engine.new
8
+ engine.add_rule(Janus::Core::DimensionsRule.new(configuration))
9
+ engine.add_rule(Janus::Core::ThresholdRule.new(configuration))
10
+ engine
11
+ end
12
+
13
+ def initialize
14
+ @rules = []
15
+ end
16
+
17
+ def add_rule(rule)
18
+ @rules << rule
19
+ end
20
+
21
+ def execute(original, fresh)
22
+ @rules.each { |r| r.execute(original, fresh) }
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,6 @@
1
+ module Janus
2
+ module Core
3
+ class ComparisonError < StandardError
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,46 @@
1
+ require 'oily_png'
2
+ require 'janus/core/error'
3
+
4
+ module Janus
5
+ module Core
6
+ class Rule
7
+ def initialize(configuration)
8
+ @configuration = configuration
9
+ end
10
+
11
+ def execute(original, fresh)
12
+ end
13
+ end
14
+
15
+ class DimensionsRule < Rule
16
+ def execute(original, fresh)
17
+ raise Janus::Core::ComparisonError unless original.dimensions == fresh.dimensions
18
+ end
19
+ end
20
+
21
+ class ThresholdRule < Rule
22
+ def execute(original, fresh)
23
+ difference = calculate_total_difference(original, fresh)
24
+ raise Janus::Core::ComparisonError unless difference <= @configuration.threshold
25
+ end
26
+
27
+ private
28
+
29
+ def calculate_total_difference(original, fresh)
30
+ pixels = original.pixels.zip(fresh.pixels)
31
+ pixel_differences = pixels.map do |a, b|
32
+ calculate_pixel_difference(a, b)
33
+ end
34
+
35
+ pixel_differences.reduce { |sum, value| sum + value } / pixels.length
36
+ end
37
+
38
+ def calculate_pixel_difference(original, fresh)
39
+ r = (ChunkyPNG::Color.r(fresh) - ChunkyPNG::Color.r(original)) ** 2
40
+ g = (ChunkyPNG::Color.g(fresh) - ChunkyPNG::Color.g(original)) ** 2
41
+ b = (ChunkyPNG::Color.b(fresh) - ChunkyPNG::Color.b(original)) ** 2
42
+ Math.sqrt(r + g + b) / Math.sqrt(ChunkyPNG::Color::MAX ** 2 * 3)
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,40 @@
1
+ require 'oily_png'
2
+ require 'janus/screenshot'
3
+
4
+ module Janus
5
+ module IO
6
+ class Directory
7
+ def initialize(directory, browser)
8
+ @directory = directory
9
+ @browser = browser
10
+ end
11
+
12
+ def read(test)
13
+ path = image_path(test)
14
+ image = ChunkyPNG::Image.from_file(path)
15
+ Janus::Screenshot.new(image)
16
+ end
17
+
18
+ def write(test, screenshot)
19
+ directory = image_directory(test)
20
+ FileUtils.mkpath(directory) unless Dir.exists?(directory)
21
+
22
+ path = image_path(test)
23
+ screenshot.image.save(path)
24
+ end
25
+
26
+ private
27
+
28
+ def image_path(test)
29
+ segments = [@browser.platform, @browser.name, @browser.version]
30
+ file_name = segments.compact.join('-')
31
+
32
+ File.join(image_directory(test), "#{file_name}.png")
33
+ end
34
+
35
+ def image_directory(test)
36
+ File.join(@directory, "#{test.name}.janus")
37
+ end
38
+ end
39
+ end
40
+ end