monet 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +19 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +6 -0
- data/Gemfile +10 -0
- data/LICENSE.txt +22 -0
- data/README.md +68 -0
- data/Rakefile +10 -0
- data/lib/monet/capture.rb +37 -0
- data/lib/monet/capture_map.rb +72 -0
- data/lib/monet/compare.rb +58 -0
- data/lib/monet/config.rb +45 -0
- data/lib/monet/diff_strategy.rb +70 -0
- data/lib/monet/errors.rb +8 -0
- data/lib/monet/version.rb +3 -0
- data/lib/monet.rb +9 -0
- data/monet.gemspec +42 -0
- data/spec/capture_map_spec.rb +116 -0
- data/spec/capture_spec.rb +47 -0
- data/spec/cassettes/spider.yml +28572 -0
- data/spec/compare_spec.rb +51 -0
- data/spec/config_spec.rb +58 -0
- data/spec/fixtures/base.png +0 -0
- data/spec/fixtures/diff.png +0 -0
- data/spec/fixtures/diff_size.png +0 -0
- data/spec/fixtures/same.png +0 -0
- data/spec/spec_helper.rb +13 -0
- metadata +195 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 4ba54b53a3cb965bdcc834da924e37f3df244e76
|
4
|
+
data.tar.gz: ca5a32475a8411d8dcc61e750ca6e534582a38a2
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e2debbc40dc2411f580a40ee1dd8e8bf6691ca342779ac773a8ca48d059edd35d442a89cbb3cffb28c402f88f0413ff99b7ad16c6313cac9b1607c078545ffd9
|
7
|
+
data.tar.gz: 0308e17eeac8c8e00d60a1e752b74711186f1faa16ec0bc729082b14a8d0f3e1d8dc45132fec54b46441f7eb972a66bfcdc5fc1de0ce56ad323d246fcadfa8e7
|
data/.gitignore
ADDED
data/.ruby-gemset
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
monet
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.0
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Luke van der Hoeven
|
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,68 @@
|
|
1
|
+
# Monet
|
2
|
+
[![Build Status](https://travis-ci.org/plukevdh/monet.png?branch=master)](https://travis-ci.org/plukevdh/monet)
|
3
|
+
[![Code Climate](https://codeclimate.com/github/plukevdh/monet.png)](https://codeclimate.com/github/plukevdh/monet)
|
4
|
+
|
5
|
+
Monet is a libary built for making testing interfaces and design easy. We all have interfaces that we've added a new button, changed some CSS, or added new javascript interaction to and had the page layout explode unexpectedly. Monet is meant to make tracking those changes and ensuring consistent automatably easy.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
gem 'monet'
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install monet
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
The basic gem requires a config file that is somehow called on the app startup. This config primarily exists to give the gem a list of paths it needs to collect and either baseline or compare to previous baselines. This config might look something like this:
|
24
|
+
|
25
|
+
```ruby
|
26
|
+
Monet.config do |config|
|
27
|
+
config.driver = :poltergeist
|
28
|
+
config.dimensions = [1440,900]
|
29
|
+
|
30
|
+
config.map do |map|
|
31
|
+
map.add 'home/index'
|
32
|
+
map.add 'home/show'
|
33
|
+
end
|
34
|
+
|
35
|
+
# alternatively...
|
36
|
+
|
37
|
+
config.map :spider
|
38
|
+
end
|
39
|
+
```
|
40
|
+
|
41
|
+
## Todo
|
42
|
+
- Site spidering
|
43
|
+
- Baseline caching
|
44
|
+
- Browser/driver config
|
45
|
+
- Parallelize PNG diffing
|
46
|
+
- Dashboard
|
47
|
+
- Rails integration
|
48
|
+
- Sinatra/Rack integration
|
49
|
+
- Web UI
|
50
|
+
|
51
|
+
## Contributing
|
52
|
+
|
53
|
+
1. Fork it
|
54
|
+
2. Branch it (`git checkout -b my-new-feature`)
|
55
|
+
3. Commit it (`git commit -am 'Add some feature'`)
|
56
|
+
4. Push it (`git push origin my-new-feature`)
|
57
|
+
5. Pull Request it!
|
58
|
+
|
59
|
+
Willing to consider commit bit priviledges to anyone who expresses extreme interest in the project.
|
60
|
+
|
61
|
+
## Credits
|
62
|
+
|
63
|
+
Big shout to __Jeff Kreeftmeijer__ for his blog post on [ChunkyPNG](http://jeffkreeftmeijer.com/2011/comparing-images-and-creating-image-diffs/). The Monet::Compare::CompareStrategy code is basically what he wrote in the post.
|
64
|
+
|
65
|
+
## Alternatives
|
66
|
+
|
67
|
+
- https://github.com/intridea/green_onion : Mostly the same thing I did here, but I didn't know about it starting out. I like what they did, but I had a different take, so I decided to pursue my version to the end.
|
68
|
+
- https://github.com/BBC-News/wraith : Similar idea again, but more limited in features, also doesn't allow for baselining, more of a multi-site/URL compare.
|
data/Rakefile
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
require "capybara"
|
2
|
+
require 'capybara/poltergeist'
|
3
|
+
require "capybara/dsl"
|
4
|
+
|
5
|
+
require 'monet/config'
|
6
|
+
|
7
|
+
module Monet
|
8
|
+
class Capture
|
9
|
+
include Capybara::DSL
|
10
|
+
|
11
|
+
def initialize(config={})
|
12
|
+
@config = (config.is_a? Monet::Config) ? config : Monet::Config.new(config)
|
13
|
+
|
14
|
+
# TODO: make configurable
|
15
|
+
Capybara.default_driver = @config.driver
|
16
|
+
end
|
17
|
+
|
18
|
+
def capture(path)
|
19
|
+
visit normalize_path(path)
|
20
|
+
page.driver.render(image_name_from_path(path), full: true)
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
def capture_path
|
25
|
+
@config.capture_dir
|
26
|
+
end
|
27
|
+
|
28
|
+
def normalize_path(path)
|
29
|
+
"http://#{path}" unless path.start_with?("https?")
|
30
|
+
end
|
31
|
+
|
32
|
+
def image_name_from_path(path)
|
33
|
+
name = path.gsub(/https?:\/\//, '').gsub('.', '_')
|
34
|
+
"#{capture_path}/#{name}-#{Time.now.to_i}.png"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'spidr'
|
2
|
+
|
3
|
+
module Monet
|
4
|
+
class CaptureMap
|
5
|
+
extend Forwardable
|
6
|
+
|
7
|
+
class PathCollection
|
8
|
+
attr_reader :paths, :root_url
|
9
|
+
|
10
|
+
def initialize(root_url)
|
11
|
+
@root_url = root_url
|
12
|
+
@paths = []
|
13
|
+
end
|
14
|
+
|
15
|
+
def add(path)
|
16
|
+
@paths << normalized_path(path)
|
17
|
+
end
|
18
|
+
|
19
|
+
def normalized_path(path)
|
20
|
+
path.chomp "/"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class PathSpider < PathCollection
|
25
|
+
SKIP_EXT = %w(js css png jpg mp4 txt zip ico ogv ogg pdf gz)
|
26
|
+
SKIP_PATHS = [/\?.*/]
|
27
|
+
|
28
|
+
def paths
|
29
|
+
@paths = normalize Spidr.site(@root_url, ignore_links: ignores)
|
30
|
+
end
|
31
|
+
|
32
|
+
def ignores
|
33
|
+
SKIP_EXT.map {|x| Regexp.new x }.concat SKIP_PATHS
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
def normalize(spider_results)
|
38
|
+
spider_results.history.map &:to_s
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
InvalidURL = Class.new(StandardError)
|
43
|
+
|
44
|
+
attr_reader :type
|
45
|
+
|
46
|
+
def initialize(root_url, type=:explicit, &block)
|
47
|
+
@type = type
|
48
|
+
@path_helper = type_mapper.new parse_uri(root_url)
|
49
|
+
|
50
|
+
yield(@path_helper) if block_given?
|
51
|
+
end
|
52
|
+
|
53
|
+
def_delegators :@path_helper, :paths, :add, :root_url
|
54
|
+
|
55
|
+
def type_mapper
|
56
|
+
case @type
|
57
|
+
when :explicit
|
58
|
+
PathCollection
|
59
|
+
when :spider
|
60
|
+
PathSpider
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
def parse_uri(path)
|
66
|
+
uri = URI.parse path
|
67
|
+
raise InvalidURL, "#{path} is not a valid url" if uri.class == URI::Generic
|
68
|
+
|
69
|
+
uri.to_s
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'oily_png'
|
2
|
+
require 'monet/diff_strategy'
|
3
|
+
|
4
|
+
module Monet
|
5
|
+
class Compare
|
6
|
+
extend Forwardable
|
7
|
+
|
8
|
+
class Changeset
|
9
|
+
def initialize(base_image, pixel_array)
|
10
|
+
@base_image = base_image
|
11
|
+
@changed_pixels = pixel_array
|
12
|
+
end
|
13
|
+
|
14
|
+
def modified?
|
15
|
+
pixels_changed > 0
|
16
|
+
end
|
17
|
+
|
18
|
+
def pixels_changed
|
19
|
+
@changed_pixels.count
|
20
|
+
end
|
21
|
+
|
22
|
+
def percentage_changed
|
23
|
+
((pixels_changed.to_f / @base_image.area.to_f) * 100).round(2)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def initialize(strategy=ColorBlend)
|
28
|
+
@strategy_class = strategy
|
29
|
+
end
|
30
|
+
|
31
|
+
def compare(base_image, new_image)
|
32
|
+
base_png = ChunkyPNG::Image.from_file(base_image)
|
33
|
+
new_png = ChunkyPNG::Image.from_file(new_image)
|
34
|
+
|
35
|
+
diff_stats = []
|
36
|
+
|
37
|
+
# TODO: make configurable
|
38
|
+
diff_strategy = @strategy_class.new(base_png, new_png)
|
39
|
+
|
40
|
+
base_png.height.times do |y|
|
41
|
+
base_png.row(y).each_with_index do |pixel, x|
|
42
|
+
diff_strategy.calculate_for_pixel(pixel, x, y)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
changeset = Changeset.new(base_png, diff_strategy.score)
|
47
|
+
diff_strategy.save(diff_filename(base_image)) if changeset.modified?
|
48
|
+
|
49
|
+
changeset
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def diff_filename(base_filename)
|
55
|
+
base_filename[0..-5] << "-diff.png"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
data/lib/monet/config.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'monet/capture_map'
|
2
|
+
|
3
|
+
module Monet
|
4
|
+
class Config
|
5
|
+
MissingBaseURL = Class.new(Exception)
|
6
|
+
|
7
|
+
DEFAULT_OPTIONS = {
|
8
|
+
driver: :poltergeist,
|
9
|
+
dimensions: [1440],
|
10
|
+
map: nil,
|
11
|
+
base_url: nil,
|
12
|
+
capture_dir: "./captures"
|
13
|
+
}
|
14
|
+
|
15
|
+
attr_accessor *DEFAULT_OPTIONS.keys
|
16
|
+
|
17
|
+
def initialize(opts={})
|
18
|
+
DEFAULT_OPTIONS.each do |opt, default|
|
19
|
+
send "#{opt}=", opts[opt] || default
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.config(&block)
|
24
|
+
cfg = new
|
25
|
+
block.call cfg
|
26
|
+
cfg
|
27
|
+
end
|
28
|
+
|
29
|
+
def base_url
|
30
|
+
raise MissingBaseURL, "Please set the base_url in the config" unless @base_url
|
31
|
+
@base_url
|
32
|
+
end
|
33
|
+
|
34
|
+
def capture_dir=(path)
|
35
|
+
@capture_dir = File.expand_path(path)
|
36
|
+
end
|
37
|
+
|
38
|
+
def map(type=:explicit, &block)
|
39
|
+
@map ||= CaptureMap.new(base_url, type)
|
40
|
+
|
41
|
+
block.call(@map) if block_given? && type == :explicit
|
42
|
+
@map
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module Monet
|
2
|
+
class DiffStrategy
|
3
|
+
include ChunkyPNG::Color
|
4
|
+
|
5
|
+
attr_reader :score
|
6
|
+
|
7
|
+
def initialize(base_image, diff_image)
|
8
|
+
@base_image = base_image
|
9
|
+
@diff_image = diff_image
|
10
|
+
@score = []
|
11
|
+
|
12
|
+
raise Errors::DifferentDimensions unless dimensions_match?
|
13
|
+
end
|
14
|
+
|
15
|
+
def calculate_for_pixel(pixel, x, y)
|
16
|
+
@score << [x,y] unless pixel == @diff_image[x,y]
|
17
|
+
end
|
18
|
+
|
19
|
+
def save(filename)
|
20
|
+
@output.save(filename)
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
def dimensions_match?
|
25
|
+
@base_image.width == @diff_image.width &&
|
26
|
+
@base_image.height == @diff_image.height
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
def for_color(color, *params)
|
31
|
+
send color, *params
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class Grayscale < DiffStrategy
|
36
|
+
def initialize(base_image, diff_image)
|
37
|
+
super
|
38
|
+
@output = ChunkyPNG::Image.new(base_image.width, base_image.height, WHITE)
|
39
|
+
end
|
40
|
+
|
41
|
+
def calculate_for_pixel(pixel, x, y)
|
42
|
+
return if pixel == @diff_image[x,y]
|
43
|
+
|
44
|
+
rgb_colors = %w(r g b).map do |color|
|
45
|
+
for_color(color, @diff_image[x,y]) - for_color(color, pixel)
|
46
|
+
end
|
47
|
+
|
48
|
+
score = Math.sqrt(rgb_colors.reduce(0) {|memo, diff| memo += (diff ** 2) } ) / Math.sqrt(MAX ** 2 * 3)
|
49
|
+
|
50
|
+
@output[x,y] = grayscale(MAX - (score * MAX).round)
|
51
|
+
super
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
class ColorBlend < DiffStrategy
|
56
|
+
def initialize(base_image, diff_image)
|
57
|
+
super
|
58
|
+
@output = ChunkyPNG::Image.new(base_image.width, diff_image.width, BLACK)
|
59
|
+
end
|
60
|
+
|
61
|
+
def calculate_for_pixel(pixel, x, y)
|
62
|
+
rgb_colors = %w(r g b).map do |color|
|
63
|
+
for_color(color, pixel) + for_color(color, @diff_image[x,y]) - 2 * [for_color(color, pixel), for_color(color, @diff_image[x,y])].min
|
64
|
+
end
|
65
|
+
|
66
|
+
@output[x,y] = rgb(*rgb_colors)
|
67
|
+
super
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
data/lib/monet/errors.rb
ADDED
data/lib/monet.rb
ADDED
data/monet.gemspec
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'monet/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "monet"
|
8
|
+
gem.version = Monet::VERSION
|
9
|
+
gem.authors = ["Luke van der Hoeven"]
|
10
|
+
gem.email = ["hungerandthirst@gmail.com"]
|
11
|
+
gem.description = %q{Monet is a web UI change comparer.}
|
12
|
+
gem.summary = %q{
|
13
|
+
Monet captures your web pages, sets up a baseline
|
14
|
+
and then ensures that future changes to either backend
|
15
|
+
or front end code leaves your UI intact. No more wondering
|
16
|
+
if CSS changes blow your UI up. Simply capture your page,
|
17
|
+
make changes, run tests and compare the diff!
|
18
|
+
}
|
19
|
+
gem.homepage = "http://plukevdh.github.com/monet"
|
20
|
+
|
21
|
+
gem.files = `git ls-files`.split($/)
|
22
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
23
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
24
|
+
gem.require_paths = ["lib"]
|
25
|
+
|
26
|
+
gem.add_dependency('rake')
|
27
|
+
gem.add_dependency('poltergeist')
|
28
|
+
gem.add_dependency('capybara')
|
29
|
+
# gem.add_dependency('chunky_png')
|
30
|
+
gem.add_dependency('oily_png')
|
31
|
+
gem.add_dependency('spidr')
|
32
|
+
|
33
|
+
if RUBY_ENGINE == 'rbx'
|
34
|
+
gem.add_dependency('rubysl')
|
35
|
+
gem.add_dependency('racc')
|
36
|
+
gem.add_dependency('json')
|
37
|
+
end
|
38
|
+
|
39
|
+
gem.add_development_dependency('rspec-given')
|
40
|
+
gem.add_development_dependency('rspec')
|
41
|
+
gem.add_development_dependency('timecop')
|
42
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'monet/capture_map'
|
3
|
+
|
4
|
+
describe Monet::CaptureMap::PathCollection do
|
5
|
+
context "base helper" do
|
6
|
+
Given(:helper) { Monet::CaptureMap::PathCollection.new("http://google.com") }
|
7
|
+
Then { helper.paths.should == [] }
|
8
|
+
end
|
9
|
+
|
10
|
+
context "add items" do
|
11
|
+
Given(:helper) { Monet::CaptureMap::PathCollection.new("http://google.com") }
|
12
|
+
When { helper.add('path') }
|
13
|
+
Then { helper.paths.should == ['path'] }
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
describe Monet::CaptureMap::PathSpider do
|
18
|
+
Given(:spider) { Monet::CaptureMap::PathSpider.new("http://spider.io/") }
|
19
|
+
|
20
|
+
context "ignores" do
|
21
|
+
When(:ignores) { spider.ignores }
|
22
|
+
Then { ignores.all? {|x| x.is_a? Regexp}.should be_true }
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe Monet::CaptureMap do
|
27
|
+
context "no arguments" do
|
28
|
+
When(:result) { Monet::CaptureMap.new }
|
29
|
+
Then { result.should have_failed(ArgumentError) }
|
30
|
+
end
|
31
|
+
|
32
|
+
context "requires full url" do
|
33
|
+
When(:result) { Monet::CaptureMap.new("google.com") }
|
34
|
+
Then { result.should have_failed(Monet::CaptureMap::InvalidURL, /google.com is not a valid url/) }
|
35
|
+
end
|
36
|
+
|
37
|
+
context "requires valid url" do
|
38
|
+
When(:result) { Monet::CaptureMap.new("google") }
|
39
|
+
Then { result.should have_failed(Monet::CaptureMap::InvalidURL, /google is not a valid url/) }
|
40
|
+
end
|
41
|
+
|
42
|
+
context "with name" do
|
43
|
+
Given(:map) { Monet::CaptureMap.new("http://google.com") }
|
44
|
+
Then { map.root_url.should == "http://google.com" }
|
45
|
+
And { map.paths.should == [] }
|
46
|
+
end
|
47
|
+
|
48
|
+
context "with paths" do
|
49
|
+
Given(:map) {
|
50
|
+
Monet::CaptureMap.new("http://google.com") do |map|
|
51
|
+
map.add 'home/'
|
52
|
+
map.add 'test/new'
|
53
|
+
end
|
54
|
+
}
|
55
|
+
|
56
|
+
context "add paths" do
|
57
|
+
Then { map.root_url.should == "http://google.com" }
|
58
|
+
And { map.paths.should == ['home', 'test/new'] }
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
context "spider mapper", vcr: { cassette_name: "spider", record: :new_episodes } do
|
63
|
+
Given(:map) { Monet::CaptureMap.new("http://www.spider.io", :spider) }
|
64
|
+
When(:paths) { map.paths }
|
65
|
+
Then { paths.should == [
|
66
|
+
"http://www.spider.io",
|
67
|
+
"http://www.spider.io/anti-malware/",
|
68
|
+
"http://www.spider.io/viewability/",
|
69
|
+
"http://www.spider.io/press/",
|
70
|
+
"http://www.spider.io/team/",
|
71
|
+
"http://www.spider.io/blog/",
|
72
|
+
"http://www.spider.io/",
|
73
|
+
"http://www.spider.io/blog/2013/03/chameleon-botnet/",
|
74
|
+
"http://www.spider.io/blog/2013/12/cyber-criminals-defraud-display-advertisers-with-tdss/",
|
75
|
+
"http://www.spider.io/zeus",
|
76
|
+
"http://www.spider.io/blog/2013/11/how-to-defraud-display-advertisers-with-zeus/",
|
77
|
+
"http://www.spider.io/blog/2013/05/a-botnet-primer-for-display-advertisers/",
|
78
|
+
"http://www.spider.io/blog/2013/09/display-advertisers-funding-cybercriminals-since-2011/",
|
79
|
+
"http://www.spider.io/blog/2013/09/securing-the-legitimacy-of-display-ad-inventory/",
|
80
|
+
"http://www.spider.io/blog/2013/04/display-advertising-fraud-is-a-sell-side-problem/",
|
81
|
+
"http://www.spider.io/blog/2012/12/internet-explorer-data-leakage/",
|
82
|
+
"http://www.spider.io/blog/2013/08/sambreel-is-still-injecting-ads-video-advertisers-beware/",
|
83
|
+
"http://www.spider.io/blog/page/2/",
|
84
|
+
"http://www.spider.io/blog/2011/10/the-problem-with-client-side-analytics/",
|
85
|
+
"http://www.spider.io/blog/2012/12/responsible-disclosure/",
|
86
|
+
"http://www.spider.io/blog/2013/05/spider-io-granted-mrc-accreditation-for-viewable-impression-measurement/",
|
87
|
+
"http://www.spider.io/blog/2013/04/at-least-two-percent-of-monitored-display-ad-exchange-inventory-is-hidden/",
|
88
|
+
"http://www.spider.io/blog/2013/03/who-is-behind-the-chameleon-botnet/",
|
89
|
+
"http://www.spider.io/blog/page/3/",
|
90
|
+
"http://www.spider.io/blog/2013/02/which-display-ad-exchange-sells-the-highest-quality-inventory/",
|
91
|
+
"http://www.spider.io/blog/2012/12/there-are-two-ways-to-measure-ad-viewability-there-is-only-one-right-way/",
|
92
|
+
"http://www.spider.io/blog/page/4/",
|
93
|
+
"http://www.spider.io/blog/2012/12/review-of-iab-safeframe-1-0/",
|
94
|
+
"http://www.spider.io/blog/2012/10/qa-about-ad-viewability/",
|
95
|
+
"http://www.spider.io/blog/2012/10/the-first-technology-to-measure-the-viewability-of-iframed-ads-across-all-major-browsers-press-release/",
|
96
|
+
"http://www.spider.io/vSta98h",
|
97
|
+
"http://www.spider.io/blog/2012/07/join-us-for-a-tipple-at-spider-towers/",
|
98
|
+
"http://www.spider.io/blog/2012/07/startups-acquiring-startups-for-equity/",
|
99
|
+
"http://www.spider.io/blog/page/5/",
|
100
|
+
"http://www.spider.io/visibility-demo-screencast/",
|
101
|
+
"http://www.spider.io/blog/2012/07/whats-in-an-ip-address/",
|
102
|
+
"http://www.spider.io/blog/2011/12/physical-hack-day/",
|
103
|
+
"http://www.spider.io/blog/2011/11/our-first-hack-day/",
|
104
|
+
"http://www.spider.io/careers/",
|
105
|
+
"http://www.spider.io/blog/2011/10/extreme-architecting/",
|
106
|
+
"http://www.spider.io/blog/2011/09/how-to-catch-a-bot/",
|
107
|
+
"http://www.spider.io/blog/page/6/",
|
108
|
+
"http://www.spider.io/blog/2011/10/testing-javascript-with-mturk/",
|
109
|
+
"http://www.spider.io/blog/2011/10/demonstration-screencast-verifying-that-display-ads-are-visible-from-within-iframes/",
|
110
|
+
"http://www.spider.io/blog/2011/10/calling-out-to-researchersacademics/",
|
111
|
+
"http://www.spider.io/blog/page/7/"
|
112
|
+
]}
|
113
|
+
And { paths.any? {|p| p.end_with? "css" }.should be_false }
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'monet/capture'
|
3
|
+
|
4
|
+
describe Monet::Capture do
|
5
|
+
Given(:path) { File.expand_path './spec/tmp/output' }
|
6
|
+
Given(:capture_agent) { Monet::Capture.new(capture_dir: path) }
|
7
|
+
|
8
|
+
before do
|
9
|
+
Timecop.freeze
|
10
|
+
end
|
11
|
+
|
12
|
+
after do
|
13
|
+
Timecop.return
|
14
|
+
|
15
|
+
Dir.glob("#{path}/*.png").each do |file|
|
16
|
+
File.delete(file)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
context "can pass config" do
|
21
|
+
context "as a hash" do
|
22
|
+
Given(:capture_agent) { Monet::Capture.new(capture_dir: path) }
|
23
|
+
When(:config) { capture_agent.instance_variable_get :@config }
|
24
|
+
Then { config.should be_a(Monet::Config) }
|
25
|
+
Then { config.capture_dir.should == path }
|
26
|
+
end
|
27
|
+
|
28
|
+
context "as a Monet::Config" do
|
29
|
+
Given(:config) { Monet::Config.new(capture_dir: path) }
|
30
|
+
Given(:capture_agent) { Monet::Capture.new(config) }
|
31
|
+
When(:final) { capture_agent.instance_variable_get :@config }
|
32
|
+
Then { final.should be_a(Monet::Config) }
|
33
|
+
Then { final.capture_dir.should == path }
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
context "converts name properly" do
|
38
|
+
When { capture_agent.capture('https://google.com') }
|
39
|
+
Then { File.exist?("#{path}/google_com-#{Time.now.to_i}.png").should be_true }
|
40
|
+
end
|
41
|
+
|
42
|
+
context "prepends default protocol if missing" do
|
43
|
+
When { capture_agent.capture('www.facebook.com') }
|
44
|
+
Then { File.exist?("#{path}/www_facebook_com-#{Time.now.to_i}.png").should be_true }
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|