kontrast 0.2.1 → 0.6.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 +4 -4
- data/README.md +135 -96
- data/bin/kontrast +9 -5
- data/lib/kontrast.rb +17 -5
- data/lib/kontrast/api_client.rb +56 -0
- data/lib/kontrast/api_endpoint_comparator.rb +126 -0
- data/lib/kontrast/api_endpoint_runner.rb +99 -0
- data/lib/kontrast/api_endpoint_test.rb +8 -0
- data/lib/kontrast/configuration.rb +36 -7
- data/lib/kontrast/exceptions.rb +2 -1
- data/lib/kontrast/gallery/template.erb +152 -21
- data/lib/kontrast/gallery_creator.rb +105 -47
- data/lib/kontrast/global_runner.rb +121 -0
- data/lib/kontrast/image_helper.rb +63 -0
- data/lib/kontrast/image_uploader.rb +18 -0
- data/lib/kontrast/page_comparator.rb +46 -0
- data/lib/kontrast/page_runner.rb +95 -0
- data/lib/kontrast/page_test.rb +32 -0
- data/lib/kontrast/selenium_handler.rb +18 -7
- data/lib/kontrast/spec.rb +21 -0
- data/lib/kontrast/spec_builder.rb +54 -0
- data/lib/kontrast/test.rb +27 -0
- data/lib/kontrast/test_builder.rb +25 -9
- data/lib/kontrast/test_suite.rb +42 -0
- data/lib/kontrast/thumbnail_creator.rb +18 -0
- data/lib/kontrast/version.rb +1 -1
- data/spec/api_endpoint_comparator_spec.rb +125 -0
- data/spec/configuration_spec.rb +19 -0
- data/spec/gallery_creator_spec.rb +26 -20
- data/spec/{image_handler_spec.rb → global_runner_spec.rb} +17 -12
- data/spec/page_comparator_spec.rb +31 -0
- data/spec/page_runner_spec.rb +45 -0
- data/spec/spec_builder_spec.rb +32 -0
- data/spec/support/fixtures/image.jpg +0 -0
- data/spec/support/fixtures/image_clone.jpg +0 -0
- data/spec/support/fixtures/img1.jpg +0 -0
- data/spec/support/fixtures/img2.jpg +0 -0
- data/spec/support/fixtures/other_image.jpg +0 -0
- data/spec/test_builder_spec.rb +6 -3
- data/spec/test_spec.rb +53 -0
- data/spec/test_suite_spec.rb +56 -0
- metadata +91 -30
- data/lib/kontrast/image_handler.rb +0 -119
- data/lib/kontrast/runner.rb +0 -141
- data/spec/runner_spec.rb +0 -37
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
|
3
|
+
module Kontrast
|
4
|
+
class ApiClient
|
5
|
+
|
6
|
+
attr_reader :responses, :env
|
7
|
+
attr_writer :headers
|
8
|
+
|
9
|
+
def initialize(env, host, app_id, app_secret, headers: {})
|
10
|
+
@env = env
|
11
|
+
@host = host
|
12
|
+
@app_id = app_id
|
13
|
+
@app_secret = app_secret
|
14
|
+
@connection = nil
|
15
|
+
@responses = {}
|
16
|
+
@headers = headers
|
17
|
+
end
|
18
|
+
|
19
|
+
def fetch(path, save_file: false, folder_name: "")
|
20
|
+
response = connection.get(path) do |req|
|
21
|
+
req.headers['Authorization'] = "Bearer #{token}"
|
22
|
+
req.headers.merge!(@headers)
|
23
|
+
end
|
24
|
+
data = JSON.parse(response.body)
|
25
|
+
if save_file
|
26
|
+
open(File.join(Kontrast.path, folder_name, "#{@env}.json"), 'wb') do |file|
|
27
|
+
file << JSON.pretty_generate(data)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
@responses[path] = data
|
31
|
+
return data
|
32
|
+
end
|
33
|
+
|
34
|
+
def token
|
35
|
+
return @token || fetch_token
|
36
|
+
end
|
37
|
+
|
38
|
+
def fetch_token
|
39
|
+
return @token if !@token.nil? && @token != ''
|
40
|
+
|
41
|
+
response = connection.post(Kontrast.configuration.oauth_token_url, {
|
42
|
+
grant_type: 'client_credentials',
|
43
|
+
client_id: @app_id,
|
44
|
+
client_secret: @app_secret,
|
45
|
+
})
|
46
|
+
@token = Kontrast.configuration.oauth_token_from_response.call(response.body)
|
47
|
+
end
|
48
|
+
|
49
|
+
def connection
|
50
|
+
@connection ||= Faraday.new(url: @host) do |faraday|
|
51
|
+
faraday.request :url_encoded
|
52
|
+
faraday.adapter Faraday.default_adapter
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,126 @@
|
|
1
|
+
require "workers"
|
2
|
+
require "kontrast/api_client"
|
3
|
+
|
4
|
+
module Kontrast
|
5
|
+
class ApiEndpointComparator
|
6
|
+
attr_reader :diffs, :prod_client, :test_client
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
|
10
|
+
@prod_client = Kontrast::ApiClient.new(
|
11
|
+
'production',
|
12
|
+
Kontrast.configuration.production_domain,
|
13
|
+
Kontrast.configuration.production_oauth_app_uid,
|
14
|
+
Kontrast.configuration.production_oauth_app_secret,
|
15
|
+
)
|
16
|
+
|
17
|
+
test_oauth_app = Kontrast.configuration.test_oauth_app_proc.call
|
18
|
+
@test_client = Kontrast::ApiClient.new(
|
19
|
+
'test',
|
20
|
+
Kontrast.configuration.test_domain,
|
21
|
+
test_oauth_app.uid,
|
22
|
+
test_oauth_app.secret,
|
23
|
+
)
|
24
|
+
|
25
|
+
@image_index = 0
|
26
|
+
|
27
|
+
@result = {}
|
28
|
+
|
29
|
+
# This is where failed diffs will be stored
|
30
|
+
@diffs = {}
|
31
|
+
end
|
32
|
+
|
33
|
+
def diff(test)
|
34
|
+
@image_index = 0
|
35
|
+
@prod_client.headers = test.headers
|
36
|
+
@test_client.headers = test.headers
|
37
|
+
|
38
|
+
# Create the folder
|
39
|
+
FileUtils.mkdir_p(File.join(Kontrast.path, test.to_s))
|
40
|
+
|
41
|
+
Workers.map([@test_client, @prod_client]) do |client|
|
42
|
+
client.fetch(test.path, save_file: true, folder_name: test.to_s)
|
43
|
+
end
|
44
|
+
|
45
|
+
@diffs[test.to_s] = {images: []}
|
46
|
+
if !compare(@prod_client.responses[test.path], @test_client.responses[test.path], test)
|
47
|
+
@diffs[test.to_s].merge!({
|
48
|
+
type: 'api_endpoint',
|
49
|
+
name: test.name,
|
50
|
+
diff: 1,
|
51
|
+
})
|
52
|
+
else
|
53
|
+
# Clear the diff
|
54
|
+
@diffs.delete test.to_s
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def compare(prod_data, test_data, test, key: nil)
|
59
|
+
|
60
|
+
if prod_data == test_data
|
61
|
+
return true
|
62
|
+
elsif is_image_string?(prod_data, key)
|
63
|
+
# If it's an image, we need to compare both files
|
64
|
+
if compare_images(prod_data, test_data, test)
|
65
|
+
return true
|
66
|
+
else
|
67
|
+
diff_details = { index: @image_index - 1 }
|
68
|
+
@diffs[test.to_s][:images] << diff_details
|
69
|
+
return false
|
70
|
+
end
|
71
|
+
elsif prod_data.is_a?(Hash)
|
72
|
+
return false if prod_data.keys != test_data.keys
|
73
|
+
|
74
|
+
return prod_data.map do |key, value|
|
75
|
+
compare(prod_data[key], test_data[key], test, key: key)
|
76
|
+
end.all?
|
77
|
+
elsif prod_data.is_a?(Array) # Make it more generic?
|
78
|
+
return false if prod_data.length != test_data.length
|
79
|
+
|
80
|
+
return prod_data.map.with_index do |value, i|
|
81
|
+
compare(prod_data[i], test_data[i], test)
|
82
|
+
end.all?
|
83
|
+
else
|
84
|
+
return false
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def is_image_string?(image_string, key)
|
89
|
+
# Either a URL or a local path
|
90
|
+
if !key.nil? && key != ''
|
91
|
+
return key.match(/(image|url)/) && image_string.is_a?(String)
|
92
|
+
else
|
93
|
+
return image_string.is_a?(String) && image_string.match(/^(http|\/)/)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def compare_images(prod_image, test_image, test)
|
98
|
+
images = [
|
99
|
+
{'env' => 'production', 'image' => prod_image},
|
100
|
+
{'env' => 'test', 'image' => test_image},
|
101
|
+
]
|
102
|
+
files = Workers.map(images) do |image|
|
103
|
+
load_image_file(image['image'], test, image['env'])
|
104
|
+
end
|
105
|
+
|
106
|
+
image_helper = Kontrast::ImageHelper.new(files[0].path, files[1].path)
|
107
|
+
|
108
|
+
diff = image_helper.compare(test.to_s, "diff_#{@image_index}.png")
|
109
|
+
|
110
|
+
@image_index += 1
|
111
|
+
return diff == 0
|
112
|
+
end
|
113
|
+
|
114
|
+
def load_image_file(image, test, prefix)
|
115
|
+
if image.start_with?('http')
|
116
|
+
extension = image.split('.')[-1]
|
117
|
+
file_name = "#{prefix}_#{@image_index}.#{extension}"
|
118
|
+
open(File.join(Kontrast.path, test.to_s, file_name), 'wb') do |file|
|
119
|
+
file << open(image).read
|
120
|
+
end
|
121
|
+
else
|
122
|
+
File.new(image)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
module Kontrast
|
2
|
+
class ApiEndpointRunner
|
3
|
+
include ImageUploader
|
4
|
+
include ThumbnailCreator
|
5
|
+
|
6
|
+
attr_accessor :diffs
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@api_diff_comparator = ApiEndpointComparator.new
|
10
|
+
@diffs = {}
|
11
|
+
end
|
12
|
+
|
13
|
+
def run(total_nodes, current_node)
|
14
|
+
# Assign tests and run them
|
15
|
+
suite = split_run(total_nodes, current_node)
|
16
|
+
parallel_run(suite, current_node)
|
17
|
+
end
|
18
|
+
|
19
|
+
# Given the total number of nodes and the index of the current node,
|
20
|
+
# we determine which tests the current node will run
|
21
|
+
def split_run(total_nodes, current_node)
|
22
|
+
test_suite = Kontrast.api_endpoint_test_suite
|
23
|
+
if test_suite.nil?
|
24
|
+
return []
|
25
|
+
end
|
26
|
+
|
27
|
+
# Load lazy tests
|
28
|
+
# Some tests are lazy loaded from the initializer
|
29
|
+
# In that case, we stored a block instead of adding a test to the
|
30
|
+
# suite when reading the initializer
|
31
|
+
# We need to execute the block, this will add the test to the suite
|
32
|
+
# This is needed for tests that are dynamically defined: like,
|
33
|
+
# get all the product pages in the DB and create a test for each
|
34
|
+
# one.
|
35
|
+
test_suite.lazy_tests.each do |lazy_test|
|
36
|
+
Kontrast.api_endpoint_test_builder.prefix = lazy_test.prefix
|
37
|
+
Kontrast.api_endpoint_test_builder.headers = lazy_test.headers
|
38
|
+
lazy_test.block.call(Kontrast.api_endpoint_test_builder)
|
39
|
+
end
|
40
|
+
tests_to_run = []
|
41
|
+
|
42
|
+
index = 0
|
43
|
+
test_suite.tests.each do |test|
|
44
|
+
if index % total_nodes == current_node
|
45
|
+
tests_to_run << test
|
46
|
+
end
|
47
|
+
index += 1
|
48
|
+
end
|
49
|
+
|
50
|
+
return tests_to_run
|
51
|
+
end
|
52
|
+
|
53
|
+
# Runs tests
|
54
|
+
def parallel_run(suite, current_node)
|
55
|
+
|
56
|
+
# Run per-page tasks
|
57
|
+
suite.each do |test|
|
58
|
+
begin
|
59
|
+
print "Processing #{test.name} @ #{test.prefix}... "
|
60
|
+
|
61
|
+
# Download the json file
|
62
|
+
# Create the diff hash, there
|
63
|
+
@api_diff_comparator.diff(test)
|
64
|
+
|
65
|
+
# Create thumbnails for gallery
|
66
|
+
print "Creating thumbnails... "
|
67
|
+
images = Dir.entries(File.join(Kontrast.path, test.to_s)).reject { |file_name|
|
68
|
+
['.', '..'].include?(file_name) || file_name.include?('.json')
|
69
|
+
}
|
70
|
+
|
71
|
+
create_thumbnails(test, images)
|
72
|
+
|
73
|
+
# Upload to S3
|
74
|
+
if Kontrast.configuration.run_parallel
|
75
|
+
print "Uploading... "
|
76
|
+
upload_images(test)
|
77
|
+
end
|
78
|
+
|
79
|
+
puts "\n", ("=" * 85)
|
80
|
+
rescue Net::ReadTimeout => e
|
81
|
+
puts "Test timed out. Message: #{e.inspect}"
|
82
|
+
if Kontrast.configuration.fail_build
|
83
|
+
raise e
|
84
|
+
end
|
85
|
+
rescue StandardError => e
|
86
|
+
puts "Exception: #{e.inspect}"
|
87
|
+
puts e.backtrace.inspect
|
88
|
+
if Kontrast.configuration.fail_build
|
89
|
+
raise e
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
ensure
|
94
|
+
# We need the diff at the runner level to create the manifest
|
95
|
+
@diffs = @api_diff_comparator.diffs
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
end
|
@@ -1,14 +1,26 @@
|
|
1
1
|
module Kontrast
|
2
2
|
class << self
|
3
|
-
attr_accessor :configuration, :
|
3
|
+
attr_accessor :configuration, :page_builder, :api_endpoint_builder
|
4
4
|
|
5
5
|
def configure
|
6
6
|
self.configuration ||= Configuration.new
|
7
7
|
yield(configuration)
|
8
8
|
end
|
9
9
|
|
10
|
-
def
|
11
|
-
self.
|
10
|
+
def page_test_builder
|
11
|
+
self.page_builder ||= TestBuilder.new
|
12
|
+
end
|
13
|
+
|
14
|
+
def api_endpoint_test_builder
|
15
|
+
self.api_endpoint_builder ||= TestBuilder.new
|
16
|
+
end
|
17
|
+
|
18
|
+
def page_test_suite
|
19
|
+
self.page_builder ? self.page_builder.suite : nil
|
20
|
+
end
|
21
|
+
|
22
|
+
def api_endpoint_test_suite
|
23
|
+
self.api_endpoint_builder ? self.api_endpoint_builder.suite : nil
|
12
24
|
end
|
13
25
|
end
|
14
26
|
|
@@ -20,6 +32,10 @@ module Kontrast
|
|
20
32
|
attr_accessor :test_domain, :production_domain
|
21
33
|
attr_accessor :browser_driver, :browser_profile
|
22
34
|
attr_accessor :fail_build
|
35
|
+
attr_accessor :workers_pool_size
|
36
|
+
attr_accessor :production_oauth_app_uid, :production_oauth_app_secret,
|
37
|
+
:test_oauth_app_uid, :test_oauth_app_secret, :oauth_token_url,
|
38
|
+
:oauth_token_from_response, :test_oauth_app_proc
|
23
39
|
|
24
40
|
def initialize
|
25
41
|
# Set defaults
|
@@ -40,7 +56,7 @@ module Kontrast
|
|
40
56
|
def validate
|
41
57
|
# Check that Kontrast has everything it needs to proceed
|
42
58
|
check_nil_vars(["test_domain", "production_domain"])
|
43
|
-
if Kontrast.
|
59
|
+
if Kontrast.page_test_suite.nil? && Kontrast.api_endpoint_test_suite.nil?
|
44
60
|
raise ConfigurationException.new("Kontrast has no tests to run.")
|
45
61
|
end
|
46
62
|
|
@@ -56,12 +72,21 @@ module Kontrast
|
|
56
72
|
end
|
57
73
|
end
|
58
74
|
|
59
|
-
def pages(width)
|
75
|
+
def pages(width, url_params = {})
|
60
76
|
if !block_given?
|
61
77
|
raise ConfigurationException.new("You must pass a block to the pages config option.")
|
62
78
|
end
|
63
|
-
Kontrast.
|
64
|
-
|
79
|
+
Kontrast.page_test_builder.prefix = width
|
80
|
+
Kontrast.page_test_builder.url_params = url_params
|
81
|
+
yield(Kontrast.page_test_builder)
|
82
|
+
end
|
83
|
+
|
84
|
+
def api_endpoints(group_name)
|
85
|
+
if !block_given?
|
86
|
+
raise ConfigurationException.new("You must pass a block to the api_endpoints config option.")
|
87
|
+
end
|
88
|
+
Kontrast.api_endpoint_test_builder.prefix = group_name
|
89
|
+
yield(Kontrast.api_endpoint_test_builder)
|
65
90
|
end
|
66
91
|
|
67
92
|
def before_run(&block)
|
@@ -112,6 +137,10 @@ module Kontrast
|
|
112
137
|
end
|
113
138
|
end
|
114
139
|
|
140
|
+
def workers_pool_size
|
141
|
+
@workers_pool_size || 5
|
142
|
+
end
|
143
|
+
|
115
144
|
private
|
116
145
|
def check_nil_vars(vars)
|
117
146
|
vars.each do |var|
|
data/lib/kontrast/exceptions.rb
CHANGED
@@ -2,11 +2,18 @@
|
|
2
2
|
<html lang="en">
|
3
3
|
<head>
|
4
4
|
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css">
|
5
|
+
<script type="text/javascript" src="https://code.jquery.com/jquery-2.1.4.min.js"></script>
|
6
|
+
<script type="text/javascript" src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
|
5
7
|
<style type="text/css">
|
6
8
|
.short-screenshot {
|
7
9
|
height: 200px;
|
8
10
|
width: 200px;
|
9
11
|
}
|
12
|
+
#diffs {
|
13
|
+
position: fixed;
|
14
|
+
z-index: 1;
|
15
|
+
margin-left: 33%;
|
16
|
+
}
|
10
17
|
</style>
|
11
18
|
</head>
|
12
19
|
<body>
|
@@ -19,10 +26,10 @@
|
|
19
26
|
<div class="panel">
|
20
27
|
<div class="panel-heading">Screenshots:</div>
|
21
28
|
<ul class="list-group list-group-flush">
|
22
|
-
<%
|
23
|
-
<li class="list-group-item"><strong><%=
|
24
|
-
<%
|
25
|
-
<li class="list-group-item"><a href="#<%= "#{
|
29
|
+
<% groups.sort.each do |group| %>
|
30
|
+
<li class="list-group-item"><strong><%=group%></strong></li>
|
31
|
+
<% without_diffs[group].keys.each do |name| %>
|
32
|
+
<li class="list-group-item"><a href="#<%= "#{group}_#{name}" %>-nondiff"><%=name%></a></li>
|
26
33
|
<% end %>
|
27
34
|
<% end %>
|
28
35
|
</ul>
|
@@ -31,12 +38,12 @@
|
|
31
38
|
<div class="col-lg-10">
|
32
39
|
<% if diffs.any? %>
|
33
40
|
<div class="row">
|
34
|
-
<div class="alert alert-warning" role="alert">
|
41
|
+
<div id="diffs" class="alert alert-warning" role="alert">
|
35
42
|
Tests with diffs:
|
36
43
|
<ul>
|
37
44
|
<% diffs.each do |test, diff| %>
|
38
45
|
<li>
|
39
|
-
<a href="#<%= test
|
46
|
+
<a href="#<%= test %>-diff"><%= test %></a>
|
40
47
|
</li>
|
41
48
|
<% end %>
|
42
49
|
</ul>
|
@@ -44,25 +51,149 @@
|
|
44
51
|
</div>
|
45
52
|
<% end %>
|
46
53
|
|
47
|
-
|
54
|
+
<h1>Diffs</h1>
|
55
|
+
<% with_diffs.keys.sort.each do |group| %>
|
56
|
+
<div class="row">
|
57
|
+
<a name="<%= group %>"></a>
|
58
|
+
<h2><%= group %></h2>
|
59
|
+
</div>
|
60
|
+
|
61
|
+
<% with_diffs[group].each do |test_name, variants| %>
|
62
|
+
<div id="<%= "#{group}_#{test_name}" %>-diff" class="row">
|
63
|
+
<div class="row">
|
64
|
+
<h2><%= test_name %></h2>
|
65
|
+
|
66
|
+
<% if variants.first[:type] == 'page' %>
|
67
|
+
<% variants.each do |variant| %>
|
68
|
+
<div class="col-lg-3">
|
69
|
+
<a href="<%=variant[:image]%>">
|
70
|
+
<img class="short-screenshot img-thumbnail" src="<%=variant[:thumb]%>">
|
71
|
+
</a>
|
72
|
+
<p class="text-center"><%=variant[:domain]%></p>
|
73
|
+
<% if variant[:diff_amt] %>
|
74
|
+
<p class="text-center text-muted"><%=variant[:diff_amt]%></p>
|
75
|
+
<% end %>
|
76
|
+
</div>
|
77
|
+
<% end %>
|
78
|
+
<% elsif variants.first[:type] == 'api_endpoint' %>
|
79
|
+
<% variants.each do |variant| %>
|
80
|
+
<div class="col-lg-3">
|
81
|
+
<% if variant[:domain] == 'diff' %>
|
82
|
+
<a>
|
83
|
+
<img class="short-screenshot img-thumbnail" src="http://dummyimage.com/200x200/?text=N/A">
|
84
|
+
</a>
|
85
|
+
<% else %>
|
86
|
+
<a href="<%=variant[:file]%>">
|
87
|
+
<img class="short-screenshot img-thumbnail" src="http://dummyimage.com/200x200/?text=JSON">
|
88
|
+
</a>
|
89
|
+
<% end %>
|
90
|
+
<p class="text-center"><%=variant[:domain]%></p>
|
91
|
+
</div>
|
92
|
+
<% end %>
|
93
|
+
<% end %>
|
94
|
+
</div>
|
95
|
+
<% if variants.first[:type] == 'api_endpoint' && variants.first[:images].any? %>
|
96
|
+
<h3>Images</h3>
|
97
|
+
<% div_id = ["images", test_name, SecureRandom.hex(6)].join('-') %>
|
98
|
+
<a class="btn btn-primary" role="button" data-toggle="collapse" href="#<%= div_id %>">
|
99
|
+
Expand / Collapse
|
100
|
+
</a>
|
101
|
+
<div class="collapse" id="<%= div_id %>">
|
102
|
+
<% variants.first[:images].each_with_index do |image, i| %>
|
103
|
+
<div class="row">
|
104
|
+
<% second_image = variants[1][:images][i] %>
|
105
|
+
<% diff_image = variants[-1][:images][i] %>
|
106
|
+
<div class="col-lg-3">
|
107
|
+
<a href="<%= image[:image] %>">
|
108
|
+
<img class="short-screenshot img-thumbnail" src="<%=image[:thumb]%>">
|
109
|
+
</a>
|
110
|
+
</div>
|
111
|
+
<div class="col-lg-3">
|
112
|
+
<a href="<%=second_image[:image]%>">
|
113
|
+
<img class="short-screenshot img-thumbnail" src="<%=second_image[:thumb]%>">
|
114
|
+
</a>
|
115
|
+
</div>
|
116
|
+
<div class="col-lg-3">
|
117
|
+
<a href="<%=diff_image[:image]%>">
|
118
|
+
<img class="short-screenshot img-thumbnail" src="<%=diff_image[:thumb]%>">
|
119
|
+
</a>
|
120
|
+
</div>
|
121
|
+
</div>
|
122
|
+
<% end %>
|
123
|
+
</div>
|
124
|
+
<% end %>
|
125
|
+
</div>
|
126
|
+
<% end %>
|
127
|
+
<% end %>
|
128
|
+
|
129
|
+
<h1>Non-diffs</h1>
|
130
|
+
<% without_diffs.keys.sort.each do |group| %>
|
48
131
|
<div class="row">
|
49
|
-
<a name="<%=
|
50
|
-
<h2><%=
|
132
|
+
<a name="<%= group %>"></a>
|
133
|
+
<h2><%= group %></h2>
|
51
134
|
</div>
|
52
135
|
|
53
|
-
<%
|
54
|
-
<div id="<%= "#{
|
136
|
+
<% without_diffs[group].each do |test_name, variants| %>
|
137
|
+
<div id="<%= "#{group}_#{test_name}" %>-nondiff" class="row">
|
55
138
|
<div class="row">
|
56
|
-
<h2><%=
|
139
|
+
<h2><%= test_name %></h2>
|
140
|
+
|
141
|
+
<% if variants.first[:type] == 'page' %>
|
142
|
+
<% variants.each do |variant| %>
|
143
|
+
<div class="col-lg-3">
|
144
|
+
<a href="<%=variant[:image]%>">
|
145
|
+
<img class="short-screenshot img-thumbnail" src="<%=variant[:thumb]%>">
|
146
|
+
</a>
|
147
|
+
<p class="text-center"><%=variant[:domain]%></p>
|
148
|
+
<% if variant[:diff_amt] %>
|
149
|
+
<p class="text-center text-muted"><%=variant[:diff_amt]%></p>
|
150
|
+
<% end %>
|
151
|
+
</div>
|
152
|
+
<% end %>
|
153
|
+
<% elsif variants.first[:type] == 'api_endpoint' %>
|
154
|
+
<% variants.each do |variant| %>
|
155
|
+
<div class="col-lg-3">
|
156
|
+
<% if variant[:domain] == 'diff' %>
|
157
|
+
<a href="<%=variant[:file]%>">
|
158
|
+
<img class="short-screenshot img-thumbnail" src="http://dummyimage.com/200x200/?text=N/A">
|
159
|
+
</a>
|
160
|
+
<% else %>
|
161
|
+
<a href="<%=variant[:file]%>">
|
162
|
+
<img class="short-screenshot img-thumbnail" src="http://dummyimage.com/200x200/?text=JSON">
|
163
|
+
</a>
|
164
|
+
<% end %>
|
165
|
+
<p class="text-center"><%=variant[:domain]%></p>
|
166
|
+
</div>
|
167
|
+
<% end %>
|
168
|
+
<% end %>
|
57
169
|
</div>
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
170
|
+
|
171
|
+
<% if variants.first[:type] == 'api_endpoint' && variants.first[:images].any? %>
|
172
|
+
<% div_id = ["images", test_name, SecureRandom.hex(6)].join('-') %>
|
173
|
+
<a class="btn btn-primary" role="button" data-toggle="collapse" href="#<%= div_id %>">
|
174
|
+
Expand / Collapse
|
175
|
+
</a>
|
176
|
+
<div class="collapse" id="<%= div_id %>">
|
177
|
+
<% variants.first[:images].each_with_index do |image, i| %>
|
178
|
+
<div class="row">
|
179
|
+
<% second_image = variants[1][:images][i] %>
|
180
|
+
<% diff_image = variants[-1][:images][i] %>
|
181
|
+
<div class="col-lg-3">
|
182
|
+
<a href="<%= image[:image] %>">
|
183
|
+
<img class="short-screenshot img-thumbnail" src="<%=image[:thumb]%>">
|
184
|
+
</a>
|
185
|
+
</div>
|
186
|
+
<div class="col-lg-3">
|
187
|
+
<a href="<%=second_image[:image]%>">
|
188
|
+
<img class="short-screenshot img-thumbnail" src="<%=second_image[:thumb]%>">
|
189
|
+
</a>
|
190
|
+
</div>
|
191
|
+
<div class="col-lg-3">
|
192
|
+
<a href="<%=diff_image[:image]%>">
|
193
|
+
<img class="short-screenshot img-thumbnail" src="<%=diff_image[:thumb]%>">
|
194
|
+
</a>
|
195
|
+
</div>
|
196
|
+
</div>
|
66
197
|
<% end %>
|
67
198
|
</div>
|
68
199
|
<% end %>
|
@@ -73,4 +204,4 @@
|
|
73
204
|
</div>
|
74
205
|
</div>
|
75
206
|
</body>
|
76
|
-
</html>
|
207
|
+
</html>
|