capybara-rc 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +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
|
+
* [](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: []
|