Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 31 additions & 14 deletions lib/capybara/cuprite/browser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,19 @@ class Browser < Ferrum::Browser
extend Forwardable

DRAG_HTML5_JS = File.read(File.expand_path("javascripts/drag.js", __dir__))
CALLBACK_JS = <<~JS
function(...args) {
return new Promise((resolve, reject) => {
try {
(function() {
SCRIPT
}).apply(this, [...args, resolve]);
} catch (error) {
reject(error);
}
});
}
JS

delegate %i[send_keys select set hover trigger before_click switch_to_frame
find_modal accept_confirm dismiss_confirm accept_prompt
Expand Down Expand Up @@ -138,6 +151,10 @@ def close_window(target_id)
targets.delete(target_id) # page.close is async, delete target asap
end

def evaluate_async(script, wait, *args)
evaluate(CALLBACK_JS.sub("SCRIPT") { script }, *args, timeout: wait)
end

def active_element
evaluate("document.activeElement")
end
Expand Down Expand Up @@ -166,7 +183,7 @@ def drag(node, other, drop_modifiers, options = {})
mouse.down
sleep delay if delay

html5 = !evaluate_on(node: node, expression: "_cuprite.legacyDragCheck(this)") if html5.nil?
html5 = !node.evaluate("_cuprite.legacyDragCheck(this)") if html5.nil?

if html5
drag_html5(node, other, drop_modifiers, delay, drop_offset)
Expand All @@ -192,7 +209,7 @@ def drag_by(node, dx, dy, steps, delay = nil, scroll = true)

sleep delay if delay

evaluate("window.scrollBy(#{dx}, #{dy})") if scroll # should be extracted to Mouse#scroll_by in ferrum
mouse.scroll_by(dx, dy) if scroll

x2, y2 = node.find_position
mouse.move(x: x2 + dx, y: y2 + dy, steps: steps)
Expand All @@ -204,10 +221,10 @@ def drop(node, *args)
execute("_cuprite.attachDropInput()")
input = find(:css, "#_cuprite_drop_file").first
select_file(input, args)
evaluate_on(node: node, expression: "_cuprite.dropFile(this)")
node.evaluate("_cuprite.dropFile(this)")
else
strings = args.flat_map { |arg| arg.map { |type, data| { "type" => type, "data" => data } } }
evaluate_on(node: node, expression: "_cuprite.dropString(#{strings.to_json}, this)")
node.evaluate("_cuprite.dropString(#{strings.to_json}, this)")
end
end

Expand All @@ -216,44 +233,44 @@ def select_file(node, value)
end

def parents(node)
evaluate_on(node: node, expression: "_cuprite.parents(this)", by_value: false)
node.evaluate("function() { return _cuprite.parents(this) }")
end

def visible_text(node)
evaluate_on(node: node, expression: "_cuprite.visibleText(this)")
node.evaluate("_cuprite.visibleText(this)")
end

def delete_text(node)
evaluate_on(node: node, expression: "_cuprite.deleteText(this)")
node.evaluate("_cuprite.deleteText(this)")
end

def attributes(node)
value = evaluate_on(node: node, expression: "_cuprite.getAttributes(this)")
value = node.evaluate("_cuprite.getAttributes(this)")
JSON.parse(value)
end

def attribute(node, name)
evaluate_on(node: node, expression: %(_cuprite.getAttribute(this, "#{name}")))
node.evaluate(%(_cuprite.getAttribute(this, "#{name}")))
end

def value(node)
evaluate_on(node: node, expression: "_cuprite.value(this)")
node.evaluate("_cuprite.value(this)")
end

def visible?(node)
evaluate_on(node: node, expression: "_cuprite.isVisible(this)")
node.evaluate("_cuprite.isVisible(this)")
end

def disabled?(node)
evaluate_on(node: node, expression: "_cuprite.isDisabled(this)")
node.evaluate("_cuprite.isDisabled(this)")
end

def path(node)
evaluate_on(node: node, expression: "_cuprite.path(this)")
node.evaluate("_cuprite.path(this)")
end

def obscured?(node)
evaluate_on(node: node, expression: "_cuprite.isObscured(this)")
node.evaluate("_cuprite.isObscured(this)")
end

def all_text(node)
Expand Down
8 changes: 4 additions & 4 deletions lib/capybara/cuprite/node.rb
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,10 @@ def [](name)
return command(:attribute, name) && command(:property, name)
end

value = property(name)
value = command(:attribute, name) if value.nil? || value.is_a?(Hash)

value
case (value = property(name))
when String, Numeric, true, false then value
else command(:attribute, name)
end
end

def attributes
Expand Down
22 changes: 11 additions & 11 deletions lib/capybara/cuprite/page.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class Page < Ferrum::Page

delegate %i[at_css at_xpath css xpath
current_url current_title body execution_id execution_id!
evaluate evaluate_on evaluate_async execute] => :active_frame
evaluate execute] => :active_frame

