capybara-select 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/Changes.md +34 -0
- data/LICENSE.txt +22 -0
- data/README.md +70 -0
- data/capybara-select2.gemspec +37 -0
- data/lib/capybara/selectors/tag_selector.rb +17 -0
- data/lib/capybara-select2/version.rb +7 -0
- data/lib/capybara-select2.rb +243 -0
- metadata +135 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 9dfbc64a457c2deff5829d629d5b36d76bc171272708cf986b6c11cc7e248c5e
|
|
4
|
+
data.tar.gz: 674a66dc213e0df0721c907d28a4537b47c2e19959b623354ff6644e4318e1cf
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 8a8ef90e15eafee60a35af1b3b5c0c53bf8061a2b6168010882ed98cdaa1647a5fdb8fc1b3c5ea49bf251249aba92c13c2ff31663b8f52ff04aed2c905f7217a
|
|
7
|
+
data.tar.gz: fb63296bfcf654617b6922852202a1928bd77ce108e11376760a799dee4375da80d56340070f4de492bc1a720c063e5eddd1c43fa499ceda55c14f12d691a45c
|
data/Changes.md
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# capybara-select Changes
|
|
2
|
+
|
|
3
|
+
1.0.0
|
|
4
|
+
---------
|
|
5
|
+
|
|
6
|
+
First release of this fork under its own name, `capybara-select`; `capybara-select2` on rubygems.org is the
|
|
7
|
+
original (1.0.1, 2015). Keep `require 'capybara-select2'` and `Capybara::Select2`.
|
|
8
|
+
|
|
9
|
+
- A click that does not open the dropdown is retried (3 clicks), so a field that re-renders under the click, or a
|
|
10
|
+
dropdown that closes straight away, still gets its option picked.
|
|
11
|
+
- A dropdown that closes before the option is listed (a lookup re-rendering the form) is opened again.
|
|
12
|
+
- A field re-rendered under the click (`StaleElementReferenceError`) is found again with the same locator.
|
|
13
|
+
- A field that never opens fails fast with `select2 dropdown did not open after 3 clicks on …`, instead of waiting
|
|
14
|
+
`default_max_wait_time` several times and reporting missing 3.x `li.select2-result-selectable` options. The
|
|
15
|
+
select2 version is read from the field, not from the dropdown.
|
|
16
|
+
- `search: true` and the "Searching…" wait no longer spend a full `default_max_wait_time` when nothing is loading,
|
|
17
|
+
and every wait is bounded.
|
|
18
|
+
- Fix: `from:` with select2 3.x matched both the label and select2's off-screen copy of it.
|
|
19
|
+
- Fix: `Capybara::Ambiguous` reported the number of listed options, not of matches.
|
|
20
|
+
- `search: true` works with select2 3.x.
|
|
21
|
+
- Private helpers are prefixed with `select2_` (`select2_find_and_open`, `select2_fetch_options`, …). They are mixed
|
|
22
|
+
into every example, so a spec helper with 1.0.4's names (`find_select2_and_open`, `open_select2`,
|
|
23
|
+
`find_select2_container`, `fetch_options`) used to replace them. Call `select2` instead of those helpers.
|
|
24
|
+
- `css:` and `xpath:` may point at an element wrapping the select2 container, such as a form field.
|
|
25
|
+
- Removed `lib/capybara/select2.rb`, which could not be loaded.
|
|
26
|
+
- Specs against select2 4.0.13 and 3.5.4 in headless Chrome. Requires Ruby >= 3.2, Capybara 3 and
|
|
27
|
+
selenium-webdriver 4. Packages only files committed to git. Release tasks in `rakelib/release.rake`.
|
|
28
|
+
|
|
29
|
+
1.0.4 (fork, never published)
|
|
30
|
+
---------
|
|
31
|
+
|
|
32
|
+
- `mode:` (`:exact_text`, `:insensitive`, `:case_insensitive`), `await_option:`, `max_retries:` and retries while
|
|
33
|
+
select2 shows "Searching…".
|
|
34
|
+
- `wait:` to sleep before reading options (was `sleep:`).
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
Copyright (c) 2012 William Yeung
|
|
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,70 @@
|
|
|
1
|
+
# capybara-select
|
|
2
|
+
|
|
3
|
+
[](https://github.com/moskvin/capybara-select2/actions/workflows/ci.yml)
|
|
4
|
+
|
|
5
|
+
Capybara helpers to pick an option in a [select2](https://select2.org) field (4.x and 3.x), for RSpec feature specs
|
|
6
|
+
run with Selenium.
|
|
7
|
+
|
|
8
|
+
A fork of [capybara-select2](https://github.com/goodwill/capybara-select2), released as `capybara-select` because the
|
|
9
|
+
original name is taken on rubygems.org. It keeps the original's `require` name and `Capybara::Select2` module, and
|
|
10
|
+
adds what flaky browser specs need:
|
|
11
|
+
|
|
12
|
+
- a click that does not open the dropdown is retried, and a dropdown that closes before its option is listed (a form
|
|
13
|
+
re-rendering under the click) is opened again;
|
|
14
|
+
- a field re-rendered under the click is found again with the same locator;
|
|
15
|
+
- a field that never opens fails fast with `select2 dropdown did not open after 3 clicks on …`;
|
|
16
|
+
- results that load slowly are waited for ("Searching…"), within `Capybara.default_max_wait_time`.
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```ruby
|
|
21
|
+
group :test do
|
|
22
|
+
gem 'capybara-select', require: 'capybara-select2'
|
|
23
|
+
end
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
The gem includes its helpers into every RSpec example. Outside RSpec (for example Cucumber), add
|
|
27
|
+
`include Capybara::Select2` to your support file.
|
|
28
|
+
|
|
29
|
+
## Usage
|
|
30
|
+
|
|
31
|
+
Locate the field by its label, the `for` of its label, CSS or XPath of the select2 container:
|
|
32
|
+
|
|
33
|
+
```ruby
|
|
34
|
+
select2('Banana', from: 'Fruit')
|
|
35
|
+
select2('Banana', field: 'fruit')
|
|
36
|
+
select2('Banana', css: '#question_12 .select2-container')
|
|
37
|
+
select2('Banana', xpath: container.path)
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Options:
|
|
41
|
+
|
|
42
|
+
| Option | Default | |
|
|
43
|
+
|---|---|---|
|
|
44
|
+
| `mode:` | `:exact_text` | `:insensitive` matches part of the text (or a Regexp), `:case_insensitive` ignores case |
|
|
45
|
+
| `search: true` | | types the value into the search box first, for fields with a minimum input length |
|
|
46
|
+
| `await_option: true` | | polls for the option before reading the list (`max_retries:`, default 5) |
|
|
47
|
+
| `wait:` | | sleeps this many seconds before reading the list |
|
|
48
|
+
|
|
49
|
+
`select2_filter('an', from: 'Fruit', search: true)` returns the listed options without picking one.
|
|
50
|
+
|
|
51
|
+
For a field with [tags](https://select2.github.io/select2/#tags):
|
|
52
|
+
|
|
53
|
+
```ruby
|
|
54
|
+
select2_tag('value', from: 'Label of input')
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Development
|
|
58
|
+
|
|
59
|
+
bundle install
|
|
60
|
+
bundle exec rake # RuboCop and the specs (headless Chrome, select2 4.0.13 and 3.5.4)
|
|
61
|
+
|
|
62
|
+
## Releasing
|
|
63
|
+
|
|
64
|
+
rake "release:bump[minor]" # bump version.rb, add a Changes.md section
|
|
65
|
+
rake release:dry_run # checks, lint, specs, package inspection; pushes nothing
|
|
66
|
+
rake release # the same checks, then tag vX.Y.Z and push to rubygems.org
|
|
67
|
+
|
|
68
|
+
## License
|
|
69
|
+
|
|
70
|
+
MIT, see [LICENSE.txt](LICENSE.txt). Original author: William Yeung.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'lib/capybara-select2/version'
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = 'capybara-select'
|
|
7
|
+
spec.version = Capybara::Select2::VERSION
|
|
8
|
+
spec.authors = ['William Yeung', 'Nikolay Moskvin']
|
|
9
|
+
spec.email = ['william@tofugear.com']
|
|
10
|
+
|
|
11
|
+
spec.summary = 'Capybara helpers to pick options in select2 (3.x and 4.x) fields'
|
|
12
|
+
spec.description = 'select2 and select2_filter for Capybara/RSpec, with retries for dropdowns that close or ' \
|
|
13
|
+
're-render under the click and for results that load slowly. A fork of capybara-select2.'
|
|
14
|
+
spec.homepage = 'https://github.com/moskvin/capybara-select2'
|
|
15
|
+
spec.license = 'MIT'
|
|
16
|
+
|
|
17
|
+
# Only files committed to git, so a local scratch file can never be published.
|
|
18
|
+
spec.files = IO.popen(%w[git ls-files -z], chdir: __dir__, &:read).split("\x0").select do |f|
|
|
19
|
+
f.start_with?('lib/') || %w[Changes.md LICENSE.txt README.md capybara-select2.gemspec].include?(f)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
spec.require_paths = ['lib']
|
|
23
|
+
spec.required_ruby_version = '>= 3.2.0'
|
|
24
|
+
|
|
25
|
+
spec.metadata = {
|
|
26
|
+
'homepage_uri' => 'https://github.com/moskvin/capybara-select2',
|
|
27
|
+
'bug_tracker_uri' => 'https://github.com/moskvin/capybara-select2/issues',
|
|
28
|
+
'changelog_uri' => 'https://github.com/moskvin/capybara-select2/blob/master/Changes.md',
|
|
29
|
+
'source_code_uri' => 'https://github.com/moskvin/capybara-select2',
|
|
30
|
+
'rubygems_mfa_required' => 'true'
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
spec.add_dependency 'capybara', '>= 3.0', '< 4'
|
|
34
|
+
spec.add_dependency 'rspec-core', '>= 3.0', '< 4'
|
|
35
|
+
spec.add_dependency 'rspec-expectations', '>= 3.0', '< 4'
|
|
36
|
+
spec.add_dependency 'selenium-webdriver', '>= 4.0', '< 5'
|
|
37
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Capybara
|
|
4
|
+
module Selectors
|
|
5
|
+
module TagSelector
|
|
6
|
+
def select2_tag(value, options = {})
|
|
7
|
+
if options[:from]
|
|
8
|
+
find(:fillable_field, options[:from]).set(value)
|
|
9
|
+
else
|
|
10
|
+
find('input.select2-input').set(value)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
find('.select2-drop li', text: value).click
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'capybara-select2/version'
|
|
4
|
+
require 'capybara/selectors/tag_selector'
|
|
5
|
+
require 'rspec/core'
|
|
6
|
+
require 'selenium/webdriver'
|
|
7
|
+
|
|
8
|
+
module Capybara
|
|
9
|
+
module Select2
|
|
10
|
+
# Clicks used to open a dropdown. A click can be lost when the field re-renders under it, or the dropdown can
|
|
11
|
+
# close straight away; each attempt waits default_max_wait_time / OPEN_ATTEMPTS for it to open.
|
|
12
|
+
OPEN_ATTEMPTS = 3
|
|
13
|
+
|
|
14
|
+
# Markup per select2 major version. The version is read from the field itself, never from an open dropdown,
|
|
15
|
+
# so a dropdown that failed to open is reported as such instead of as missing 3.x options.
|
|
16
|
+
MARKUP = {
|
|
17
|
+
4 => {
|
|
18
|
+
trigger: '.select2-selection',
|
|
19
|
+
open: '.select2-container--open',
|
|
20
|
+
dropdown: '.select2-container--open .select2-dropdown',
|
|
21
|
+
option: 'li.select2-results__option',
|
|
22
|
+
search: '.select2-container--open input.select2-search__field',
|
|
23
|
+
loading: '.loading-results'
|
|
24
|
+
}.freeze,
|
|
25
|
+
3 => {
|
|
26
|
+
trigger: '.select2-choice, .select2-choices',
|
|
27
|
+
open: '.select2-dropdown-open',
|
|
28
|
+
dropdown: '.select2-drop-active',
|
|
29
|
+
option: 'li.select2-result-selectable',
|
|
30
|
+
search: '.select2-drop-active input.select2-input',
|
|
31
|
+
loading: '.select2-searching'
|
|
32
|
+
}.freeze
|
|
33
|
+
}.freeze
|
|
34
|
+
|
|
35
|
+
# Fill in a select2 filter and return the options.
|
|
36
|
+
# @return [Array] the filtered options
|
|
37
|
+
def select2_filter(value, **args)
|
|
38
|
+
markup = select2_find_and_open(value, **args)
|
|
39
|
+
select2_fetch_options(value, markup:, **args).find_all(select2_option_selector(markup))
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Fill in a select2 field and select the value.
|
|
43
|
+
# @param value [String, Regexp]
|
|
44
|
+
# @param args
|
|
45
|
+
# - xpath, css, from or field [String] - locates the select2 container
|
|
46
|
+
# - mode [Symbol] - :exact_text (default), :insensitive or :case_insensitive
|
|
47
|
+
# - search [Boolean] - type the value into the search box first
|
|
48
|
+
# - wait [Float] - sleep this long before reading the options
|
|
49
|
+
# - await_option [Boolean] - poll for the option before reading the options
|
|
50
|
+
# - max_retries [Integer] - polls for await_option (default 5)
|
|
51
|
+
# @raise [Capybara::ElementNotFound]
|
|
52
|
+
# @raise [Capybara::Ambiguous]
|
|
53
|
+
# @raise [Capybara::ExpectationNotMet] when the dropdown does not open
|
|
54
|
+
def select2(value, **args)
|
|
55
|
+
text = select2_match_text(value, args[:mode])
|
|
56
|
+
exact_text = select2_exact_text?(args[:mode])
|
|
57
|
+
option = exact_text ? { exact_text: text } : { text:, count: 1 }
|
|
58
|
+
markup = select2_open_with_option(value, text, option, **args)
|
|
59
|
+
expect(select2_body).to have_css(select2_option_selector(markup), wait: 0, **option)
|
|
60
|
+
|
|
61
|
+
select2_pick_option(value, text, exact_text, markup)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
private
|
|
65
|
+
|
|
66
|
+
# Private helpers are prefixed with select2_: they are mixed into every example, so a spec helper with the same
|
|
67
|
+
# name would otherwise replace them.
|
|
68
|
+
|
|
69
|
+
def select2_pick_option(value, text, exact_text, markup)
|
|
70
|
+
matches = select2_body.find_all(select2_option_selector(markup)).select do |result|
|
|
71
|
+
exact_text ? result.text == text : result.text.match?(text)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
case matches.size
|
|
75
|
+
when 0
|
|
76
|
+
raise Capybara::ElementNotFound, "Unable to find a matching option for #{value}"
|
|
77
|
+
when 1
|
|
78
|
+
matches.first.click
|
|
79
|
+
else
|
|
80
|
+
raise Capybara::Ambiguous, "Ambiguous match, found #{matches.size} options for #{value}"
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def select2_monotonic_now
|
|
85
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def select2_match_text(value, mode)
|
|
89
|
+
case mode
|
|
90
|
+
when :insensitive
|
|
91
|
+
value.is_a?(Regexp) ? value : /#{Regexp.escape(value)}/
|
|
92
|
+
when :case_insensitive
|
|
93
|
+
/#{Regexp.escape(value)}/i
|
|
94
|
+
else
|
|
95
|
+
value
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def select2_exact_text?(mode)
|
|
100
|
+
mode.nil? || mode == :exact_text
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Opens the dropdown and waits for the option. A dropdown that closes before the option is listed (the field
|
|
104
|
+
# re-rendered, as a lookup updating a form does) is opened again; one that stays open without it is left to
|
|
105
|
+
# select2's expectation to report.
|
|
106
|
+
def select2_open_with_option(value, text, option, **args)
|
|
107
|
+
OPEN_ATTEMPTS.times do
|
|
108
|
+
markup = select2_find_and_open(value, **args)
|
|
109
|
+
select2_fetch_options(text, markup:, **args)
|
|
110
|
+
return markup if select2_option_listed_or_open?(markup, option)
|
|
111
|
+
end
|
|
112
|
+
raise Capybara::ExpectationNotMet, "select2 dropdown closed before listing #{value} #{OPEN_ATTEMPTS} times"
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# true once the option is listed, or when default_max_wait_time passes with the dropdown still open;
|
|
116
|
+
# false as soon as the dropdown closes.
|
|
117
|
+
def select2_option_listed_or_open?(markup, option)
|
|
118
|
+
deadline = select2_monotonic_now + Capybara.default_max_wait_time
|
|
119
|
+
body = select2_body
|
|
120
|
+
loop do
|
|
121
|
+
return true if body.has_css?(select2_option_selector(markup), wait: 0, **option)
|
|
122
|
+
return false unless body.has_selector?(markup[:dropdown], wait: 0)
|
|
123
|
+
return true if select2_monotonic_now >= deadline
|
|
124
|
+
|
|
125
|
+
sleep(0.05)
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def select2_option_selector(markup)
|
|
130
|
+
"#{markup[:dropdown]} #{markup[:option]}"
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def select2_body
|
|
134
|
+
find(:xpath, '//body')
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def select2_fetch_options(text, markup:, **args)
|
|
138
|
+
sleep(args[:wait]) unless args[:wait].nil?
|
|
139
|
+
if args[:await_option]
|
|
140
|
+
select2_await_option(text, markup:, max_retries: args[:max_retries] || 5, mode: args[:mode])
|
|
141
|
+
end
|
|
142
|
+
select2_await_loaded(markup)
|
|
143
|
+
select2_body
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Polls with a growing wait until the option is listed; select2 then fails with Capybara's own message.
|
|
147
|
+
def select2_await_option(text, markup:, mode:, max_retries:)
|
|
148
|
+
options = select2_exact_text?(mode) ? { exact_text: text } : { text:, count: 1 }
|
|
149
|
+
(max_retries + 1).times do |attempt|
|
|
150
|
+
break if select2_body.has_css?(select2_option_selector(markup), wait: attempt / 10.0, **options)
|
|
151
|
+
|
|
152
|
+
sleep(0.3)
|
|
153
|
+
select2_logger.warn("Retry to find a matching option for #{text}, mode: #{mode}, " \
|
|
154
|
+
"attempt: #{attempt + 1}/#{max_retries}")
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# Waits, up to default_max_wait_time, while the dropdown shows "Searching…".
|
|
159
|
+
def select2_await_loaded(markup)
|
|
160
|
+
deadline = select2_monotonic_now + Capybara.default_max_wait_time
|
|
161
|
+
loading = "#{markup[:dropdown]} #{markup[:loading]}"
|
|
162
|
+
sleep(0.05) while select2_body.has_selector?(loading, wait: 0) && select2_monotonic_now < deadline
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def select2_find_container(**args)
|
|
166
|
+
if args[:xpath]
|
|
167
|
+
select2_container_in(find(:xpath, args[:xpath]))
|
|
168
|
+
elsif args[:css]
|
|
169
|
+
select2_container_in(find(:css, args[:css]))
|
|
170
|
+
else
|
|
171
|
+
select2_container_next_to(select2_find_label(**args))
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def select2_find_label(**args)
|
|
176
|
+
if args[:field]
|
|
177
|
+
find(%(label[for="#{args[:field]}"]))
|
|
178
|
+
elsif args[:from]
|
|
179
|
+
# select2 3.x copies the label into its container as an off-screen label
|
|
180
|
+
find('label:not(.select2-offscreen)', text: args[:from])
|
|
181
|
+
else
|
|
182
|
+
raise ArgumentError, 'None of xpath, css, field, nor from given'
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def select2_container_next_to(label)
|
|
187
|
+
label.find(:xpath, '..').find('.select2-container')
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# xpath: and css: may point at the select2 container or at an element wrapping it (such as a form field).
|
|
191
|
+
def select2_container_in(element)
|
|
192
|
+
return element if element.matches_css?('.select2-container', wait: 0)
|
|
193
|
+
|
|
194
|
+
element.find('.select2-container', match: :first)
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def select2_version(container)
|
|
198
|
+
return 4 if container.has_selector?(MARKUP[4][:trigger], wait: 0)
|
|
199
|
+
return 3 if container.has_selector?(MARKUP[3][:trigger], wait: 0)
|
|
200
|
+
|
|
201
|
+
# Not rendered yet: wait once for either version.
|
|
202
|
+
container.has_selector?("#{MARKUP[4][:trigger]}, #{MARKUP[3][:trigger]}")
|
|
203
|
+
container.has_selector?(MARKUP[4][:trigger], wait: 0) ? 4 : 3
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
# Clicks the field until its dropdown opens. A field re-rendered under the click is found again.
|
|
207
|
+
def select2_open(container, markup, **args)
|
|
208
|
+
wait = Capybara.default_max_wait_time.to_f / OPEN_ATTEMPTS
|
|
209
|
+
OPEN_ATTEMPTS.times do
|
|
210
|
+
return if container.matches_css?(markup[:open], wait: 0)
|
|
211
|
+
|
|
212
|
+
container.find(markup[:trigger], match: :first).click
|
|
213
|
+
return if container.matches_css?(markup[:open], wait:)
|
|
214
|
+
rescue Selenium::WebDriver::Error::StaleElementReferenceError
|
|
215
|
+
container = select2_find_container(**args)
|
|
216
|
+
end
|
|
217
|
+
raise Capybara::ExpectationNotMet,
|
|
218
|
+
"select2 dropdown did not open after #{OPEN_ATTEMPTS} clicks on #{markup[:trigger]}"
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
# Opens the field (typing the value into the search box with `search: true`) and returns its MARKUP.
|
|
222
|
+
def select2_find_and_open(value, **args)
|
|
223
|
+
container = select2_find_container(**args)
|
|
224
|
+
markup = MARKUP.fetch(select2_version(container))
|
|
225
|
+
select2_open(container, markup, **args)
|
|
226
|
+
|
|
227
|
+
if args[:search]
|
|
228
|
+
select2_body.find(markup[:search]).send_keys(value)
|
|
229
|
+
select2_await_loaded(markup)
|
|
230
|
+
end
|
|
231
|
+
markup
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
def select2_logger
|
|
235
|
+
::Selenium::WebDriver.logger
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
RSpec.configure do |config|
|
|
241
|
+
config.include Capybara::Select2
|
|
242
|
+
config.include Capybara::Selectors::TagSelector
|
|
243
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: capybara-select
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- William Yeung
|
|
8
|
+
- Nikolay Moskvin
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: capybara
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '3.0'
|
|
20
|
+
- - "<"
|
|
21
|
+
- !ruby/object:Gem::Version
|
|
22
|
+
version: '4'
|
|
23
|
+
type: :runtime
|
|
24
|
+
prerelease: false
|
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
26
|
+
requirements:
|
|
27
|
+
- - ">="
|
|
28
|
+
- !ruby/object:Gem::Version
|
|
29
|
+
version: '3.0'
|
|
30
|
+
- - "<"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '4'
|
|
33
|
+
- !ruby/object:Gem::Dependency
|
|
34
|
+
name: rspec-core
|
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '3.0'
|
|
40
|
+
- - "<"
|
|
41
|
+
- !ruby/object:Gem::Version
|
|
42
|
+
version: '4'
|
|
43
|
+
type: :runtime
|
|
44
|
+
prerelease: false
|
|
45
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
46
|
+
requirements:
|
|
47
|
+
- - ">="
|
|
48
|
+
- !ruby/object:Gem::Version
|
|
49
|
+
version: '3.0'
|
|
50
|
+
- - "<"
|
|
51
|
+
- !ruby/object:Gem::Version
|
|
52
|
+
version: '4'
|
|
53
|
+
- !ruby/object:Gem::Dependency
|
|
54
|
+
name: rspec-expectations
|
|
55
|
+
requirement: !ruby/object:Gem::Requirement
|
|
56
|
+
requirements:
|
|
57
|
+
- - ">="
|
|
58
|
+
- !ruby/object:Gem::Version
|
|
59
|
+
version: '3.0'
|
|
60
|
+
- - "<"
|
|
61
|
+
- !ruby/object:Gem::Version
|
|
62
|
+
version: '4'
|
|
63
|
+
type: :runtime
|
|
64
|
+
prerelease: false
|
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
66
|
+
requirements:
|
|
67
|
+
- - ">="
|
|
68
|
+
- !ruby/object:Gem::Version
|
|
69
|
+
version: '3.0'
|
|
70
|
+
- - "<"
|
|
71
|
+
- !ruby/object:Gem::Version
|
|
72
|
+
version: '4'
|
|
73
|
+
- !ruby/object:Gem::Dependency
|
|
74
|
+
name: selenium-webdriver
|
|
75
|
+
requirement: !ruby/object:Gem::Requirement
|
|
76
|
+
requirements:
|
|
77
|
+
- - ">="
|
|
78
|
+
- !ruby/object:Gem::Version
|
|
79
|
+
version: '4.0'
|
|
80
|
+
- - "<"
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: '5'
|
|
83
|
+
type: :runtime
|
|
84
|
+
prerelease: false
|
|
85
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
86
|
+
requirements:
|
|
87
|
+
- - ">="
|
|
88
|
+
- !ruby/object:Gem::Version
|
|
89
|
+
version: '4.0'
|
|
90
|
+
- - "<"
|
|
91
|
+
- !ruby/object:Gem::Version
|
|
92
|
+
version: '5'
|
|
93
|
+
description: select2 and select2_filter for Capybara/RSpec, with retries for dropdowns
|
|
94
|
+
that close or re-render under the click and for results that load slowly. A fork
|
|
95
|
+
of capybara-select2.
|
|
96
|
+
email:
|
|
97
|
+
- william@tofugear.com
|
|
98
|
+
executables: []
|
|
99
|
+
extensions: []
|
|
100
|
+
extra_rdoc_files: []
|
|
101
|
+
files:
|
|
102
|
+
- Changes.md
|
|
103
|
+
- LICENSE.txt
|
|
104
|
+
- README.md
|
|
105
|
+
- capybara-select2.gemspec
|
|
106
|
+
- lib/capybara-select2.rb
|
|
107
|
+
- lib/capybara-select2/version.rb
|
|
108
|
+
- lib/capybara/selectors/tag_selector.rb
|
|
109
|
+
homepage: https://github.com/moskvin/capybara-select2
|
|
110
|
+
licenses:
|
|
111
|
+
- MIT
|
|
112
|
+
metadata:
|
|
113
|
+
homepage_uri: https://github.com/moskvin/capybara-select2
|
|
114
|
+
bug_tracker_uri: https://github.com/moskvin/capybara-select2/issues
|
|
115
|
+
changelog_uri: https://github.com/moskvin/capybara-select2/blob/master/Changes.md
|
|
116
|
+
source_code_uri: https://github.com/moskvin/capybara-select2
|
|
117
|
+
rubygems_mfa_required: 'true'
|
|
118
|
+
rdoc_options: []
|
|
119
|
+
require_paths:
|
|
120
|
+
- lib
|
|
121
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
122
|
+
requirements:
|
|
123
|
+
- - ">="
|
|
124
|
+
- !ruby/object:Gem::Version
|
|
125
|
+
version: 3.2.0
|
|
126
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
127
|
+
requirements:
|
|
128
|
+
- - ">="
|
|
129
|
+
- !ruby/object:Gem::Version
|
|
130
|
+
version: '0'
|
|
131
|
+
requirements: []
|
|
132
|
+
rubygems_version: 4.0.20
|
|
133
|
+
specification_version: 4
|
|
134
|
+
summary: Capybara helpers to pick options in select2 (3.x and 4.x) fields
|
|
135
|
+
test_files: []
|