gatling 1.0.7 → 1.0.8
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -1
- data/Gemfile +0 -7
- data/README.rdoc +35 -27
- data/Rakefile +15 -5
- data/gatling.gemspec +9 -5
- data/lib/gatling.rb +34 -17
- data/lib/gatling/capture_element.rb +3 -2
- data/lib/gatling/comparison.rb +10 -14
- data/lib/gatling/config.rb +32 -27
- data/lib/gatling/image.rb +24 -20
- data/lib/gatling/image_wrangler.rb +2 -4
- data/lib/gatling/version.rb +1 -1
- data/spec/acceptance/gatling_acceptance_spec.rb +94 -0
- data/spec/acceptance/rspec_matcher_spec.rb +39 -0
- data/spec/comparison_spec.rb +9 -8
- data/spec/configuration_spec.rb +55 -13
- data/spec/gatling_spec.rb +64 -59
- data/spec/image_spec.rb +64 -30
- data/spec/integration/gatling_integration_spec.rb +37 -0
- data/spec/support/assets/fruit_app.html +11 -0
- metadata +55 -20
- data/.rbenv-version +0 -1
- data/lib/gatling/file_helper.rb +0 -17
- data/spec/file_helper_spec.rb +0 -18
data/.gitignore
CHANGED
data/Gemfile
CHANGED
data/README.rdoc
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
= Gatling
|
2
2
|
|
3
|
-
|
3
|
+
An integrated visual RSpec matcher which makes real visual testing easy.
|
4
4
|
|
5
5
|
-------------------------------------
|
6
6
|
|
7
|
-
|
7
|
+
== Installation:
|
8
8
|
|
9
9
|
gem install gatling
|
10
10
|
|
@@ -16,63 +16,71 @@ In spec_helper.rb :
|
|
16
16
|
#plug and play custom rspec matcher
|
17
17
|
require 'gatling/matchers/look_like_matcher'
|
18
18
|
|
19
|
-
===
|
19
|
+
=== Configuration settings:
|
20
20
|
|
21
|
-
|
21
|
+
==== Reference_image_path - sets where the reference and diff images are saved to.
|
22
22
|
|
23
|
-
|
24
|
-
|
25
|
-
Use Gatling's custom matcher (conveniently supplied in the gatling gem) and specify the reference image:
|
23
|
+
For Rails application, a default reference images folder will be created at spec/reference_images. This folder is root to all the reference
|
24
|
+
images to be compares.
|
26
25
|
|
27
|
-
|
28
|
-
|
29
|
-
|
26
|
+
Also created are subfolders:
|
27
|
+
- /candidate - will hold candidate images which can be used as reference images
|
28
|
+
- /diff - will hold the diff images generated by failed comparison tests
|
29
|
+
- /temp - will only ever hold one screenshot at a time, used to create the cropped element
|
30
30
|
|
31
|
+
=== max_no_tries - sets how many times Gatling will try and match the element against the reference image. Handy to reduce fragility of tests due to animations and load times. Defaults to 5.
|
31
32
|
|
32
|
-
|
33
|
+
=== Sleep_between_tries - sets the sleep time (in seconds) between match tries (requires max_no_tries > 1). Defaults to 0.5
|
33
34
|
|
35
|
+
=== Configuration settings are set with the following:
|
34
36
|
|
35
|
-
|
37
|
+
Gatling.config do |setting|
|
38
|
+
Gatling.reference_image_path = 'my_custom_path'
|
39
|
+
Gatling.max_no_tries = 3
|
40
|
+
Gatling.sleep_between_tries = 0.7
|
41
|
+
end
|
36
42
|
|
43
|
+
GATLING::CONFIGURATION will be depreciated in future versions.
|
37
44
|
-------------------------------------
|
45
|
+
=== Usage:
|
38
46
|
|
39
|
-
|
47
|
+
Identify an element to match, for example:
|
40
48
|
|
41
|
-
|
42
|
-
images to be compares.
|
49
|
+
@element = page.find(:css, #button)
|
43
50
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
- /temp - will only ever hold one screenshot at a time, used to create the cropped element
|
51
|
+
Use Gatling's custom matcher (conveniently supplied in the gatling gem) and specify the reference image:
|
52
|
+
|
53
|
+
@element.should look_like('button_ref.png')
|
48
54
|
|
49
|
-
|
55
|
+
Gatling will take care of cropping the element and try and make a targeted match without the noise of the whole page.
|
50
56
|
|
51
|
-
|
57
|
+
If the matcher fails, the test will fail, an exception will be raised and both a diff image and a new candidate image will be created.
|
52
58
|
|
59
|
+
If no reference image exits, the test will fail, an exception will be raised and a candidate will be created which can be used as a reference
|
60
|
+
(see Training mode below)
|
53
61
|
|
54
62
|
-------------------------------------
|
55
63
|
|
56
64
|
=== Training mode:
|
57
65
|
|
58
|
-
Gatling can be run in training mode which will create reference images where ones do not exist.
|
66
|
+
Gatling can be run in training mode which will create reference images where ones do not exist.
|
59
67
|
|
60
|
-
<b>CAUTION: Trainer mode will pass all the test which rely on the Gatling custom matcher, this can mean a false green build.
|
68
|
+
<b>CAUTION: Trainer mode will pass all the test which rely on the Gatling custom matcher, this can mean a false green build.
|
61
69
|
Please make sure you are saving 'clean' references</b>
|
62
70
|
|
63
71
|
Trainer mode will not overwrite existing references. If you are updating, just delete the old reference image.
|
64
72
|
|
65
|
-
To set trainer mode, prefix rspec command line with $GATLING_TRAINER = true
|
73
|
+
To set trainer mode, prefix rspec command line with $GATLING_TRAINER = true
|
66
74
|
|
67
75
|
|
68
|
-
Example:
|
76
|
+
Example:
|
69
77
|
$GATLING_TRAINER = true rspec spec/my_spec.rb
|
70
78
|
|
71
79
|
-------------------------------------
|
72
80
|
|
73
81
|
==== Non-gem dependencies:
|
74
82
|
|
75
|
-
Imagemagick must be installed:
|
83
|
+
Imagemagick must be installed:
|
76
84
|
|
77
85
|
$sudo apt-get install imagemagick
|
78
86
|
|
@@ -80,7 +88,7 @@ Imagemagick must be installed:
|
|
80
88
|
|
81
89
|
|
82
90
|
== Contributing to gatling
|
83
|
-
|
91
|
+
|
84
92
|
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
|
85
93
|
* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
|
86
94
|
* Fork the project
|
data/Rakefile
CHANGED
@@ -1,13 +1,23 @@
|
|
1
1
|
require 'bundler/gem_tasks'
|
2
2
|
require 'rspec/core'
|
3
|
-
require 'rspec/core/rake_task'
|
3
|
+
require 'rspec/core/rake_task'
|
4
4
|
require File.expand_path('spec/spec_helper.rb')
|
5
5
|
|
6
|
-
task :default => ["run"]
|
7
6
|
|
8
|
-
|
9
|
-
|
7
|
+
task :default => :full_build
|
8
|
+
|
9
|
+
desc "full build, run all the tests"
|
10
|
+
task :full_build => [:unit, :acceptance]
|
11
|
+
|
12
|
+
desc "Run unit tests"
|
13
|
+
RSpec::Core::RakeTask.new(:unit) do |t|
|
10
14
|
t.pattern = ['spec/*_spec.rb']
|
11
15
|
t.rspec_opts = ['--options', "spec/spec.opts"]
|
12
16
|
end
|
13
|
-
|
17
|
+
|
18
|
+
|
19
|
+
desc "Run acceptance tests"
|
20
|
+
RSpec::Core::RakeTask.new(:acceptance) do |t|
|
21
|
+
t.pattern = ['spec/acceptance/*_spec.rb']
|
22
|
+
t.rspec_opts = ['--options', "spec/spec.opts"]
|
23
|
+
end
|
data/gatling.gemspec
CHANGED
@@ -5,7 +5,7 @@ require "gatling/version"
|
|
5
5
|
Gem::Specification.new do |s|
|
6
6
|
s.name = "gatling"
|
7
7
|
s.version = Gatling::VERSION
|
8
|
-
s.authors = ["Gabriel Rotbart, Amanda Koh"]
|
8
|
+
s.authors = ["Gabriel Rotbart, Amanda Koh, Mike Bain"]
|
9
9
|
s.email = ["grotbart@gmail.com"]
|
10
10
|
s.homepage = "http://github.com/GabrielRotbart/gatling"
|
11
11
|
s.summary = %q{Automated visual testing}
|
@@ -18,8 +18,12 @@ Gem::Specification.new do |s|
|
|
18
18
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
19
|
s.require_paths = ["lib"]
|
20
20
|
|
21
|
-
s.
|
22
|
-
s.
|
23
|
-
s.
|
24
|
-
s.
|
21
|
+
s.add_runtime_dependency('rmagick', ['>=2.13.1'])
|
22
|
+
s.add_runtime_dependency('rspec-core',['>=2.8.0'])
|
23
|
+
s.add_runtime_dependency('rspec',['>=2.8.0'])
|
24
|
+
s.add_runtime_dependency('capybara',['>=1.1.2'])
|
25
|
+
|
26
|
+
s.add_development_dependency('rake',['>=0.9.2'])
|
27
|
+
s.add_development_dependency('rspec-instafail',['>=0.1.8'])
|
28
|
+
s.add_development_dependency('pry',['>=0.9.8.2'])
|
25
29
|
end
|
data/lib/gatling.rb
CHANGED
@@ -1,18 +1,12 @@
|
|
1
|
-
#Dir["/gatling/*.rb"].each {|file| require file}
|
2
|
-
|
3
1
|
require 'RMagick'
|
4
2
|
require 'capybara'
|
5
3
|
require 'capybara/dsl'
|
6
4
|
|
7
5
|
require 'gatling/config'
|
8
|
-
require 'gatling/file_helper'
|
9
6
|
require 'gatling/image'
|
10
7
|
require 'gatling/comparison'
|
11
8
|
require 'gatling/capture_element'
|
12
9
|
|
13
|
-
# include Gatling::Comparison
|
14
|
-
|
15
|
-
#TODO: Fuzz matches
|
16
10
|
#TODO: Helpers for cucumber
|
17
11
|
#TODO: Make directories as needed
|
18
12
|
|
@@ -20,39 +14,58 @@ module Gatling
|
|
20
14
|
|
21
15
|
class << self
|
22
16
|
|
17
|
+
attr_accessor :reference_image_path, :max_no_tries, :sleep_between_tries
|
18
|
+
|
23
19
|
def matches?(expected_reference_filename, actual_element)
|
24
20
|
|
25
|
-
Gatling::
|
21
|
+
expected_reference_file = (File.join(Gatling::Configuration.path(:reference), expected_reference_filename))
|
26
22
|
|
27
|
-
expected_reference_file = (File.join(Gatling::Configuration.paths[:reference], expected_reference_filename))
|
28
|
-
actual_image = Gatling::Image.new(:from_element, expected_reference_filename, actual_element)
|
29
23
|
|
30
24
|
if Gatling::Configuration.trainer_toggle
|
25
|
+
actual_image = Gatling::ImageFromElement.new(actual_element, expected_reference_filename)
|
31
26
|
save_image_as_reference(actual_image)
|
32
27
|
return true
|
33
28
|
end
|
34
29
|
|
35
30
|
if !File.exists?(expected_reference_file)
|
31
|
+
actual_image = Gatling::ImageFromElement.new(actual_element, expected_reference_filename)
|
36
32
|
save_image_as_candidate(actual_image)
|
37
33
|
return false
|
38
34
|
else
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
actual_image.save(:as => :candidate)
|
35
|
+
comparison = compare_until_match(actual_element, expected_reference_filename, Gatling::Configuration.max_no_tries)
|
36
|
+
matches = comparison.matches?
|
37
|
+
if !matches
|
38
|
+
comparison.actual_image.save(:as => :candidate)
|
43
39
|
save_image_as_diff(comparison.diff_image)
|
44
40
|
end
|
45
|
-
|
41
|
+
matches
|
46
42
|
end
|
47
43
|
end
|
48
44
|
|
49
|
-
|
45
|
+
def compare_until_match actual_element, expected_reference_filename, max_no_tries
|
46
|
+
tries = max_no_tries
|
47
|
+
try = 0
|
48
|
+
match = false
|
49
|
+
expected_image = Gatling::ImageFromFile.new(expected_reference_filename)
|
50
|
+
comparison = nil
|
51
|
+
while !match && try < tries
|
52
|
+
actual_image = Gatling::ImageFromElement.new(actual_element, expected_reference_filename)
|
53
|
+
comparison = Gatling::Comparison.new(expected_image, actual_image)
|
54
|
+
match = comparison.matches?
|
55
|
+
if !match
|
56
|
+
sleep 0.5
|
57
|
+
try += 1
|
58
|
+
puts "Tried to match #{try} times"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
comparison
|
62
|
+
end
|
50
63
|
|
51
64
|
def save_image_as_diff(image)
|
52
65
|
image.save(:as => :diff)
|
53
66
|
raise "element did not match #{image.file_name}. A diff image: #{image.file_name} was created in " +
|
54
|
-
"#{File.join(Gatling::Configuration.
|
55
|
-
"A new reference #{File.join(Gatling::Configuration.
|
67
|
+
"#{File.join(Gatling::Configuration.path(:diff),image.file_name)}. " +
|
68
|
+
"A new reference #{File.join(Gatling::Configuration.path(:candidate),image.file_name)} can be used to fix the test"
|
56
69
|
end
|
57
70
|
|
58
71
|
def save_image_as_candidate(image)
|
@@ -70,5 +83,9 @@ module Gatling
|
|
70
83
|
end
|
71
84
|
end
|
72
85
|
|
86
|
+
def config
|
87
|
+
yield
|
88
|
+
end
|
89
|
+
|
73
90
|
end
|
74
91
|
end
|
@@ -3,15 +3,16 @@ require_relative 'image_wrangler'
|
|
3
3
|
module Gatling
|
4
4
|
class CaptureElement
|
5
5
|
|
6
|
+
include ImageWrangler
|
7
|
+
|
6
8
|
def initialize element_to_capture, *element_to_exclude
|
7
9
|
@reference_image_path = Gatling::Configuration.reference_image_path
|
8
10
|
@element_to_capture = element_to_capture
|
9
|
-
@element_to_exclude = element_to_exclude.first
|
11
|
+
# @element_to_exclude = element_to_exclude.first
|
10
12
|
|
11
13
|
end
|
12
14
|
|
13
15
|
def capture
|
14
|
-
|
15
16
|
screenshot = self.take_screenshot
|
16
17
|
screenshot = exclude(screenshot, @element_to_exclude) if @element_to_exclude
|
17
18
|
Gatling::ImageWrangler.crop_element(screenshot, @element_to_capture)
|
data/lib/gatling/comparison.rb
CHANGED
@@ -1,24 +1,20 @@
|
|
1
1
|
module Gatling
|
2
2
|
class Comparison
|
3
3
|
|
4
|
-
attr_accessor :
|
4
|
+
attr_accessor :diff_image, :actual_image, :expected_image
|
5
5
|
|
6
|
-
def initialize
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
unless @match
|
14
|
-
diff_image = diff_metric.first
|
15
|
-
@diff_image = Gatling::Image.new(:from_diff, actual_image.file_name, diff_image)
|
6
|
+
def initialize actual_image, expected_image
|
7
|
+
@actual_image = actual_image
|
8
|
+
@expected_image = expected_image
|
9
|
+
@comparison = @actual_image.image.compare_channel(@expected_image.image, Magick::MeanAbsoluteErrorMetric)
|
10
|
+
@match = @comparison[1] == 0.0
|
11
|
+
if !@matches
|
12
|
+
@diff_image =Gatling::Image.new(@comparison.first, @expected_image.file_name)
|
16
13
|
end
|
17
|
-
@match
|
18
14
|
end
|
19
15
|
|
20
|
-
def
|
21
|
-
@
|
16
|
+
def matches?
|
17
|
+
@match
|
22
18
|
end
|
23
19
|
|
24
20
|
end
|
data/lib/gatling/config.rb
CHANGED
@@ -1,41 +1,29 @@
|
|
1
1
|
module Gatling
|
2
|
-
|
2
|
+
module Configuration
|
3
3
|
|
4
4
|
class << self
|
5
5
|
|
6
|
-
attr_accessor :reference_image_path, :trainer_toggle
|
6
|
+
attr_accessor :reference_image_path, :trainer_toggle, :max_no_tries, :sleep_between_tries
|
7
7
|
|
8
8
|
attr_reader :paths
|
9
9
|
|
10
10
|
def reference_image_path
|
11
|
-
@reference_image_path ||= set_default_path
|
11
|
+
Gatling.reference_image_path || @reference_image_path ||= set_default_path
|
12
12
|
end
|
13
13
|
|
14
|
-
def
|
15
|
-
|
16
|
-
candidate:File.join(reference_image_path, 'candidate'),
|
17
|
-
diff:File.join(reference_image_path, 'diff'),
|
18
|
-
temp:File.join(reference_image_path, 'temp')]
|
14
|
+
def max_no_tries
|
15
|
+
Gatling.max_no_tries || @max_no_tries ||= 5
|
19
16
|
end
|
20
17
|
|
21
|
-
def
|
22
|
-
@
|
23
|
-
|
24
|
-
case @trainer_value
|
25
|
-
when nil
|
26
|
-
@trainer_value = nil
|
27
|
-
when 'true'
|
28
|
-
@trainer_value = true
|
29
|
-
when 'false'
|
30
|
-
@trainer_value = false
|
31
|
-
else
|
32
|
-
@trainer_value = false
|
33
|
-
puts 'Unknown GATLING_TRAINER argument. Please supply true, false or nil. DEFAULTING TO FALSE'
|
34
|
-
end
|
35
|
-
@trainer_toggle ||= @trainer_value ||= false
|
18
|
+
def sleep_between_tries
|
19
|
+
Gatling.sleep_between_tries || @sleep_between_tries ||= 0.5
|
36
20
|
end
|
37
21
|
|
38
|
-
def
|
22
|
+
def path(type)
|
23
|
+
paths = Hash[:reference => reference_image_path,
|
24
|
+
:candidate => File.join(reference_image_path, 'candidate'),
|
25
|
+
:diff => File.join(reference_image_path, 'diff'),
|
26
|
+
:temp => File.join(reference_image_path, 'temp')]
|
39
27
|
if paths.keys.include? type
|
40
28
|
return paths[type]
|
41
29
|
else
|
@@ -43,11 +31,28 @@ module Gatling
|
|
43
31
|
end
|
44
32
|
end
|
45
33
|
|
46
|
-
|
34
|
+
def trainer_toggle
|
35
|
+
@trainer_value = ENV['GATLING_TRAINER']
|
36
|
+
|
37
|
+
case @trainer_value
|
38
|
+
when nil
|
39
|
+
@trainer_value = nil
|
40
|
+
when 'true'
|
41
|
+
@trainer_value = true
|
42
|
+
when 'false'
|
43
|
+
@trainer_value = false
|
44
|
+
else
|
45
|
+
@trainer_value = false
|
46
|
+
puts 'Unknown GATLING_TRAINER argument. Please supply true, false or nil. DEFAULTING TO FALSE'
|
47
|
+
end
|
48
|
+
@trainer_toggle ||= @trainer_value ||= false
|
49
|
+
end
|
50
|
+
|
47
51
|
|
48
52
|
def set_default_path
|
53
|
+
private
|
49
54
|
begin
|
50
|
-
|
55
|
+
@reference_image_path ||= File.join(Rails.root, 'spec/reference_images')
|
51
56
|
rescue
|
52
57
|
@reference_image_path = 'spec/reference_images'
|
53
58
|
puts "Currently defaulting to #{@reference_image_path}. Overide this by setting Gatling::Configuration.reference_image_path=[refpath]"
|
@@ -57,4 +62,4 @@ module Gatling
|
|
57
62
|
end
|
58
63
|
|
59
64
|
end
|
60
|
-
end
|
65
|
+
end
|
data/lib/gatling/image.rb
CHANGED
@@ -5,46 +5,50 @@ module Gatling
|
|
5
5
|
|
6
6
|
attr_reader :type
|
7
7
|
|
8
|
-
def initialize
|
8
|
+
def initialize image, file_name
|
9
9
|
|
10
10
|
@file_name = file_name
|
11
11
|
|
12
|
-
|
13
|
-
when :from_file
|
14
|
-
@image = image_from_file(file_name)
|
15
|
-
when :from_element
|
16
|
-
@image = image_from_element(element_or_image)
|
17
|
-
when :from_diff
|
18
|
-
@image = element_or_image
|
19
|
-
else
|
20
|
-
raise 'WRONG IMAGE TYPE'
|
21
|
-
end
|
12
|
+
@image = image
|
22
13
|
|
23
14
|
end
|
24
15
|
|
25
16
|
def save type
|
26
|
-
|
17
|
+
path = Gatling::Configuration.path(type[:as])
|
18
|
+
FileUtils::mkdir_p(path) unless File.exists?(path)
|
19
|
+
@path = File.join(path, @file_name)
|
27
20
|
@image.write @path
|
28
21
|
@path
|
29
22
|
end
|
30
23
|
|
31
24
|
def exists?
|
32
|
-
File.exists?(File.join(Gatling::Configuration.
|
25
|
+
File.exists?(File.join(Gatling::Configuration.path(:reference), @file_name))
|
33
26
|
end
|
34
27
|
|
35
|
-
|
28
|
+
end
|
36
29
|
|
37
|
-
|
38
|
-
Gatling::CaptureElement.new(element).capture
|
39
|
-
end
|
30
|
+
class ImageFromElement < Image
|
40
31
|
|
41
|
-
def
|
42
|
-
|
32
|
+
def initialize element, file_name
|
33
|
+
super(image, file_name)
|
34
|
+
|
35
|
+
@image = Gatling::CaptureElement.new(element).capture
|
43
36
|
end
|
37
|
+
|
38
|
+
#TODO: make save a relevant subclass method
|
39
|
+
end
|
44
40
|
|
41
|
+
class ImageFromFile < Image
|
45
42
|
|
46
|
-
|
43
|
+
def initialize file_name
|
44
|
+
super(image, file_name)
|
45
|
+
|
46
|
+
@image = Magick::Image.read(File.join(Gatling::Configuration.path(:reference), @file_name)).first
|
47
|
+
end
|
47
48
|
|
49
|
+
#TODO: make save a relevant subclass method
|
50
|
+
|
51
|
+
end
|
48
52
|
end
|
49
53
|
|
50
54
|
|