capybara-rc 0.5.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +9 -0
- data/.rspec +2 -0
- data/.travis.yml +5 -0
- data/CHANGELOG.md +7 -0
- data/DIFFERENCES.md +34 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +81 -0
- data/Rakefile +6 -0
- data/bin/rspec +16 -0
- data/capybara-rc.gemspec +30 -0
- data/lib/capybara/rc.rb +7 -0
- data/lib/capybara/rc/accessors.rb +159 -0
- data/lib/capybara/rc/actions.rb +103 -0
- data/lib/capybara/rc/adapter.rb +28 -0
- data/lib/capybara/rc/extensions.rb +143 -0
- data/lib/capybara/rc/modals.rb +27 -0
- data/lib/capybara/rc/selenium_rc_locators.rb +74 -0
- data/lib/capybara/rc/version.rb +5 -0
- data/lib/capybara/rc/windows.rb +110 -0
- metadata +154 -0
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/CHANGELOG.md
ADDED
data/DIFFERENCES.md
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
# Behavior differences between Selenium-RC and Capybara
|
2
|
+
|
3
|
+
* Selenium-RC can locate elements which are not visible. Capybara only returns
|
4
|
+
visible elements. Among other things, this means that `is_element_present` and
|
5
|
+
`is_visible` behave differently in this adapter than in Selenium-RC.
|
6
|
+
* Capybara always waits for conditions to become true, so
|
7
|
+
`wait_for_page_to_load` is a no-op. Capybara does not expose a way to do what
|
8
|
+
Selenium-RC's `wait_for_page_to_load` does (wait until the next time a page
|
9
|
+
is loaded for any reason). The Capybara way to do this is to specify a
|
10
|
+
condition that will be satisfied once the new page is loaded. Capybara will
|
11
|
+
wait for that to become true. See also `Capybara.use_wait_time` if Capybara's
|
12
|
+
default wait time is too short for the page load you are waiting on.
|
13
|
+
* The Capybara-backed implementation of `is_visible` waits for an element to
|
14
|
+
show up even if what you're trying to test is that the element is _not_
|
15
|
+
present. The adapter provides a method `capybara_has_no_locator?` which allows
|
16
|
+
you to hook into Capybara's negated matchers and avoid this delay.
|
17
|
+
* Support for `mouse_out` is not available, but a workaround is calling
|
18
|
+
`mouse_over` on another element.
|
19
|
+
* Methods which take JavaScript snippets as arguments (such as `get_eval`,
|
20
|
+
`run_script`, and `wait_for_condition`) behave differently. Because Capybara
|
21
|
+
uses Selenium-Webdriver and not RC, the JavaScript environment is different:
|
22
|
+
the execution context is the active window, not the `selenium` object and the
|
23
|
+
Selenium-RC API is not present (`browserbot`, etc.). Details for specific
|
24
|
+
adapted methods:
|
25
|
+
* `get_eval` is translated to Capybara's `execute_script`. While `get_eval`
|
26
|
+
returns the value of the last expression automatically, `execute_script`
|
27
|
+
only returns a value if the script snippet has an explicit return.
|
28
|
+
* `run_script` is also translated to Capybara's `execute_script`. Unlike
|
29
|
+
Selenium-RC, this will not necessarily be implemented by creating a script
|
30
|
+
tag with the provided JavaScript source.
|
31
|
+
* `wait_for_condition`'s condition is evaluated using `evaluate_script`. The
|
32
|
+
script must only contain a single expression.
|
33
|
+
* Capybara-RC does not support switching to a window based on a handle
|
34
|
+
referenced as a local variable in the current page.
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2015 Collaborative Drug Discovery.
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
# Capybara::Rc
|
2
|
+
|
3
|
+
This gem provides an adapter that allows Selenium-RC-based code to execute using
|
4
|
+
[Capybara](http://jnicklas.github.io/capybara/) instead. This is useful because:
|
5
|
+
|
6
|
+
* Selenium-RC has been deprecated for a while
|
7
|
+
* Capybara allows your tests to run against different backends (not just selenium)
|
8
|
+
* Porting an existing test suite to a new library is dangerous — you can wind up
|
9
|
+
modifying your tests in such a way that they still pass but are no longer
|
10
|
+
actually testing what you want.
|
11
|
+
|
12
|
+
### Caveats
|
13
|
+
|
14
|
+
* This is not a full adapter yet — it only adapts the methods that we are using
|
15
|
+
at CDD.
|
16
|
+
* Some things that were possible with Selenium-RC are not possible in Capybara.
|
17
|
+
See DIFFERENCES.md for a list of some of these.
|
18
|
+
|
19
|
+
## Installation
|
20
|
+
|
21
|
+
Add this line to your application's Gemfile:
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
gem 'capybara-rc'
|
25
|
+
```
|
26
|
+
|
27
|
+
And then execute:
|
28
|
+
|
29
|
+
$ bundle
|
30
|
+
|
31
|
+
Or install it yourself as:
|
32
|
+
|
33
|
+
$ gem install capybara-rc
|
34
|
+
|
35
|
+
## Usage
|
36
|
+
|
37
|
+
* Configure Capybara as is appropriate for your application
|
38
|
+
* Replace your Selenium-RC initialization with something like this:
|
39
|
+
|
40
|
+
```ruby
|
41
|
+
require 'capybara/rc'
|
42
|
+
# If @browser was your Selenium-RC browser before …
|
43
|
+
@browser = Capybara::Rc::Adapter.new(Capybara.current_session)
|
44
|
+
```
|
45
|
+
|
46
|
+
### Additions
|
47
|
+
|
48
|
+
The ideal with this adapter is that you'd be able to drop it in and run your
|
49
|
+
existing test suite with no changes. The reality is that you probably use some
|
50
|
+
features of Selenium-RC that can't be adapted to Capybara (`get_eval` and the
|
51
|
+
Selenium-RC in-browser JavaScript API are likely sources of this).
|
52
|
+
|
53
|
+
In order to limit the changes you need to make to your test suite, the `Adapter`
|
54
|
+
class provides some lower-level translation methods:
|
55
|
+
|
56
|
+
* `capybara_find_by_locator(locator)`: returns a Capybara element corresponding
|
57
|
+
to the given Selenium-RC locator. You can use the Capybara API to do further
|
58
|
+
querying/testing on the returned element.
|
59
|
+
* `capybara_has_no_locator?(locator)`: allows you to bypass Capybara's wait
|
60
|
+
when you are trying to test that something isn't present. Maps from a
|
61
|
+
Selenium-RC locator to the appropriate Capybara `has_no_*` method
|
62
|
+
(`has_no_css`, `has_no_xpath`, etc.).
|
63
|
+
* `capybara_has_locator?(locator)`: The positive version of the previous method.
|
64
|
+
Similarly maps to the appropriate Capybara `has_*` method.
|
65
|
+
|
66
|
+
## Development
|
67
|
+
|
68
|
+
### Project infrastructure
|
69
|
+
|
70
|
+
* [Source on GitHub](https://github.com/cdd/capybara-rc)
|
71
|
+
* [![Build Status](https://travis-ci.org/cdd/capybara-rc.svg?branch=master)](https://travis-ci.org/cdd/capybara-rc)
|
72
|
+
[Continuous Integration on Travis-CI](https://travis-ci.org/cdd/capybara-rc)
|
73
|
+
|
74
|
+
## Contributing
|
75
|
+
|
76
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/cdd/capybara-rc.
|
77
|
+
|
78
|
+
|
79
|
+
## License
|
80
|
+
|
81
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
data/bin/rspec
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# This file was generated by Bundler.
|
4
|
+
#
|
5
|
+
# The application 'rspec' is installed as part of a gem, and
|
6
|
+
# this file is here to facilitate running it.
|
7
|
+
#
|
8
|
+
|
9
|
+
require 'pathname'
|
10
|
+
ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
|
11
|
+
Pathname.new(__FILE__).realpath)
|
12
|
+
|
13
|
+
require 'rubygems'
|
14
|
+
require 'bundler/setup'
|
15
|
+
|
16
|
+
load Gem.bin_path('rspec-core', 'rspec')
|
data/capybara-rc.gemspec
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'capybara/rc/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "capybara-rc"
|
8
|
+
spec.version = Capybara::Rc::VERSION
|
9
|
+
spec.authors = ["Rhett Sutphin"]
|
10
|
+
spec.email = ["rhett@collaborativedrug.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{Provides the Selenium-RC API on top of Capybara}
|
13
|
+
spec.description = %q{
|
14
|
+
Provides a wrapper for a Capybara session which exposes the ancient Selenium-RC API.
|
15
|
+
Allows gradually porting a legacy Selenium suite to newer technologies.
|
16
|
+
}
|
17
|
+
spec.homepage = "https://github.com/cdd/capybara-rc"
|
18
|
+
spec.license = "MIT"
|
19
|
+
|
20
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
21
|
+
spec.bindir = "exe"
|
22
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
23
|
+
spec.require_paths = ["lib"]
|
24
|
+
|
25
|
+
spec.add_development_dependency "bundler", "~> 1.10"
|
26
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
27
|
+
spec.add_development_dependency "rspec", '~> 3.3'
|
28
|
+
spec.add_development_dependency "capybara"
|
29
|
+
spec.add_development_dependency "mime-types", "2.99" # 3.0+ requires Ruby 2.0+, which we currently do not support
|
30
|
+
end
|
data/lib/capybara/rc.rb
ADDED
@@ -0,0 +1,159 @@
|
|
1
|
+
require 'timeout'
|
2
|
+
|
3
|
+
module Capybara
|
4
|
+
module Rc
|
5
|
+
##
|
6
|
+
# Contributes Selenium-RC reader methods to the adapter.
|
7
|
+
#
|
8
|
+
# Expects a `session` accessor to be provided where it is mixed in.
|
9
|
+
module Accessors
|
10
|
+
WAIT_FOR_CONDITION_DELAY_SECONDS = 0.2
|
11
|
+
|
12
|
+
def is_checked(locator)
|
13
|
+
element = capybara_find_by_locator(locator)
|
14
|
+
if capybara_element_is_radio_or_checkbox?(element)
|
15
|
+
element.checked?
|
16
|
+
else
|
17
|
+
fail "#{locator.inspect} is not a checkable element"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
##
|
22
|
+
# Note that Capybara does not allow access to invisible elements, so this
|
23
|
+
# is equivalent to `is_visible`.
|
24
|
+
#
|
25
|
+
# @return [Boolean]
|
26
|
+
def is_element_present(locator)
|
27
|
+
capybara_has_locator?(locator)
|
28
|
+
end
|
29
|
+
|
30
|
+
##
|
31
|
+
# Returns true if the locator matches an element. Capybara does not make
|
32
|
+
# invisible elements available at all, so there is no distinction between
|
33
|
+
# an invisible element and one that is not present. (This method returns
|
34
|
+
# false for both.)
|
35
|
+
#
|
36
|
+
# @return [Boolean]
|
37
|
+
def is_visible(locator)
|
38
|
+
capybara_has_locator?(locator)
|
39
|
+
end
|
40
|
+
|
41
|
+
def is_text_present(pattern)
|
42
|
+
page_text = session.text
|
43
|
+
parsed = parse_selenium_rc_string_pattern(pattern)
|
44
|
+
case parsed[:type]
|
45
|
+
when :glob
|
46
|
+
page_text.include?(parsed[:string])
|
47
|
+
else
|
48
|
+
fail "Don't know how to search a string using a #{parsed[:type].inspect} Selenium pattern"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def is_editable(locator)
|
53
|
+
!capybara_find_by_locator(locator).disabled?
|
54
|
+
end
|
55
|
+
|
56
|
+
def get_title
|
57
|
+
session.title
|
58
|
+
end
|
59
|
+
|
60
|
+
def get_html_source
|
61
|
+
session.source
|
62
|
+
end
|
63
|
+
|
64
|
+
def get_location
|
65
|
+
session.current_url
|
66
|
+
end
|
67
|
+
|
68
|
+
def get_text(locator)
|
69
|
+
capybara_find_by_locator(locator).text
|
70
|
+
end
|
71
|
+
|
72
|
+
def get_value(locator)
|
73
|
+
c_element = capybara_find_by_locator(locator)
|
74
|
+
if (capybara_element_is_radio_or_checkbox?(c_element))
|
75
|
+
c_element.checked? ? 'on' : 'off'
|
76
|
+
else
|
77
|
+
c_element.value.strip
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def get_attribute(attribute_locator)
|
82
|
+
match = (attribute_locator.match(/\A(.*)@(\w+)\Z/))
|
83
|
+
if match
|
84
|
+
element_locator = match[1]
|
85
|
+
attribute_name = match[2]
|
86
|
+
capybara_find_by_locator(element_locator)[attribute_name]
|
87
|
+
else
|
88
|
+
fail "Unable to parse attribute_locator #{attribute_locator.inspect}"
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def get_select_options(locator)
|
93
|
+
capybara_find_select_by_locator(locator).
|
94
|
+
all('option').map { |opt_elt| opt_elt.text.strip }
|
95
|
+
end
|
96
|
+
|
97
|
+
def get_selected_label(locator)
|
98
|
+
capybara_get_selected_option_elements_by_locator(locator).first.text
|
99
|
+
end
|
100
|
+
|
101
|
+
def get_selected_value(locator)
|
102
|
+
capybara_get_selected_option_elements_by_locator(locator).first.value
|
103
|
+
end
|
104
|
+
|
105
|
+
def get_xpath_count(xpath)
|
106
|
+
session.all(:xpath, xpath).size
|
107
|
+
end
|
108
|
+
|
109
|
+
def get_table(table_cell_address)
|
110
|
+
address_parts = table_cell_address.split(".")
|
111
|
+
locator = address_parts[0..-3].join(".")
|
112
|
+
row = address_parts[-2].to_i
|
113
|
+
column = address_parts[-1].to_i
|
114
|
+
|
115
|
+
table = capybara_find_by_locator(locator)
|
116
|
+
row = table.all("tr")[row]
|
117
|
+
cell = (row.all('th').to_a + row.all('td').to_a)[column]
|
118
|
+
|
119
|
+
if cell
|
120
|
+
cell.text
|
121
|
+
else
|
122
|
+
fail Capybara::ElementNotFound
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def wait_for_condition(script, timeout_milliseconds)
|
127
|
+
begin
|
128
|
+
Timeout.timeout(timeout_milliseconds / 1000.0) do
|
129
|
+
until session.evaluate_script(script)
|
130
|
+
sleep(WAIT_FOR_CONDITION_DELAY_SECONDS)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
rescue Timeout::Error
|
134
|
+
fail "Condition did not become true within #{timeout_milliseconds}ms"
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
private
|
139
|
+
|
140
|
+
def capybara_element_is_radio_or_checkbox?(element)
|
141
|
+
element.tag_name == 'input' && %w(checkbox radio).include?(element['type'])
|
142
|
+
end
|
143
|
+
|
144
|
+
def capybara_find_select_by_locator(locator)
|
145
|
+
element = capybara_find_by_locator(locator)
|
146
|
+
if element.tag_name == 'select'
|
147
|
+
element
|
148
|
+
else
|
149
|
+
fail "#{locator.inspect} does not point to a select element"
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def capybara_get_selected_option_elements_by_locator(locator)
|
154
|
+
capybara_find_select_by_locator(locator).all('option').
|
155
|
+
select { |opt_elt| opt_elt.selected? }
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
require 'capybara/rc/selenium_rc_locators'
|
2
|
+
require 'capybara/rc/extensions'
|
3
|
+
|
4
|
+
module Capybara
|
5
|
+
module Rc
|
6
|
+
##
|
7
|
+
# Contributes Selenium-RC action methods to the adapter.
|
8
|
+
#
|
9
|
+
# Expects a `session` accessor to be provided where it is mixed in.
|
10
|
+
module Actions
|
11
|
+
include SeleniumRcLocators
|
12
|
+
include Extensions
|
13
|
+
|
14
|
+
def open(url)
|
15
|
+
session.visit(url)
|
16
|
+
end
|
17
|
+
|
18
|
+
def get_eval(js)
|
19
|
+
session.execute_script(js)
|
20
|
+
end
|
21
|
+
|
22
|
+
def run_script(js)
|
23
|
+
session.execute_script(js); nil
|
24
|
+
end
|
25
|
+
|
26
|
+
def click(locator)
|
27
|
+
capybara_find_by_locator(locator).click
|
28
|
+
end
|
29
|
+
|
30
|
+
def check(locator)
|
31
|
+
capybara_find_by_locator(locator).set(true)
|
32
|
+
end
|
33
|
+
|
34
|
+
def uncheck(locator)
|
35
|
+
capybara_find_by_locator(locator).set(false)
|
36
|
+
end
|
37
|
+
|
38
|
+
def select(select_locator, option_locator)
|
39
|
+
select_element = capybara_find_by_locator(select_locator)
|
40
|
+
parsed = parse_selenium_rc_select_option_locator(option_locator)
|
41
|
+
if parsed[:type] == :text
|
42
|
+
select_element.select(parsed[:string])
|
43
|
+
return
|
44
|
+
end
|
45
|
+
options = select_element.all('option')
|
46
|
+
option_to_select = nil
|
47
|
+
case parsed[:type]
|
48
|
+
when :value
|
49
|
+
option_to_select = options.detect { |o| o['value'] == parsed[:string] }
|
50
|
+
else
|
51
|
+
fail "Don't know how to find a #{parsed[:type].inspect} Selenium option locator with Capybara"
|
52
|
+
end
|
53
|
+
if option_to_select
|
54
|
+
option_to_select.select_option
|
55
|
+
else
|
56
|
+
fail Capybara::ElementNotFound, "No option matching #{option_locator.inspect} for select element #{select_locator}"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def type(locator, value)
|
61
|
+
element = capybara_find_by_locator(locator)
|
62
|
+
element.set(value)
|
63
|
+
end
|
64
|
+
|
65
|
+
def type_keys(locator, value)
|
66
|
+
element = capybara_find_by_locator(locator)
|
67
|
+
element.send_keys(value)
|
68
|
+
end
|
69
|
+
|
70
|
+
def drag_and_drop_to_object(draggable_locator, target_locator)
|
71
|
+
draggable = capybara_find_by_locator(draggable_locator)
|
72
|
+
target = capybara_find_by_locator(target_locator)
|
73
|
+
|
74
|
+
draggable.drag_to(target)
|
75
|
+
end
|
76
|
+
|
77
|
+
def drag_and_drop(draggable_locator, move_by)
|
78
|
+
draggable = capybara_find_by_locator(draggable_locator).native
|
79
|
+
delta_x, delta_y = move_by.split(",").map { |int| Kernel.Integer(int) }
|
80
|
+
|
81
|
+
session.driver.browser.action.drag_and_drop_by(draggable, delta_x, delta_y).perform
|
82
|
+
end
|
83
|
+
|
84
|
+
def attach_file(locator, file_name)
|
85
|
+
capybara_element = capybara_find_by_locator(locator)
|
86
|
+
session.attach_file(capybara_element[:id] || capybara_element[:name], file_name)
|
87
|
+
end
|
88
|
+
|
89
|
+
def go_back
|
90
|
+
session.execute_script("window.history.back()")
|
91
|
+
end
|
92
|
+
|
93
|
+
def refresh
|
94
|
+
session.execute_script("window.location.reload()")
|
95
|
+
end
|
96
|
+
|
97
|
+
def mouse_over(locator)
|
98
|
+
element = capybara_find_by_locator(locator)
|
99
|
+
element.hover
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'capybara/rc/accessors'
|
2
|
+
require 'capybara/rc/actions'
|
3
|
+
require 'capybara/rc/extensions'
|
4
|
+
require 'capybara/rc/modals'
|
5
|
+
require 'capybara/rc/windows'
|
6
|
+
|
7
|
+
module Capybara
|
8
|
+
module Rc
|
9
|
+
class Adapter
|
10
|
+
include Accessors
|
11
|
+
include Actions
|
12
|
+
include Extensions
|
13
|
+
include Modals
|
14
|
+
include Windows
|
15
|
+
|
16
|
+
attr_reader :session
|
17
|
+
|
18
|
+
def initialize(session)
|
19
|
+
super
|
20
|
+
@session = session
|
21
|
+
end
|
22
|
+
|
23
|
+
def wait_for_page_to_load(timeout)
|
24
|
+
# Capybara always waits, so nothing to do here.
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,143 @@
|
|
1
|
+
require 'capybara/rc/selenium_rc_locators'
|
2
|
+
require 'xpath'
|
3
|
+
|
4
|
+
module Capybara
|
5
|
+
module Rc
|
6
|
+
##
|
7
|
+
# Contributes methods to the adapter that are not part of Selenium-RC, but
|
8
|
+
# which are useful in a test suite in transition.
|
9
|
+
#
|
10
|
+
# Expects a `session` accessor to be provided where it is mixed in.
|
11
|
+
module Extensions
|
12
|
+
include SeleniumRcLocators
|
13
|
+
|
14
|
+
##
|
15
|
+
# Parses the locator and finds the corresponding Capybara element.
|
16
|
+
# Raises Capybara::ElementNotFound when the element can't be found, or
|
17
|
+
# a StandardError if the locator is not supported.
|
18
|
+
#
|
19
|
+
# @return [Capybara::Node::Element]
|
20
|
+
def capybara_find_by_locator(locator)
|
21
|
+
parsed = parse_selenium_rc_locator(locator)
|
22
|
+
|
23
|
+
css_selector = locator_to_css_selector(parsed)
|
24
|
+
return session.find(css_selector) if css_selector
|
25
|
+
|
26
|
+
xpath_expression = locator_to_xpath_expression(parsed)
|
27
|
+
return session.find(:xpath, xpath_expression) if xpath_expression
|
28
|
+
|
29
|
+
fail "Don't know how to find a #{parsed[:type].inspect} Selenium locator with Capybara"
|
30
|
+
end
|
31
|
+
|
32
|
+
##
|
33
|
+
# Parses the locator and queries the session to see if it is present.
|
34
|
+
#
|
35
|
+
# @return [Boolean]
|
36
|
+
def capybara_has_locator?(locator)
|
37
|
+
parsed = parse_selenium_rc_locator(locator)
|
38
|
+
|
39
|
+
css_selector = locator_to_css_selector(parsed)
|
40
|
+
return session.has_css?(css_selector) if css_selector
|
41
|
+
|
42
|
+
xpath_expression = locator_to_xpath_expression(parsed)
|
43
|
+
return session.has_xpath?(xpath_expression) if xpath_expression
|
44
|
+
|
45
|
+
fail "Don't know how to find a #{parsed[:type].inspect} Selenium locator with Capybara"
|
46
|
+
end
|
47
|
+
|
48
|
+
##
|
49
|
+
# Parses the locator and queries the session to see if it is _not_ present.
|
50
|
+
# This saves time over `!adapter.is_visible(locator)` because Capybara
|
51
|
+
# [pauses]( https://github.com/jnicklas/capybara#asynchronous-javascript-ajax-and-friends)
|
52
|
+
# to wait for an element to show up after you find it. If you want to
|
53
|
+
# check that it isn't there, this pause is a waste of time. As a side
|
54
|
+
# effect of this Capybara behavior, this method will also wait for an
|
55
|
+
# element to disappear.
|
56
|
+
def capybara_has_no_locator?(locator)
|
57
|
+
parsed = parse_selenium_rc_locator(locator)
|
58
|
+
|
59
|
+
css_selector = locator_to_css_selector(parsed)
|
60
|
+
return session.has_no_css?(css_selector) if css_selector
|
61
|
+
|
62
|
+
xpath_expression = locator_to_xpath_expression(parsed)
|
63
|
+
return session.has_no_xpath?(xpath_expression) if xpath_expression
|
64
|
+
|
65
|
+
fail "Don't know how to find a #{parsed[:type].inspect} Selenium locator with Capybara"
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
##
|
71
|
+
# @return [String, nil] A CSS selector for the given Selenium-RC locator,
|
72
|
+
# or nil if the locator is of a type which cannot be converted into a
|
73
|
+
# CSS selector.
|
74
|
+
def locator_to_css_selector(parsedLocator)
|
75
|
+
case parsedLocator[:type]
|
76
|
+
when :class
|
77
|
+
".#{parsedLocator[:string]}"
|
78
|
+
when :css
|
79
|
+
parsedLocator[:string]
|
80
|
+
when :id
|
81
|
+
"##{parsedLocator[:string]}"
|
82
|
+
when :identifier
|
83
|
+
possibilities = [
|
84
|
+
("##{parsedLocator[:string]}" if parsedLocator[:string] =~ /\A[a-z][a-z0-9_:\-\.]*\Z/i),
|
85
|
+
"[name=#{parsedLocator[:string].inspect}]"
|
86
|
+
]
|
87
|
+
possibilities.compact.join(', ')
|
88
|
+
when :name
|
89
|
+
"[name=#{parsedLocator[:string].inspect}]"
|
90
|
+
else
|
91
|
+
nil
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
##
|
96
|
+
# @return [String, nil] An XPath selector for the given Selenium-RC
|
97
|
+
# locator, or nil if the locator is of a type which cannot be converted
|
98
|
+
# into an XPath selector. N.b. — intended to be used after the CSS
|
99
|
+
# version, so it doesn't attempt to translate locator types handled
|
100
|
+
# there.
|
101
|
+
def locator_to_xpath_expression(parsed_locator)
|
102
|
+
case parsed_locator[:type]
|
103
|
+
when :xpath
|
104
|
+
parsed_locator[:string]
|
105
|
+
when :link
|
106
|
+
link_pattern = parse_selenium_rc_string_pattern(parsed_locator[:string])
|
107
|
+
link_pattern_to_xpath_expression(link_pattern)
|
108
|
+
else
|
109
|
+
nil
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def link_pattern_to_xpath_expression(parsed_pattern)
|
114
|
+
case parsed_pattern[:type]
|
115
|
+
when :glob
|
116
|
+
link_glob_to_xpath_expression(parsed_pattern[:string])
|
117
|
+
else
|
118
|
+
fail "Don't know how to find a link using a #{parsed[:type].inspect} Selenium pattern"
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def link_glob_to_xpath_expression(glob)
|
123
|
+
pos = 0
|
124
|
+
asterisk_locations = glob.split('*', glob.size).map { |part| pos += part.size }
|
125
|
+
asterisk_locations.pop # the last index is the remainder after all the asterisks have been accounted for
|
126
|
+
|
127
|
+
glob_without_asterisks = glob.gsub('*', '')
|
128
|
+
content_expression =
|
129
|
+
if asterisk_locations.empty?
|
130
|
+
XPath.string.n.equals(glob_without_asterisks)
|
131
|
+
elsif asterisk_locations == [glob_without_asterisks.size]
|
132
|
+
XPath.string.n.starts_with(glob_without_asterisks)
|
133
|
+
elsif asterisk_locations == [0, glob_without_asterisks.size]
|
134
|
+
XPath.string.n.is(glob_without_asterisks)
|
135
|
+
else
|
136
|
+
fail "Don't know how to translate the glob #{glob.inspect} into an XPath link-matching expression"
|
137
|
+
end
|
138
|
+
|
139
|
+
XPath.generate { |x| x.descendant(:a)[x.attr(:href)][content_expression] }.to_s
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Capybara
|
2
|
+
module Rc
|
3
|
+
##
|
4
|
+
# Contributes Selenium-RC methods related to modal dialogs to the adapter.
|
5
|
+
#
|
6
|
+
# Expects a `session` accessor to be provided where it is mixed in.
|
7
|
+
module Modals
|
8
|
+
def initialize(*)
|
9
|
+
choose_ok_on_next_confirmation
|
10
|
+
end
|
11
|
+
|
12
|
+
def choose_ok_on_next_confirmation
|
13
|
+
@next_confirmation = :accept_confirm
|
14
|
+
end
|
15
|
+
|
16
|
+
def choose_cancel_on_next_confirmation
|
17
|
+
@next_confirmation = :dismiss_confirm
|
18
|
+
end
|
19
|
+
|
20
|
+
def get_confirmation
|
21
|
+
result = session.send(@next_confirmation)
|
22
|
+
choose_ok_on_next_confirmation
|
23
|
+
result
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module Capybara
|
2
|
+
module Rc
|
3
|
+
module SeleniumRcLocators
|
4
|
+
##
|
5
|
+
# Follows the logic & heuristics in
|
6
|
+
# https://github.com/SeleniumHQ/selenium/blob/master/javascript/selenium-atoms/se_locators.js
|
7
|
+
# function core.locators.parseLocator_
|
8
|
+
def parse_selenium_rc_locator(locator)
|
9
|
+
match = locator.match(/\A([A-Za-z]+)=.+\Z/)
|
10
|
+
if match
|
11
|
+
type = match[1]
|
12
|
+
string = locator.split('=', 2)[1]
|
13
|
+
{
|
14
|
+
type: type.to_sym,
|
15
|
+
string: string
|
16
|
+
}
|
17
|
+
elsif locator =~ %r{^//}
|
18
|
+
{ type: :xpath, string: locator }
|
19
|
+
elsif locator =~ /^document\./
|
20
|
+
{ type: :dom, string: locator }
|
21
|
+
else
|
22
|
+
{ type: :identifier, string: locator }
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
##
|
27
|
+
# Follows the logic & heuristics in
|
28
|
+
# https://github.com/SeleniumHQ/selenium/blob/master/javascript/selenium-atoms/select.js
|
29
|
+
# function core.select.option.getOptionLocator_
|
30
|
+
def parse_selenium_rc_select_option_locator(option_locator)
|
31
|
+
match = option_locator.match(/\A([A-Za-z]+)=.*\Z/)
|
32
|
+
if match
|
33
|
+
type = match[1]
|
34
|
+
type = "text" if type == "label"
|
35
|
+
string = option_locator.split('=', 2)[1]
|
36
|
+
{
|
37
|
+
type: type.to_sym,
|
38
|
+
string: string
|
39
|
+
}
|
40
|
+
else
|
41
|
+
{ type: :text, string: option_locator }
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def parse_selenium_rc_window_locator(window_locator)
|
46
|
+
if window_locator == nil || window_locator == "null"
|
47
|
+
return { type: :main_window, string: nil }
|
48
|
+
end
|
49
|
+
match = window_locator.match(/\A([A-Za-z]+)=.*\Z/)
|
50
|
+
if match
|
51
|
+
type = match[1]
|
52
|
+
string = window_locator.split('=', 2)[1]
|
53
|
+
{ type: type.to_sym, string: string }
|
54
|
+
else
|
55
|
+
{ type: :window_id, string: window_locator }
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def parse_selenium_rc_string_pattern(pattern)
|
60
|
+
match = pattern.match(/\A([A-Za-z]+):.+\Z/)
|
61
|
+
if match
|
62
|
+
type = match[1]
|
63
|
+
string = pattern.split(':', 2)[1]
|
64
|
+
{
|
65
|
+
type: type.to_sym,
|
66
|
+
string: string
|
67
|
+
}
|
68
|
+
else
|
69
|
+
{ type: :glob, string: pattern }
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
module Capybara
|
2
|
+
module Rc
|
3
|
+
##
|
4
|
+
# Contributes Selenium-RC methods related to windows to the adapter.
|
5
|
+
#
|
6
|
+
# Expects a `session` accessor to be provided where it is mixed in.
|
7
|
+
# Also requires that the adapter class's initialize method call super.
|
8
|
+
module Windows
|
9
|
+
MAIN_APP_WINDOW_NAME = "selenium_main_app_window"
|
10
|
+
|
11
|
+
def initialize(session)
|
12
|
+
super
|
13
|
+
if session.respond_to?(:current_window)
|
14
|
+
# The result from Capybara.string does not have a current window
|
15
|
+
@main_window = session.current_window
|
16
|
+
session.execute_script("window.name = #{MAIN_APP_WINDOW_NAME.inspect}")
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def open_window(url, window_id)
|
21
|
+
session.execute_script("window.open(#{url.inspect}, #{window_id.inspect})")
|
22
|
+
end
|
23
|
+
|
24
|
+
def select_window(window_locator)
|
25
|
+
parsed = parse_selenium_rc_window_locator(window_locator)
|
26
|
+
|
27
|
+
window =
|
28
|
+
case parsed[:type]
|
29
|
+
when :main_window
|
30
|
+
@main_window
|
31
|
+
when :name
|
32
|
+
find_window_by_name(parsed[:string])
|
33
|
+
when :title
|
34
|
+
find_window_by_title(parsed[:string])
|
35
|
+
when :window_id
|
36
|
+
find_window_by_name_or_title(parsed[:string])
|
37
|
+
else
|
38
|
+
fail "Don't know how to find a #{parsed[:type].inspect} Selenium window locator with Capybara"
|
39
|
+
end
|
40
|
+
|
41
|
+
unless window
|
42
|
+
fail "Could not find a window matching #{window_locator.inspect}"
|
43
|
+
end
|
44
|
+
session.switch_to_window(window)
|
45
|
+
end
|
46
|
+
|
47
|
+
def close
|
48
|
+
window = session.current_window
|
49
|
+
window.close
|
50
|
+
end
|
51
|
+
|
52
|
+
def window_maximize
|
53
|
+
session.current_window.maximize
|
54
|
+
end
|
55
|
+
|
56
|
+
def get_all_window_names
|
57
|
+
all_window_names = []
|
58
|
+
in_each_window do |window, page|
|
59
|
+
all_window_names << page.execute_script("return window.name;");
|
60
|
+
end
|
61
|
+
all_window_names
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
def find_window_by_name(name)
|
67
|
+
in_each_window do |window, page|
|
68
|
+
this_name = page.execute_script("return window.name;");
|
69
|
+
break window if name == this_name
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def find_window_by_title(title)
|
74
|
+
in_each_window do |window, page|
|
75
|
+
break window if title == page.title
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def find_window_by_name_or_title(id)
|
80
|
+
# Check for name matches & return immediately if found. At the same
|
81
|
+
# time collect titles so that we can do that check if necessary without
|
82
|
+
# flipping through the windows again.
|
83
|
+
titles = {}
|
84
|
+
by_name = in_each_window do |window, page|
|
85
|
+
name = page.execute_script("return window.name;");
|
86
|
+
break window if name == id
|
87
|
+
titles[page.title] = window
|
88
|
+
end
|
89
|
+
return by_name if by_name
|
90
|
+
|
91
|
+
# If the other checks didn't find anything, return the window matching
|
92
|
+
# by title or nil if there isn't one.
|
93
|
+
titles[id]
|
94
|
+
end
|
95
|
+
|
96
|
+
def in_each_window(&block)
|
97
|
+
starting_window = session.current_window
|
98
|
+
begin
|
99
|
+
session.windows.each do |window|
|
100
|
+
session.switch_to_window(window)
|
101
|
+
yield window, session
|
102
|
+
end
|
103
|
+
nil
|
104
|
+
ensure
|
105
|
+
session.switch_to_window(starting_window)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
metadata
ADDED
@@ -0,0 +1,154 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: capybara-rc
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.5.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Rhett Sutphin
|
9
|
+
autorequire:
|
10
|
+
bindir: exe
|
11
|
+
cert_chain: []
|
12
|
+
date: 2015-12-15 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: bundler
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '1.10'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '1.10'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rake
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ~>
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '10.0'
|
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: '10.0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: rspec
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ~>
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '3.3'
|
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: '3.3'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: capybara
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: mime-types
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - '='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '2.99'
|
86
|
+
type: :development
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - '='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '2.99'
|
94
|
+
description: ! "\n Provides a wrapper for a Capybara session which exposes the
|
95
|
+
ancient Selenium-RC API.\n Allows gradually porting a legacy Selenium suite to
|
96
|
+
newer technologies.\n "
|
97
|
+
email:
|
98
|
+
- rhett@collaborativedrug.com
|
99
|
+
executables: []
|
100
|
+
extensions: []
|
101
|
+
extra_rdoc_files: []
|
102
|
+
files:
|
103
|
+
- .gitignore
|
104
|
+
- .rspec
|
105
|
+
- .travis.yml
|
106
|
+
- CHANGELOG.md
|
107
|
+
- DIFFERENCES.md
|
108
|
+
- Gemfile
|
109
|
+
- LICENSE.txt
|
110
|
+
- README.md
|
111
|
+
- Rakefile
|
112
|
+
- bin/rspec
|
113
|
+
- capybara-rc.gemspec
|
114
|
+
- lib/capybara/rc.rb
|
115
|
+
- lib/capybara/rc/accessors.rb
|
116
|
+
- lib/capybara/rc/actions.rb
|
117
|
+
- lib/capybara/rc/adapter.rb
|
118
|
+
- lib/capybara/rc/extensions.rb
|
119
|
+
- lib/capybara/rc/modals.rb
|
120
|
+
- lib/capybara/rc/selenium_rc_locators.rb
|
121
|
+
- lib/capybara/rc/version.rb
|
122
|
+
- lib/capybara/rc/windows.rb
|
123
|
+
homepage: https://github.com/cdd/capybara-rc
|
124
|
+
licenses:
|
125
|
+
- MIT
|
126
|
+
post_install_message:
|
127
|
+
rdoc_options: []
|
128
|
+
require_paths:
|
129
|
+
- lib
|
130
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
131
|
+
none: false
|
132
|
+
requirements:
|
133
|
+
- - ! '>='
|
134
|
+
- !ruby/object:Gem::Version
|
135
|
+
version: '0'
|
136
|
+
segments:
|
137
|
+
- 0
|
138
|
+
hash: -3019246126874219789
|
139
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
140
|
+
none: false
|
141
|
+
requirements:
|
142
|
+
- - ! '>='
|
143
|
+
- !ruby/object:Gem::Version
|
144
|
+
version: '0'
|
145
|
+
segments:
|
146
|
+
- 0
|
147
|
+
hash: -3019246126874219789
|
148
|
+
requirements: []
|
149
|
+
rubyforge_project:
|
150
|
+
rubygems_version: 1.8.25
|
151
|
+
signing_key:
|
152
|
+
specification_version: 3
|
153
|
+
summary: Provides the Selenium-RC API on top of Capybara
|
154
|
+
test_files: []
|