selenium_surfer 0.0.1
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.
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +29 -0
- data/Rakefile +1 -0
- data/lib/selenium_surfer/driver_bucket.rb +78 -0
- data/lib/selenium_surfer/errors.rb +15 -0
- data/lib/selenium_surfer/robot.rb +104 -0
- data/lib/selenium_surfer/search_context.rb +75 -0
- data/lib/selenium_surfer/surf_context.rb +150 -0
- data/lib/selenium_surfer/version.rb +3 -0
- data/lib/selenium_surfer.rb +38 -0
- data/selenium_surfer.gemspec +24 -0
- metadata +108 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Ignacio Baixas
|
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,29 @@
|
|
1
|
+
# SeleniumSurfer
|
2
|
+
|
3
|
+
TODO: Write a gem description
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'selenium_surfer'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install selenium_surfer
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
TODO: Write usage instructions here
|
22
|
+
|
23
|
+
## Contributing
|
24
|
+
|
25
|
+
1. Fork it
|
26
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
27
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
28
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
29
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module SeleniumSurfer
|
2
|
+
|
3
|
+
# ### Webdriver connection wrapper
|
4
|
+
#
|
5
|
+
# By wrapping the connection is posible to control reconnection and bound context,
|
6
|
+
# this allows for safe context navigation.
|
7
|
+
#
|
8
|
+
class DriverBucket
|
9
|
+
|
10
|
+
attr_reader :session_id
|
11
|
+
|
12
|
+
def initialize(_session_id, _anonymous)
|
13
|
+
@session_id = _session_id
|
14
|
+
@bound_ctx = nil
|
15
|
+
@anonymous = _anonymous
|
16
|
+
end
|
17
|
+
|
18
|
+
# get the current driver instance, reset it if required
|
19
|
+
def driver(_reset=false)
|
20
|
+
reset if _reset
|
21
|
+
|
22
|
+
# TODO retrieve config data from config file instead of ENV
|
23
|
+
|
24
|
+
if @driver.nil?
|
25
|
+
driver_name = SeleniumSurfer.config[:webdriver]
|
26
|
+
raise ConfigurationError.new 'must provide a webdriver type' if driver_name.nil?
|
27
|
+
|
28
|
+
@driver = case driver_name.to_sym
|
29
|
+
when 'remote'
|
30
|
+
url = SeleniumSurfer.config[:remote_host]
|
31
|
+
|
32
|
+
# setup a custom client to use longer timeouts
|
33
|
+
client = Selenium::WebDriver::Remote::Http::Default.new
|
34
|
+
client.timeout = SeleniumSurfer.config[:remote_timeout]
|
35
|
+
|
36
|
+
@driver = Selenium::WebDriver.for :remote, :url => url, :http_client => client
|
37
|
+
else
|
38
|
+
@driver = Selenium::WebDriver.for driver_name.to_sym
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
return @driver
|
43
|
+
end
|
44
|
+
|
45
|
+
# force current driver connection to be discarded
|
46
|
+
def reset
|
47
|
+
if @driver
|
48
|
+
@driver.quit rescue nil
|
49
|
+
@driver = nil
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# return true if there is a context bound to this bucket
|
54
|
+
def bound?
|
55
|
+
not @bound_ctx.nil?
|
56
|
+
end
|
57
|
+
|
58
|
+
# bind a context to this bucket
|
59
|
+
#
|
60
|
+
# The context may implement the `on_unbind` method to be notified when
|
61
|
+
# the bucket it is unbound from the bucket
|
62
|
+
#
|
63
|
+
def bind(_ctx)
|
64
|
+
@bound_ctx.on_unbind if @bound_ctx and @bound_ctx.respond_to? :on_unbind
|
65
|
+
@bound_ctx = _ctx
|
66
|
+
end
|
67
|
+
|
68
|
+
# unbinds the currently bound context.
|
69
|
+
def unbind(_force_reset=false)
|
70
|
+
if @bound_ctx
|
71
|
+
@bound_ctx.on_unbind if @bound_ctx.respond_to? :on_unbind
|
72
|
+
@bound_ctx = nil
|
73
|
+
end
|
74
|
+
reset if _force_reset or @anonymous # reset bucket if required
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module SeleniumSurfer
|
2
|
+
|
3
|
+
# Error thrown when a bad configuration parameter is passed or missing
|
4
|
+
class ConfigurationError < StandardError; end
|
5
|
+
|
6
|
+
# Error thrown when a driver operation is attempted in an unbound context
|
7
|
+
class UnboundContextError < StandardError; end
|
8
|
+
|
9
|
+
# Error thrown when a programming setup error is found
|
10
|
+
class SetupError < StandardError; end
|
11
|
+
|
12
|
+
# Error thrown when an element operation is attempted in an empty search result set
|
13
|
+
class EmptySetError < StandardError; end
|
14
|
+
|
15
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
module SeleniumSurfer
|
2
|
+
|
3
|
+
# ### Base class for selenium surfer robots
|
4
|
+
#
|
5
|
+
# This class defines the interface every test engine must implement.
|
6
|
+
#
|
7
|
+
# It also provides webdriver managed shared access, this allows for safe
|
8
|
+
# webdriver session persistance between tests.
|
9
|
+
#
|
10
|
+
# Usage: TODO
|
11
|
+
#
|
12
|
+
class Robot
|
13
|
+
|
14
|
+
@@all_buckets = {}
|
15
|
+
@@loaded_buckets = nil
|
16
|
+
@@block_options = nil
|
17
|
+
|
18
|
+
# execute a block with managed webdriver connections
|
19
|
+
#
|
20
|
+
# by putting all web related logic inside a managed block the
|
21
|
+
# user can rest asured that any unhandled exception inside the block will
|
22
|
+
# discard the requested webdrivers connections. This means that future
|
23
|
+
# calls to connect will always return valid driver connections.
|
24
|
+
#
|
25
|
+
def self.managed(_opt={})
|
26
|
+
|
27
|
+
raise SetupError.new 'cannot call managed block inside managed block' unless @@loaded_buckets.nil?
|
28
|
+
|
29
|
+
keep_sessions = _opt.fetch(:keep_sessions, false)
|
30
|
+
session_error = false
|
31
|
+
|
32
|
+
# TODO: When `keep_sessions` is used, special care should be taken in preventing a large number
|
33
|
+
# of sessions to remain in memory (like in de case a new session id for each run)
|
34
|
+
|
35
|
+
unless keep_sessions
|
36
|
+
# use separate bucket collection if sessions are to be discarded
|
37
|
+
temp = @@all_buckets
|
38
|
+
@all_buckets = {}
|
39
|
+
end
|
40
|
+
|
41
|
+
@@loaded_buckets = []
|
42
|
+
@@block_options = _opt
|
43
|
+
|
44
|
+
begin
|
45
|
+
return yield
|
46
|
+
rescue
|
47
|
+
session_error = true
|
48
|
+
raise
|
49
|
+
ensure
|
50
|
+
force_reset = session_error or !keep_sessions
|
51
|
+
@@loaded_buckets.each { |b| b.unbind(force_reset) }
|
52
|
+
@@loaded_buckets = nil
|
53
|
+
@all_buckets = temp unless keep_sessions
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# creates a new surf context and passes it to the given block.
|
58
|
+
#
|
59
|
+
# * Can only be called inside a `managed` block.
|
60
|
+
# * The context is released when block exits.
|
61
|
+
#
|
62
|
+
def self.new_surf_session(_session=nil, _opt={}, &_block)
|
63
|
+
|
64
|
+
# load context class to be used, must be a SurfContext or SurfContext subclass
|
65
|
+
ctx_class = _opt.fetch(:use, SurfContext)
|
66
|
+
raise SetupError.new 'invalid context class' unless ctx_class == SurfContext or ctx_class < SurfContext
|
67
|
+
|
68
|
+
# make sure this is called within a managed block
|
69
|
+
raise SetupError.new 'context is not managed' if @@loaded_buckets.nil?
|
70
|
+
|
71
|
+
if _session.nil? and not @@block_options.fetch(:nil_sessions, false)
|
72
|
+
# create an anonymous bucket
|
73
|
+
bucket = DriverBucket.new nil, true
|
74
|
+
else
|
75
|
+
bucket = @@all_buckets[_session]
|
76
|
+
bucket = @@all_buckets[_session] = DriverBucket.new _session, false if bucket.nil?
|
77
|
+
raise SetupError.new 'session already bound' if bucket.bound?
|
78
|
+
end
|
79
|
+
|
80
|
+
@@loaded_buckets << bucket
|
81
|
+
ctx = ctx_class.new bucket
|
82
|
+
|
83
|
+
# if block is not given, just return context, if given, pass it to block
|
84
|
+
# and ensure release
|
85
|
+
return ctx unless _block
|
86
|
+
begin
|
87
|
+
return _block.call ctx
|
88
|
+
ensure
|
89
|
+
bucket.unbind _opt.fetch(:on_exit, :release) == :discard
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# Object instance flavor of `self.managed`
|
94
|
+
def managed(_opt={}, &_block)
|
95
|
+
return self.class.managed _opt, &_block
|
96
|
+
end
|
97
|
+
|
98
|
+
# Object instance flavor of `self.surf`
|
99
|
+
def new_surf_session(_session=nil, _opt={}, &_block)
|
100
|
+
return self.class.new_surf_session(_session, _opt, &_block)
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
3
|
+
module SeleniumSurfer
|
4
|
+
|
5
|
+
# ### WebDriver Element wrapper
|
6
|
+
#
|
7
|
+
# Provides jQuery-like access to elements.
|
8
|
+
#
|
9
|
+
class SearchContext
|
10
|
+
include Enumerable
|
11
|
+
extend Forwardable
|
12
|
+
|
13
|
+
def initialize(_elements)
|
14
|
+
@elements = _elements
|
15
|
+
end
|
16
|
+
|
17
|
+
# forward read-only array methods to context
|
18
|
+
def_delegators :context, :each, :[], :length, :count, :empty?, :first, :last
|
19
|
+
|
20
|
+
def explode(&_block)
|
21
|
+
return enum_for(__method__) if _block.nil?
|
22
|
+
context.each do |el|
|
23
|
+
_block.call SearchContext.new([el])
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def search(_selector=nil, _options={})
|
28
|
+
_options[:css] = _selector if _selector
|
29
|
+
SearchContext.new search_elements(_options)
|
30
|
+
end
|
31
|
+
|
32
|
+
def fill(_value)
|
33
|
+
raise EmptySetError.new if empty?
|
34
|
+
context.first.clear
|
35
|
+
context.first.send_keys _value
|
36
|
+
end
|
37
|
+
|
38
|
+
# Any methods missing are forwarded to the first element.
|
39
|
+
def method_missing(_method, *_args, &_block)
|
40
|
+
m = /^(.*)_all$/.match _method.to_s
|
41
|
+
if m then
|
42
|
+
return [] if empty?
|
43
|
+
context.map { |e| e.send(m[1], *_args, &_block) }
|
44
|
+
else
|
45
|
+
raise EmptySetError.new if empty?
|
46
|
+
context.first.send(_method, *_args, &_block)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def respond_to?(_method, _include_all=false)
|
51
|
+
return true if super
|
52
|
+
m = /^.*_all$/.match _method.to_s
|
53
|
+
if m then
|
54
|
+
return true if empty?
|
55
|
+
context.first.respond_to? m[1], _include_all
|
56
|
+
else
|
57
|
+
raise EmptySetError.new if empty?
|
58
|
+
context.first.respond_to? _method, _include_all
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def search_elements(_options)
|
65
|
+
context.inject([]) do |r, element|
|
66
|
+
r + element.find_elements(_options)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def context
|
71
|
+
@elements
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,150 @@
|
|
1
|
+
module SeleniumSurfer
|
2
|
+
|
3
|
+
# ### Base class for robot contexts
|
4
|
+
#
|
5
|
+
class SurfContext < SearchContext
|
6
|
+
|
7
|
+
# add a macro attribute writer to context.
|
8
|
+
#
|
9
|
+
# A macro attribute persist through context changes.
|
10
|
+
#
|
11
|
+
def self.macro_attr_writer(*_names)
|
12
|
+
_names.each do |name|
|
13
|
+
send :define_method, "#{name}=" do |v| @macro[name.to_sym] = v end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# add a macro attribute reader to context.
|
18
|
+
#
|
19
|
+
# A macro attribute persist through context changes.
|
20
|
+
#
|
21
|
+
def self.macro_attr_reader(*_names)
|
22
|
+
_names.each do |name|
|
23
|
+
send :define_method, "#{name}" do @macro[name.to_sym] end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# add a macro attribute accessor to context.
|
28
|
+
#
|
29
|
+
# A macro attribute persist through context changes.
|
30
|
+
#
|
31
|
+
def self.macro_attr_accessor(*_names)
|
32
|
+
macro_attr_reader *_names
|
33
|
+
macro_attr_writer *_names
|
34
|
+
end
|
35
|
+
|
36
|
+
macro_attr_accessor :max_retries
|
37
|
+
|
38
|
+
def initialize(_bucket, _macro=nil, _stack=nil)
|
39
|
+
@bucket = _bucket
|
40
|
+
@macro = _macro || {}
|
41
|
+
@stack = _stack || []
|
42
|
+
|
43
|
+
@bucket.bind self
|
44
|
+
end
|
45
|
+
|
46
|
+
# return true if context is bound
|
47
|
+
def bound?
|
48
|
+
not @bucket.nil?
|
49
|
+
end
|
50
|
+
|
51
|
+
# switch to another context
|
52
|
+
# new context class should be a SurfContext subclass
|
53
|
+
def switch_to(_klass=nil)
|
54
|
+
raise UnboundContextError.new unless bound?
|
55
|
+
_klass.new @bucket, @macro, @stack
|
56
|
+
end
|
57
|
+
|
58
|
+
# ## Helpers
|
59
|
+
|
60
|
+
# retrieves the current driver being used by this context
|
61
|
+
def driver
|
62
|
+
load_driver
|
63
|
+
end
|
64
|
+
|
65
|
+
# return the current page title
|
66
|
+
def title
|
67
|
+
load_driver.title
|
68
|
+
end
|
69
|
+
|
70
|
+
# navigate to a given url (uses the max_retries setting)
|
71
|
+
def navigate(_url, _params=nil)
|
72
|
+
_url += "?#{_params.to_query}" if _params
|
73
|
+
retries = 0
|
74
|
+
|
75
|
+
loop do
|
76
|
+
begin
|
77
|
+
load_driver(retries > 0).get(_url)
|
78
|
+
@stack = [] # clear stack after successfull navigation
|
79
|
+
break
|
80
|
+
rescue Timeout::Error, Selenium::WebDriver::Error::UnknownError
|
81
|
+
trace "Error when opening #{_url}!"
|
82
|
+
raise if retries >= @max_retries
|
83
|
+
retries += 1
|
84
|
+
sleep 1.0
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# changes the context
|
90
|
+
# TODO: this method may be unecesary...
|
91
|
+
def step(_selector=nil, _options={})
|
92
|
+
_options[:css] = _selector if _selector
|
93
|
+
new_context = search_elements(_options)
|
94
|
+
begin
|
95
|
+
@stack << new_context
|
96
|
+
yield
|
97
|
+
ensure
|
98
|
+
@stack.pop
|
99
|
+
end
|
100
|
+
|
101
|
+
return true
|
102
|
+
end
|
103
|
+
|
104
|
+
# release current driver connection
|
105
|
+
def release
|
106
|
+
return false if not bound?
|
107
|
+
@bucket.unbind
|
108
|
+
return true
|
109
|
+
end
|
110
|
+
|
111
|
+
# release and discard the current driver connection.
|
112
|
+
def quit
|
113
|
+
return false if not bound?
|
114
|
+
@bucket.unbind true
|
115
|
+
return true
|
116
|
+
end
|
117
|
+
|
118
|
+
# resets the current driver connection, does not release it.
|
119
|
+
def reset
|
120
|
+
return false if not bound?
|
121
|
+
@bucket.reset
|
122
|
+
return true
|
123
|
+
end
|
124
|
+
|
125
|
+
# bucket context interface implementation
|
126
|
+
# not to be called directly
|
127
|
+
def on_unbind
|
128
|
+
@bucket = @stack = nil
|
129
|
+
end
|
130
|
+
|
131
|
+
private
|
132
|
+
|
133
|
+
def load_driver(_reset=false)
|
134
|
+
raise UnboundContextError.new if not bound?
|
135
|
+
@bucket.reset if _reset
|
136
|
+
@bucket.driver
|
137
|
+
end
|
138
|
+
|
139
|
+
def context
|
140
|
+
raise UnboundContextError.new if not bound?
|
141
|
+
@stack.last || [load_driver]
|
142
|
+
end
|
143
|
+
|
144
|
+
def observe
|
145
|
+
# get current url
|
146
|
+
return yield
|
147
|
+
# compare url after function call, if changed reset stack
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require "selenium_surfer/version"
|
2
|
+
require "selenium_surfer/errors"
|
3
|
+
require "selenium_surfer/driver_bucket"
|
4
|
+
require "selenium_surfer/search_context"
|
5
|
+
require "selenium_surfer/surf_context"
|
6
|
+
require "selenium_surfer/robot"
|
7
|
+
|
8
|
+
module SeleniumSurfer
|
9
|
+
|
10
|
+
# Configuration defaults
|
11
|
+
@@config = {
|
12
|
+
:webdriver => nil,
|
13
|
+
:remote_host => 'http://localhost:8080',
|
14
|
+
:remote_timeout => 120
|
15
|
+
}
|
16
|
+
|
17
|
+
# Configure through hash
|
18
|
+
def self.configure(_opts = {})
|
19
|
+
_opts.each { |k,v| @@config[k.to_sym] = v if @@config.has_key? k.to_sym }
|
20
|
+
end
|
21
|
+
|
22
|
+
# Configure through yaml file
|
23
|
+
def self.configure_with(_path_to_yaml_file)
|
24
|
+
begin
|
25
|
+
config = YAML::load(IO.read(_path_to_yaml_file))
|
26
|
+
rescue Errno::ENOENT
|
27
|
+
log(:warning, "YAML configuration file couldn't be found. Using defaults."); return
|
28
|
+
rescue Psych::SyntaxError
|
29
|
+
log(:warning, "YAML configuration file contains invalid syntax. Using defaults."); return
|
30
|
+
end
|
31
|
+
|
32
|
+
configure(config)
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.config
|
36
|
+
@@config
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'selenium_surfer/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "selenium_surfer"
|
8
|
+
spec.version = SeleniumSurfer::VERSION
|
9
|
+
spec.authors = ["Ignacio Baixas"]
|
10
|
+
spec.email = ["ignacio@platan.us"]
|
11
|
+
spec.description = %q{Selenium Surfer Robots!}
|
12
|
+
spec.summary = %q{Base infrastructure for webdriver robots with session managing and rich DSL}
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_runtime_dependency 'selenium-webdriver', "~> 2.33.0"
|
22
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
23
|
+
spec.add_development_dependency "rake"
|
24
|
+
end
|
metadata
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: selenium_surfer
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Ignacio Baixas
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-08-09 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: selenium-webdriver
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 2.33.0
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 2.33.0
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: bundler
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ~>
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '1.3'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '1.3'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: rake
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
description: Selenium Surfer Robots!
|
63
|
+
email:
|
64
|
+
- ignacio@platan.us
|
65
|
+
executables: []
|
66
|
+
extensions: []
|
67
|
+
extra_rdoc_files: []
|
68
|
+
files:
|
69
|
+
- .gitignore
|
70
|
+
- Gemfile
|
71
|
+
- LICENSE.txt
|
72
|
+
- README.md
|
73
|
+
- Rakefile
|
74
|
+
- lib/selenium_surfer.rb
|
75
|
+
- lib/selenium_surfer/driver_bucket.rb
|
76
|
+
- lib/selenium_surfer/errors.rb
|
77
|
+
- lib/selenium_surfer/robot.rb
|
78
|
+
- lib/selenium_surfer/search_context.rb
|
79
|
+
- lib/selenium_surfer/surf_context.rb
|
80
|
+
- lib/selenium_surfer/version.rb
|
81
|
+
- selenium_surfer.gemspec
|
82
|
+
homepage: ''
|
83
|
+
licenses:
|
84
|
+
- MIT
|
85
|
+
post_install_message:
|
86
|
+
rdoc_options: []
|
87
|
+
require_paths:
|
88
|
+
- lib
|
89
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
90
|
+
none: false
|
91
|
+
requirements:
|
92
|
+
- - ! '>='
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
version: '0'
|
95
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
96
|
+
none: false
|
97
|
+
requirements:
|
98
|
+
- - ! '>='
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
version: '0'
|
101
|
+
requirements: []
|
102
|
+
rubyforge_project:
|
103
|
+
rubygems_version: 1.8.23
|
104
|
+
signing_key:
|
105
|
+
specification_version: 3
|
106
|
+
summary: Base infrastructure for webdriver robots with session managing and rich DSL
|
107
|
+
test_files: []
|
108
|
+
has_rdoc:
|