From 83ca882255aca06d75ef5884d854903cd10be16d Mon Sep 17 00:00:00 2001 From: Aleksey Strizhak Date: Fri, 7 Jan 2022 05:36:53 +0500 Subject: [PATCH] implement #wait_for_selector method for browser --- lib/ferrum/browser.rb | 2 +- lib/ferrum/frame/dom.rb | 30 ++++++++++++++++++ lib/ferrum/page.rb | 2 +- spec/browser_spec.rb | 58 ++++++++++++++++++++++++++++++++++ spec/support/views/with_js.erb | 17 ++++++++++ 5 files changed, 107 insertions(+), 2 deletions(-) diff --git a/lib/ferrum/browser.rb b/lib/ferrum/browser.rb index 8926195f..751366cf 100644 --- a/lib/ferrum/browser.rb +++ b/lib/ferrum/browser.rb @@ -28,7 +28,7 @@ class Browser evaluate evaluate_on evaluate_async execute evaluate_func add_script_tag add_style_tag bypass_csp on goto position position= - playback_rate playback_rate=] => :page + playback_rate playback_rate= wait_for_selector] => :page delegate %i[default_user_agent] => :process attr_reader :client, :process, :contexts, :logger, :js_errors, :pending_connection_errors, diff --git a/lib/ferrum/frame/dom.rb b/lib/ferrum/frame/dom.rb index b60c6b0a..95835fa7 100644 --- a/lib/ferrum/frame/dom.rb +++ b/lib/ferrum/frame/dom.rb @@ -36,6 +36,36 @@ def body evaluate("document.documentElement.outerHTML") end + def wait_for_selector(css: nil, xpath: nil, timeout: 1000, interval: 100) + tap do + evaluate_func(%( + function(selector, isXpath, timeout, interval) { + var attempts = 0; + var max = timeout / interval; + function waitForSelector(resolve, reject) { + if (attempts > ((max < 1) ? 1 : max)) { + return reject(new Error("Not found element match the selector:" + selector)); + } + var element = isXpath + ? document. + evaluate(selector, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue + : document.querySelector(selector); + if (element !== null) { + return resolve(element); + } + setTimeout(function () { + waitForSelector(resolve, reject); + }, interval); + attempts++; + } + return new Promise(function (resolve, reject) { + waitForSelector(resolve, reject); + }); + } + ), css || xpath, css.nil? && !xpath.nil?, timeout, interval, awaitPromise: true) + end + end + def xpath(selector, within: nil) expr = <<~JS function(selector, within) { diff --git a/lib/ferrum/page.rb b/lib/ferrum/page.rb index 5a5c25f7..52db751d 100644 --- a/lib/ferrum/page.rb +++ b/lib/ferrum/page.rb @@ -34,7 +34,7 @@ def reset delegate %i[at_css at_xpath css xpath current_url current_title url title body doctype content= execution_id evaluate evaluate_on evaluate_async execute evaluate_func - add_script_tag add_style_tag] => :main_frame + add_script_tag add_style_tag wait_for_selector] => :main_frame include Animation include Screenshot diff --git a/spec/browser_spec.rb b/spec/browser_spec.rb index 7bc6672e..b7193182 100644 --- a/spec/browser_spec.rb +++ b/spec/browser_spec.rb @@ -504,6 +504,64 @@ module Ferrum expect(browser.evaluate("window.last_hashchange")).to eq("#foo") end + context "wait_for_selector" do + before do + browser.go_to("/ferrum/with_js") + end + + it "waits for provided css selector" do + expect( + browser.wait_for_selector(css: "div#wait_for_selector").at_css("div#wait_for_selector") + ).not_to be_nil + end + + it "waits for provided css hidden selector" do + expect( + browser.wait_for_selector(css: "div#wait_for_hidden_selector").at_css("div#wait_for_hidden_selector") + ).not_to be_nil + end + + it "waits for provided xpath selector" do + expect( + browser.wait_for_selector(xpath: "//div[@id='wait_for_selector']").at_css("div#wait_for_selector") + ).not_to be_nil + end + + it "waits for provided xpath hidden selector" do + expect( + browser + .wait_for_selector(xpath: "//div[@id='wait_for_hidden_selector']") + .at_css("div#wait_for_hidden_selector") + ).not_to be_nil + end + + it "raises error when timeout exceed" do + expect do + browser.wait_for_selector(css: "div#wait_for_selector", timeout: 800) + end.to raise_error(Ferrum::JavaScriptError, /Not found element match the selector/) + end + + it "raises error when provided invalid css" do + expect do + browser.wait_for_selector(css: "//div[@id='wait_for_selector']") + end.to raise_error(Ferrum::JavaScriptError, /Failed to execute 'querySelector' on 'Document'/) + end + + it "raises error when provided invalid xpath" do + expect do + browser.wait_for_selector(xpath: "div#wait_for_selector") + end.to raise_error(Ferrum::JavaScriptError, /Failed to execute 'evaluate' on 'Document'/) + end + + it "waits less than provided timeout when node found" do + Timeout.timeout(1) do + expect( + browser.wait_for_selector(css: "div#wait_for_selector", timeout: 2000).at_css("div#wait_for_selector") + ).not_to be_nil + end + end + end + context "current_url" do it "supports whitespace characters" do browser.go_to("/ferrum/arbitrary_path/200/foo%20bar%20baz") diff --git a/spec/support/views/with_js.erb b/spec/support/views/with_js.erb index 2ddefe35..50375efe 100644 --- a/spec/support/views/with_js.erb +++ b/spec/support/views/with_js.erb @@ -24,6 +24,23 @@ display: inline; } +