watircats 0.2.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -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>