lineup 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: ca168f6253bc6111e22696d8b4f72e5a468ca804
4
+ data.tar.gz: bc2ffad3a74d03b3dbff81e5f980d81fd1c76644
5
+ SHA512:
6
+ metadata.gz: a87243428920002cd40dc99f824d97dc98630dccd022cc579e3dea71718598b7242fab291abf0b85dac13a86596b9dd0b394c1644ce48d32bda1b73badcd4592
7
+ data.tar.gz: 10ea27a5f54e0cb04ed9da5881ba276978d599c29be3c38dfb7fb983fd11349365746ac7cf93e70aaa630a48979da66688f95c355a2e4359b82cc6cf4e92935f
data/.gitignore ADDED
@@ -0,0 +1,13 @@
1
+ lib/images/**
2
+ .idea/
3
+ out.html
4
+ screenshots/
5
+ *.gem
6
+
7
+ *~
8
+ chromedriver.log
9
+ .ruby-version
10
+ .DS_Store
11
+ .kate-swp
12
+ .ruby-gemset
13
+ .swp
data/Gemfile ADDED
@@ -0,0 +1,12 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ gem 'rspec'
6
+ gem 'pxdoppelganger', '0.1.1'
7
+ gem 'selenium-webdriver'
8
+ gem 'watir-webdriver'
9
+ gem 'watir'
10
+ gem 'headless'
11
+ gem 'dimensions'
12
+ gem 'oily_png'
data/Gemfile.lock ADDED
@@ -0,0 +1,62 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ lineup (0.1.0)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ childprocess (0.5.5)
10
+ ffi (~> 1.0, >= 1.0.11)
11
+ chunky_png (1.3.4)
12
+ commonwatir (4.0.0)
13
+ diff-lcs (1.2.5)
14
+ dimensions (1.3.0)
15
+ ffi (1.9.6)
16
+ headless (2.0.0)
17
+ multi_json (1.11.0)
18
+ oily_png (1.2.0)
19
+ chunky_png (~> 1.3.1)
20
+ pxdoppelganger (0.1.1)
21
+ rspec (3.2.0)
22
+ rspec-core (~> 3.2.0)
23
+ rspec-expectations (~> 3.2.0)
24
+ rspec-mocks (~> 3.2.0)
25
+ rspec-core (3.2.3)
26
+ rspec-support (~> 3.2.0)
27
+ rspec-expectations (3.2.1)
28
+ diff-lcs (>= 1.2.0, < 2.0)
29
+ rspec-support (~> 3.2.0)
30
+ rspec-mocks (3.2.1)
31
+ diff-lcs (>= 1.2.0, < 2.0)
32
+ rspec-support (~> 3.2.0)
33
+ rspec-support (3.2.2)
34
+ rubyzip (1.1.7)
35
+ selenium-webdriver (2.46.2)
36
+ childprocess (~> 0.5)
37
+ multi_json (~> 1.0)
38
+ rubyzip (~> 1.0)
39
+ websocket (~> 1.0)
40
+ watir (5.0.0)
41
+ commonwatir (~> 4)
42
+ watir-webdriver
43
+ watir-webdriver (0.7.0)
44
+ selenium-webdriver (>= 2.45)
45
+ websocket (1.2.1)
46
+
47
+ PLATFORMS
48
+ ruby
49
+
50
+ DEPENDENCIES
51
+ dimensions
52
+ headless
53
+ lineup!
54
+ oily_png
55
+ pxdoppelganger (= 0.1.1)
56
+ rspec
57
+ selenium-webdriver
58
+ watir
59
+ watir-webdriver
60
+
61
+ BUNDLED WITH
62
+ 1.10.6
data/README.md ADDED
@@ -0,0 +1,143 @@
1
+ # Lineup
2
+
3
+ Lineup is doing automated testing of webpage designs, eg. in continious delivery.
4
+ If you push new code to production, you can evaluate the design of you page compared to a defined base design and
5
+ get an analysis about the difference of the designs:
6
+
7
+ For all images that you want to compare, you will receive information about how many pixel are different
8
+ between the two image version and an image, that contains only the parts, that changed, a "difference image".
9
+
10
+ ![Example view of base (left), new (right) as well as diff image.](doc/example.png)
11
+ Picture: Example view of base (left), new (right) as well as diff image. In this example, the margin around the bottom headline increased,
12
+ thus some of the elements moved down.
13
+
14
+ ## Requirements
15
+
16
+ A firefox browser must be installed, as well as phantomjs.
17
+
18
+ ## Usage
19
+
20
+ Add it into your Gemfile:
21
+ ````ruby
22
+ gem "lineup"
23
+ ````
24
+
25
+ Or install it manually with the following command:
26
+ ````
27
+ gem install lineup
28
+ ````
29
+
30
+ Do a base reference screenshot of you application:
31
+ ````ruby
32
+ require 'lineup'
33
+ lineup = Lineup::Screenshot.new('https://www.otto.de')
34
+ lineup.record_screenshot('base')
35
+ ````
36
+
37
+ Do something (deployment of your new code) and take a new screenshot
38
+ ````ruby
39
+ lineup.record_screenshot('new')
40
+ ````
41
+
42
+ Analyse the results:
43
+ ````ruby
44
+ lineup.compare('new', 'base')
45
+ => [{:url => 'sport', :width => 600, :difference => 0.7340442722738748,
46
+ :base_file => '/home/name/lineup/screenshots/base_frontpage_600.png'
47
+ :new_file => '/home/name/lineup/screenshots/new_frontpage_600.png'
48
+ :diff_file => '/home/name/lineup/screenshots/DIFFERENCE_frontpage_600.png' }]
49
+ ````
50
+
51
+ You can save it for later use:
52
+ ````ruby
53
+ lineup.save_json('/home/name/lineup/results/')
54
+ => '/home/name/lineup/results/json.log'
55
+ ````
56
+
57
+ ## More configuration:
58
+
59
+ There are multiple ways to specify what to lineup and compare.
60
+
61
+ By specifying different urls via ````#urls````:
62
+ ````ruby
63
+ lineup = Lineup::Screenshot.new('https://www.otto.de')
64
+ lineup.urls("/, /multimedia, /sport")
65
+ ````
66
+ This will do analysis of otto.de root (frontpage), otto.de/multimedia and otto.de/sport.
67
+ It requires a comma separated string. Default value is only root.
68
+
69
+ By specifying different resolutions via ````#resolutions````:
70
+ ````ruby
71
+ lineup = Lineup::Screenshot.new('https://www.otto.de')
72
+ lineup.resolutions("600, 800, 1200")
73
+ ````
74
+ The values are the browser width in pixel. For each size an analysis is done.
75
+ It require a comma separated string. Default values are 640px, 800px and 1180px.
76
+
77
+ By specifying a filepath for the screenshots via ````#filepath_for_images````:
78
+ ````ruby
79
+ lineup = Lineup::Screenshot.new('https://www.otto.de')
80
+ lineup.filepath_for_images(/home/myname/lineup/screenshots)
81
+ ````
82
+ Creates a file and saves the screenshots in the file. Default is ````"#{Dir.pwd}/screenshots"````
83
+
84
+ By specifying a filepath for the difference image via ````#difference_path````:
85
+ ````ruby
86
+ lineup = Lineup::Screenshot.new('https://www.otto.de')
87
+ lineup.difference_path(/home/myname/lineup/result)
88
+ ````
89
+ Creates a file and saves the difference image in the file. Default is ````"#{Dir.pwd}/screenshots"````
90
+
91
+ By specifying wether or not to use phantomjs via ````#use_phantomjs````:
92
+ ````ruby
93
+ lineup = Lineup::Screenshot.new('https://www.otto.de')
94
+ lineup.use_phantomjs(true)
95
+ ````
96
+ If ````false```` the screenshots are taken in Firefox. ````#load_json_config````:
97
+
98
+ Load all above configs from a json file via
99
+ ````ruby
100
+ lineup = Lineup::Screenshot.new('https://www.otto.de')
101
+ lineup.load_json_config(/home/myname/lineup/config.json)
102
+ ````
103
+ While my file contains all relevant information
104
+ ````json
105
+ {
106
+ "urls":"/multimedia, /sport",
107
+ "resolutions":"600,800,1200",
108
+ "filepath_for_images":"~/images/",
109
+ "use_headless":true,
110
+ "difference_path":"#/images/diff"
111
+ }
112
+ ````
113
+
114
+ ## Example:
115
+
116
+ ````ruby
117
+ base_name = 'name-for-base-screenshot'
118
+ new_name = 'name-for-new-screenshot'
119
+ urls = '/, multimedia, sport'
120
+ resolutions = '600, 800, 1200'
121
+ images_path = '/home/myname/lineup/screenshots'
122
+ difference_path = '/home/myname/lineup/results'
123
+ json_path = 'home/myname/lineup/results'
124
+ phantom = true
125
+
126
+ lineup = Lineup::Screenshot.new('https://www.otto.de')
127
+ lineup.urls(urls)
128
+ lineup.resolutions(resolutions)
129
+ lineup.filepath_for_images(images_path
130
+ lineup.difference_path(difference_path)
131
+
132
+ lineup.record_screenshot(base_name)
133
+ # do sth. (eg. deploy new software)
134
+ lineup.record_screenshot(new_name)
135
+ lineup.save_json(json_path)
136
+ ````
137
+ Now open home/myname/lineup/results and find:
138
+ the difference files and a log.json with all information about what images are not the same.
139
+
140
+ ## Contribute
141
+
142
+ Please do!
143
+
data/bin/lineup ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'lineup'
4
+
5
+ lineup = Lineup::Screenshot.new('otto.de')
6
+ lineup.use_phantomjs false
7
+ lineup.record_screenshot('base')
data/doc/example.png ADDED
Binary file
@@ -0,0 +1,58 @@
1
+ require 'watir'
2
+ require 'watir-webdriver'
3
+ include Selenium
4
+ require 'fileutils'
5
+ require 'headless'
6
+ require_relative '../helper'
7
+
8
+ class Browser
9
+
10
+ def initialize(baseurl, urls, resolutions, path, headless)
11
+ @absolute_image_path = path
12
+ FileUtils.mkdir_p @absolute_image_path
13
+ @baseurl = baseurl
14
+ @urls = urls
15
+ @resolutions = resolutions
16
+ @headless = headless
17
+ end
18
+
19
+ def record(version)
20
+ browser_loader
21
+ @urls.each do |url|
22
+ @resolutions.each do |width|
23
+ screenshot_recorder(width, url, version)
24
+ end
25
+ end
26
+ end
27
+
28
+ def end
29
+ begin #Timeout::Error
30
+ Timeout::timeout(10) { @browser.close }
31
+ rescue Timeout::Error
32
+ browser_pid = @browser.driver.instance_variable_get(:@bridge).instance_variable_get(:@service).instance_variable_get(:@process).pid
33
+ ::Process.kill('KILL', browser_pid)
34
+ sleep 1
35
+ end
36
+ sleep 5 # to prevent xvfb to freeze
37
+ end
38
+
39
+ private
40
+
41
+ def browser_loader
42
+ if @headless
43
+ @browser = Watir::Browser.new :phantomjs
44
+ else
45
+ @browser = Watir::Browser.new :firefox
46
+ end
47
+ end
48
+
49
+ def screenshot_recorder(width, url, version)
50
+ filename = Helper.filename(@absolute_image_path, url, width, version)
51
+ @browser.driver.manage.window.resize_to(width, 1000)
52
+ @browser.cookies.clear
53
+ url = Helper.url(@baseurl, url)
54
+ @browser.goto url
55
+ @browser.screenshot.save( File.expand_path(filename))
56
+ end
57
+
58
+ end
@@ -0,0 +1,79 @@
1
+ require 'pxdoppelganger'
2
+ require 'json'
3
+
4
+ class Comparer
5
+
6
+ attr_accessor :difference
7
+
8
+ def initialize(base, new, difference_path, baseurl, urls, resolutions, screenshot_path)
9
+ @base = base
10
+ @new = new
11
+ @baseurl = baseurl
12
+ @urls = urls
13
+ @resolutions = resolutions
14
+ @absolute_image_path = screenshot_path
15
+ @difference_path = difference_path
16
+ FileUtils.mkdir_p difference_path
17
+ compare_images
18
+ end
19
+
20
+ def json(path)
21
+ FileUtils.mkdir_p path
22
+ generate_json(path)
23
+ end
24
+
25
+ private
26
+
27
+ def compare_images
28
+ self.difference = []
29
+ @urls.each do |url|
30
+ @resolutions.each do |width|
31
+ base_name = Helper.filename(
32
+ @absolute_image_path,
33
+ url,
34
+ width,
35
+ @base
36
+ )
37
+ new_name = Helper.filename(
38
+ @absolute_image_path,
39
+ url,
40
+ width,
41
+ @new
42
+ )
43
+ images = PXDoppelganger::Images.new(
44
+ base_name,
45
+ new_name
46
+ )
47
+ if images.difference > 1e-03 # for changes bigger than 1 per 1.000; otherwise we see mathematical artifacts
48
+ diff_name = Helper.filename(
49
+ @difference_path,
50
+ url,
51
+ width,
52
+ 'DIFFERENCE'
53
+ )
54
+ images.save_difference_image diff_name
55
+ result = {
56
+ url: url,
57
+ width: width,
58
+ difference: images.difference,
59
+ base_file: base_name,
60
+ new_file: new_name,
61
+ difference_file: diff_name
62
+ }
63
+ self.difference << result
64
+ end
65
+ end
66
+ end
67
+ end
68
+
69
+ def generate_json(path)
70
+ json = difference.to_json
71
+ file = File.open(
72
+ "#{path}/log.json", "a+"
73
+ )
74
+ file.write(json)
75
+ file.close
76
+ end
77
+
78
+
79
+ end
data/lib/helper.rb ADDED
@@ -0,0 +1,31 @@
1
+ module Helper
2
+ extend self
3
+
4
+ def filename(path, url, width, version)
5
+ "#{path}/#{version}_#{name(url)}_#{width}.png"
6
+ end
7
+
8
+ def url(base, url)
9
+ ("#{base}/#{clean(url)}")
10
+ end
11
+
12
+ private
13
+
14
+ def name(page)
15
+ if page == '/'
16
+ name = 'frontpage'
17
+ else #remove forward slash
18
+ name = page.gsub(/\//, "")
19
+ end
20
+ name
21
+ end
22
+
23
+ def clean(url)
24
+ if url == '/' #avoid two dashes at the end, e.g. www.otto.de//
25
+ ''
26
+ else
27
+ url
28
+ end
29
+ end
30
+
31
+ end
data/lib/lineup.rb ADDED
@@ -0,0 +1,275 @@
1
+ require 'rubygems'
2
+ require_relative 'controller/browser'
3
+ require_relative 'controller/comparer'
4
+
5
+ module Lineup
6
+
7
+ attr_accessor :difference
8
+
9
+ class Screenshot
10
+
11
+ def initialize(baseurl)
12
+
13
+ # the base URL is the root url (in normal projects the frontpage or for us storefront)
14
+ # while the base url is passed in, defaults for other values are set, too
15
+ #
16
+ # for us the base url is https://www.otto.
17
+ #
18
+ # the base url needs to be a string and cannot be an empty sting
19
+
20
+ raise "base URL needs to be a string" unless baseurl.is_a? String
21
+ raise "base URL is needed, cannot be empty" if baseurl == ''
22
+
23
+ @baseurl = baseurl
24
+
25
+ # the urls are combined with the root url and give the absolute url of the pages to be tested
26
+ # see more in the according method below
27
+ # the default value is the baseurl itself, represented by a forward slash
28
+ # the images will be saved as "frontpage" images
29
+
30
+ urls('/')
31
+
32
+ # the resolutions can be as many as desired, we use a mobile, a tablet and a desktop resolution
33
+ # by default this is 640px, 800px and 1180px width
34
+ # see more in the according method below
35
+
36
+ resolutions('640, 800, 1180')
37
+
38
+ # this sets the path where to store the screenshots, by default this is the current directory
39
+ # see more in the according method below
40
+
41
+ filepath_for_images("#{Dir.pwd}/screenshots")
42
+
43
+ # using selenium in a headless environment vs firefox.
44
+ # by default in headless
45
+ # see more in according method below
46
+
47
+ use_phantomjs(true)
48
+
49
+ # this is the path where to save the difference images of two not alike screenshots
50
+ # by default the current directory, like for the other images
51
+ # see more in according method below
52
+
53
+ difference_path("#{Dir.pwd}/screenshots")
54
+ end
55
+
56
+
57
+ def urls(urls)
58
+
59
+ # all urls to be tested are defined here
60
+ # they need to be passed as a comma separated string (with or without whitespaces)
61
+ #
62
+ # e.g "/, /multimedia, /sport"
63
+ #
64
+ # the pages are used to name the image files, too
65
+ #
66
+ # if it is not a string or the string is empty an exception is raised
67
+
68
+ raise "url for screenshots needs to be a string" unless urls.is_a? String
69
+ raise "url for screenshots cannot be <empty string>" if urls == ''
70
+
71
+ # after the base screenshots are taken, the urls cannot be changed, an exception would be raised
72
+
73
+ raise_base_screenshots_taken('The urls')
74
+
75
+ #we remove whitespaces from the urls, replace ; by , and generate an array, splitted by comma
76
+
77
+ begin
78
+ @urls= clean(urls).split(",")
79
+ rescue NoMethodError
80
+ raise "urls must be in a comma separated string"
81
+ end
82
+ end
83
+
84
+
85
+
86
+ def resolutions(resolutions)
87
+
88
+ # all resolutions to be tested are defined here
89
+ # they need to be passed as a comma separated string (with or without whitespaces)
90
+ #
91
+ # e.g "400, 800, 1200"
92
+ #
93
+ # if its not a string or the string is empty an exception is raised
94
+
95
+ raise "resolutions for screenshots needs to be a string" unless resolutions.is_a? String
96
+ raise "the resolutions for screenshot cannot be <empty string>" if resolutions == ''
97
+
98
+ # after the base screenshots are taken, the resolutions cannot be changed, an exception would be raised
99
+
100
+ raise_base_screenshots_taken('The resolutions')
101
+
102
+ #we remove whitespaces from the urls, replace ; by , and generate an array of integers
103
+
104
+ begin
105
+ @resolutions = clean(resolutions).split(",").map { |s| s.to_i }
106
+ rescue NoMethodError
107
+ raise "resolutions must be in a comma separated string"
108
+ end
109
+ end
110
+
111
+
112
+
113
+ def filepath_for_images(path)
114
+
115
+ # if required an absolute path to store all images can be passed here.
116
+ # at the path a file "screenshots" will be generated
117
+ #
118
+ # e.g '/home/finn/pictures/otto'
119
+ #
120
+ # if its not a string or the string is empty an exception is raised
121
+
122
+ raise "path for screenshots needs to be a string" unless path.is_a? String
123
+ raise "the path for the screenshots cannot be <empty string>" if path == ''
124
+
125
+ # after the base screenshots are taken, the path cannot be changed, an exception would be raised
126
+
127
+ raise_base_screenshots_taken('The path')
128
+
129
+ # the path is one string. we just assign the variable
130
+
131
+ @screenshots_path = path
132
+ end
133
+
134
+
135
+
136
+ def use_phantomjs(boolean)
137
+
138
+ # if required the headless environment can we skipped and firefox used for the screenshots
139
+ #
140
+ # e.g use_headless = false
141
+ #
142
+ # if its not a boolean an exception is raised
143
+
144
+ raise "use_headless can only be true or false" unless boolean == !!boolean
145
+
146
+ # after the base screenshots are taken, the browser cannot be changed, an exception would be raised
147
+
148
+ raise_base_screenshots_taken('The browser type (headless)')
149
+
150
+ # sometimes packages are missing on ubuntu to run the headless environment, installing these should resolve it:
151
+ # sudo apt-get install -y xfonts-100dpi xfonts-75dpi xfonts-scalable xfonts-cyrillic xvfb x11-apps imagemagick
152
+
153
+ @headless = boolean
154
+ end
155
+
156
+
157
+
158
+ def difference_path(path)
159
+
160
+ # if required an absolute path to store all difference images can be passed here.
161
+ # in most usecases you may want to save them along with the base and new images
162
+ #
163
+ # e.g '/home/finn/pictures/otto'
164
+ #
165
+ # if its not a string or the string is empty an exception is raised
166
+
167
+ raise "path for difference images needs to be a string" unless path.is_a? String
168
+ raise "the path for the difference images cannot be <empty string>" if path == ''
169
+
170
+ # assign the variable
171
+
172
+ @difference_path = path
173
+ end
174
+
175
+
176
+
177
+ def load_json_config(path)
178
+
179
+ # loads all possible configs from a json file.
180
+ # in this file all parameters need to be set
181
+ # an example configuration is
182
+ # '{"urls":"/multimedia, /sport","resolutions":"600,800,1200","filepath_for_images":"~/images/","use_headless":true,"difference_path":"#/images/diff"}'
183
+
184
+ #open the file and parse JSON format
185
+ configuration = JSON.parse(File.read(path))
186
+
187
+ # write to method above
188
+ urls(configuration["urls"])
189
+ resolutions(configuration["resolutions"])
190
+ filepath_for_images(configuration["filepath_for_images"])
191
+ use_phantomjs(configuration["use_phantomjs"])
192
+ difference_path(configuration["difference_path"])
193
+
194
+ # the method calls set the variables for the parameters, we return an array with all of them.
195
+ # for the example above it is:
196
+ # [["/multimedia", "/sport"], [600, 800, 1200], "~/images/", true, "#/images/diff"]
197
+ [@urls, @resolutions, @screenshots_path, @headless, @difference_path]
198
+ end
199
+
200
+
201
+
202
+ def record_screenshot(version)
203
+
204
+ # to take a screenshot we have all parameters given from the methods above (or set to default values)
205
+ # selenium is started in
206
+ # @headless or firefox
207
+ # and takes a screenshot of the urls
208
+ # @baseurl/@url[0], @baseurl/@url[1], etc...
209
+ # and takes a screenshot for each url for all given resolutions
210
+ # @resolutions[0], @resolutions[1], etc...
211
+ # and saves the screenshot in the file
212
+ # @screenshot_path
213
+
214
+ browser = Browser.new(@baseurl, @urls, @resolutions, @screenshots_path, @headless)
215
+
216
+ # the only argument missing is if this is the "base" or "new" screenshot, this can be
217
+ # passed as an argument. The value does not need to be "base" or "new", but can be anything
218
+
219
+ browser.record(version)
220
+
221
+ # this will close the browser and terminate the headless environment
222
+
223
+ browser.end
224
+
225
+ # this flag is set, so that parameters like resolution or urls cannot be changed any more
226
+
227
+ @got_base_screenshots = true
228
+ end
229
+
230
+
231
+
232
+ def compare(base, new)
233
+
234
+ # this compares two previously taken screenshots
235
+ # the "base" and "new" variable need to be the same as previously assigned
236
+ # as "variable" in the method "record_screenshot"!
237
+ # all other information are constants and are passed along
238
+
239
+ @comparer = Comparer.new(base, new, @difference_path, @baseurl, @urls, @resolutions, @screenshots_path)
240
+
241
+ # this gives back an array, which as one element for each difference image.
242
+ # [ {diff_1}, {diff_2}, ...]
243
+ # while each diff is a hash with keys:
244
+ # {url: <url>, width: <width in px>, difference: <%of changed pixel>, base_file: <file path>, new_file: <file path>, diff_file: <file path>}
245
+
246
+ @comparer.difference
247
+ end
248
+
249
+ def save_json(path)
250
+
251
+ # json output can be saved if needed. A path is required to save the file
252
+
253
+ raise "screenshots need to be compared before json output can be gernerated" unless @comparer
254
+
255
+ # the array from @comparer.difference is saved as json
256
+
257
+ @comparer.json(path)
258
+
259
+ end
260
+
261
+ private
262
+
263
+ def raise_base_screenshots_taken(value)
264
+ if @got_base_screenshots
265
+ raise ArgumentError, "#{value} cannot be changed after first set of screenshots were taken"
266
+ end
267
+ end
268
+
269
+ def clean(urls)
270
+ urls.gsub(' ', '').gsub(';',',')
271
+ end
272
+
273
+ end
274
+
275
+ end
@@ -0,0 +1,13 @@
1
+ module Lineup
2
+ class Version
3
+ MAJOR = 0
4
+ MINOR = 1
5
+ PATCH = 0
6
+
7
+ class << self
8
+ def to_s
9
+ [MAJOR, MINOR, PATCH].join('.')
10
+ end
11
+ end
12
+ end
13
+ end
data/lineup.gemspec ADDED
@@ -0,0 +1,16 @@
1
+ require File.expand_path('../lib/lineup/version', __FILE__)
2
+
3
+ Gem::Specification.new do |gem|
4
+ gem.authors = %w(Finn Lorbeer)
5
+ gem.version = Lineup::Version
6
+ gem.name = 'lineup'
7
+ gem.platform = Gem::Platform::RUBY
8
+ gem.require_paths = %w(lib)
9
+ gem.license = 'MIT'
10
+ gem.email = %w(finn.von.friesland@googlemail.com)
11
+ gem.summary = "lineup will help you in your automated design regression testing"
12
+ gem.description = %q{lineup takes to screenshots of your app and compares them to references in order to find design flaws in your new code.}
13
+ gem.homepage = 'https://www.otto.de'
14
+ gem.files = `git ls-files`.split("\n")
15
+
16
+ end
@@ -0,0 +1,217 @@
1
+ require 'rspec'
2
+ require 'dimensions'
3
+ require 'fileutils'
4
+ require 'json'
5
+ require_relative '../../lib/lineup'
6
+
7
+ describe '#screeshot_recorder' do
8
+
9
+ BASE_URL = 'https://www.otto.de'
10
+ SCREENSHOTS = "#{Dir.pwd}/screenshots/"
11
+
12
+ after(:each) { FileUtils.rmtree SCREENSHOTS }
13
+
14
+ it 'loads all configuration from a json file' do
15
+ # Given
16
+ file = "#{Dir.pwd}/test_configuration.json"
17
+ FileUtils.rm file if (File.exists? file)
18
+ json = '{"urls":"page1, page2","resolutions":"13,42","filepath_for_images":"some/path","use_phantomjs":true,"difference_path":"some/difference/image/path"}'
19
+ save_json(json, file)
20
+
21
+ # When
22
+ lineup = Lineup::Screenshot.new(BASE_URL)
23
+
24
+ # Then
25
+ expect(
26
+ lineup.load_json_config(file)
27
+ ).to eq([['page1', 'page2'], [13,42], 'some/path', true, 'some/difference/image/path'])
28
+
29
+ # cleanup:
30
+ FileUtils.rm file if (File.exists? file)
31
+ end
32
+
33
+ it 'opens a URL and takes mobile/tablet/desktop screenshots' do
34
+ # Given
35
+ lineup = Lineup::Screenshot.new(BASE_URL)
36
+
37
+ # When
38
+ lineup.record_screenshot('base')
39
+
40
+ # Then
41
+ expect(
42
+ File.exist? ("#{Dir.pwd}/screenshots/base_frontpage_640.png")
43
+ ).to be(true)
44
+ # And
45
+ expect(
46
+ File.exist? ("#{Dir.pwd}/screenshots/base_frontpage_800.png")
47
+ ).to be(true)
48
+ # And
49
+ expect(
50
+ File.exist? ("#{Dir.pwd}/screenshots/base_frontpage_1180.png")
51
+ ).to be(true)
52
+
53
+ end
54
+
55
+ it 'takes a screenshot a desired resolution' do
56
+ # Given
57
+ width = '320' #min width firefox as of Sep 2015
58
+ lineup = Lineup::Screenshot.new(BASE_URL)
59
+
60
+ # When
61
+ lineup.resolutions(width)
62
+
63
+ # Then
64
+ lineup.record_screenshot('base')
65
+ imagewidth = Dimensions.width("#{Dir.pwd}/screenshots/base_frontpage_#{width}.png")
66
+ expect(
67
+ imagewidth
68
+ ).to be < (width.to_i + 10) #depending on the browser:
69
+ # 'width' set the browser to a certain width. The browser itself may then have some frame/border
70
+ # that means, that the viewport is smaller than the width of the browser, thus the image will be a
71
+ # bit smaller then 'width'. To compensate it, we have a +10 here.
72
+
73
+ end
74
+
75
+ it 'takes screenshots of different pages, if specified' do
76
+ # Given
77
+ urls = '/, multimedia, sport'
78
+ lineup = Lineup::Screenshot.new(BASE_URL)
79
+ lineup.resolutions('1180')
80
+ lineup.urls(urls)
81
+
82
+ # When
83
+ lineup.record_screenshot('base')
84
+
85
+ # Then
86
+ expect(
87
+ File.exist? ("#{Dir.pwd}/screenshots/base_frontpage_1180.png")
88
+ ).to be(true)
89
+
90
+ expect(
91
+ File.exist? ("#{Dir.pwd}/screenshots/base_multimedia_1180.png")
92
+ ).to be(true)
93
+
94
+ end
95
+
96
+ it 'raises and exception if, parameters are changed after the base screenshot' do
97
+ # Given
98
+ lineup = Lineup::Screenshot.new(BASE_URL)
99
+ lineup.urls('/')
100
+ lineup.resolutions('400')
101
+
102
+ # When
103
+ lineup.record_screenshot('base')
104
+ expect{
105
+ lineup.use_phantomjs true
106
+
107
+ # Then
108
+ }.to raise_error ArgumentError
109
+
110
+ end
111
+
112
+ it 'compares a base and a new screenshot and detects no difference if images are the same' do
113
+ # Given
114
+ lineup = Lineup::Screenshot.new(BASE_URL)
115
+ lineup.urls('/shoppages/begood')
116
+ lineup.resolutions('400')
117
+ lineup.record_screenshot('base')
118
+ lineup.record_screenshot('new')
119
+
120
+ expect(
121
+ # When
122
+ lineup.compare('base', 'new')
123
+
124
+ # Then
125
+ ).to eq([])
126
+
127
+ end
128
+
129
+ it 'compares a base and a new screenshot and returns the difference if the images are NOT the same as json log' do
130
+ # Given
131
+ width = '600'
132
+ base_site = 'multimedia'
133
+ new_site = 'sport'
134
+ json_path = "#{Dir.pwd}"
135
+ json_file = "#{json_path}/log.json"
136
+
137
+ # And Given
138
+ lineup = Lineup::Screenshot.new(BASE_URL)
139
+ lineup.urls(base_site)
140
+ lineup.resolutions(width)
141
+ lineup.record_screenshot('base')
142
+ FileUtils.mv "#{Dir.pwd}/screenshots/base_#{base_site}_#{width}.png", "#{Dir.pwd}/screenshots/base_#{new_site}_#{width}.png"
143
+ # change the url and go to a different page, in this way we ensure a conflict and thus a result from the comparison
144
+ lineup = Lineup::Screenshot.new(BASE_URL)
145
+ lineup.urls(new_site)
146
+ lineup.resolutions(width)
147
+
148
+ # When
149
+ lineup.record_screenshot('new')
150
+
151
+ # Then
152
+ # the output will be similar to the values here:
153
+ # [
154
+ # {
155
+ # :url => 'sport',
156
+ # :width => 600,
157
+ # :difference => 0.7340442722738748,
158
+ # :base_file => '/home/myname/lineup/tests/respec/screenshots/base_sport_600.png'
159
+ # :new_file => '/home/myname/lineup/tests/respec/screenshots/new_sport_600.png'
160
+ # :diff_file => '/home/myname/lineup/tests/rspec/screenshots/DIFFERENCE_sport_600.png'
161
+ # }
162
+ # ]
163
+ #
164
+ expect(
165
+ (lineup.compare('base', 'new').first)[:url]
166
+ ).to eq('sport')
167
+ # And
168
+ expect(
169
+ (lineup.compare('base', 'new').first)[:width]
170
+ ).to eq(600)
171
+ # And
172
+ result = (lineup.compare('base', 'new').first)[:difference]
173
+ expect(
174
+ result
175
+ ).to be_within(15).of(20) # 'compare' returns the difference of pixel between the screenshots in %
176
+ # 15-20% of pixel works toady (12.3 on 2015/09) for the difference between sport and multimedia page of OTTO.de,
177
+ # but the pages may some day look more or less alike, then these values can be changed
178
+ # And
179
+ expect(
180
+ (lineup.compare('base', 'new').first)[:base_file]
181
+ ).to include("/lineup/tests/rspec/screenshots/base_sport_600.png")
182
+ # And
183
+ expect(
184
+ (lineup.compare('base', 'new').first)[:new_file]
185
+ ).to include("/lineup/tests/rspec/screenshots/new_sport_600.png")
186
+ # And
187
+ expect(
188
+ (lineup.compare('base', 'new').first)[:difference_file]
189
+ ).to include("/lineup/tests/rspec/screenshots/DIFFERENCE_sport_600.png")
190
+
191
+ # And When
192
+ lineup.save_json(json_path)
193
+
194
+ # Then
195
+ expect(
196
+ File.exist? json_file
197
+ ).to be(true)
198
+ # And
199
+ expect(
200
+ File.read json_file
201
+ ).to include("\"difference\":#{result},")
202
+
203
+ # cleanup:
204
+ FileUtils.rm json_file if (File.exists? json_file)
205
+ end
206
+
207
+ private
208
+
209
+ def save_json(json, file)
210
+ file = File.open(
211
+ file, 'a'
212
+ )
213
+ file.write(json)
214
+ file.close
215
+ end
216
+
217
+ end
metadata ADDED
@@ -0,0 +1,59 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: lineup
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Finn
8
+ - Lorbeer
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2015-09-24 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: lineup takes to screenshots of your app and compares them to references
15
+ in order to find design flaws in your new code.
16
+ email:
17
+ - finn.von.friesland@googlemail.com
18
+ executables: []
19
+ extensions: []
20
+ extra_rdoc_files: []
21
+ files:
22
+ - ".gitignore"
23
+ - Gemfile
24
+ - Gemfile.lock
25
+ - README.md
26
+ - bin/lineup
27
+ - doc/example.png
28
+ - lib/controller/browser.rb
29
+ - lib/controller/comparer.rb
30
+ - lib/helper.rb
31
+ - lib/lineup.rb
32
+ - lib/lineup/version.rb
33
+ - lineup.gemspec
34
+ - tests/rspec/lineup_spec.rb
35
+ homepage: https://www.otto.de
36
+ licenses:
37
+ - MIT
38
+ metadata: {}
39
+ post_install_message:
40
+ rdoc_options: []
41
+ require_paths:
42
+ - lib
43
+ required_ruby_version: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ required_rubygems_version: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: '0'
53
+ requirements: []
54
+ rubyforge_project:
55
+ rubygems_version: 2.4.8
56
+ signing_key:
57
+ specification_version: 4
58
+ summary: lineup will help you in your automated design regression testing
59
+ test_files: []