def initialize(...)
@frame_stack = []
Expand Down Expand Up @@ -50,28 +50,28 @@ def set_viewport(width: 0, height: 0, scale_factor: @options.scale_factor, mobil
end

def set(node, value)
object_id = command("DOM.resolveNode", nodeId: node.node_id).dig("object", "objectId")
evaluate("_cuprite.set(arguments[0], arguments[1])", { "objectId" => object_id }, value)
node.evaluate("_cuprite.set(this, arguments[0])", value)
end

def select(node, value)
evaluate_on(node: node, expression: "_cuprite.select(this, #{value})")
node.evaluate("_cuprite.select(this, #{value})")
end

def trigger(node, event)
options = {}
options.merge!(wait: TRIGGER_CLICK_WAIT) if event.to_s == "click" && TRIGGER_CLICK_WAIT.positive?
evaluate_on(node: node, expression: %(_cuprite.trigger(this, "#{event}")), **options)
script = %(_cuprite.trigger(this, "#{event}"))
return node.evaluate(script) unless event.to_s == "click" && TRIGGER_CLICK_WAIT.positive?

wait_for_navigation!(TRIGGER_CLICK_WAIT) { node.evaluate(script) }
end

def hover(node)
evaluate_on(node: node, expression: "_cuprite.scrollIntoViewport(this)")
node.evaluate("_cuprite.scrollIntoViewport(this)")
x, y = find_position(node)
command("Input.dispatchMouseEvent", type: "mouseMoved", x: x, y: y)
end

def send_keys(node, keys)
unless evaluate_on(node: node, expression: %(_cuprite.containsSelection(this) || _cuprite.hasFocus(this)))
unless node.evaluate(%(_cuprite.containsSelection(this) || _cuprite.hasFocus(this)))
before_click(node, "click")
node.click(mode: :left, keys: keys)
end
Expand Down Expand Up @@ -125,14 +125,14 @@ def reset_modals
end

def before_click(node, name, _keys = [], offset = {})
evaluate_on(node: node, expression: "_cuprite.scrollIntoViewport(this)")
node.evaluate("_cuprite.scrollIntoViewport(this)")

# If offset is given it may go outside of the element and likely error
# will be raised that we detected another element at this position.
return true if offset[:x] || offset[:y]

x, y = find_position(node, **offset)
evaluate_on(node: node, expression: "_cuprite.mouseEventTest(this, '#{name}', #{x}, #{y})")
node.evaluate("_cuprite.mouseEventTest(this, '#{name}', #{x}, #{y})")
true
rescue Ferrum::JavaScriptError => e
raise MouseEventFailed, e.message if e.class_name == "MouseEventFailed"
Expand Down
21 changes: 20 additions & 1 deletion spec/features/session_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,20 @@
input.trigger("custom:ping")
expect(output.text).to eq("custom:ping")
end

it "waits for the page a triggered click opens" do
page = @session.driver.browser.page
navigated = loaded = false
page.on("Page.frameNavigated") { |params| navigated = params.dig("frame", "url").end_with?("/cuprite/slow") }
page.on("Page.loadEventFired") { loaded = navigated }
@session.execute_script(<<~JS)
document.body.insertAdjacentHTML("beforeend", '<a id="slow" href="/cuprite/slow">slow</a>')
JS

@session.find(:css, "#slow").trigger(:click)

expect(loaded).to be(true)
end
end

describe "Node#set" do
Expand Down Expand Up @@ -518,6 +532,10 @@
expect(@session.find_field("checked")["checked"]).to be true
expect(@session.find_field("unchecked")["checked"]).to be false
end

it "gets the attribute for a property holding a node" do
expect(@session.find_field("outside_form")["form"]).to eq "outer_form"
end
end

describe "Node#==" do
Expand Down Expand Up @@ -631,7 +649,8 @@
})()
JS

expect(@session.evaluate_script(code)).to eq(Ferrum::CyclicObject.instance)
cyclic = Ferrum::CyclicObject.instance
expect(@session.evaluate_script(code)).to eq("a" => cyclic, "b" => {}, "c" => { "a" => cyclic })
end

it "synchronises page loads properly" do
Expand Down
2 changes: 2 additions & 0 deletions spec/support/views/attributes_properties.erb
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,7 @@
<a href="/cuprite/attributes_properties" onclick="return false;" data-random="42">Loop</a>
<input type="checkbox" id="checked" checked="checked"/>
<input type="checkbox" id="unchecked"/>
<form id="outer_form"></form>
<input type="text" id="outside_form" form="outer_form"/>
</body>
</html>
Loading