seleniumrecord 0.0.1.beta1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Gem Version](https://badge.fury.io/rb/selenium-record.svg)](http://badge.fury.io/rb/selenium-record)
|
2
|
+
[![Code Climate](https://codeclimate.com/github/dsaenztagarro/selenium-record/badges/gpa.svg)](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: []
|