watchdoge 0.1.3 → 0.1.4
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 +4 -4
- data/lib/watchdoge/configuration.rb +13 -9
- data/lib/watchdoge/cookie_pool.rb +4 -13
- data/lib/watchdoge/image_diff.rb +16 -6
- data/lib/watchdoge/notification/matter_most.rb +11 -11
- data/lib/watchdoge/notification/{slack.rb → slack_webhook.rb} +3 -3
- data/lib/watchdoge/notification.rb +29 -6
- data/lib/watchdoge/rails/railtie.rb +6 -2
- data/lib/watchdoge/regression/dsl.rb +30 -12
- data/lib/watchdoge/regression/utils.rb +26 -18
- data/lib/watchdoge/regression.rb +22 -40
- data/lib/watchdoge/version.rb +2 -1
- data/lib/watchdoge/{install.rb → webdriver_manager.rb} +10 -2
- data/lib/watchdoge/worker.rb +58 -42
- data/lib/watchdoge.rb +5 -5
- data/watchdoge.gemspec +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c3e163dcbfe0adf1bdd0cad7294c47475d7007792820a79a80d150cc86d58227
|
|
4
|
+
data.tar.gz: 8c03e0dff98141f87e822ac561a47b3267754e258425424d968aa5aef05c2c87
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1f6f3564b357805882226d960aa97b69ab4f11d3a1d8f82d5a176995f5b15d9e42fbfadf9c38c9fed09b43cf2c33dda79abe7c316b277e599c0ae4def9d853e6
|
|
7
|
+
data.tar.gz: 8a7af25b9f84fe6475471dd5bd98207f51d3253f2197b37749126712125e052ed1732081eb479d582d3bb07e9cd98fe35006924d8beab30c73dacc8fa42cff5b
|
|
@@ -1,22 +1,23 @@
|
|
|
1
|
+
# All configurable variables
|
|
2
|
+
|
|
3
|
+
# spec:
|
|
4
|
+
#
|
|
5
|
+
|
|
1
6
|
require 'watir'
|
|
2
7
|
|
|
3
8
|
module WatchDoge
|
|
4
9
|
class Configuration
|
|
5
10
|
class Hooks
|
|
6
|
-
attr_accessor :sign_in
|
|
7
|
-
attr_accessor :sign_out
|
|
8
11
|
attr_accessor :before_regression
|
|
9
12
|
attr_accessor :after_regression
|
|
10
13
|
attr_accessor :before_scenario
|
|
11
14
|
attr_accessor :after_scenario
|
|
12
15
|
|
|
13
16
|
def initialize
|
|
14
|
-
@
|
|
15
|
-
@
|
|
16
|
-
@
|
|
17
|
-
@
|
|
18
|
-
@before_scenario = -> {}
|
|
19
|
-
@after_scenario = -> {}
|
|
17
|
+
@before_regression = []
|
|
18
|
+
@after_regression = []
|
|
19
|
+
@before_scenario = []
|
|
20
|
+
@after_scenario = []
|
|
20
21
|
end
|
|
21
22
|
end
|
|
22
23
|
|
|
@@ -34,6 +35,8 @@ module WatchDoge
|
|
|
34
35
|
attr_accessor :regression_dir
|
|
35
36
|
|
|
36
37
|
attr_accessor :hooks
|
|
38
|
+
attr_accessor :sign_in_proc
|
|
39
|
+
attr_accessor :sign_out_proc
|
|
37
40
|
|
|
38
41
|
attr_accessor :chrome_version
|
|
39
42
|
attr_accessor :firefox_version
|
|
@@ -50,7 +53,6 @@ module WatchDoge
|
|
|
50
53
|
@engine = Watir::Browser
|
|
51
54
|
@browser = :firefox
|
|
52
55
|
|
|
53
|
-
# cuurently using local fs, probably support NoSQL also maybe?
|
|
54
56
|
@cookie_pool = @base_dir + '/.doge_cookie'
|
|
55
57
|
|
|
56
58
|
@http_timeout = 120
|
|
@@ -81,6 +83,8 @@ module WatchDoge
|
|
|
81
83
|
end
|
|
82
84
|
|
|
83
85
|
@hooks = WatchDoge::Configuration::Hooks.new
|
|
86
|
+
@sign_in_proc = -> (worker, resource) {}
|
|
87
|
+
@sign_out_proc = -> (worker, resource) {}
|
|
84
88
|
|
|
85
89
|
@chrome_version = nil
|
|
86
90
|
@firefox_version = nil
|
|
@@ -1,25 +1,16 @@
|
|
|
1
|
+
# TODO: Improve or should just remove it.
|
|
2
|
+
|
|
1
3
|
require 'yaml'
|
|
2
4
|
require 'digest'
|
|
3
5
|
|
|
4
6
|
module WatchDoge
|
|
5
7
|
module CookiePool
|
|
6
8
|
class << self
|
|
7
|
-
def source
|
|
9
|
+
def source uid, uri = nil
|
|
8
10
|
suffix = "_#{Digest::MD5.hexdigest(uri.to_s)[0..5]}" if uri
|
|
9
11
|
|
|
10
|
-
"#{WatchDoge.configuration.cookie_pool}/#{
|
|
12
|
+
"#{WatchDoge.configuration.cookie_pool}/#{uid}#{suffix}"
|
|
11
13
|
end
|
|
12
|
-
|
|
13
|
-
# TODO: implement these two methods return Cookie object in future.
|
|
14
|
-
# def save uuid, json
|
|
15
|
-
# File.write source(uuid), json
|
|
16
|
-
# end
|
|
17
|
-
|
|
18
|
-
# def load uuid
|
|
19
|
-
# return "" unless File.exist? source(uuid)
|
|
20
|
-
|
|
21
|
-
# JSON.parse File.read(source(uuid))
|
|
22
|
-
# end
|
|
23
14
|
end
|
|
24
15
|
end
|
|
25
16
|
end
|
data/lib/watchdoge/image_diff.rb
CHANGED
|
@@ -1,12 +1,22 @@
|
|
|
1
|
+
# generating diff image of two input image
|
|
2
|
+
|
|
3
|
+
# usage:
|
|
4
|
+
# diff_image = WatchDoge::ImageDiff.diff(chunky_png_image, another_chunky_png_image)
|
|
5
|
+
|
|
6
|
+
# spec:
|
|
7
|
+
# 1. input only accept ChunkyPNG::Image, output image is also ChunkyPNG::Image
|
|
8
|
+
# 2. Testing RGBA pixel by pixel. blending mask color if diff detected
|
|
9
|
+
|
|
10
|
+
# todo: should improve testing boundary for more accurate diff imagenot.
|
|
11
|
+
|
|
1
12
|
module WatchDoge
|
|
2
13
|
module ImageDiff
|
|
3
|
-
OPACITY = 0.7
|
|
4
|
-
HIGHLIGHT_COLOR = "#F163FF#{(OPACITY * 256).to_i.to_s(16).upcase}"
|
|
5
|
-
|
|
6
14
|
class << self
|
|
7
|
-
def diff input_image, reference_image
|
|
15
|
+
def diff input_image, reference_image, opacity: 0.7, mask_base_color: "#F163FF"
|
|
8
16
|
return nil if input_image.pixels.hash == reference_image.pixels.hash
|
|
9
17
|
|
|
18
|
+
mask_color = mask_base_color.concat (opacity * 256).to_i.to_s(16).upcase
|
|
19
|
+
|
|
10
20
|
dup_input_image = input_image.dup
|
|
11
21
|
|
|
12
22
|
input_image.height.times do |y|
|
|
@@ -14,7 +24,7 @@ module WatchDoge
|
|
|
14
24
|
|
|
15
25
|
if y >= reference_image.height
|
|
16
26
|
pixels.each_with_index do |pixel, x|
|
|
17
|
-
dup_input_image.compose_pixel(x, y,
|
|
27
|
+
dup_input_image.compose_pixel(x, y, mask_color)
|
|
18
28
|
end
|
|
19
29
|
else
|
|
20
30
|
reference_pixels = reference_image.row(y)
|
|
@@ -24,7 +34,7 @@ module WatchDoge
|
|
|
24
34
|
pixels.each_with_index do |pixel, x|
|
|
25
35
|
if pixels[x] != reference_pixels[x]
|
|
26
36
|
dup_input_image.compose_pixel(x, y, reference_pixels[x])
|
|
27
|
-
dup_input_image.compose_pixel(x, y,
|
|
37
|
+
dup_input_image.compose_pixel(x, y, mask_color)
|
|
28
38
|
end
|
|
29
39
|
end
|
|
30
40
|
end
|
|
@@ -16,23 +16,23 @@ module WatchDoge
|
|
|
16
16
|
def push message
|
|
17
17
|
case message
|
|
18
18
|
when String
|
|
19
|
-
|
|
19
|
+
post matter_most_meta(message)
|
|
20
20
|
when ChunkyPNG::Image
|
|
21
|
-
|
|
21
|
+
post_file matter_most_meta(message)
|
|
22
22
|
end
|
|
23
23
|
end
|
|
24
24
|
|
|
25
25
|
private
|
|
26
26
|
|
|
27
|
-
def
|
|
28
|
-
Net::HTTP.post
|
|
27
|
+
def post meta
|
|
28
|
+
Net::HTTP.post api_uri('/posts'),
|
|
29
29
|
meta.to_json,
|
|
30
30
|
"Content-Type" => "application/json",
|
|
31
31
|
"Authorization" => "Bearer #{@auth_token}"
|
|
32
32
|
end
|
|
33
33
|
|
|
34
|
-
def
|
|
35
|
-
uri =
|
|
34
|
+
def post_file meta
|
|
35
|
+
uri = api_uri("/files")
|
|
36
36
|
|
|
37
37
|
req = Net::HTTP::Post::Multipart.new uri.path, meta
|
|
38
38
|
req.add_field("Authorization", "Bearer #{@auth_token}")
|
|
@@ -45,22 +45,22 @@ module WatchDoge
|
|
|
45
45
|
|
|
46
46
|
file_id = JSON.parse(res)['file_infos'].first['id']
|
|
47
47
|
|
|
48
|
-
|
|
48
|
+
post_file_to_channel file_id
|
|
49
49
|
end
|
|
50
50
|
|
|
51
|
-
def
|
|
52
|
-
|
|
51
|
+
def post_file_to_channel file_id
|
|
52
|
+
post({
|
|
53
53
|
channel_id: @channel_id,
|
|
54
54
|
message: 'file uploaded from WatchDoge',
|
|
55
55
|
file_ids: [file_id]
|
|
56
56
|
})
|
|
57
57
|
end
|
|
58
58
|
|
|
59
|
-
def
|
|
59
|
+
def api_uri path
|
|
60
60
|
URI("#{@host}/api/v4#{path}")
|
|
61
61
|
end
|
|
62
62
|
|
|
63
|
-
def
|
|
63
|
+
def matter_most_meta message
|
|
64
64
|
case message
|
|
65
65
|
when String
|
|
66
66
|
{
|
|
@@ -5,13 +5,13 @@ require 'uri'
|
|
|
5
5
|
|
|
6
6
|
module WatchDoge
|
|
7
7
|
module Notification
|
|
8
|
-
class
|
|
8
|
+
class SlackWebhook
|
|
9
9
|
def initialize opt
|
|
10
10
|
@incoming_url = opt[:incoming_url]
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
def push message
|
|
14
|
-
body =
|
|
14
|
+
body = slack_body(message).to_json
|
|
15
15
|
Net::HTTP.post URI(@incoming_url),
|
|
16
16
|
body,
|
|
17
17
|
"Content-Type" => "application/json"
|
|
@@ -19,7 +19,7 @@ module WatchDoge
|
|
|
19
19
|
|
|
20
20
|
private
|
|
21
21
|
|
|
22
|
-
def
|
|
22
|
+
def slack_body message
|
|
23
23
|
case message
|
|
24
24
|
when String
|
|
25
25
|
{
|
|
@@ -1,15 +1,39 @@
|
|
|
1
|
+
# Notification send message to sources setting in configuration
|
|
2
|
+
|
|
3
|
+
# usage:
|
|
4
|
+
# WatchDoge::Notification.push(message)
|
|
5
|
+
|
|
6
|
+
# spec:
|
|
7
|
+
# 1. could adding custom sources
|
|
8
|
+
# 2. custom source MUST under WatchDoge::Notification namespace and having #push method
|
|
9
|
+
|
|
10
|
+
# for example:
|
|
11
|
+
# class WatchDoge::Notification::CustomSource
|
|
12
|
+
# # args passing from value of WatchDoge::Configuration.notifications['CustomSource']
|
|
13
|
+
# def initialize **args
|
|
14
|
+
# # implement your source setup
|
|
15
|
+
# end
|
|
16
|
+
#
|
|
17
|
+
# # called by WatchDoge::Notification.push(msg)
|
|
18
|
+
# def push msg
|
|
19
|
+
# # do your source push here
|
|
20
|
+
# end
|
|
21
|
+
# end
|
|
22
|
+
|
|
23
|
+
# built-in sources
|
|
24
|
+
require 'watchdoge/notification/slack_webhook'
|
|
25
|
+
require 'watchdoge/notification/matter_most'
|
|
26
|
+
|
|
1
27
|
module WatchDoge
|
|
2
28
|
module Notification
|
|
3
|
-
Dir["#{File.expand_path File.dirname(__FILE__)}/notification/*.rb"].each {|file| require file }
|
|
4
|
-
|
|
5
29
|
class << self
|
|
6
30
|
def push message
|
|
7
31
|
sources = WatchDoge.configuration.notifications
|
|
8
32
|
|
|
9
33
|
raise "can't find notification sources in configuration" if sources.empty?
|
|
10
34
|
|
|
11
|
-
sources.each do |
|
|
12
|
-
source_class = get_source_class
|
|
35
|
+
sources.each do |klass, source_args|
|
|
36
|
+
source_class = get_source_class klass
|
|
13
37
|
source = source_class.new source_args
|
|
14
38
|
source.push message
|
|
15
39
|
end
|
|
@@ -17,8 +41,7 @@ module WatchDoge
|
|
|
17
41
|
|
|
18
42
|
private
|
|
19
43
|
|
|
20
|
-
def get_source_class
|
|
21
|
-
klass = source_sym.to_s.split('_').collect(&:capitalize).join
|
|
44
|
+
def get_source_class klass
|
|
22
45
|
"WatchDoge::Notification::#{klass}".constantize
|
|
23
46
|
end
|
|
24
47
|
end
|
|
@@ -6,13 +6,17 @@ module WatchDoge
|
|
|
6
6
|
namespace :watchdoge do
|
|
7
7
|
desc "generating reference images"
|
|
8
8
|
task :make_reference => :environment do
|
|
9
|
-
WatchDoge::Regression::Manager.make_reference
|
|
9
|
+
WatchDoge::Regression::Manager.run_all_scenarios :make_reference
|
|
10
10
|
end
|
|
11
11
|
desc "scrap screenshots and compare with reference images"
|
|
12
12
|
task :regression => :environment do
|
|
13
|
-
WatchDoge::Regression::Manager.regression
|
|
13
|
+
WatchDoge::Regression::Manager.run_all_scenarios :regression
|
|
14
14
|
end
|
|
15
15
|
end
|
|
16
16
|
end
|
|
17
|
+
|
|
18
|
+
initializer "watchdoge.initialize" do |app|
|
|
19
|
+
WatchDoge.initialize!
|
|
20
|
+
end
|
|
17
21
|
end
|
|
18
22
|
end
|
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
# DSL for described regression scenario
|
|
2
|
+
|
|
3
|
+
# usage:
|
|
4
|
+
# compare 'https://google.com'
|
|
5
|
+
|
|
6
|
+
# spec: todo
|
|
7
|
+
|
|
8
|
+
# todo: finish document
|
|
9
|
+
|
|
1
10
|
module WatchDoge
|
|
2
11
|
module Regression
|
|
3
12
|
module DSL
|
|
@@ -8,15 +17,14 @@ module WatchDoge
|
|
|
8
17
|
end
|
|
9
18
|
|
|
10
19
|
def sign_in_as resoruce
|
|
11
|
-
|
|
12
20
|
puts "[sign_in_as] #{resoruce}"
|
|
13
|
-
WatchDoge.
|
|
21
|
+
WatchDoge.configuration.sign_in_proc.call(@worker, resoruce)
|
|
14
22
|
|
|
15
23
|
return unless block_given?
|
|
16
24
|
|
|
17
25
|
yield(@worker)
|
|
18
26
|
puts "sign_out #{resoruce}"
|
|
19
|
-
WatchDoge.
|
|
27
|
+
WatchDoge.configuration.sign_out_proc.call(@worker, resoruce)
|
|
20
28
|
end
|
|
21
29
|
|
|
22
30
|
def with_viewport **kwargs
|
|
@@ -27,13 +35,7 @@ module WatchDoge
|
|
|
27
35
|
|
|
28
36
|
def compare path, **kwargs
|
|
29
37
|
path = URI.join(WatchDoge.configuration.host, path).to_s
|
|
30
|
-
|
|
31
|
-
@reference_file_name =
|
|
32
|
-
if kwargs[:with]
|
|
33
|
-
kwargs[:with]
|
|
34
|
-
else
|
|
35
|
-
path.gsub(/\//, '_')
|
|
36
|
-
end
|
|
38
|
+
@reference_file_name = WatchDoge::Regression::Utils.path_to_filename(kwargs[:with] || path)
|
|
37
39
|
|
|
38
40
|
puts "[compare] #{path} #{ ('[with]'+ @reference_file_name) if kwargs[:with] }"
|
|
39
41
|
|
|
@@ -53,8 +55,24 @@ module WatchDoge
|
|
|
53
55
|
})
|
|
54
56
|
end
|
|
55
57
|
|
|
56
|
-
@worker.
|
|
57
|
-
@
|
|
58
|
+
@worker.resize_by_viewport(@view_port) do
|
|
59
|
+
case @regression_flag
|
|
60
|
+
when :make_reference
|
|
61
|
+
puts " -> make reference images on worker #{@worker}"
|
|
62
|
+
@worker.screenshot.save reference_image
|
|
63
|
+
when :regression
|
|
64
|
+
puts " -> compare reference images on worker #{@worker}"
|
|
65
|
+
reference_image = WatchDoge::Regression::Utils.load_png self.reference_image
|
|
66
|
+
cycle_image = WatchDoge::Regression::Utils.load_png @worker.screenshot.png
|
|
67
|
+
diff = WatchDoge::ImageDiff.diff(cycle_image, reference_image)
|
|
68
|
+
if diff
|
|
69
|
+
if ENV['WATCHDOGE_DEBUG']
|
|
70
|
+
WatchDoge::Notification.push cycle_image
|
|
71
|
+
WatchDoge::Notification.push reference_image
|
|
72
|
+
end
|
|
73
|
+
WatchDoge::Notification.push diff
|
|
74
|
+
end
|
|
75
|
+
end
|
|
58
76
|
end
|
|
59
77
|
|
|
60
78
|
@teardown_proc.call if @teardown_proc
|
|
@@ -1,33 +1,41 @@
|
|
|
1
|
+
# this file is part of Regression, mostly helpers methods
|
|
2
|
+
|
|
1
3
|
require 'digest'
|
|
2
4
|
|
|
3
5
|
module WatchDoge
|
|
4
6
|
module Regression
|
|
5
7
|
module Utils
|
|
6
|
-
|
|
7
|
-
|
|
8
|
+
class << self
|
|
9
|
+
def load_png input
|
|
10
|
+
raise 'Invalid Input' unless input.respond_to? :length
|
|
8
11
|
|
|
9
|
-
|
|
12
|
+
png_magic = ["89", "50", "4E", "47"]
|
|
10
13
|
|
|
11
|
-
|
|
12
|
-
|
|
14
|
+
is_png_blob =
|
|
15
|
+
(input.length > 3 && input[0..3].each_byte.map {|t| t.to_s(16).upcase} == png_magic)
|
|
13
16
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
if is_png_blob
|
|
18
|
+
ChunkyPNG::Image.from_blob input
|
|
19
|
+
else
|
|
20
|
+
ChunkyPNG::Image.from_file input
|
|
21
|
+
end
|
|
18
22
|
end
|
|
19
|
-
end
|
|
20
23
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
+
def path_to_filename path
|
|
25
|
+
path.gsub(/\//, '_')
|
|
26
|
+
end
|
|
24
27
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
+
def regression_dir
|
|
29
|
+
WatchDoge.configuration.regression_dir
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def scenarios_dir
|
|
33
|
+
"#{regression_dir}/scenarios"
|
|
34
|
+
end
|
|
28
35
|
|
|
29
|
-
|
|
30
|
-
|
|
36
|
+
def reference_dir
|
|
37
|
+
"#{regression_dir}/reference"
|
|
38
|
+
end
|
|
31
39
|
end
|
|
32
40
|
end
|
|
33
41
|
end
|
data/lib/watchdoge/regression.rb
CHANGED
|
@@ -1,62 +1,44 @@
|
|
|
1
|
+
# Regression testing realtime screenshot with reference images for scenario described by DSL
|
|
2
|
+
|
|
3
|
+
# usage:
|
|
4
|
+
# WatchDoge::Regression.run_all_scenarios(:make_reference)
|
|
5
|
+
# WatchDoge::Regression.run_all_scenarios(:regression)
|
|
6
|
+
|
|
7
|
+
# spec:
|
|
8
|
+
# 1. rake tasks `rake watchdoge:make_reference` and `rake watchdoge:regression` while in Rails
|
|
9
|
+
# 2. generate scenario template by Rails generator
|
|
10
|
+
|
|
11
|
+
# todo: support run single scenario
|
|
12
|
+
|
|
13
|
+
require 'watchdoge/regression/dsl'
|
|
14
|
+
require 'watchdoge/regression/utils'
|
|
15
|
+
|
|
1
16
|
module WatchDoge
|
|
2
17
|
module Regression
|
|
3
|
-
Dir["#{File.expand_path File.dirname(__FILE__)}/regression/*.rb"].each {|file| require file }
|
|
4
|
-
|
|
5
18
|
class Manager
|
|
6
|
-
extend WatchDoge::Regression::Utils
|
|
7
19
|
include WatchDoge::Regression::DSL
|
|
8
20
|
|
|
9
21
|
class << self
|
|
10
|
-
def regression
|
|
11
|
-
run_all_scenarios :regression
|
|
12
|
-
end
|
|
13
|
-
def make_reference
|
|
14
|
-
run_all_scenarios :make_reference
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
private
|
|
18
|
-
|
|
19
22
|
def run_all_scenarios regression_flag
|
|
20
|
-
WatchDoge.hooks.before_regression.call
|
|
23
|
+
WatchDoge.hooks.before_regression.each { |t| t.call }
|
|
21
24
|
|
|
22
|
-
Dir["#{scenarios_dir}/*"].each do |scenario_path|
|
|
23
|
-
WatchDoge.hooks.before_scenario.call
|
|
25
|
+
Dir["#{WatchDoge::Regression::Utils.scenarios_dir}/*"].each do |scenario_path|
|
|
26
|
+
WatchDoge.hooks.before_scenario.each { |t| t.call }
|
|
24
27
|
|
|
25
28
|
mgr = self.new scenario_path, regression_flag: regression_flag
|
|
26
29
|
mgr.eval_scenario
|
|
27
30
|
|
|
28
|
-
WatchDoge.hooks.after_scenario.call
|
|
31
|
+
WatchDoge.hooks.after_scenario.each { |t| t.call }
|
|
29
32
|
end
|
|
30
33
|
|
|
31
|
-
WatchDoge.hooks.after_regression.call
|
|
34
|
+
WatchDoge.hooks.after_regression.each { |t| t.call }
|
|
32
35
|
end
|
|
33
36
|
end
|
|
34
37
|
|
|
35
38
|
def initialize scenario_path, regression_flag:
|
|
36
39
|
@scenario_path = scenario_path
|
|
37
40
|
@scenario_name = File.basename @scenario_path, '.rb'
|
|
38
|
-
|
|
39
|
-
case regression_flag
|
|
40
|
-
when :make_reference
|
|
41
|
-
@regression_proc = -> (worker) {
|
|
42
|
-
puts " -> make reference images on worker #{worker}"
|
|
43
|
-
worker.screenshot.save reference_image
|
|
44
|
-
}
|
|
45
|
-
when :regression
|
|
46
|
-
@regression_proc = -> (worker) {
|
|
47
|
-
puts " -> compare reference images on worker #{worker}"
|
|
48
|
-
reference_image = self.class.load_png self.reference_image
|
|
49
|
-
cycle_image = self.class.load_png worker.screenshot.png
|
|
50
|
-
diff = WatchDoge::ImageDiff.diff(cycle_image, reference_image)
|
|
51
|
-
if diff
|
|
52
|
-
if ENV['WATCHDOGE_DEBUG']
|
|
53
|
-
WatchDoge::Notification.push cycle_image
|
|
54
|
-
WatchDoge::Notification.push reference_image
|
|
55
|
-
end
|
|
56
|
-
WatchDoge::Notification.push diff
|
|
57
|
-
end
|
|
58
|
-
}
|
|
59
|
-
end
|
|
41
|
+
@regression_flag = regression_flag
|
|
60
42
|
|
|
61
43
|
@setup_proc = nil
|
|
62
44
|
@teardown_proc = nil
|
|
@@ -75,7 +57,7 @@ module WatchDoge
|
|
|
75
57
|
end
|
|
76
58
|
|
|
77
59
|
def reference_image
|
|
78
|
-
|
|
60
|
+
WatchDoge::Regression::Utils.reference_dir + "/#{@scenario_name}/#{@reference_file_name}.png"
|
|
79
61
|
end
|
|
80
62
|
end
|
|
81
63
|
end
|
data/lib/watchdoge/version.rb
CHANGED
|
@@ -1,8 +1,16 @@
|
|
|
1
|
+
# WebdriverManager setup specific version of webdriver in configuration for Selenium
|
|
2
|
+
|
|
3
|
+
# usage:
|
|
4
|
+
# WatchDoge.configuration.firefox_version = '0.17.0'
|
|
5
|
+
# WatchDoge::WebdriverManager.new(:firefox)
|
|
6
|
+
|
|
7
|
+
# todo: shouldn't this class singleton with #setup instead of initializing instance?
|
|
8
|
+
|
|
1
9
|
require 'net_http_ssl_fix'
|
|
2
10
|
require 'webdrivers'
|
|
3
11
|
|
|
4
12
|
module WatchDoge
|
|
5
|
-
class
|
|
13
|
+
class WebdriverManager
|
|
6
14
|
attr_reader :driver
|
|
7
15
|
attr_reader :install_version
|
|
8
16
|
attr_reader :install_path
|
|
@@ -16,7 +24,7 @@ module WatchDoge
|
|
|
16
24
|
Webdrivers::Geckodriver
|
|
17
25
|
end
|
|
18
26
|
|
|
19
|
-
@install_version =
|
|
27
|
+
@install_version = WatchDoge.configuration.send("#{browser}_version") || @driver.latest_version.to_s
|
|
20
28
|
|
|
21
29
|
@install_path = "#{WatchDoge.configuration.web_drivers_dir}/#{browser}/#{@install_version}/"
|
|
22
30
|
Webdrivers.install_dir = @install_path
|
data/lib/watchdoge/worker.rb
CHANGED
|
@@ -1,45 +1,70 @@
|
|
|
1
|
+
# Worker contains Selenium browser interface. and installing required WebDriver binaries
|
|
2
|
+
|
|
3
|
+
# usage:
|
|
4
|
+
# worker = WatchDoge::Worker.new("WORKER_NAME")
|
|
5
|
+
# worker.goto "http://google.com"
|
|
6
|
+
# screenshot = worker.fullpage_screenshot
|
|
7
|
+
|
|
8
|
+
# spec:
|
|
9
|
+
# 1. Worker instance having customized configuration Selenium as its engine
|
|
10
|
+
# 2. Worker engine default to headless mode
|
|
11
|
+
# 3. Worker passing all missing method to its engine
|
|
12
|
+
|
|
13
|
+
# todo:
|
|
14
|
+
# 1. improve or remove cookie related functions, is it necessary for frontend regression?
|
|
15
|
+
|
|
1
16
|
module WatchDoge
|
|
2
17
|
class Worker
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
18
|
+
# options:
|
|
19
|
+
# 1. width: browser's width, default to 1280
|
|
20
|
+
# 2. height: browser's height, default to 768
|
|
21
|
+
# 3. http_client: instance of Selenium::WebDriver::Remote::Http
|
|
22
|
+
# 4. all acceptable arguments of Watir::Browser class
|
|
23
|
+
def initialize uid, options={}
|
|
24
|
+
@http = Selenium::WebDriver::Remote::Http::Default.new
|
|
25
|
+
@browser = WatchDoge.configuration.browser
|
|
26
|
+
|
|
27
|
+
# HTTP timeout default to 120 sec
|
|
28
|
+
@http.read_timeout = options.delete(:timeout) || WatchDoge.configuration.http_timeout
|
|
29
|
+
|
|
30
|
+
@uid = uid
|
|
7
31
|
|
|
8
|
-
|
|
32
|
+
default_options = {
|
|
9
33
|
width: 1280,
|
|
10
34
|
height: 768,
|
|
11
35
|
headless: true,
|
|
36
|
+
|
|
37
|
+
# Selenium HTTP configuration
|
|
12
38
|
http_client: @http
|
|
13
|
-
}.merge(
|
|
39
|
+
}.merge(options).merge(browser_profile)
|
|
14
40
|
|
|
15
|
-
|
|
16
|
-
|
|
41
|
+
# intall required version driver
|
|
42
|
+
WatchDoge::WebdriverManager.new @browser
|
|
43
|
+
@core = WatchDoge.configuration.engine.new @browser, default_options
|
|
17
44
|
|
|
18
45
|
# for generate uri hash
|
|
19
46
|
@landed_page = nil
|
|
20
47
|
|
|
21
|
-
#
|
|
22
|
-
@
|
|
48
|
+
# autosaving cookies while worker closed
|
|
49
|
+
@cookie_autosave = false
|
|
23
50
|
|
|
24
51
|
# flag for cookies loaded
|
|
25
52
|
@cookie_loaded = false
|
|
26
53
|
|
|
27
|
-
#
|
|
28
|
-
@width =
|
|
29
|
-
@height =
|
|
54
|
+
# width/height
|
|
55
|
+
@width = default_options[:width]
|
|
56
|
+
@height = default_options[:height]
|
|
30
57
|
@core.window.resize_to @width, @height
|
|
31
|
-
|
|
32
|
-
_after_initialize
|
|
33
58
|
end
|
|
34
59
|
|
|
35
60
|
def fullpage_screenshot **kwargs
|
|
36
|
-
|
|
61
|
+
resize_by_viewport(kwargs) do |resize|
|
|
37
62
|
return resize.screenshot
|
|
38
63
|
end
|
|
39
64
|
end
|
|
40
65
|
|
|
41
66
|
def close
|
|
42
|
-
|
|
67
|
+
save_cookie
|
|
43
68
|
@core.close
|
|
44
69
|
end
|
|
45
70
|
|
|
@@ -48,17 +73,12 @@ module WatchDoge
|
|
|
48
73
|
|
|
49
74
|
@landed_page = uri
|
|
50
75
|
|
|
51
|
-
|
|
76
|
+
load_cookie unless cookie_loaded?
|
|
52
77
|
|
|
53
78
|
@core.refresh
|
|
54
79
|
end
|
|
55
80
|
|
|
56
|
-
|
|
57
|
-
def core
|
|
58
|
-
@core
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
def resize_by_document **kwargs
|
|
81
|
+
def resize_by_viewport **kwargs
|
|
62
82
|
width = (kwargs.delete :width) || @core.body.width
|
|
63
83
|
height = (kwargs.delete :height) || @core.body.height
|
|
64
84
|
|
|
@@ -84,13 +104,8 @@ module WatchDoge
|
|
|
84
104
|
end
|
|
85
105
|
|
|
86
106
|
private
|
|
87
|
-
# callbacks before/after init
|
|
88
|
-
def _before_initialize
|
|
89
|
-
@http = Selenium::WebDriver::Remote::Http::Default.new
|
|
90
|
-
@browser = WatchDoge.configuration.browser
|
|
91
|
-
end
|
|
92
107
|
|
|
93
|
-
def
|
|
108
|
+
def browser_profile
|
|
94
109
|
case @browser
|
|
95
110
|
when :chrome
|
|
96
111
|
prefs = {}
|
|
@@ -102,35 +117,36 @@ module WatchDoge
|
|
|
102
117
|
}
|
|
103
118
|
when :firefox
|
|
104
119
|
profile = Selenium::WebDriver::Firefox::Profile.new
|
|
120
|
+
|
|
121
|
+
# basically, firefox default sanbox level to 4, which denied access to most folders
|
|
122
|
+
# that broken many env config such as FONTCONFIG_PATH
|
|
105
123
|
profile['security.sandbox.content.level'] = 2
|
|
106
|
-
profile['network.dns.disableIPv6'] = true
|
|
107
124
|
{
|
|
108
125
|
profile: profile
|
|
109
126
|
}
|
|
110
127
|
end
|
|
111
128
|
end
|
|
112
129
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
# for cookie load/save
|
|
117
|
-
def _cookie_loaded?
|
|
130
|
+
# Cookies
|
|
131
|
+
def cookie_loaded?
|
|
118
132
|
@cookie_loaded
|
|
119
133
|
end
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
134
|
+
|
|
135
|
+
def save_cookie
|
|
136
|
+
if @cookie_autosave
|
|
137
|
+
@core.cookies.save WatchDoge::CookiePool.source(@uid, @landed_page)
|
|
123
138
|
end
|
|
124
139
|
end
|
|
125
|
-
|
|
126
|
-
|
|
140
|
+
|
|
141
|
+
def load_cookie
|
|
142
|
+
source = WatchDoge::CookiePool.source(@uid, @landed_page)
|
|
127
143
|
return unless File.exist? source
|
|
128
144
|
|
|
129
145
|
@core.cookies.load source
|
|
130
146
|
@cookie_loaded = true
|
|
131
147
|
end
|
|
132
148
|
|
|
133
|
-
# try delegate all methods to
|
|
149
|
+
# try delegate all missing methods to selenium
|
|
134
150
|
def method_missing(method_name, *args)
|
|
135
151
|
result = @core.__send__(method_name, *args)
|
|
136
152
|
|
data/lib/watchdoge.rb
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
require 'pry'
|
|
2
|
-
require 'watchdoge/
|
|
2
|
+
require 'watchdoge/webdriver_manager'
|
|
3
3
|
require 'watchdoge/version'
|
|
4
4
|
require 'watchdoge/configuration'
|
|
5
5
|
require 'watchdoge/cookie_pool'
|
|
@@ -26,18 +26,18 @@ module WatchDoge
|
|
|
26
26
|
end
|
|
27
27
|
|
|
28
28
|
def configuration
|
|
29
|
-
@
|
|
29
|
+
@configuration ||= Configuration.new
|
|
30
30
|
end
|
|
31
31
|
|
|
32
32
|
def initialize!
|
|
33
|
-
|
|
33
|
+
config_file = ENV['WATCHDOGE_CONFIG'] ||
|
|
34
34
|
if defined?(Rails)
|
|
35
|
-
"#{
|
|
35
|
+
"#{Rails.root}/config/watchdoge.rb"
|
|
36
36
|
else
|
|
37
37
|
"#{Dir.pwd}/watchdoge.rb"
|
|
38
38
|
end
|
|
39
39
|
|
|
40
|
-
load
|
|
40
|
+
load config_file
|
|
41
41
|
end
|
|
42
42
|
end
|
|
43
43
|
end
|
data/watchdoge.gemspec
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: watchdoge
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Shen Lee
|
|
@@ -115,16 +115,16 @@ files:
|
|
|
115
115
|
- lib/watchdoge/configuration.rb
|
|
116
116
|
- lib/watchdoge/cookie_pool.rb
|
|
117
117
|
- lib/watchdoge/image_diff.rb
|
|
118
|
-
- lib/watchdoge/install.rb
|
|
119
118
|
- lib/watchdoge/notification.rb
|
|
120
119
|
- lib/watchdoge/notification/matter_most.rb
|
|
121
|
-
- lib/watchdoge/notification/
|
|
120
|
+
- lib/watchdoge/notification/slack_webhook.rb
|
|
122
121
|
- lib/watchdoge/rails/generator.rb
|
|
123
122
|
- lib/watchdoge/rails/railtie.rb
|
|
124
123
|
- lib/watchdoge/regression.rb
|
|
125
124
|
- lib/watchdoge/regression/dsl.rb
|
|
126
125
|
- lib/watchdoge/regression/utils.rb
|
|
127
126
|
- lib/watchdoge/version.rb
|
|
127
|
+
- lib/watchdoge/webdriver_manager.rb
|
|
128
128
|
- lib/watchdoge/worker.rb
|
|
129
129
|
- watchdoge.gemspec
|
|
130
130
|
homepage:
|