seleniumrecord 0.0.1.beta1
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 +7 -0
- data/.gitignore +22 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +65 -0
- data/Rakefile +2 -0
- data/lib/selenium_record.rb +7 -0
- data/lib/selenium_record/action_builder.rb +41 -0
- data/lib/selenium_record/actions.rb +80 -0
- data/lib/selenium_record/axis.rb +54 -0
- data/lib/selenium_record/base.rb +93 -0
- data/lib/selenium_record/configuration.rb +11 -0
- data/lib/selenium_record/core.rb +38 -0
- data/lib/selenium_record/html.rb +17 -0
- data/lib/selenium_record/lookup.rb +123 -0
- data/lib/selenium_record/navigation_item.rb +71 -0
- data/lib/selenium_record/preconditions.rb +50 -0
- data/lib/selenium_record/scopes.rb +40 -0
- data/lib/selenium_record/theme.rb +40 -0
- data/lib/selenium_record/translations.rb +10 -0
- data/lib/selenium_record/version.rb +3 -0
- data/lib/selenium_record/waits.rb +106 -0
- data/selenium_record.gemspec +35 -0
- metadata +194 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: e34d57392ef8365deafdf42fce9a442de4de5b5c
|
4
|
+
data.tar.gz: dee1cdc484930e6b3ee7553da9813cd90b728a79
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 07a8ce0f127b8de9aa73531a492b18f5913e7665e02be7bd6b7b5236b8bb792461fa684030cc3a9a64df686d26c38d8618d5d788bffbdc0f7e3f93183f40a196
|
7
|
+
data.tar.gz: d201431d8b93319963930d5e8aea010f62ca5264118538e9fa55f06c6812513f2f88f55e9c703ec76b32ceecf8c7994ad4dff71a09122f3f49ddbcaa4becd51b
|
data/.gitignore
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
.bundle
|
4
|
+
.config
|
5
|
+
.yardoc
|
6
|
+
Gemfile.lock
|
7
|
+
InstalledFiles
|
8
|
+
_yardoc
|
9
|
+
coverage
|
10
|
+
doc/
|
11
|
+
lib/bundler/man
|
12
|
+
pkg
|
13
|
+
rdoc
|
14
|
+
spec/reports
|
15
|
+
test/tmp
|
16
|
+
test/version_tmp
|
17
|
+
tmp
|
18
|
+
*.bundle
|
19
|
+
*.so
|
20
|
+
*.o
|
21
|
+
*.a
|
22
|
+
mkmf.log
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 David Saenz Tagarro
|
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,65 @@
|
|
1
|
+
[](http://badge.fury.io/rb/selenium-record)
|
2
|
+
[](https://codeclimate.com/github/dsaenztagarro/selenium-record)
|
3
|
+
|
4
|
+
# SeleniumRecord
|
5
|
+
|
6
|
+
Selenium Record is a DSL for easily writing acceptance tests. It is a wrapper
|
7
|
+
over Selenium ruby bindings to let you easily apply the well known page object
|
8
|
+
pattern.
|
9
|
+
|
10
|
+
## Why to use
|
11
|
+
|
12
|
+
Within your web app's UI there are areas that your tests interact with. A
|
13
|
+
Selenium Record object simply models these as objects within the test code.
|
14
|
+
This reduces the amount of duplicated code and means that if the UI changes,
|
15
|
+
the fix need only be applied in one place.
|
16
|
+
|
17
|
+
## Warning
|
18
|
+
|
19
|
+
This gem is still under development! As this gem was born while I was trying to
|
20
|
+
right acceptance tests in a more maintainable and productive way, it is not
|
21
|
+
already tested!! Check the roadmap for upcoming updates. All pending tasks
|
22
|
+
should be completed for '1.0.0' version. Keep up to date!
|
23
|
+
|
24
|
+
## Roadmap
|
25
|
+
|
26
|
+
[ ] Full test coverage
|
27
|
+
[ ] Wiki Documentation
|
28
|
+
[ ] Basic install generator
|
29
|
+
[ ] ComponentAutoload integration in core (Currently present as a framework
|
30
|
+
extension)
|
31
|
+
|
32
|
+
## Installation
|
33
|
+
|
34
|
+
Add this line to your application's Gemfile:
|
35
|
+
|
36
|
+
gem 'seleniumrecord'
|
37
|
+
|
38
|
+
And then execute:
|
39
|
+
|
40
|
+
$ bundle
|
41
|
+
|
42
|
+
Or install it yourself as:
|
43
|
+
|
44
|
+
$ gem install seleniumrecord
|
45
|
+
|
46
|
+
## Usage
|
47
|
+
|
48
|
+
TODO: Write usage instructions here
|
49
|
+
|
50
|
+
## Contributing
|
51
|
+
|
52
|
+
1. Fork it ( https://github.com/dsaenztagarro/selenium-record/fork )
|
53
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
54
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
55
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
56
|
+
5. Create a new Pull Request
|
57
|
+
|
58
|
+
## References
|
59
|
+
|
60
|
+
- [Selenium Wiki Page](https://code.google.com/p/selenium/wiki/PageObjects)
|
61
|
+
- "Selenium 2 Testing Tools" by David Burns
|
62
|
+
|
63
|
+
## Thanks
|
64
|
+
|
65
|
+
Thanks to Hola Internet for let me right this kind of tools
|
data/Rakefile
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
Dir["#{File.dirname(__FILE__)}/selenium_record/*.rb"].each { |f| require f }
|
2
|
+
|
3
|
+
# Selenium Record provides objects for interacting with browser and managing
|
4
|
+
# the application during tests. Also joins in every object behaviour and
|
5
|
+
# expectations
|
6
|
+
module SeleniumRecord
|
7
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module SeleniumRecord
|
2
|
+
# Helpers for building complex actions
|
3
|
+
module ActionBuilder
|
4
|
+
# Class for storing action info
|
5
|
+
class Action
|
6
|
+
attr_reader :method, :args
|
7
|
+
def initialize(method, args = [])
|
8
|
+
@method = method
|
9
|
+
@args = args
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
# Class for building complex actions
|
14
|
+
class Builder
|
15
|
+
attr_accessor :actions
|
16
|
+
|
17
|
+
def initialize(&block)
|
18
|
+
@actions = []
|
19
|
+
@blk = block
|
20
|
+
end
|
21
|
+
|
22
|
+
def perform
|
23
|
+
@actions.each { |action| @blk.call(action.method, action.args) }
|
24
|
+
@actions.freeze
|
25
|
+
end
|
26
|
+
|
27
|
+
def method_missing(method, *args)
|
28
|
+
@actions << Action.new(method, *args)
|
29
|
+
self
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# @param block [Block] the block over all actions will be performed. It
|
34
|
+
# should contain two params: |method, args|
|
35
|
+
# @return [SeleniumRecord::Builder::ActionBuilder] a new action builder
|
36
|
+
# instance
|
37
|
+
def action_builder(&block)
|
38
|
+
Builder.new(&block)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module SeleniumRecord
|
2
|
+
# Contains simple actions to play from selenium objects
|
3
|
+
module Actions
|
4
|
+
def textarea_content(locator, content)
|
5
|
+
find(locator).clear # gain focus on textarea
|
6
|
+
find(locator).send_keys content
|
7
|
+
end
|
8
|
+
|
9
|
+
# Clicks accept button on popup
|
10
|
+
def accept_popup
|
11
|
+
popup = browser.switch_to.alert
|
12
|
+
popup.accept
|
13
|
+
end
|
14
|
+
|
15
|
+
# Chooses an option from select enhanced by javascript
|
16
|
+
# @param id [String] id of the select
|
17
|
+
# @param text [String] the option text
|
18
|
+
def choose_option(id, text)
|
19
|
+
click_wait xpath: select_xpath(id)
|
20
|
+
click_wait xpath: select_option_xpath(id, text)
|
21
|
+
end
|
22
|
+
|
23
|
+
# Chooses a menu from dropdown enhanced by javascript
|
24
|
+
# @param dropdown [String] text of dropdown
|
25
|
+
# @param menu [String] text of menu
|
26
|
+
def choose_menu(dropdown, menu)
|
27
|
+
click_wait xpath: dropdown_xpath(trans dropdown)
|
28
|
+
click_wait xpath: dropdown_menu_xpath(trans menu)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Clicks on element and wait until all jquery events are dispatched
|
32
|
+
# @param how [Symbol] (:class, :class_name, :css, :id, :link_text, :link,
|
33
|
+
# :partial_link_text, :name, :tag_name, :xpath)
|
34
|
+
# @param what [String]
|
35
|
+
def click_wait(locator)
|
36
|
+
when_present(locator).click
|
37
|
+
wait_js_inactive
|
38
|
+
end
|
39
|
+
|
40
|
+
def click(locator)
|
41
|
+
find(locator).click
|
42
|
+
end
|
43
|
+
|
44
|
+
def submit
|
45
|
+
click(xpath: ".//button[@type='submit']")
|
46
|
+
wait_page_load
|
47
|
+
load_dom
|
48
|
+
end
|
49
|
+
|
50
|
+
# @param text [String] text of the link to be clicked
|
51
|
+
def click_link(text)
|
52
|
+
finder = root_el || browser
|
53
|
+
finder.find_element(link_text: text).click
|
54
|
+
end
|
55
|
+
|
56
|
+
# Drops the model view on the bottom to the top of the model type view list
|
57
|
+
# @param model_type [Symbol] the type of the views to be affected
|
58
|
+
def pop_last(model_type)
|
59
|
+
elements = send("#{model_type}_elements")
|
60
|
+
browser.action
|
61
|
+
.drag_and_drop(elements.last, elements.first).perform
|
62
|
+
end
|
63
|
+
|
64
|
+
# @param id [Symbol] id of select element
|
65
|
+
# @param text [String] text of the option to be selected
|
66
|
+
def select_from_chosen(id, text)
|
67
|
+
browser.execute_script <<-script
|
68
|
+
var optValue = $("##{id} option:contains('#{text}')").val();
|
69
|
+
var value = [optValue];
|
70
|
+
if ($('##{id}').val()) {
|
71
|
+
$.merge(value, $('##{id}').val());
|
72
|
+
}
|
73
|
+
$('##{id}').val(value).trigger('chosen:updated');
|
74
|
+
script
|
75
|
+
end
|
76
|
+
|
77
|
+
# Remove once all helper references belongs to selenium objects
|
78
|
+
module_function :choose_option
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module SeleniumRecord
|
2
|
+
# Helper methods for comparing the relative position in DOM among selenium
|
3
|
+
# objects
|
4
|
+
module Axis
|
5
|
+
# @param view [SeleniumRecord::Base]
|
6
|
+
# @return [Boolean] Marks whether the current view is located in DOM
|
7
|
+
# after the view passed as parameter
|
8
|
+
def after?(other_view)
|
9
|
+
preceding_sibling_elements.member? other_view.root_el
|
10
|
+
end
|
11
|
+
|
12
|
+
# @param view [SeleniumRecord::Base]
|
13
|
+
# @return [Boolean] Marks whether the current view is located in DOM
|
14
|
+
# before the view passed as parameter
|
15
|
+
def before?(other_view)
|
16
|
+
following_sibling_elements.member? other_view.root_el
|
17
|
+
end
|
18
|
+
|
19
|
+
# @param models [Array<PORO>] list of models associated to views
|
20
|
+
# @return [Boolean] Marks whether the model views are ordered in the dom
|
21
|
+
def ordered?(*models)
|
22
|
+
result = []
|
23
|
+
models.reduce(nil) do |prev, current|
|
24
|
+
result << (view_for(prev).before? view_for(current)) if prev
|
25
|
+
current
|
26
|
+
end
|
27
|
+
result.any?
|
28
|
+
end
|
29
|
+
|
30
|
+
# Returns all elements belonging to preceding sibling xpath axe
|
31
|
+
# @return [Array<Selenium::WebDriver::Element>]
|
32
|
+
def preceding_sibling_elements
|
33
|
+
find_elements(preceding_sibling_locator)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Returns all elements belonging to following sibling xpath axe
|
37
|
+
# @return [Array<Selenium::WebDriver::Element>]
|
38
|
+
def following_sibling_elements
|
39
|
+
find_elements(following_sibling_locator)
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
# @return [String] locator for finding preceding sibling dom elements
|
45
|
+
def preceding_sibling_locator
|
46
|
+
{ xpath: "./preceding-sibling::#{tag_name}" }
|
47
|
+
end
|
48
|
+
|
49
|
+
# @return [String] locator for finding following sibling dom elements
|
50
|
+
def following_sibling_locator
|
51
|
+
{ xpath: "./following-sibling::#{tag_name}" }
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
require_relative 'core'
|
2
|
+
require_relative 'lookup'
|
3
|
+
require_relative 'actions'
|
4
|
+
require_relative 'action_builder'
|
5
|
+
require_relative 'scopes'
|
6
|
+
require_relative 'waits'
|
7
|
+
require_relative 'preconditions'
|
8
|
+
require_relative 'axis'
|
9
|
+
require_relative 'html'
|
10
|
+
require_relative 'theme'
|
11
|
+
require_relative 'translations'
|
12
|
+
|
13
|
+
# SeleniumRecord provides a framework based on the Selenium Page Object pattern
|
14
|
+
#
|
15
|
+
# ### More references:
|
16
|
+
# - [Selenium Wiki Page](https://code.google.com/p/selenium/wiki/PageObjects)
|
17
|
+
# - "Selenium 2 Testing Tools" by David Burns
|
18
|
+
module SeleniumRecord
|
19
|
+
# @abstract Subclass and override {#run} to implement
|
20
|
+
# a custom Selenium object
|
21
|
+
class Base
|
22
|
+
include Core
|
23
|
+
include Lookup
|
24
|
+
include Actions
|
25
|
+
include ActionBuilder
|
26
|
+
include Scopes
|
27
|
+
include Waits
|
28
|
+
include Preconditions
|
29
|
+
include Axis
|
30
|
+
include Html
|
31
|
+
include Theme
|
32
|
+
include Translations
|
33
|
+
attr_reader :browser, :parent_el, :root_el, :object
|
34
|
+
alias_method :__rootel__, :root_el
|
35
|
+
|
36
|
+
# @params browser [Selenium::WebDriver::Driver]
|
37
|
+
# @param [Hash] opts the options to create a new record
|
38
|
+
# @option opts [Selenium::WebDriver::Element] :parent_el The parent element
|
39
|
+
# @option opts [Selenium::WebDriver::Element] :root_el The root element
|
40
|
+
# @option opts [PORO] :object Plain Old Ruby Object contains main info
|
41
|
+
# related to the record
|
42
|
+
def initialize(browser, opts = {})
|
43
|
+
@browser = browser
|
44
|
+
@parent_el = opts[:parent_el]
|
45
|
+
@root_el = opts[:root_el]
|
46
|
+
@object = opts[:object]
|
47
|
+
end
|
48
|
+
|
49
|
+
# Creates a view in the scope of current instance based on object model
|
50
|
+
# passed as parameter
|
51
|
+
# @param object [ActiveRecord::Base] the object related to the new view
|
52
|
+
# @param [Hash] opts the options for the new view created
|
53
|
+
# @option opts [Module] :namespace The namespace in which the new view
|
54
|
+
# should be created
|
55
|
+
# @option opts [String] :suffix The suffix to be appended to the new view
|
56
|
+
# class name
|
57
|
+
def create_record(object, opts = {})
|
58
|
+
subject = opts[:subject] || object.class.name
|
59
|
+
klass = self.class.extract_klass(subject, opts)
|
60
|
+
klass.new(@browser, parent_el: root_el, object: object)
|
61
|
+
end
|
62
|
+
|
63
|
+
# Creates a view in the scope of current instance based on the action name
|
64
|
+
# @param action [Symbol]
|
65
|
+
# @param [Hash] opts the options for the new view created
|
66
|
+
# @option opts [Module] :namespace The namespace in which the new view
|
67
|
+
# should be created
|
68
|
+
# @option opts [String] :suffix The suffix to be appended to the new view
|
69
|
+
# class name
|
70
|
+
def create_record_for_action(action, opts = {})
|
71
|
+
klass = self.class.extract_klass(action.to_s.camelize, opts)
|
72
|
+
klass.new(@browser, parent_el: root_el)
|
73
|
+
end
|
74
|
+
|
75
|
+
# @return [Boolean] returns whether the view is attached to the dom
|
76
|
+
def exist?
|
77
|
+
load_dom if respond_to? :lookup_sequence
|
78
|
+
root_el != nil
|
79
|
+
end
|
80
|
+
|
81
|
+
# @param [Hash] opts the options for the new view created
|
82
|
+
# @option opts [Module] :namespace The namespace in which the new view
|
83
|
+
# should be created
|
84
|
+
# @option opts [String] :suffix The suffix to be appended to the new view
|
85
|
+
# class name
|
86
|
+
# @return [SeleniumRecord::Base]
|
87
|
+
def self.extract_klass(subject, opts)
|
88
|
+
namespace = opts[:namespace] || Object
|
89
|
+
suffix = opts[:suffix] || ''
|
90
|
+
namespace.const_get("#{subject}#{suffix}")
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module SeleniumRecord
|
2
|
+
# Core helpers to get easier access to selenium api
|
3
|
+
module Core
|
4
|
+
SUBCLASS_RESPONSABILITY = 'SubclassResponsibilityError'
|
5
|
+
|
6
|
+
def load_dom!(attrs = {})
|
7
|
+
@load_attributes = attrs
|
8
|
+
before_load_dom if respond_to? :before_load_dom
|
9
|
+
before_lookup if respond_to? :before_lookup
|
10
|
+
lookup
|
11
|
+
after_load_dom if respond_to? :after_load_dom
|
12
|
+
self
|
13
|
+
end
|
14
|
+
|
15
|
+
def load_dom(attrs = {})
|
16
|
+
load_dom! attrs
|
17
|
+
rescue
|
18
|
+
false
|
19
|
+
end
|
20
|
+
|
21
|
+
def click_on(locator)
|
22
|
+
find(locator).click
|
23
|
+
end
|
24
|
+
|
25
|
+
def find(locator)
|
26
|
+
root_el.find_element(locator)
|
27
|
+
end
|
28
|
+
|
29
|
+
def find_elements(locator)
|
30
|
+
root_el.find_elements(locator)
|
31
|
+
end
|
32
|
+
|
33
|
+
def first_last(list)
|
34
|
+
blk = ->(first, *_, last) { [first, last] }
|
35
|
+
blk.call(*list)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module SeleniumRecord
|
2
|
+
# Helpers for getting html related info to selenium objects
|
3
|
+
module Html
|
4
|
+
# @return [String] the html content for the root element
|
5
|
+
def to_html
|
6
|
+
return root_el.attribute('innerHTML') if exist?
|
7
|
+
nil
|
8
|
+
end
|
9
|
+
|
10
|
+
# @return [String] the tag name for the DOM element at the root of the
|
11
|
+
# object. Used to define xpath locators.
|
12
|
+
# @see preceding_sibling_locator
|
13
|
+
def tag_name
|
14
|
+
@tag_name ||= root_el.attribute('tagName')
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
module SeleniumRecord
|
2
|
+
# Responsible methods for looking up the root element for each selenium object
|
3
|
+
module Lookup
|
4
|
+
def self.included(base)
|
5
|
+
base.extend(ClassMethods)
|
6
|
+
end
|
7
|
+
|
8
|
+
# Searchs for root element of current object based on other element
|
9
|
+
# @return [Webdriver::Element]
|
10
|
+
def lookup
|
11
|
+
@root_el = parent_el || browser
|
12
|
+
lookup_sequence.each { |locator| @root_el = lookup_step(locator) }
|
13
|
+
rescue
|
14
|
+
@root_el = nil
|
15
|
+
raise
|
16
|
+
end
|
17
|
+
|
18
|
+
# Given the current root element for the view, applies a scoped search for
|
19
|
+
# the web element identified by the locator passed as parameter
|
20
|
+
#
|
21
|
+
# @raise [LookupMultipleElementsError] if it is found multiple web elements
|
22
|
+
# in the scope of the current root element for the locator passed
|
23
|
+
# @raise [LookupUndefinedElementError] if it isn't found any web elements
|
24
|
+
# in the scope of the current root element for the locator passed
|
25
|
+
#
|
26
|
+
# @param locator [Hash] contains unique {key: value} where the key is the
|
27
|
+
# locator_type (:class, :class_name, :css, :id, :link_text, :link,
|
28
|
+
# :partial_link_text, :name, :tag_name, :xpath)
|
29
|
+
# @return element [Selenium::WebDriver::Element]
|
30
|
+
def lookup_step(locator)
|
31
|
+
lookup_elements = find_elements(locator)
|
32
|
+
size = lookup_elements.size
|
33
|
+
fail 'LookupMultipleElementsError' if size > 1
|
34
|
+
fail 'LookupUndefinedElementError' if size == 0
|
35
|
+
lookup_elements.first
|
36
|
+
end
|
37
|
+
|
38
|
+
# Clasess extending SeleniumRecord::Base should overwrite this method or
|
39
|
+
# call to class method `lookup_strategy` defined in `SeleniumRecord::Lookup`
|
40
|
+
# in order to search for Selenium::WebDriver::Element used as scope for
|
41
|
+
# finding elements inside the instance object
|
42
|
+
def lookup_sequence
|
43
|
+
fail 'LookupUndefinedSequenceError'
|
44
|
+
end
|
45
|
+
|
46
|
+
# Contains class method helpers and definition of classes for lookup
|
47
|
+
# strategy
|
48
|
+
module ClassMethods
|
49
|
+
# @param strategy_sym [Symbol] lookup strategy corresponding with the
|
50
|
+
# name of a lookup strategy locator
|
51
|
+
def lookup_strategy(strategy_sym, opts = {})
|
52
|
+
locator_klass = "Lookup#{strategy_sym.to_s.camelize}Strategy"
|
53
|
+
Module.nesting.shift.const_get(locator_klass).new(self, opts).run
|
54
|
+
end
|
55
|
+
|
56
|
+
# Base class for all lookup strategies
|
57
|
+
class LookupStrategy
|
58
|
+
attr_reader :lookup_attributes
|
59
|
+
|
60
|
+
# @param klass [SeleniumObject::Base] class on top over it is defined
|
61
|
+
# the lookup strategy
|
62
|
+
# @param attrs [Hash] attributes used while it is the defined the
|
63
|
+
# lookup sequence
|
64
|
+
def initialize(klass, attrs = {})
|
65
|
+
@klass = klass
|
66
|
+
@attributes_blk = -> { attrs }
|
67
|
+
end
|
68
|
+
|
69
|
+
# Defines for the class the instance methods required for the lookup
|
70
|
+
# sequence. Inside the block you have access to the "lookup_attributes"
|
71
|
+
# specified in the constructor call
|
72
|
+
# @param [Block] block defining the lookup sequence
|
73
|
+
def lookup_sequence(&block)
|
74
|
+
attributes_blk = @attributes_blk
|
75
|
+
before_lookup_blk = before_run if respond_to? :before_run
|
76
|
+
@klass.instance_eval do
|
77
|
+
define_method :lookup_attributes, attributes_blk
|
78
|
+
define_method :lookup_sequence, &block
|
79
|
+
define_method :before_lookup, before_lookup_blk if before_lookup_blk
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# Defines a lookup sequence relative to the title xpath
|
85
|
+
class LookupRelativeTitleStrategy < LookupStrategy
|
86
|
+
def run
|
87
|
+
lookup_sequence do
|
88
|
+
[title_locator, lookup_attributes[:locator]]
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# Defines a lookup sequence relative to the xpath of an element present in
|
94
|
+
# the selenium object
|
95
|
+
class LookupRelativePathStrategy < LookupStrategy
|
96
|
+
def run
|
97
|
+
lookup_sequence do
|
98
|
+
[send("#{lookup_attributes[:to]}_locator"),
|
99
|
+
lookup_attributes[:locator]]
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# Defines a lookup sequence matching an element path
|
105
|
+
class LookupMatchingStrategy < LookupStrategy
|
106
|
+
def run
|
107
|
+
lookup_sequence { [lookup_attrs[:locator]] }
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
# Defines a lookup sequence matching the whole document body
|
112
|
+
class LookupRootStrategy < LookupStrategy
|
113
|
+
def run
|
114
|
+
lookup_sequence { [{ xpath: '//body' }] }
|
115
|
+
end
|
116
|
+
|
117
|
+
def before_run
|
118
|
+
-> { @parent_el = nil }
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require_relative 'base'
|
2
|
+
|
3
|
+
module SeleniumRecord
|
4
|
+
# Base model to be extended by all Selenium page objects
|
5
|
+
class NavigationItem < Base
|
6
|
+
lookup_strategy :root
|
7
|
+
|
8
|
+
def before_load_dom
|
9
|
+
before_navigate if respond_to? :before_navigate
|
10
|
+
navigate unless current?
|
11
|
+
end
|
12
|
+
|
13
|
+
def click_link(*args)
|
14
|
+
super
|
15
|
+
self
|
16
|
+
end
|
17
|
+
|
18
|
+
def reload
|
19
|
+
find(link_active_locator).click
|
20
|
+
wait_page_load
|
21
|
+
self
|
22
|
+
rescue => error
|
23
|
+
if error.is_a? Selenium::WebDriver::Error::StaleElementReferenceError
|
24
|
+
load_dom
|
25
|
+
retry
|
26
|
+
else
|
27
|
+
raise
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Class method to create the 'before_navigate' hook defining the title
|
32
|
+
# of the page
|
33
|
+
# @param key [String] key to lookup text translation for menu title of
|
34
|
+
# the page
|
35
|
+
def self.navigate_to(key)
|
36
|
+
define_method :before_navigate do
|
37
|
+
@title = trans key
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
# Checks whether the current page corresponds to the instance page and
|
44
|
+
# in case of mismatch clicks on the navigation menu for loading the
|
45
|
+
# instance page
|
46
|
+
def navigate
|
47
|
+
when_present(link_inactive_locator).click
|
48
|
+
wait_displayed(link_active_locator)
|
49
|
+
end
|
50
|
+
|
51
|
+
# @param [String] Returns the xpath to be used to identify if the current
|
52
|
+
# browser page matches this object
|
53
|
+
# @raise [SubclassResponsabilityError] if subclasses don't implement this
|
54
|
+
# method
|
55
|
+
def link_inactive_locator
|
56
|
+
fail SUBCLASS_RESPONSABILITY
|
57
|
+
end
|
58
|
+
|
59
|
+
def link_active_locator
|
60
|
+
fail SUBCLASS_RESPONSABILITY
|
61
|
+
end
|
62
|
+
|
63
|
+
# @return [Boolean] Marks whether the current browser page matches the new
|
64
|
+
# new instantiated page object
|
65
|
+
def current?
|
66
|
+
browser.find_element(link_active_locator)
|
67
|
+
rescue Selenium::WebDriver::Error::NoSuchElementError
|
68
|
+
false
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module SeleniumRecord
|
2
|
+
# Selenium helpers for doing an action after a precondition takes place
|
3
|
+
module Preconditions
|
4
|
+
# Returns the first element matching the given arguments once this
|
5
|
+
# element is displayed in the DOM
|
6
|
+
# @param how [Symbol] (:class, :class_name, :css, :id, :link_text, :link,
|
7
|
+
# :partial_link_text, :name, :tag_name, :xpath)
|
8
|
+
# @param what [String]
|
9
|
+
# @return element [Selenium::WebDriver::Element]
|
10
|
+
def when_present(locator)
|
11
|
+
element = wait_displayed(locator)
|
12
|
+
yield if block_given?
|
13
|
+
element
|
14
|
+
end
|
15
|
+
|
16
|
+
# @raise [Selenium::WebDriver::Error::TimeOutError] whether the element
|
17
|
+
# stays no clickable after time out period
|
18
|
+
# @param locator [Hash] contains unique {key: value} where the key is the
|
19
|
+
# locator_type (:class, :class_name, :css, :id, :link_text, :link,
|
20
|
+
# :partial_link_text, :name, :tag_name, :xpath)
|
21
|
+
# @return [Selenium::WebDriver::Element] once the element is clickable
|
22
|
+
def when_clickable(locator)
|
23
|
+
element = wait_clickable(locator)
|
24
|
+
yield if block_given?
|
25
|
+
element
|
26
|
+
end
|
27
|
+
|
28
|
+
def when_modal_present(title, &block)
|
29
|
+
when_present(:xpath, modal_header_xpath(title), &block)
|
30
|
+
end
|
31
|
+
|
32
|
+
# @raise [Selenium::WebDriver::Error::TimeOutError] whether the element
|
33
|
+
# stays visible after time out period
|
34
|
+
# @param locator [Hash] contains unique {key: value} where the key is the
|
35
|
+
# locator_type (:class, :class_name, :css, :id, :link_text, :link,
|
36
|
+
# :partial_link_text, :name, :tag_name, :xpath)
|
37
|
+
# @return [Selenium::WebDriver::Element] once the element is hidden
|
38
|
+
def when_hidden(locator)
|
39
|
+
self.class.wait_for do
|
40
|
+
begin
|
41
|
+
element = root_el.find_element(locator)
|
42
|
+
element unless element.displayed?
|
43
|
+
rescue => error
|
44
|
+
raise if error.is_a? Selenium::WebDriver::Error::TimeOutError
|
45
|
+
true
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module SeleniumRecord
|
2
|
+
# Helpers for executing actions in custom scope
|
3
|
+
module Scopes
|
4
|
+
# Class for giving custom scope to selenium objects
|
5
|
+
class LocatorScope < SimpleDelegator
|
6
|
+
include Actions
|
7
|
+
attr_accessor :scoped_locator
|
8
|
+
|
9
|
+
def root_el
|
10
|
+
@root_el ||= __rootel__.find_element(scoped_locator)
|
11
|
+
end
|
12
|
+
|
13
|
+
def find(locator)
|
14
|
+
root_el.find_element(locator)
|
15
|
+
end
|
16
|
+
|
17
|
+
def find_elements(locator)
|
18
|
+
root_el.find_elements(locator)
|
19
|
+
end
|
20
|
+
|
21
|
+
def run(&block)
|
22
|
+
instance_eval(&block)
|
23
|
+
end
|
24
|
+
|
25
|
+
def class
|
26
|
+
__getobj__.class
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Executes the block passed as parameter in the scope associated to the
|
31
|
+
# locator referenced by the scope name
|
32
|
+
# @param name [Symbol] The name of the scope. Adding the suffix '_locator'
|
33
|
+
# it should match a locator name
|
34
|
+
def scope(scope_name, &block)
|
35
|
+
scope_obj = LocatorScope.new(self)
|
36
|
+
scope_obj.scoped_locator = send "#{scope_name}_locator"
|
37
|
+
scope_obj.run(&block)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module SeleniumRecord
|
2
|
+
# Helpers specific for the bootstrap theme
|
3
|
+
module Theme
|
4
|
+
def modal_header_xpath(key)
|
5
|
+
"//div[@class='modal-header']/h3[text()='#{trans(key)}')]"
|
6
|
+
end
|
7
|
+
|
8
|
+
def find_headline(text)
|
9
|
+
xpath = "//div[contains(@class,'headline')]//div[contains(.,'#{text}')]"
|
10
|
+
find(:xpath, xpath)
|
11
|
+
end
|
12
|
+
|
13
|
+
# Returns xpath for select decorated with chosen.jquery.js
|
14
|
+
# @param [String] id of the select
|
15
|
+
def select_xpath(id)
|
16
|
+
".//div[@id='#{id}_chosen']/a"
|
17
|
+
end
|
18
|
+
|
19
|
+
def select_option_xpath(id, text)
|
20
|
+
".//div[@id='#{id}_chosen']//li[text()='#{text}']"
|
21
|
+
end
|
22
|
+
|
23
|
+
# Returns xpath for the dropdown button that matches the text
|
24
|
+
# @param key [String] text of dropdown button
|
25
|
+
def dropdown_xpath(text)
|
26
|
+
".//button[contains(.,'#{text}')]"
|
27
|
+
end
|
28
|
+
|
29
|
+
# Returns xpath for the select option that matches the text
|
30
|
+
# @param key [String] text of select option
|
31
|
+
def dropdown_menu_xpath(text)
|
32
|
+
".//ul[@class='dropdown-menu']/li[contains(.,'#{text}')]/a"
|
33
|
+
end
|
34
|
+
|
35
|
+
def section_xpath(text)
|
36
|
+
".//section//div[@class='panel-header']
|
37
|
+
/h3[@class='suspended area-name']/strong/a[contains(.,'#{text}')]"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
module SeleniumRecord
|
2
|
+
# Contains helper methods for translations
|
3
|
+
module Translations
|
4
|
+
# @param key [String] the key to be used to lookup text translations
|
5
|
+
# @return [String] the translation for the given key
|
6
|
+
def trans(key)
|
7
|
+
I18n.t(key)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
#:nodoc:
|
2
|
+
module SeleniumRecord
|
3
|
+
# Helpers to make easy waiting for something to happen
|
4
|
+
module Waits
|
5
|
+
DEFAULT_WAITING_TIME = 20
|
6
|
+
|
7
|
+
def self.included(base)
|
8
|
+
base.extend(ClassMethods)
|
9
|
+
end
|
10
|
+
|
11
|
+
# Wait selenium execution until no ajax request is pending in the browser
|
12
|
+
# @param seconds [Integer] number of seconds to wait
|
13
|
+
def wait_js_inactive(seconds = DEFAULT_WAITING_TIME)
|
14
|
+
klass = self.class
|
15
|
+
yield if block_given?
|
16
|
+
klass.wait_for(seconds) do
|
17
|
+
browser.execute_script(klass.js_inactive_script) == 0
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def wait_page_load
|
22
|
+
self.class.wait_for do
|
23
|
+
browser.execute_script('return document.readyState;') == 'complete'
|
24
|
+
end
|
25
|
+
load_dom
|
26
|
+
end
|
27
|
+
|
28
|
+
# Wait selenium execution until the element is displayed
|
29
|
+
# @param locator [Hash] contains unique {key: value} where the key is the
|
30
|
+
# locator_type (:class, :class_name, :css, :id, :link_text, :link,
|
31
|
+
# :partial_link_text, :name, :tag_name, :xpath)
|
32
|
+
def wait_displayed(locator, opts = {})
|
33
|
+
klass = self.class
|
34
|
+
klass.wait_for(klass.seconds_for(opts)) do
|
35
|
+
begin
|
36
|
+
evaluate_displayed(locator)
|
37
|
+
rescue Selenium::WebDriver::Error::StaleElementReferenceError
|
38
|
+
lookup unless parent_el
|
39
|
+
false
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Waits until the 'model_view' corresponding to the model is completely
|
45
|
+
# visible
|
46
|
+
# @param model[PORO] plain old ruby object with a related 'model_view'
|
47
|
+
def wait_fade_in(model)
|
48
|
+
web_el = view_for(model).root_el
|
49
|
+
self.class.wait_for { web_el.css_value('opacity').to_i == 1 }
|
50
|
+
end
|
51
|
+
|
52
|
+
# Waits until the 'model_view' corresponding to the model is completely
|
53
|
+
# hidden
|
54
|
+
# @param locator [Hash] contains unique {key: value} where the key is the
|
55
|
+
# locator_type (:class, :class_name, :css, :id, :link_text, :link,
|
56
|
+
# :partial_link_text, :name, :tag_name, :xpath)
|
57
|
+
def wait_hidden(locator)
|
58
|
+
self.class.wait_for do
|
59
|
+
begin
|
60
|
+
finder = root_el || browser
|
61
|
+
element = finder.find_element(locator)
|
62
|
+
!element.displayed?
|
63
|
+
rescue Selenium::WebDriver::Error::StaleElementReferenceError
|
64
|
+
true
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
# @return element [Selenium::WebDriver::Element] if the element is visible.
|
72
|
+
# Otherwise returns nil.
|
73
|
+
def evaluate_displayed(locator)
|
74
|
+
finder = root_el || browser
|
75
|
+
element = finder.find_element(locator)
|
76
|
+
element if element.displayed?
|
77
|
+
end
|
78
|
+
|
79
|
+
# Utilities for waiting methods
|
80
|
+
module ClassMethods
|
81
|
+
# Wait selenium execution until a condition take place
|
82
|
+
# @raise [Selenium::WebDriver::Error::TimeOutError] if the precondition we
|
83
|
+
# are waiting for doesn't take place after completing the wait period
|
84
|
+
# @param seconds [Integer] number of seconds to wait
|
85
|
+
# @yieldreturn [Boolean] marks whether the condition we are waiting for
|
86
|
+
# passes
|
87
|
+
def wait_for(seconds = DEFAULT_WAITING_TIME)
|
88
|
+
Selenium::WebDriver::Wait.new(timeout: seconds).until { yield }
|
89
|
+
end
|
90
|
+
|
91
|
+
# @return [String] the string containing javascript code to be evaluated
|
92
|
+
# to check if there are ajax calls pending
|
93
|
+
def js_inactive_script
|
94
|
+
{
|
95
|
+
jquery: 'return $.active'
|
96
|
+
}[Configuration.js_library]
|
97
|
+
end
|
98
|
+
|
99
|
+
def seconds_for(opts)
|
100
|
+
seconds = opts[:seconds]
|
101
|
+
return seconds if seconds
|
102
|
+
20
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'selenium_record/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'seleniumrecord'
|
8
|
+
spec.version = SeleniumRecord::VERSION
|
9
|
+
spec.authors = ['David Saenz Tagarro']
|
10
|
+
spec.email = ['david.saenz.tagarro@gmail.com']
|
11
|
+
spec.summary = <<-summary
|
12
|
+
Selenium Record is a DSL for easily writing acceptance tests.
|
13
|
+
summary
|
14
|
+
spec.description = <<-desc
|
15
|
+
Selenium Record is a wrapper over Selenium ruby bindings to let you easily
|
16
|
+
apply the well known page object pattern.
|
17
|
+
desc
|
18
|
+
spec.homepage = 'https://github.com/dsaenztagarro/selenium-record'
|
19
|
+
spec.license = 'MIT'
|
20
|
+
|
21
|
+
spec.files = `git ls-files -z`.split("\x0")
|
22
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
23
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
24
|
+
spec.require_paths = ['lib']
|
25
|
+
|
26
|
+
spec.add_development_dependency 'bundler', '~> 1.6'
|
27
|
+
spec.add_development_dependency 'rake'
|
28
|
+
spec.add_development_dependency 'rspec'
|
29
|
+
spec.add_development_dependency 'codeclimate-test-reporter'
|
30
|
+
spec.add_development_dependency 'simplecov'
|
31
|
+
spec.add_development_dependency 'coveralls'
|
32
|
+
spec.add_development_dependency 'rubocop'
|
33
|
+
spec.add_development_dependency 'reek'
|
34
|
+
spec.add_development_dependency 'cane'
|
35
|
+
end
|
metadata
ADDED
@@ -0,0 +1,194 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: seleniumrecord
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1.beta1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- David Saenz Tagarro
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-11-24 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.6'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.6'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: codeclimate-test-reporter
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: simplecov
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: coveralls
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rubocop
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: reek
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: cane
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
139
|
+
description: |
|
140
|
+
Selenium Record is a wrapper over Selenium ruby bindings to let you easily
|
141
|
+
apply the well known page object pattern.
|
142
|
+
email:
|
143
|
+
- david.saenz.tagarro@gmail.com
|
144
|
+
executables: []
|
145
|
+
extensions: []
|
146
|
+
extra_rdoc_files: []
|
147
|
+
files:
|
148
|
+
- ".gitignore"
|
149
|
+
- Gemfile
|
150
|
+
- LICENSE.txt
|
151
|
+
- README.md
|
152
|
+
- Rakefile
|
153
|
+
- lib/selenium_record.rb
|
154
|
+
- lib/selenium_record/action_builder.rb
|
155
|
+
- lib/selenium_record/actions.rb
|
156
|
+
- lib/selenium_record/axis.rb
|
157
|
+
- lib/selenium_record/base.rb
|
158
|
+
- lib/selenium_record/configuration.rb
|
159
|
+
- lib/selenium_record/core.rb
|
160
|
+
- lib/selenium_record/html.rb
|
161
|
+
- lib/selenium_record/lookup.rb
|
162
|
+
- lib/selenium_record/navigation_item.rb
|
163
|
+
- lib/selenium_record/preconditions.rb
|
164
|
+
- lib/selenium_record/scopes.rb
|
165
|
+
- lib/selenium_record/theme.rb
|
166
|
+
- lib/selenium_record/translations.rb
|
167
|
+
- lib/selenium_record/version.rb
|
168
|
+
- lib/selenium_record/waits.rb
|
169
|
+
- selenium_record.gemspec
|
170
|
+
homepage: https://github.com/dsaenztagarro/selenium-record
|
171
|
+
licenses:
|
172
|
+
- MIT
|
173
|
+
metadata: {}
|
174
|
+
post_install_message:
|
175
|
+
rdoc_options: []
|
176
|
+
require_paths:
|
177
|
+
- lib
|
178
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
179
|
+
requirements:
|
180
|
+
- - ">="
|
181
|
+
- !ruby/object:Gem::Version
|
182
|
+
version: '0'
|
183
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
184
|
+
requirements:
|
185
|
+
- - ">"
|
186
|
+
- !ruby/object:Gem::Version
|
187
|
+
version: 1.3.1
|
188
|
+
requirements: []
|
189
|
+
rubyforge_project:
|
190
|
+
rubygems_version: 2.2.2
|
191
|
+
signing_key:
|
192
|
+
specification_version: 4
|
193
|
+
summary: Selenium Record is a DSL for easily writing acceptance tests.
|
194
|
+
test_files: []
|