Skip to content

Commit e4d6d8d

Browse files
authored
Merge pull request #191 from rubycdp/issue_58/pick_option_in_select
Feature: Node#select / Node#selected
2 parents 8a5a143 + cdc0d7d commit e4d6d8d

File tree

3 files changed

+126
-0
lines changed

3 files changed

+126
-0
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1048,6 +1048,8 @@ browser.playback_rate # => 2000
10481048
#### property
10491049
#### attribute
10501050
#### evaluate
1051+
#### selected : `Array`
1052+
#### select
10511053

10521054

10531055
## Thread safety ##

lib/ferrum/node.rb

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,42 @@ def attribute(name)
127127
evaluate("this.getAttribute('#{name}')")
128128
end
129129

130+
def selected
131+
function = <<~JS
132+
function(element) {
133+
if (element.nodeName.toLowerCase() !== 'select') {
134+
throw new Error('Element is not a <select> element.');
135+
}
136+
return Array.from(element).filter(option => option.selected).map((option) => option.text);
137+
}
138+
JS
139+
page.evaluate_func(function, self)
140+
end
141+
142+
def select(*values)
143+
tap do
144+
function = <<~JS
145+
function(element, values) {
146+
if (element.nodeName.toLowerCase() !== 'select') {
147+
throw new Error('Element is not a <select> element.');
148+
}
149+
const options = Array.from(element.options);
150+
element.value = undefined;
151+
for (const option of options) {
152+
option.selected = values.includes(option.value);
153+
if (option.selected && !element.multiple) break;
154+
}
155+
element.dispatchEvent(new Event('input', { bubbles: true }));
156+
element.dispatchEvent(new Event('change', { bubbles: true }));
157+
return options
158+
.filter((option) => option.selected)
159+
.map((option) => option.value);
160+
}
161+
JS
162+
page.evaluate_func(function, self, values.join(','))
163+
end
164+
end
165+
130166
def evaluate(expression)
131167
page.evaluate_on(node: self, expression: expression)
132168
end

spec/node_spec.rb

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,94 @@ module Ferrum
8383
expect(links.first.text).to eq("Open for match")
8484
end
8585

86+
describe "#selected" do
87+
before do
88+
browser.goto("/ferrum/form")
89+
end
90+
91+
it "returns texts of selected options" do
92+
expect(browser.at_xpath("//*[@id='form_region']").selected).to eq(["Norway"])
93+
end
94+
95+
context "when options exists but no selected option" do
96+
it "returns first option text as default value" do
97+
expect(browser.at_xpath("//*[@id='form_title']").selected).to eq(["Mrs"])
98+
end
99+
end
100+
101+
context "when no selected options" do
102+
it "returns empty array" do
103+
expect(browser.at_xpath("//*[@id='form_tendency']").selected).to eq([])
104+
end
105+
end
106+
107+
context "when selector is not <select>" do
108+
it "raises JavaScriptError with proper message" do
109+
expect { browser.at_xpath("//*[@id='customer_name']").selected }.
110+
to raise_exception(Ferrum::JavaScriptError, /Element is not a <select> element/)
111+
end
112+
end
113+
end
114+
115+
describe "#select" do
116+
before do
117+
browser.goto("/ferrum/form")
118+
end
119+
120+
it "picks option in select by match string argument" do
121+
expect(browser.at_xpath("//*[@id='form_title']").select("Miss").selected).to eq(["Miss"])
122+
end
123+
124+
shared_examples "clears selected options with no exception" do |options|
125+
it "clears selected options with no exception" do
126+
expect(browser.at_xpath("//*[@id='form_title']").selected).to eq(["Mrs"])
127+
expect(browser.at_xpath("//*[@id='form_title']").select(options).selected).to eq([])
128+
end
129+
end
130+
131+
context "when option with provided text does not exist" do
132+
include_examples "clears selected options with no exception", "Gotcha"
133+
end
134+
135+
context "when provided empty array" do
136+
include_examples "clears selected options with no exception", []
137+
end
138+
139+
context "when provided empty string" do
140+
include_examples "clears selected options with no exception", ""
141+
end
142+
143+
context "when one of option with provided texts does not exist" do
144+
it "picks only existed options with no exception" do
145+
expect(browser.at_xpath("//*[@id='form_title']").selected).to eq(["Mrs"])
146+
expect(browser.at_xpath("//*[@id='form_title']").select(%w[Mrs SQL]).selected).to eq(["Mrs"])
147+
end
148+
end
149+
150+
context "when select has multiple property" do
151+
it "picks options in select by match arguments as array" do
152+
expect(browser.at_xpath("//*[@id='form_languages']").select(%w[SQL Ruby]).selected).to eq(%w[Ruby SQL])
153+
end
154+
155+
it "picks options in select by match arguments as string" do
156+
expect(browser.at_xpath("//*[@id='form_languages']").select("SQL, Ruby").selected).to eq(%w[Ruby SQL])
157+
end
158+
end
159+
160+
context "when selector is not <select>" do
161+
it "raises JavaScriptError with proper message" do
162+
expect { browser.at_xpath("//*[@id='customer_name']").select(anything) }.
163+
to raise_exception(Ferrum::JavaScriptError, /Element is not a <select> element/)
164+
end
165+
end
166+
167+
context "when provided texts of disabled option" do
168+
it "picks disabled option with no exception" do
169+
expect(browser.at_xpath("//*[@id='form_title']").select(["Other"]).selected).to eq(["Other"])
170+
end
171+
end
172+
end
173+
86174
context "when the element is not in the viewport" do
87175
before do
88176
browser.go_to("/ferrum/with_js")

0 commit comments

Comments
 (0)