watircats 0.2.3

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.
@@ -0,0 +1,149 @@
1
+ require "open-uri"
2
+ require "xmlsimple"
3
+
4
+ module WatirCats
5
+ class Mapper
6
+
7
+ def initialize( base_url, scheme )
8
+ # Class variables that persist throughout the life of the application
9
+
10
+ @limit = WatirCats.config.limit
11
+ @proxy = WatirCats.config.proxy
12
+
13
+ @@master_paths = {}
14
+ @@new_paths = {}
15
+
16
+ # Subtree is a way to limit screenshots based on a path. It gets placed into a regular expression.
17
+ restrict_paths
18
+
19
+ @base_url = base_url
20
+
21
+ # If the :url_list option is passed, it is assumed to be a file of urls,
22
+ # one on each line. Otherwise, assume there is a sitemap
23
+ if WatirCats.config.url_list
24
+ begin
25
+ urls = File.readlines WatirCats.config.url_list
26
+ urls.each { |url| url.strip! }
27
+ rescue Exception => msg
28
+ print msg
29
+ exit 1
30
+ end
31
+ else
32
+ xml = get_sitemap_as_xml( scheme, @base_url )
33
+ urls = parse_sitemap_into_urls(xml)
34
+ end
35
+
36
+ @the_paths = paths(urls)
37
+ end
38
+
39
+ def get_sitemap_as_xml(scheme, base_url)
40
+
41
+ the_scheme = scheme || "http"
42
+ if @proxy
43
+ proxy = the_scheme + "://" + @proxy
44
+ else
45
+ proxy = nil
46
+ end
47
+
48
+ begin
49
+ sitemap_data = ::OpenURI.open_uri((the_scheme + "://" + base_url + "/sitemap.xml"), :proxy => proxy )
50
+ sitemap_xml = ::XmlSimple.xml_in(sitemap_data)
51
+ rescue Exception => msg
52
+ print msg
53
+ exit 1
54
+ end
55
+ end
56
+
57
+ def parse_sitemap_into_urls(xml)
58
+ # Empty Array to dump URLs into
59
+ urls = []
60
+ xml["url"].each do |key|
61
+ urls << key["loc"].first
62
+ end
63
+ # Return the URLs
64
+ urls
65
+ end
66
+
67
+ def paths(urls)
68
+
69
+ # Determine if this is the first pass
70
+ if @@master_paths.size == 0
71
+ mode = :first_run
72
+ else
73
+ mode = :not_first
74
+ end
75
+
76
+ # Create an empty hash to store paths in
77
+ pending_paths = {}
78
+
79
+ # Iterate through the urls, grab only the valid paths post domain
80
+ # Store each path_key with the extrapolated path
81
+ urls.each do |url|
82
+ path = URI.parse(url).path
83
+
84
+ # Handled paths to look for and avoid
85
+ if @subtree
86
+ next unless path.match @subtree
87
+ end
88
+ if @avoided_path
89
+ next if path.match @avoided_path
90
+ end
91
+
92
+ path_key = path.tr("/","-")[1..-2] unless path == "/"
93
+ path_key ||= "root" # Nil guard to set 'root' for home
94
+
95
+ # If this is the first run, definitely process it
96
+ # If not the first run, ensure path_key exists
97
+ # If not the first run, path_key not present, put it in @@new_paths
98
+ if mode == :first_run
99
+ pending_paths[path_key] = path
100
+ else
101
+ if @@master_paths[path_key]
102
+ pending_paths[path_key] = path
103
+ else
104
+ @@new_paths[path_key] = path
105
+ end
106
+ end
107
+ end
108
+
109
+ # Store master paths if this is the first pass
110
+ @@master_paths.merge! pending_paths if mode == :first_run
111
+ # return the paths to crawl
112
+ pending_paths
113
+ end
114
+
115
+ def the_paths
116
+ # TODO: Insert logic to limit to a specific subtree here
117
+
118
+ # Return the paths if there is a @limit
119
+ return @the_paths.first(@limit.to_i) if @limit
120
+ @the_paths.first(@the_paths.length)
121
+ end
122
+
123
+ def master_paths
124
+ # Enforce limit on @@master_paths, too, because why not
125
+ return @@master_paths.first(@limit.to_i) if @limit
126
+ @@master_paths.first(@@master_paths.length)
127
+ end
128
+
129
+ def new_paths
130
+ @@new_paths.first(@@new_paths.length)
131
+ end
132
+
133
+ def restrict_paths
134
+
135
+ # Only visit paths that match @subtree
136
+ @subtree = WatirCats.config.limited_path
137
+ if @subtree
138
+ @subtree = /#{WatirCats.config.limited_path}/
139
+ end
140
+
141
+ # If avoided_path is specified, avoid those
142
+ @avoided_path = WatirCats.config.avoided_path
143
+ if @avoided_path
144
+ @avoided_path = /#{WatirCats.config.avoided_path}/
145
+ end
146
+ end
147
+
148
+ end
149
+ end
@@ -0,0 +1,80 @@
1
+ require "haml"
2
+
3
+ module WatirCats
4
+ class Reporter
5
+ # Handles the report generation
6
+
7
+ def initialize(results_array)
8
+ @data = results_array
9
+ end
10
+
11
+ def build_html
12
+ # Create a report based off of the template data.
13
+ # binding gives Haml context, and @data dumps the relevant information in
14
+ Haml::Engine.new(get_screenshot_template).render binding, :data => @data
15
+ end
16
+
17
+ def get_screenshot_template
18
+ template = <<TEMPLATE
19
+ !!! 5
20
+ %html
21
+ %head
22
+ %title Comparison courtesy of WatirCats
23
+ %script{:type => "text/javascript",
24
+ :src => "https://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"}
25
+ %script{:type => "text/javascript",
26
+ :src => "https://cdnjs.cloudflare.com/ajax/libs/masonry/3.1.1/masonry.pkgd.min.js"}
27
+ :css
28
+ p { padding-left: 10px; }
29
+ .item { width:140px; font-family: sans-serif; font-size: 10px; }
30
+ div.item { padding: 5px ;border: 2px; border-color: black; overflow: auto; box-shadow: inset 1px 1px 8px 1px gray; margin-top: 10px;}
31
+ div.item:hover{ transition: .3s; box-shadow: 1px 1px 8px 1px slategray; adding: 8px; margin-left: -3px; margin-top: 6px; }
32
+ img{ width: 50px; height: 100px; float: left}
33
+ %script
34
+ $(function(){$('#container').masonry({itemSelector : '.item',columnWidth : 160});});
35
+ %body
36
+ %div#container
37
+ - data.each do |path|
38
+ %div{ :class => "item", :style => "background-color: " + path[:status_color] }
39
+ %a{ :href => path[:compared_shot] }
40
+ - thumb = "thumbs/" + path[:compared_shot]
41
+ %img{ :src => thumb, :alt => path[:compared_shot], :title => path[:compared_shot] }
42
+ %p<
43
+ %strong Pixels Changed:
44
+ = path[:result]
45
+ TEMPLATE
46
+ end
47
+
48
+ def self.build_custom_results(hash)
49
+ AwesomePrint.defaults = { :html => true, :indent => 2, :plain => true}
50
+ template = <<TEMPLATE
51
+ !!! 5
52
+ %html
53
+ %head
54
+ %title Custom Test Results
55
+ %script{:type => "text/javascript",
56
+ :src => "https://google-code-prettify.googlecode.com/svn/loader/run_prettify.js"}
57
+ %script{:type => "text/javascript",
58
+ :src => "https://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"}
59
+ :css
60
+ .item { width:auto; font-family: sans-serif; font-size: 10px; }
61
+ .clear { float: clear }
62
+ %body
63
+ %div#container
64
+ - hash.each_pair do |key, value|
65
+ %div{ :class => "item" }
66
+ %div{:class => "left"}
67
+ %pre{:class => "prettyprint lang-rb"}
68
+ = key
69
+ %div
70
+ %pre{:class => "prettyprint lang-rb"}
71
+ != value.to_s.gsub(",",",\n")
72
+ %div{ :class => "clear" }
73
+ %script
74
+ $(window).load("prettyPrint()")
75
+ TEMPLATE
76
+ Haml::Engine.new(template).render binding, :hash => hash
77
+ end
78
+
79
+ end
80
+ end
@@ -0,0 +1,63 @@
1
+ module WatirCats
2
+
3
+ class Runner
4
+ def initialize(task, sources)
5
+
6
+ # Setup the directories for screenshots and comparison output
7
+
8
+ # Using a case statement to determine how to operate.
9
+ # case and when are not to be waste an indent level
10
+ case task
11
+ when :compare
12
+ sources.each do |source|
13
+ # If source is nil, go to the next one (which probably won't exist)
14
+ next unless source
15
+
16
+ # Prep the URI object
17
+ uri = URI.parse(source)
18
+ base_url = uri.host
19
+ scheme = uri.scheme || "http"
20
+
21
+ # Grab a Mapper object to pass to Snapper
22
+ site_map = WatirCats::Mapper.new( base_url, scheme )
23
+
24
+ # Snapper will snap screenshots, using a site_map object
25
+ WatirCats::Snapper.new( base_url, site_map )
26
+ end
27
+
28
+ folders = WatirCats::Snapper.folders
29
+ base_folder = folders.first
30
+ changed_folder = folders.last
31
+
32
+ WatirCats::Comparer.new( base_folder, changed_folder )
33
+
34
+ # Only take screenshots
35
+ when :screenshots_only
36
+ sources.each do |source|
37
+ # If source is nil, go to the next one (which probably won't exist)
38
+ next unless source
39
+
40
+ # Prep the URI object
41
+ uri = URI.parse(source)
42
+ base_url = uri.host
43
+ scheme = uri.scheme
44
+
45
+ # Grab a Mapper object to pass to Snapper
46
+ site_map = WatirCats::Mapper.new( base_url, scheme )
47
+
48
+ # Snapper will snap screenshots, using a site_map object
49
+ WatirCats::Snapper.new( base_url, site_map )
50
+ end
51
+
52
+ # Let's just compare folders
53
+ when :folders
54
+ base_folder = sources.first
55
+ changed_folder = sources.last
56
+ WatirCats::Comparer.new( base_folder, changed_folder )
57
+ # End case
58
+ end
59
+
60
+
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,167 @@
1
+ require 'watir-webdriver'
2
+
3
+ module WatirCats
4
+ class Snapper
5
+
6
+ BROWSER_HEIGHT = 5000
7
+
8
+ def initialize(base_url, site_map)
9
+ @base_url = base_url
10
+ @screenshot_dir = WatirCats.config.screenshot_dir
11
+ @widths = WatirCats.config.widths
12
+ @images_dir = WatirCats.config.images_dir
13
+
14
+ # Handle the environments that require profile configuration
15
+ configure_browser
16
+
17
+ # Allowing for custom page class tests
18
+ @class_tests = [ ]
19
+ @class_test_mapping = { }
20
+ @@custom_test_results = { }
21
+
22
+ if WatirCats.config.custom_body_class_tests
23
+ load WatirCats.config.custom_body_class_tests
24
+ $custom_body_class_tests.each do |custom_test|
25
+ # Store the custom class tests in the class_tests array
26
+ # Map the class to the relevant method name
27
+
28
+ body_class = custom_test[:body_class_name]
29
+ @class_tests << body_class
30
+ if @class_test_mapping[ body_class ]
31
+ @class_test_mapping[ body_class ] = @class_test_mapping[ body_class ] + [ custom_test[:method_name] ]
32
+ else
33
+ @class_test_mapping[ body_class ] = [ custom_test[:method_name] ]
34
+ end
35
+
36
+ custom_code = "def #{custom_test[:method_name]}; #{custom_test[:custom_code]}; end"
37
+ WatirCats::Snapper.class_eval custom_code
38
+ end
39
+ end
40
+
41
+ # Retrieve the paths from the sitemap
42
+ @paths = site_map.the_paths
43
+ @time_stamp = Time.now.to_i.to_s
44
+
45
+ process_paths
46
+ @browser.quit
47
+ end
48
+
49
+ def resize_browser(width)
50
+ # Set a max height using the constant BROWSER_HEIGHT
51
+ @browser.window.resize_to(width, BROWSER_HEIGHT)
52
+ end
53
+
54
+ def capture_page_image(url, file_name)
55
+
56
+ # skip existing screenshots if we've specified that option
57
+ if WatirCats.config.skip_existing
58
+ if FileTest.exists?(file_name)
59
+ puts "Skipping existing file at " + file_name
60
+ return
61
+ end
62
+ end
63
+
64
+
65
+ @browser.goto url
66
+ # Wait for page to complete loading by querying document.readyState
67
+ script = "return document.readyState"
68
+ @browser.wait_until { @browser.execute_script(script) == "complete" }
69
+
70
+ # Take and save the screenshot
71
+ @browser.screenshot.save(file_name)
72
+ end
73
+
74
+ def widths
75
+ @widths = @widths || [1024]
76
+ end
77
+
78
+ def self.folders
79
+ # Class variable, to keep track of folders amongst all instances of this class
80
+ @@folders ||= []
81
+ end
82
+
83
+ def add_folder(folder)
84
+ @@folders ||= [] # Ensure @@folders exists
85
+ @@folders << folder
86
+ end
87
+
88
+ def process_paths
89
+ # Build the unique directory unless it exists
90
+ FileUtils.mkdir "#{@screenshot_dir}" unless File.directory? "#{@screenshot_dir}"
91
+
92
+ stamped_base_url_folder = "#{@screenshot_dir}/#{@base_url}-#{@time_stamp}"
93
+ if @images_dir
94
+ stamped_base_url_folder = "#{@screenshot_dir}/#{@images_dir}"
95
+ end
96
+
97
+ FileUtils.mkdir "#{stamped_base_url_folder}" unless File.directory? "#{stamped_base_url_folder}"
98
+
99
+ add_folder(stamped_base_url_folder)
100
+
101
+ # Some setup for processing
102
+ paths = @paths
103
+ widths = self.widths
104
+
105
+ # Iterate through the paths, using the key as a label, value as a path
106
+ paths.each do |label, path|
107
+ # Create our base array to use to execute tests
108
+ potential_body_classes = [:all]
109
+ # Do custom tests here
110
+ body_classes = @browser.body.class_name
111
+ # Split the class string for the <body> element on spaces, then shovel
112
+ # each body_class into the potential_body_classes array
113
+ body_classes.split.each { |body_class| potential_body_classes << body_class }
114
+
115
+ @@custom_test_results[path] = {}
116
+
117
+ potential_body_classes.each do |the_class|
118
+ if @class_tests.include? the_class
119
+ methods_to_send = @class_test_mapping[the_class]
120
+
121
+ methods_to_send.each do |custom_method|
122
+ @@custom_test_results[path][custom_method] = self.send( custom_method )
123
+ end
124
+
125
+ end
126
+ end
127
+
128
+ # For each width, resize the browser, take a screenshot
129
+ widths.each do |width|
130
+ resize_browser width
131
+ file_name = "#{stamped_base_url_folder}/#{label}_#{width}.png"
132
+ capture_page_image("#{@base_url}#{path}", file_name)
133
+ end
134
+ end
135
+ end
136
+
137
+ def configure_browser
138
+ engine = WatirCats.config.browser || :ff
139
+
140
+ # Firefox only stuff, allowing a custom binary location and proxy support
141
+ if ( engine.to_sym == :ff || engine.to_sym == :firefox )
142
+
143
+ bin_path = WatirCats.config.ff_path
144
+ proxy = WatirCats.config.proxy
145
+ ::Selenium::WebDriver::Firefox::Binary.path= bin_path if bin_path.is_a? String
146
+
147
+ profile = ::Selenium::WebDriver::Firefox::Profile.new
148
+
149
+ if proxy
150
+ profile.proxy = ::Selenium::WebDriver::Proxy.new :http => proxy, :ssl => proxy
151
+ end
152
+
153
+ profile['app.update.auto'] = false
154
+ profile['app.update.enabled'] = false
155
+
156
+ @browser = ::Watir::Browser.new engine, :profile => profile
157
+ else
158
+ @browser = ::Watir::Browser.new engine
159
+ end
160
+ end
161
+
162
+ def self.custom_test_results
163
+ @@custom_test_results ||= { }
164
+ end
165
+
166
+ end
167
+ end
@@ -0,0 +1,3 @@
1
+ module WatirCats
2
+ VERSION = "0.2.3"
3
+ end
data/lib/watircats.rb ADDED
@@ -0,0 +1,16 @@
1
+ # External requires
2
+ require "open-uri"
3
+ require "xmlsimple"
4
+ require "openssl"
5
+ require "watir-webdriver"
6
+
7
+ # Internal Requires
8
+ require "watircats/comparer"
9
+ require "watircats/mapper"
10
+ require "watircats/reporter"
11
+ require "watircats/snapper"
12
+ require "watircats/version"
13
+ require "watircats/runner"
14
+ require "watircats/cli"
15
+ require "watircats/config"
16
+
data/sample_config.yml ADDED
@@ -0,0 +1,19 @@
1
+ # Sample parameters for the config file
2
+ # Uncomment any line below and place in your custom values
3
+ #
4
+ # avoided_path : string_that_matches_paths_to_avoid
5
+ # browser : firefox
6
+ # csv : false
7
+ # custom_body_class_tests : path/to/custom_tests_file.rb
8
+ # ff_path : /Applications/FirefoxESR.app/Contents/MacOS/firefox-bin
9
+ # limit : 1
10
+ # limited_path : a_string_that_becomes_a_regex_to_narrow_screenshots
11
+ # output_dir : comparison
12
+ # proxy : my_proxy_info:8080
13
+ # reporting_enabled : true
14
+ # screenshot_dir : screenshots
15
+ # strip_zero_differences : true
16
+ # url_list : path/to/plain_text_list_of_urls.txt
17
+ # verbose : false
18
+ # widths : [1024, 800]
19
+ # working_dir : sample_working_dir/
@@ -0,0 +1,51 @@
1
+ require 'watircats'
2
+
3
+ class ComparerTest < Minitest::Test
4
+
5
+ def setup
6
+ # Initial setup
7
+ WatirCats.configure :output_dir => "test/testables/output"
8
+
9
+ @source_a = "test/testables/folder_a/"
10
+ @source_b = "test/testables/folder_b/"
11
+ @output = WatirCats.config.output_dir
12
+ end
13
+
14
+ def test_compare_directories_and_results
15
+ WatirCats.configure :reporting_enabled => false, :output_dir => "test/testables/output/no_thumbs"
16
+ output_dir = WatirCats.config.output_dir
17
+ comparer = WatirCats::Comparer.new( @source_a, @source_b )
18
+
19
+ results = comparer.results
20
+ expected = [{ :compared_shot => "watircat_compared.png",
21
+ :result => "832", :status_color => "Khaki" }]
22
+
23
+ assert_equal expected, results
24
+ assert_equal true, File.exists?("#{output_dir}/watircat_compared.png")
25
+ assert_equal false, File.exists?("#{output_dir}/watircat_no_compare_compared.png")
26
+ assert_equal false, File.directory?("#{output_dir}/thumbs")
27
+ FileUtils.rm_rf output_dir
28
+ end
29
+
30
+ def test_compare_directories_and_results_with_reporting
31
+ WatirCats.configure :reporting_enabled => true, :output_dir => "test/testables/output/with_thumbs"
32
+ output_dir = WatirCats.config.output_dir
33
+
34
+ comparer = WatirCats::Comparer.new( @source_a, @source_b )
35
+
36
+ results = comparer.results
37
+ expected = [{ :compared_shot => "watircat_compared.png",
38
+ :result => "832", :status_color => "Khaki" }]
39
+
40
+ assert_equal expected, results
41
+ assert_equal true, File.exists?("#{output_dir}/watircat_compared.png")
42
+ assert_equal false, File.exists?("#{output_dir}/watircat_no_compare_compared.png")
43
+ assert_equal true, File.directory?("#{output_dir}/thumbs/")
44
+ assert_equal true, File.exists?("#{output_dir}/thumbs/watircat_compared.png")
45
+ FileUtils.rm_rf output_dir
46
+ end
47
+
48
+ def teardown
49
+ end
50
+
51
+ end
File without changes
@@ -0,0 +1,59 @@
1
+ require 'minitest/autorun'
2
+ require 'watircats'
3
+
4
+ class MapperTest < Minitest::Test
5
+
6
+ def setup
7
+ @base_url = "www.clockwork.net"
8
+ @scheme = "http"
9
+ options = { :limit => 1 }
10
+ @xml_file = XmlSimple.xml_in(File.open("test/testables/sitemap.xml", "r"))
11
+ WatirCats.configure options
12
+ end
13
+
14
+ def test_parse_sitemap_into_urls
15
+ map = WatirCats::Mapper.new(
16
+ @base_url,
17
+ @scheme
18
+ ).parse_sitemap_into_urls(@xml_file)
19
+
20
+ expected = [
21
+ "http://www.clockwork.net/",
22
+ "http://www.clockwork.net/work/",
23
+ "http://www.clockwork.net/people/",
24
+ "http://www.clockwork.net/blog/"
25
+ ]
26
+
27
+ assert_equal map, expected
28
+ end
29
+
30
+ def test_paths
31
+ urls = [
32
+ "http://www.clockwork.net/",
33
+ "http://www.clockwork.net/work/",
34
+ "http://www.clockwork.net/people/",
35
+ "http://www.clockwork.net/blog/"
36
+ ]
37
+
38
+ paths = WatirCats::Mapper.new(
39
+ @base_url,
40
+ @scheme
41
+ ).paths(urls)
42
+
43
+ expected = {
44
+ "root" => "/",
45
+ "work" => "/work/",
46
+ "people" => "/people/",
47
+ "blog" => "/blog/"
48
+ }
49
+ assert_equal paths, expected
50
+ end
51
+
52
+ def test_mapper
53
+ mapper = WatirCats::Mapper.new( @base_url, @scheme )
54
+ expected = [["root", "/"]]
55
+ assert_equal mapper.the_paths, expected
56
+ assert_equal mapper.master_paths, expected
57
+ assert_equal mapper.new_paths, []
58
+ end
59
+ end
File without changes
@@ -0,0 +1,36 @@
1
+ require 'watircats'
2
+ require 'minitest/mock'
3
+
4
+ class SnapperTest < Minitest::Test
5
+ def setup
6
+ @base_url = "www.clockwork.net"
7
+ $custom_body_class_tests = []
8
+ @sitemap = Minitest::Mock.new
9
+
10
+ @options = {
11
+ :screenshot_dir => "test/testables/screenshot_tmp/",
12
+ :browser => :ff
13
+ }
14
+ # Configure the mock object
15
+ @sitemap.expect( :the_paths, [["root", "/"]] )
16
+ end
17
+
18
+ def test_snapper_takes_snaps
19
+
20
+ WatirCats.configure(@options)
21
+
22
+ snapped = WatirCats::Snapper.new( @base_url, @sitemap )
23
+
24
+ working_dir = Dir.pwd
25
+ Dir.chdir( WatirCats.config.screenshot_dir )
26
+ target_dir = Dir.glob("*/*.png")
27
+ expected = target_dir[0].split( "/" ).include? "root_1024.png"
28
+ assert_equal 1, target_dir.size
29
+ assert_equal true, expected
30
+ Dir.chdir working_dir
31
+ end
32
+
33
+ def teardown
34
+ FileUtils.rm_rf WatirCats.config.screenshot_dir
35
+ end
36
+ end
@@ -0,0 +1,22 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <urlset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3
+ xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd"
4
+ xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
5
+
6
+ <url>
7
+ <loc>http://www.clockwork.net/</loc>
8
+ </url>
9
+
10
+ <url>
11
+ <loc>http://www.clockwork.net/work/</loc>
12
+ </url>
13
+
14
+ <url>
15
+ <loc>http://www.clockwork.net/people/</loc>
16
+ </url>
17
+
18
+ <url>
19
+ <loc>http://www.clockwork.net/blog/</loc>
20
+ </url>
21
+
22
+ </urlset>