domreactor-redglass 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +19 -0
- data/Gemfile +3 -0
- data/LICENSE +22 -0
- data/README.md +38 -0
- data/Rakefile +7 -0
- data/domreactor-redglass.gemspec +29 -0
- data/example/domreactor_example.rb +27 -0
- data/lib/domreactor-redglass/chain_reaction.rb +119 -0
- data/lib/domreactor-redglass/config.rb +24 -0
- data/lib/domreactor-redglass/version.rb +3 -0
- data/lib/domreactor-redglass.rb +16 -59
- data/spec/chain_reaction_spec.rb +43 -0
- data/spec/data/invalid_archive_by_quota/firefox_20.0_1369614643/dom.json +271 -0
- data/spec/data/invalid_archive_by_quota/firefox_20.0_1369614643/metadata.json +1 -0
- data/spec/data/invalid_archive_by_quota/firefox_20.0_1369614643/screenshot.png +0 -0
- data/spec/data/invalid_archive_by_quota/firefox_20.0_1369614643/source.html +76 -0
- data/spec/data/valid_archive/firefox_20.0_1369614643/dom.json +271 -0
- data/spec/data/valid_archive/firefox_20.0_1369614643/metadata.json +1 -0
- data/spec/data/valid_archive/firefox_20.0_1369614643/screenshot.png +0 -0
- data/spec/data/valid_archive/firefox_20.0_1369614643/source.html +76 -0
- data/spec/data/valid_archive/firefox_20.0_1369614644/dom.json +272 -0
- data/spec/data/valid_archive/firefox_20.0_1369614644/metadata.json +1 -0
- data/spec/data/valid_archive/firefox_20.0_1369614644/screenshot.png +0 -0
- data/spec/data/valid_archive/firefox_20.0_1369614644/source.html +76 -0
- data/spec/domreactor_spec.rb +46 -0
- data/spec/spec_helper.rb +6 -0
- metadata +77 -3
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Frank O'Hara
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
# DomReactor + RedGlass
|
2
|
+
|
3
|
+
A RedGlass plugin which enables snapshot archives to be analyzed by DomReactor.
|
4
|
+
|
5
|
+
## Requirements
|
6
|
+
|
7
|
+
You will need a DomReactor account and an API token to use DomReactor's automated layout analysis service. Visit http://domreactor.com to sign up.
|
8
|
+
|
9
|
+
## Usage
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
# Configurations
|
13
|
+
browsers = [:firefox, :chrome]
|
14
|
+
archive_location = '/Users/you/Desktop/red_glass_snapshots'
|
15
|
+
test_id = 1
|
16
|
+
opts = {
|
17
|
+
baseline_browser: {name: 'firefox', version: '20.0', platform: 'darwin'},
|
18
|
+
threshold: 0.02
|
19
|
+
}
|
20
|
+
|
21
|
+
# Take a RedGlass snapshot in each browser.
|
22
|
+
browsers.each do |browser|
|
23
|
+
listener = RedGlassListener.new
|
24
|
+
driver = Selenium::WebDriver.for browser, :listener => listener
|
25
|
+
red_glass = RedGlass.new driver, {listener: listener, archive_location: archive_location, test_id: test_id}
|
26
|
+
driver.navigate.to 'http://google.com'
|
27
|
+
red_glass.take_snapshot
|
28
|
+
driver.quit
|
29
|
+
end
|
30
|
+
|
31
|
+
# Send the page archives to DomReactor.
|
32
|
+
DomReactorRedGlass.auth_token = '12345'
|
33
|
+
DomReactorRedGlass.create_chain_reaction("#{archive_location}/#{test_id}", opts)
|
34
|
+
```
|
35
|
+
|
36
|
+
# License
|
37
|
+
|
38
|
+
The MIT License - Copyright (c) 2013 Frank O'Hara
|
data/Rakefile
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'domreactor-redglass/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = 'domreactor-redglass'
|
8
|
+
s.version = DomReactorRedGlass::VERSION
|
9
|
+
s.date = '2013-05-31'
|
10
|
+
s.summary = 'DomReactor plugin for RedGlass.'
|
11
|
+
s.description = 'Send RedGlass page archives to DomReactor for automated layout analysis.'
|
12
|
+
s.authors = ["Frank O'Hara", "Chris Lamb"]
|
13
|
+
s.email = ["frankj.ohara@gmail.com", "lamb.chrisr@gmail.com"]
|
14
|
+
s.homepage = 'https://github.com/bimech/domreactor-redglass'
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split($/)
|
17
|
+
s.test_files = s.files.grep(%r{^(test|spec|features)/})
|
18
|
+
s.require_paths = ["lib"]
|
19
|
+
|
20
|
+
s.add_dependency 'rest-client'
|
21
|
+
s.add_dependency 'json'
|
22
|
+
s.add_dependency 'rubyzip'
|
23
|
+
s.add_dependency 'red-glass', '>= 0.1.1'
|
24
|
+
|
25
|
+
s.add_development_dependency "bundler", "~> 1.3"
|
26
|
+
s.add_development_dependency "rake"
|
27
|
+
s.add_development_dependency "rspec", "~> 2.13.0"
|
28
|
+
s.add_development_dependency 'selenium-webdriver'
|
29
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'selenium-webdriver'
|
2
|
+
require 'red-glass'
|
3
|
+
require 'domreactor-redglass'
|
4
|
+
include DomReactorRedGlass
|
5
|
+
|
6
|
+
# Configurations
|
7
|
+
browsers = [:firefox, :chrome]
|
8
|
+
archive_location = '/Users/you/Desktop/red_glass_snapshots'
|
9
|
+
test_id = 1
|
10
|
+
opts = {
|
11
|
+
baseline_browser: {name: 'firefox', version: '20.0', platform: 'darwin'},
|
12
|
+
threshold: 0.02
|
13
|
+
}
|
14
|
+
|
15
|
+
# Take a RedGlass snapshot in each browser.
|
16
|
+
browsers.each do |browser|
|
17
|
+
listener = RedGlassListener.new
|
18
|
+
driver = Selenium::WebDriver.for browser, :listener => listener
|
19
|
+
red_glass = RedGlass.new driver, {listener: listener, archive_location: archive_location, test_id: test_id}
|
20
|
+
driver.navigate.to 'http://google.com'
|
21
|
+
red_glass.take_snapshot
|
22
|
+
driver.quit
|
23
|
+
end
|
24
|
+
|
25
|
+
# Send the page archives to DomReactor.
|
26
|
+
DomReactorRedGlass.auth_token = '12345'
|
27
|
+
DomReactorRedGlass.create_chain_reaction('http://google.com', "#{archive_location}/#{test_id}", opts)
|
@@ -0,0 +1,119 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'rest-client'
|
3
|
+
|
4
|
+
module DomReactorRedGlass
|
5
|
+
class ChainReaction
|
6
|
+
attr_reader :id, :info, :page_url
|
7
|
+
|
8
|
+
def initialize(page_url, opts)
|
9
|
+
@page_url = page_url
|
10
|
+
@opts = opts
|
11
|
+
@info = info
|
12
|
+
end
|
13
|
+
|
14
|
+
def auth_token
|
15
|
+
Config.auth_token
|
16
|
+
end
|
17
|
+
|
18
|
+
def resource_url
|
19
|
+
"#{Config.url}/api/v1/chain_reactions"
|
20
|
+
end
|
21
|
+
|
22
|
+
def chain_reaction_url
|
23
|
+
"#{resource_url}/#{id}"
|
24
|
+
end
|
25
|
+
|
26
|
+
def id
|
27
|
+
id ||= info[:id]
|
28
|
+
end
|
29
|
+
|
30
|
+
def info
|
31
|
+
@info ||= create_chain_reaction(@opts)
|
32
|
+
end
|
33
|
+
|
34
|
+
def post_archives(archive_location)
|
35
|
+
web_browser_ids = []
|
36
|
+
#TODO: Cache the web prowser for each archive location.
|
37
|
+
Dir.foreach(archive_location) do |file|
|
38
|
+
next if file == '.' or file == '..'
|
39
|
+
path = "#{archive_location}/#{file}"
|
40
|
+
if is_valid_page_archive? path
|
41
|
+
web_browser_ids << get_web_browser_info(parse_json_file("#{path}/metadata.json")[:browser])[:id]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
update_chain_reaction(web_browser_ids: web_browser_ids)
|
45
|
+
dom_gun_reaction = create_dom_gun_reaction
|
46
|
+
Dir.foreach(archive_location) do |file|
|
47
|
+
next if file == '.' or file == '..'
|
48
|
+
path = "#{archive_location}/#{file}"
|
49
|
+
if is_valid_page_archive? path
|
50
|
+
payload = create_payload(path)
|
51
|
+
RestClient.put("#{chain_reaction_url}/dom_gun_reactions/#{dom_gun_reaction[:id]}", payload, content_type: 'application/json')
|
52
|
+
end
|
53
|
+
end
|
54
|
+
start_reaction(dom_gun_reaction[:id])
|
55
|
+
end
|
56
|
+
|
57
|
+
def start_reaction(id)
|
58
|
+
params = {
|
59
|
+
auth_token: auth_token,
|
60
|
+
}
|
61
|
+
RestClient.post("#{chain_reaction_url}/dom_gun_reactions/#{id}/start", params, content_type: 'application/json')
|
62
|
+
end
|
63
|
+
|
64
|
+
def baseline_browser
|
65
|
+
get_web_browser_info(@opts[:baseline_browser])
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def get_web_browser_info(browser_info={})
|
71
|
+
parameters = {auth_token: auth_token,content_type: 'application/json', accept: :json}.merge(browser_info)
|
72
|
+
#TODO: Handle errors.
|
73
|
+
response = RestClient.get("#{Config.url}/api/v1/web_browsers", {params: parameters})
|
74
|
+
JSON.parse(response, symbolize_names: true)[:web_browsers].first
|
75
|
+
end
|
76
|
+
|
77
|
+
def update_chain_reaction(params)
|
78
|
+
RestClient.put(chain_reaction_url, params.merge(auth_token: auth_token), content_type: 'application/json')
|
79
|
+
end
|
80
|
+
|
81
|
+
def create_chain_reaction(opts)
|
82
|
+
payload = {
|
83
|
+
auth_token: auth_token,
|
84
|
+
analysis_only: true,
|
85
|
+
displacement_threshold: opts[:threshold] || 0.04,
|
86
|
+
baseline_browser_id: baseline_browser[:id]
|
87
|
+
}.to_json
|
88
|
+
response = RestClient.post(resource_url, payload, content_type: 'application/json') do |response|
|
89
|
+
unless response.code == 200 && response.body.match(/chain_reaction/)
|
90
|
+
raise "ChainReaction initialization failed with code #{response.code} : #{response.body}"
|
91
|
+
end
|
92
|
+
response
|
93
|
+
end
|
94
|
+
JSON.parse(response, symbolize_names: true)[:chain_reaction]
|
95
|
+
end
|
96
|
+
|
97
|
+
def create_dom_gun_reaction
|
98
|
+
payload = {
|
99
|
+
chain_reaction_id: id,
|
100
|
+
auth_token: auth_token,
|
101
|
+
page_url: page_url
|
102
|
+
}
|
103
|
+
dom_gun = RestClient.post("#{chain_reaction_url}/dom_gun_reactions", payload, content_type: 'application/json')
|
104
|
+
JSON.parse(dom_gun, symbolize_names: true)
|
105
|
+
end
|
106
|
+
|
107
|
+
def create_payload(path)
|
108
|
+
meta_data = parse_json_file("#{path}/metadata.json")
|
109
|
+
{
|
110
|
+
auth_token: auth_token,
|
111
|
+
meta_data: meta_data,
|
112
|
+
web_browser_id: get_web_browser_info(meta_data[:browser])[:id],
|
113
|
+
dom_elements: File.open("#{path}/dom.json", 'rb') {|f| f.read},
|
114
|
+
page_source: File.open("#{path}/source.html") {|f| f.read},
|
115
|
+
file: File.open("#{path}/screenshot.png")
|
116
|
+
}
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module DomReactorRedGlass
|
2
|
+
class Config
|
3
|
+
#DOMREACTOR_INIT_CHAIN_REACTION_URL = 'http://domreactor.com'
|
4
|
+
DOMREACTOR_INIT_CHAIN_REACTION_URL = 'http://localhost:3000'
|
5
|
+
|
6
|
+
class << self
|
7
|
+
def url
|
8
|
+
@url || DOMREACTOR_INIT_CHAIN_REACTION_URL
|
9
|
+
end
|
10
|
+
|
11
|
+
def url=(url)
|
12
|
+
@url = url
|
13
|
+
end
|
14
|
+
|
15
|
+
def auth_token
|
16
|
+
@auth_token
|
17
|
+
end
|
18
|
+
|
19
|
+
def auth_token=(auth_token)
|
20
|
+
@auth_token = auth_token
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/domreactor-redglass.rb
CHANGED
@@ -1,22 +1,26 @@
|
|
1
|
-
require '
|
2
|
-
require '
|
3
|
-
require '
|
1
|
+
require 'domreactor-redglass/chain_reaction'
|
2
|
+
require 'domreactor-redglass/config'
|
3
|
+
require 'domreactor-redglass/version'
|
4
4
|
|
5
5
|
module DomReactorRedGlass
|
6
6
|
|
7
7
|
REQUIRED_ARCHIVE_FILES = %w(dom.json metadata.json screenshot.png source.html)
|
8
8
|
REQUIRED_BASELINE_BROWSER_CONFIG_KEYS = [:name, :version, :platform]
|
9
|
-
DOMREACTOR_INIT_CHAIN_REACTION_URL = 'http://domreactor.com/api/v1/chain_reactions'
|
10
9
|
|
11
|
-
def
|
10
|
+
def auth_token=(auth_token)
|
11
|
+
Config.auth_token=auth_token
|
12
|
+
end
|
13
|
+
|
14
|
+
def auth_token
|
15
|
+
Config.auth_token
|
16
|
+
end
|
17
|
+
|
18
|
+
def create_chain_reaction(page_url, archive_location, opts)
|
12
19
|
detect_archive_location archive_location
|
13
20
|
detect_min_archive_quota archive_location
|
14
|
-
detect_baseline_browser archive_location,
|
15
|
-
|
16
|
-
|
17
|
-
post_url = "#{api_url}/#{id}"
|
18
|
-
archive_files = zip_archives archive_location
|
19
|
-
post_archives(api_token, post_url, archive_files)
|
21
|
+
detect_baseline_browser archive_location, opts
|
22
|
+
@chain_reaction = ChainReaction.new(page_url, opts)
|
23
|
+
@chain_reaction.post_archives(archive_location)
|
20
24
|
end
|
21
25
|
|
22
26
|
def detect_archive_location(archive_location)
|
@@ -87,55 +91,8 @@ module DomReactorRedGlass
|
|
87
91
|
metadata
|
88
92
|
end
|
89
93
|
|
90
|
-
def init_chain_reaction(api_token, archive_location, config)
|
91
|
-
payload = {
|
92
|
-
auth_token: api_token,
|
93
|
-
analysis_only: true,
|
94
|
-
threshold: config[:threshold] || 0.04,
|
95
|
-
baseline_browser: config[:baseline_browser],
|
96
|
-
archive_count: sum_archive_count(archive_location),
|
97
|
-
metadata: archive_metadata(archive_location)
|
98
|
-
}.to_json
|
99
|
-
api_url = config[:api_url] || DOMREACTOR_INIT_CHAIN_REACTION_URL
|
100
|
-
response = RestClient.post(api_url, payload, content_type: 'application/json') do |response|
|
101
|
-
unless response.code == 200 && response.body.match(/chain_reaction/)
|
102
|
-
raise "ChainReaction initialization failed with code #{response.code} : #{response.body}"
|
103
|
-
end
|
104
|
-
response
|
105
|
-
end
|
106
|
-
JSON.parse(response, symbolize_names: true)
|
107
|
-
end
|
108
|
-
|
109
|
-
def zip_archives(archive_location)
|
110
|
-
zip_files = []
|
111
|
-
Dir.foreach(archive_location) do |file|
|
112
|
-
next if file == '.' or file == '..'
|
113
|
-
path = "#{archive_location}/#{file}"
|
114
|
-
if is_valid_page_archive? path
|
115
|
-
zip_file = "#{path}.zip"
|
116
|
-
zip_files << zip_file
|
117
|
-
Zip::ZipFile.open(zip_file, Zip::ZipFile::CREATE) do |zipfile|
|
118
|
-
REQUIRED_ARCHIVE_FILES.each do |filename|
|
119
|
-
zipfile.add(filename, path + '/' + filename)
|
120
|
-
end
|
121
|
-
end
|
122
|
-
end
|
123
|
-
end
|
124
|
-
zip_files
|
125
|
-
end
|
126
|
-
|
127
|
-
def post_archives(api_token, post_url, zip_files)
|
128
|
-
zip_files.each do |file|
|
129
|
-
payload = {
|
130
|
-
auth_token: api_token,
|
131
|
-
file: File.new(file, 'rb')
|
132
|
-
}
|
133
|
-
RestClient.put(post_url, payload)
|
134
|
-
end
|
135
|
-
end
|
136
|
-
|
137
94
|
def parse_json_file(path)
|
138
95
|
json_str = File.open(path, 'rb') {|f| f.read}
|
139
96
|
JSON.parse(json_str, symbolize_names: true)
|
140
97
|
end
|
141
|
-
end
|
98
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe DomReactorRedGlass::ChainReaction do
|
4
|
+
let(:chain_reaction) { DomReactorRedGlass::ChainReaction.new('http://domreactor.com', {}) }
|
5
|
+
before do
|
6
|
+
#stub chain reaction creation call
|
7
|
+
RestClient.stub(:post) { {chain_reaction: {id: 42, some_info: 'yay'}}.to_json }
|
8
|
+
ChainReaction.any_instance.stub(:baseline_browser) { {id: 1} }
|
9
|
+
end
|
10
|
+
|
11
|
+
describe '#new' do
|
12
|
+
it 'posts to domreactor to create a chain reaction' do
|
13
|
+
expected_response = {chain_reaction: {id: 42, some_info: 'yay'}}
|
14
|
+
RestClient.should_receive(:post).once.and_return(expected_response.to_json)
|
15
|
+
chain_reaction.info.should eq(expected_response[:chain_reaction])
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe '#info' do
|
20
|
+
it "gets the id from the info" do
|
21
|
+
chain_reaction.info.should eq({id: 42, some_info: 'yay'})
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe '#post_archives' do
|
26
|
+
before do
|
27
|
+
chain_reaction.stub(:create_dom_gun_reaction) { {id: 81, stuff: true} }
|
28
|
+
chain_reaction.stub(:get_web_browser_info).exactly(4).times.
|
29
|
+
with({:name=>"firefox", :version=>"20.0", :platform=>"darwin"}) { {id: 22, browser: 'firefox'} }
|
30
|
+
RestClient.should_receive(:put).exactly(3).times
|
31
|
+
RestClient.should_receive(:post).once
|
32
|
+
end
|
33
|
+
it 'creates a dom gun reaction' do
|
34
|
+
chain_reaction.post_archives("#{SPEC_ROOT}/data/valid_archive")
|
35
|
+
end
|
36
|
+
end
|
37
|
+
describe '#id' do
|
38
|
+
it "gets the id from the info" do
|
39
|
+
chain_reaction.id.should eq(42)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|