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 ADDED
@@ -0,0 +1,19 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ .idea/
7
+ Gemfile.lock
8
+ InstalledFiles
9
+ _yardoc
10
+ coverage
11
+ doc/
12
+ lib/bundler/man
13
+ pkg
14
+ rdoc
15
+ spec/reports
16
+ test/tmp
17
+ test/version_tmp
18
+ tmp
19
+ chromedriver.log
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
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,7 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ desc 'Run specs'
7
+ task :default => :spec
@@ -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
@@ -0,0 +1,3 @@
1
+ module DomReactorRedGlass
2
+ VERSION = "0.0.3"
3
+ end
@@ -1,22 +1,26 @@
1
- require 'json'
2
- require 'rest-client'
3
- require 'zip/zip'
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 create_chain_reaction(api_token, archive_location, config)
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, config
15
- id = init_chain_reaction(api_token, archive_location, config)[:chain_reaction][:id]
16
- api_url = config[:api_url] || DOMREACTOR_INIT_CHAIN_REACTION_URL
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
+