lineup 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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: []