ferrum 0.12 → 0.13
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 +4 -4
- data/README.md +15 -20
- data/lib/ferrum/browser/client.rb +4 -4
- data/lib/ferrum/browser/command.rb +5 -6
- data/lib/ferrum/browser/options/base.rb +1 -4
- data/lib/ferrum/browser/options/chrome.rb +14 -9
- data/lib/ferrum/browser/options/firefox.rb +3 -6
- data/lib/ferrum/browser/options.rb +84 -0
- data/lib/ferrum/browser/process.rb +5 -6
- data/lib/ferrum/browser/version_info.rb +71 -0
- data/lib/ferrum/browser/xvfb.rb +1 -1
- data/lib/ferrum/browser.rb +176 -62
- data/lib/ferrum/context.rb +3 -2
- data/lib/ferrum/contexts.rb +2 -2
- data/lib/ferrum/cookies/cookie.rb +126 -0
- data/lib/ferrum/cookies.rb +86 -49
- data/lib/ferrum/dialog.rb +30 -0
- data/lib/ferrum/frame/dom.rb +177 -0
- data/lib/ferrum/frame/runtime.rb +41 -61
- data/lib/ferrum/frame.rb +90 -3
- data/lib/ferrum/headers.rb +28 -0
- data/lib/ferrum/keyboard.rb +45 -2
- data/lib/ferrum/mouse.rb +84 -0
- data/lib/ferrum/network/exchange.rb +86 -5
- data/lib/ferrum/network/request.rb +64 -0
- data/lib/ferrum/network/response.rb +83 -1
- data/lib/ferrum/network.rb +160 -0
- data/lib/ferrum/page/animation.rb +16 -0
- data/lib/ferrum/page/frames.rb +66 -11
- data/lib/ferrum/page/screenshot.rb +91 -0
- data/lib/ferrum/page/tracing.rb +26 -0
- data/lib/ferrum/page.rb +151 -32
- data/lib/ferrum/proxy.rb +91 -2
- data/lib/ferrum/target.rb +6 -4
- data/lib/ferrum/version.rb +1 -1
- metadata +5 -2
    
        data/lib/ferrum/frame/dom.rb
    CHANGED
    
    | @@ -20,10 +20,59 @@ | |
| 20 20 | 
             
            module Ferrum
         | 
| 21 21 | 
             
              class Frame
         | 
| 22 22 | 
             
                module DOM
         | 
| 23 | 
            +
                  SCRIPT_SRC_TAG = <<~JS
         | 
| 24 | 
            +
                    const script = document.createElement("script");
         | 
| 25 | 
            +
                    script.src = arguments[0];
         | 
| 26 | 
            +
                    script.type = arguments[1];
         | 
| 27 | 
            +
                    script.onload = arguments[2];
         | 
| 28 | 
            +
                    document.head.appendChild(script);
         | 
| 29 | 
            +
                  JS
         | 
| 30 | 
            +
                  SCRIPT_TEXT_TAG = <<~JS
         | 
| 31 | 
            +
                    const script = document.createElement("script");
         | 
| 32 | 
            +
                    script.text = arguments[0];
         | 
| 33 | 
            +
                    script.type = arguments[1];
         | 
| 34 | 
            +
                    document.head.appendChild(script);
         | 
| 35 | 
            +
                    arguments[2]();
         | 
| 36 | 
            +
                  JS
         | 
| 37 | 
            +
                  STYLE_TAG = <<~JS
         | 
| 38 | 
            +
                    const style = document.createElement("style");
         | 
| 39 | 
            +
                    style.type = "text/css";
         | 
| 40 | 
            +
                    style.appendChild(document.createTextNode(arguments[0]));
         | 
| 41 | 
            +
                    document.head.appendChild(style);
         | 
| 42 | 
            +
                    arguments[1]();
         | 
| 43 | 
            +
                  JS
         | 
| 44 | 
            +
                  LINK_TAG = <<~JS
         | 
| 45 | 
            +
                    const link = document.createElement("link");
         | 
| 46 | 
            +
                    link.rel = "stylesheet";
         | 
| 47 | 
            +
                    link.href = arguments[0];
         | 
| 48 | 
            +
                    link.onload = arguments[1];
         | 
| 49 | 
            +
                    document.head.appendChild(link);
         | 
| 50 | 
            +
                  JS
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                  #
         | 
| 53 | 
            +
                  # Returns current top window `location href`.
         | 
| 54 | 
            +
                  #
         | 
| 55 | 
            +
                  # @return [String]
         | 
| 56 | 
            +
                  #   The window's current URL.
         | 
| 57 | 
            +
                  #
         | 
| 58 | 
            +
                  # @example
         | 
| 59 | 
            +
                  #   browser.go_to("https://google.com/")
         | 
| 60 | 
            +
                  #   browser.current_url # => "https://www.google.com/"
         | 
| 61 | 
            +
                  #
         | 
| 23 62 | 
             
                  def current_url
         | 
| 24 63 | 
             
                    evaluate("window.top.location.href")
         | 
| 25 64 | 
             
                  end
         | 
| 26 65 |  | 
| 66 | 
            +
                  #
         | 
| 67 | 
            +
                  # Returns current top window title.
         | 
| 68 | 
            +
                  #
         | 
| 69 | 
            +
                  # @return [String]
         | 
| 70 | 
            +
                  #   The window's current title.
         | 
| 71 | 
            +
                  #
         | 
| 72 | 
            +
                  # @example
         | 
| 73 | 
            +
                  #   browser.go_to("https://google.com/")
         | 
| 74 | 
            +
                  #   browser.current_title # => "Google"
         | 
| 75 | 
            +
                  #
         | 
| 27 76 | 
             
                  def current_title
         | 
| 28 77 | 
             
                    evaluate("window.top.document.title")
         | 
| 29 78 | 
             
                  end
         | 
| @@ -32,10 +81,36 @@ module Ferrum | |
| 32 81 | 
             
                    evaluate("document.doctype && new XMLSerializer().serializeToString(document.doctype)")
         | 
| 33 82 | 
             
                  end
         | 
| 34 83 |  | 
| 84 | 
            +
                  #
         | 
| 85 | 
            +
                  # Returns current page's html.
         | 
| 86 | 
            +
                  #
         | 
| 87 | 
            +
                  # @return [String]
         | 
| 88 | 
            +
                  #   The HTML source of the current page.
         | 
| 89 | 
            +
                  #
         | 
| 90 | 
            +
                  # @example
         | 
| 91 | 
            +
                  #   browser.go_to("https://google.com/")
         | 
| 92 | 
            +
                  #   browser.body # => '<html itemscope="" itemtype="http://schema.org/WebPage" lang="ru"><head>...
         | 
| 93 | 
            +
                  #
         | 
| 35 94 | 
             
                  def body
         | 
| 36 95 | 
             
                    evaluate("document.documentElement.outerHTML")
         | 
| 37 96 | 
             
                  end
         | 
| 38 97 |  | 
| 98 | 
            +
                  #
         | 
| 99 | 
            +
                  # Finds nodes by using a XPath selector.
         | 
| 100 | 
            +
                  #
         | 
| 101 | 
            +
                  # @param [String] selector
         | 
| 102 | 
            +
                  #   The XPath selector.
         | 
| 103 | 
            +
                  #
         | 
| 104 | 
            +
                  # @param [Node, nil] within
         | 
| 105 | 
            +
                  #   The parent node to search within.
         | 
| 106 | 
            +
                  #
         | 
| 107 | 
            +
                  # @return [Array<Node>]
         | 
| 108 | 
            +
                  #   The matching nodes.
         | 
| 109 | 
            +
                  #
         | 
| 110 | 
            +
                  # @example
         | 
| 111 | 
            +
                  #   browser.go_to("https://github.com/")
         | 
| 112 | 
            +
                  #   browser.xpath("//a[@aria-label='Issues you created']") # => [Node]
         | 
| 113 | 
            +
                  #
         | 
| 39 114 | 
             
                  def xpath(selector, within: nil)
         | 
| 40 115 | 
             
                    expr = <<~JS
         | 
| 41 116 | 
             
                      function(selector, within) {
         | 
| @@ -54,6 +129,22 @@ module Ferrum | |
| 54 129 | 
             
                    evaluate_func(expr, selector, within)
         | 
| 55 130 | 
             
                  end
         | 
| 56 131 |  | 
| 132 | 
            +
                  #
         | 
| 133 | 
            +
                  # Finds a node by using a XPath selector.
         | 
| 134 | 
            +
                  #
         | 
| 135 | 
            +
                  # @param [String] selector
         | 
| 136 | 
            +
                  #   The XPath selector.
         | 
| 137 | 
            +
                  #
         | 
| 138 | 
            +
                  # @param [Node, nil] within
         | 
| 139 | 
            +
                  #   The parent node to search within.
         | 
| 140 | 
            +
                  #
         | 
| 141 | 
            +
                  # @return [Node, nil]
         | 
| 142 | 
            +
                  #   The matching node.
         | 
| 143 | 
            +
                  #
         | 
| 144 | 
            +
                  # @example
         | 
| 145 | 
            +
                  #   browser.go_to("https://github.com/")
         | 
| 146 | 
            +
                  #   browser.at_xpath("//a[@aria-label='Issues you created']") # => Node
         | 
| 147 | 
            +
                  #
         | 
| 57 148 | 
             
                  def at_xpath(selector, within: nil)
         | 
| 58 149 | 
             
                    expr = <<~JS
         | 
| 59 150 | 
             
                      function(selector, within) {
         | 
| @@ -65,6 +156,22 @@ module Ferrum | |
| 65 156 | 
             
                    evaluate_func(expr, selector, within)
         | 
| 66 157 | 
             
                  end
         | 
| 67 158 |  | 
| 159 | 
            +
                  #
         | 
| 160 | 
            +
                  # Finds nodes by using a CSS path selector.
         | 
| 161 | 
            +
                  #
         | 
| 162 | 
            +
                  # @param [String] selector
         | 
| 163 | 
            +
                  #   The CSS path selector.
         | 
| 164 | 
            +
                  #
         | 
| 165 | 
            +
                  # @param [Node, nil] within
         | 
| 166 | 
            +
                  #   The parent node to search within.
         | 
| 167 | 
            +
                  #
         | 
| 168 | 
            +
                  # @return [Array<Node>]
         | 
| 169 | 
            +
                  #   The matching nodes.
         | 
| 170 | 
            +
                  #
         | 
| 171 | 
            +
                  # @example
         | 
| 172 | 
            +
                  #   browser.go_to("https://github.com/")
         | 
| 173 | 
            +
                  #   browser.css("a[aria-label='Issues you created']") # => [Node]
         | 
| 174 | 
            +
                  #
         | 
| 68 175 | 
             
                  def css(selector, within: nil)
         | 
| 69 176 | 
             
                    expr = <<~JS
         | 
| 70 177 | 
             
                      function(selector, within) {
         | 
| @@ -76,6 +183,22 @@ module Ferrum | |
| 76 183 | 
             
                    evaluate_func(expr, selector, within)
         | 
| 77 184 | 
             
                  end
         | 
| 78 185 |  | 
| 186 | 
            +
                  #
         | 
| 187 | 
            +
                  # Finds a node by using a CSS path selector.
         | 
| 188 | 
            +
                  #
         | 
| 189 | 
            +
                  # @param [String] selector
         | 
| 190 | 
            +
                  #   The CSS path selector.
         | 
| 191 | 
            +
                  #
         | 
| 192 | 
            +
                  # @param [Node, nil] within
         | 
| 193 | 
            +
                  #   The parent node to search within.
         | 
| 194 | 
            +
                  #
         | 
| 195 | 
            +
                  # @return [Node, nil]
         | 
| 196 | 
            +
                  #   The matching node.
         | 
| 197 | 
            +
                  #
         | 
| 198 | 
            +
                  # @example
         | 
| 199 | 
            +
                  #   browser.go_to("https://github.com/")
         | 
| 200 | 
            +
                  #   browser.at_css("a[aria-label='Issues you created']") # => Node
         | 
| 201 | 
            +
                  #
         | 
| 79 202 | 
             
                  def at_css(selector, within: nil)
         | 
| 80 203 | 
             
                    expr = <<~JS
         | 
| 81 204 | 
             
                      function(selector, within) {
         | 
| @@ -86,6 +209,60 @@ module Ferrum | |
| 86 209 |  | 
| 87 210 | 
             
                    evaluate_func(expr, selector, within)
         | 
| 88 211 | 
             
                  end
         | 
| 212 | 
            +
             | 
| 213 | 
            +
                  #
         | 
| 214 | 
            +
                  # Adds a `<script>` tag to the document.
         | 
| 215 | 
            +
                  #
         | 
| 216 | 
            +
                  # @param [String, nil] url
         | 
| 217 | 
            +
                  #
         | 
| 218 | 
            +
                  # @param [String, nil] path
         | 
| 219 | 
            +
                  #
         | 
| 220 | 
            +
                  # @param [String, nil] content
         | 
| 221 | 
            +
                  #
         | 
| 222 | 
            +
                  # @param [String] type
         | 
| 223 | 
            +
                  #
         | 
| 224 | 
            +
                  # @example
         | 
| 225 | 
            +
                  #   browser.add_script_tag(url: "http://example.com/stylesheet.css") # => true
         | 
| 226 | 
            +
                  #
         | 
| 227 | 
            +
                  def add_script_tag(url: nil, path: nil, content: nil, type: "text/javascript")
         | 
| 228 | 
            +
                    expr, *args = if url
         | 
| 229 | 
            +
                                    [SCRIPT_SRC_TAG, url, type]
         | 
| 230 | 
            +
                                  elsif path || content
         | 
| 231 | 
            +
                                    if path
         | 
| 232 | 
            +
                                      content = File.read(path)
         | 
| 233 | 
            +
                                      content += "\n//# sourceURL=#{path}"
         | 
| 234 | 
            +
                                    end
         | 
| 235 | 
            +
                                    [SCRIPT_TEXT_TAG, content, type]
         | 
| 236 | 
            +
                                  end
         | 
| 237 | 
            +
             | 
| 238 | 
            +
                    evaluate_async(expr, @page.timeout, *args)
         | 
| 239 | 
            +
                  end
         | 
| 240 | 
            +
             | 
| 241 | 
            +
                  #
         | 
| 242 | 
            +
                  # Adds a `<style>` tag to the document.
         | 
| 243 | 
            +
                  #
         | 
| 244 | 
            +
                  # @param [String, nil] url
         | 
| 245 | 
            +
                  #
         | 
| 246 | 
            +
                  # @param [String, nil] path
         | 
| 247 | 
            +
                  #
         | 
| 248 | 
            +
                  # @param [String, nil] content
         | 
| 249 | 
            +
                  #
         | 
| 250 | 
            +
                  # @example
         | 
| 251 | 
            +
                  #   browser.add_style_tag(content: "h1 { font-size: 40px; }") # => true
         | 
| 252 | 
            +
                  #
         | 
| 253 | 
            +
                  def add_style_tag(url: nil, path: nil, content: nil)
         | 
| 254 | 
            +
                    expr, *args = if url
         | 
| 255 | 
            +
                                    [LINK_TAG, url]
         | 
| 256 | 
            +
                                  elsif path || content
         | 
| 257 | 
            +
                                    if path
         | 
| 258 | 
            +
                                      content = File.read(path)
         | 
| 259 | 
            +
                                      content += "\n//# sourceURL=#{path}"
         | 
| 260 | 
            +
                                    end
         | 
| 261 | 
            +
                                    [STYLE_TAG, content]
         | 
| 262 | 
            +
                                  end
         | 
| 263 | 
            +
             | 
| 264 | 
            +
                    evaluate_async(expr, @page.timeout, *args)
         | 
| 265 | 
            +
                  end
         | 
| 89 266 | 
             
                end
         | 
| 90 267 | 
             
              end
         | 
| 91 268 | 
             
            end
         | 
    
        data/lib/ferrum/frame/runtime.rb
    CHANGED
    
    | @@ -16,40 +16,38 @@ module Ferrum | |
| 16 16 | 
             
                  INTERMITTENT_ATTEMPTS = ENV.fetch("FERRUM_INTERMITTENT_ATTEMPTS", 6).to_i
         | 
| 17 17 | 
             
                  INTERMITTENT_SLEEP = ENV.fetch("FERRUM_INTERMITTENT_SLEEP", 0.1).to_f
         | 
| 18 18 |  | 
| 19 | 
            -
                   | 
| 20 | 
            -
             | 
| 21 | 
            -
             | 
| 22 | 
            -
             | 
| 23 | 
            -
             | 
| 24 | 
            -
             | 
| 25 | 
            -
                   | 
| 26 | 
            -
                   | 
| 27 | 
            -
             | 
| 28 | 
            -
             | 
| 29 | 
            -
             | 
| 30 | 
            -
             | 
| 31 | 
            -
                    arguments[2]();
         | 
| 32 | 
            -
                  JS
         | 
| 33 | 
            -
                  STYLE_TAG = <<~JS
         | 
| 34 | 
            -
                    const style = document.createElement("style");
         | 
| 35 | 
            -
                    style.type = "text/css";
         | 
| 36 | 
            -
                    style.appendChild(document.createTextNode(arguments[0]));
         | 
| 37 | 
            -
                    document.head.appendChild(style);
         | 
| 38 | 
            -
                    arguments[1]();
         | 
| 39 | 
            -
                  JS
         | 
| 40 | 
            -
                  LINK_TAG = <<~JS
         | 
| 41 | 
            -
                    const link = document.createElement("link");
         | 
| 42 | 
            -
                    link.rel = "stylesheet";
         | 
| 43 | 
            -
                    link.href = arguments[0];
         | 
| 44 | 
            -
                    link.onload = arguments[1];
         | 
| 45 | 
            -
                    document.head.appendChild(link);
         | 
| 46 | 
            -
                  JS
         | 
| 47 | 
            -
             | 
| 19 | 
            +
                  #
         | 
| 20 | 
            +
                  # Evaluate and return result for given JS expression.
         | 
| 21 | 
            +
                  #
         | 
| 22 | 
            +
                  # @param [String] expression
         | 
| 23 | 
            +
                  #   The JavaScript to evaluate.
         | 
| 24 | 
            +
                  #
         | 
| 25 | 
            +
                  # @param [Array] args
         | 
| 26 | 
            +
                  #   Additional arguments to pass to the JavaScript code.
         | 
| 27 | 
            +
                  #
         | 
| 28 | 
            +
                  # @example
         | 
| 29 | 
            +
                  #   browser.evaluate("[window.scrollX, window.scrollY]")
         | 
| 30 | 
            +
                  #
         | 
| 48 31 | 
             
                  def evaluate(expression, *args)
         | 
| 49 32 | 
             
                    expression = format("function() { return %s }", expression)
         | 
| 50 33 | 
             
                    call(expression: expression, arguments: args)
         | 
| 51 34 | 
             
                  end
         | 
| 52 35 |  | 
| 36 | 
            +
                  #
         | 
| 37 | 
            +
                  # Evaluate asynchronous expression and return result.
         | 
| 38 | 
            +
                  #
         | 
| 39 | 
            +
                  # @param [String] expression
         | 
| 40 | 
            +
                  #   The JavaScript to evaluate.
         | 
| 41 | 
            +
                  #
         | 
| 42 | 
            +
                  # @param [Integer] wait
         | 
| 43 | 
            +
                  #   How long we should wait for Promise to resolve or reject.
         | 
| 44 | 
            +
                  #
         | 
| 45 | 
            +
                  # @param [Array] args
         | 
| 46 | 
            +
                  #   Additional arguments to pass to the JavaScript code.
         | 
| 47 | 
            +
                  #
         | 
| 48 | 
            +
                  # @example
         | 
| 49 | 
            +
                  #   browser.evaluate_async(%(arguments[0]({foo: "bar"})), 5) # => { "foo" => "bar" }
         | 
| 50 | 
            +
                  #
         | 
| 53 51 | 
             
                  def evaluate_async(expression, wait, *args)
         | 
| 54 52 | 
             
                    template = <<~JS
         | 
| 55 53 | 
             
                      function() {
         | 
| @@ -70,6 +68,18 @@ module Ferrum | |
| 70 68 | 
             
                    call(expression: expression, arguments: args, awaitPromise: true)
         | 
| 71 69 | 
             
                  end
         | 
| 72 70 |  | 
| 71 | 
            +
                  #
         | 
| 72 | 
            +
                  # Execute expression. Doesn't return the result.
         | 
| 73 | 
            +
                  #
         | 
| 74 | 
            +
                  # @param [String] expression
         | 
| 75 | 
            +
                  #   The JavaScript to evaluate.
         | 
| 76 | 
            +
                  #
         | 
| 77 | 
            +
                  # @param [Array] args
         | 
| 78 | 
            +
                  #   Additional arguments to pass to the JavaScript code.
         | 
| 79 | 
            +
                  #
         | 
| 80 | 
            +
                  # @example
         | 
| 81 | 
            +
                  #   browser.execute(%(1 + 1)) # => true
         | 
| 82 | 
            +
                  #
         | 
| 73 83 | 
             
                  def execute(expression, *args)
         | 
| 74 84 | 
             
                    expression = format("function() { %s }", expression)
         | 
| 75 85 | 
             
                    call(expression: expression, arguments: args, handle: false, returnByValue: true)
         | 
| @@ -87,42 +97,12 @@ module Ferrum | |
| 87 97 | 
             
                    call(expression: expression, on: node, wait: wait, **options)
         | 
| 88 98 | 
             
                  end
         | 
| 89 99 |  | 
| 90 | 
            -
                  def add_script_tag(url: nil, path: nil, content: nil, type: "text/javascript")
         | 
| 91 | 
            -
                    expr, *args = if url
         | 
| 92 | 
            -
                                    [SCRIPT_SRC_TAG, url, type]
         | 
| 93 | 
            -
                                  elsif path || content
         | 
| 94 | 
            -
                                    if path
         | 
| 95 | 
            -
                                      content = File.read(path)
         | 
| 96 | 
            -
                                      content += "\n//# sourceURL=#{path}"
         | 
| 97 | 
            -
                                    end
         | 
| 98 | 
            -
                                    [SCRIPT_TEXT_TAG, content, type]
         | 
| 99 | 
            -
                                  end
         | 
| 100 | 
            -
             | 
| 101 | 
            -
                    evaluate_async(expr, @page.timeout, *args)
         | 
| 102 | 
            -
                  end
         | 
| 103 | 
            -
             | 
| 104 | 
            -
                  def add_style_tag(url: nil, path: nil, content: nil)
         | 
| 105 | 
            -
                    expr, *args = if url
         | 
| 106 | 
            -
                                    [LINK_TAG, url]
         | 
| 107 | 
            -
                                  elsif path || content
         | 
| 108 | 
            -
                                    if path
         | 
| 109 | 
            -
                                      content = File.read(path)
         | 
| 110 | 
            -
                                      content += "\n//# sourceURL=#{path}"
         | 
| 111 | 
            -
                                    end
         | 
| 112 | 
            -
                                    [STYLE_TAG, content]
         | 
| 113 | 
            -
                                  end
         | 
| 114 | 
            -
             | 
| 115 | 
            -
                    evaluate_async(expr, @page.timeout, *args)
         | 
| 116 | 
            -
                  end
         | 
| 117 | 
            -
             | 
| 118 100 | 
             
                  private
         | 
| 119 101 |  | 
| 120 102 | 
             
                  def call(expression:, arguments: [], on: nil, wait: 0, handle: true, **options)
         | 
| 121 103 | 
             
                    errors = [NodeNotFoundError, NoExecutionContextError]
         | 
| 122 | 
            -
                    sleep = INTERMITTENT_SLEEP
         | 
| 123 | 
            -
                    attempts = INTERMITTENT_ATTEMPTS
         | 
| 124 104 |  | 
| 125 | 
            -
                    Utils::Attempt.with_retry(errors: errors, max:  | 
| 105 | 
            +
                    Utils::Attempt.with_retry(errors: errors, max: INTERMITTENT_ATTEMPTS, wait: INTERMITTENT_SLEEP) do
         | 
| 126 106 | 
             
                      params = options.dup
         | 
| 127 107 |  | 
| 128 108 | 
             
                      if on
         | 
| @@ -132,7 +112,7 @@ module Ferrum | |
| 132 112 | 
             
                      end
         | 
| 133 113 |  | 
| 134 114 | 
             
                      if params[:executionContextId].nil? && params[:objectId].nil?
         | 
| 135 | 
            -
                        params = params.merge(executionContextId: execution_id)
         | 
| 115 | 
            +
                        params = params.merge(executionContextId: execution_id!)
         | 
| 136 116 | 
             
                      end
         | 
| 137 117 |  | 
| 138 118 | 
             
                      response = @page.command("Runtime.callFunctionOn",
         | 
    
        data/lib/ferrum/frame.rb
    CHANGED
    
    | @@ -14,8 +14,30 @@ module Ferrum | |
| 14 14 | 
             
                  stopped_loading
         | 
| 15 15 | 
             
                ].freeze
         | 
| 16 16 |  | 
| 17 | 
            -
                 | 
| 18 | 
            -
                 | 
| 17 | 
            +
                # The Frame's unique id.
         | 
| 18 | 
            +
                #
         | 
| 19 | 
            +
                # @return [String]
         | 
| 20 | 
            +
                attr_accessor :id
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                # If frame was given a name it should be here.
         | 
| 23 | 
            +
                #
         | 
| 24 | 
            +
                # @return [String, nil]
         | 
| 25 | 
            +
                attr_accessor :name
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                # The page the frame belongs to.
         | 
| 28 | 
            +
                #
         | 
| 29 | 
            +
                # @return [Page]
         | 
| 30 | 
            +
                attr_reader :page
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                # Parent frame id if this one is nested in another one.
         | 
| 33 | 
            +
                #
         | 
| 34 | 
            +
                # @return [String, nil]
         | 
| 35 | 
            +
                attr_reader :parent_id
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                # One of the states frame's in.
         | 
| 38 | 
            +
                #
         | 
| 39 | 
            +
                # @return [:started_loading, :navigated, :stopped_loading, nil]
         | 
| 40 | 
            +
                attr_reader :state
         | 
| 19 41 |  | 
| 20 42 | 
             
                def initialize(id, page, parent_id = nil)
         | 
| 21 43 | 
             
                  @id = id
         | 
| @@ -30,18 +52,60 @@ module Ferrum | |
| 30 52 | 
             
                  @state = value
         | 
| 31 53 | 
             
                end
         | 
| 32 54 |  | 
| 55 | 
            +
                #
         | 
| 56 | 
            +
                # Returns current frame's `location.href`.
         | 
| 57 | 
            +
                #
         | 
| 58 | 
            +
                # @return [String]
         | 
| 59 | 
            +
                #
         | 
| 60 | 
            +
                # @example
         | 
| 61 | 
            +
                #   browser.go_to("https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe")
         | 
| 62 | 
            +
                #   frame = browser.frames[1]
         | 
| 63 | 
            +
                #   frame.url # => https://interactive-examples.mdn.mozilla.net/pages/tabbed/iframe.html
         | 
| 64 | 
            +
                #
         | 
| 33 65 | 
             
                def url
         | 
| 34 66 | 
             
                  evaluate("document.location.href")
         | 
| 35 67 | 
             
                end
         | 
| 36 68 |  | 
| 69 | 
            +
                #
         | 
| 70 | 
            +
                # Returns current frame's title.
         | 
| 71 | 
            +
                #
         | 
| 72 | 
            +
                # @return [String]
         | 
| 73 | 
            +
                #
         | 
| 74 | 
            +
                # @example
         | 
| 75 | 
            +
                #   browser.go_to("https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe")
         | 
| 76 | 
            +
                #   frame = browser.frames[1]
         | 
| 77 | 
            +
                #   frame.title # => HTML Demo: <iframe>
         | 
| 78 | 
            +
                #
         | 
| 37 79 | 
             
                def title
         | 
| 38 80 | 
             
                  evaluate("document.title")
         | 
| 39 81 | 
             
                end
         | 
| 40 82 |  | 
| 83 | 
            +
                #
         | 
| 84 | 
            +
                # If current frame is the main frame of the page (top of the tree).
         | 
| 85 | 
            +
                #
         | 
| 86 | 
            +
                # @return [Boolean]
         | 
| 87 | 
            +
                #
         | 
| 88 | 
            +
                # @example
         | 
| 89 | 
            +
                #   browser.go_to("https://www.w3schools.com/tags/tag_frame.asp")
         | 
| 90 | 
            +
                #   frame = browser.frame_by(id: "C09C4E4404314AAEAE85928EAC109A93")
         | 
| 91 | 
            +
                #   frame.main? # => false
         | 
| 92 | 
            +
                #
         | 
| 41 93 | 
             
                def main?
         | 
| 42 94 | 
             
                  @parent_id.nil?
         | 
| 43 95 | 
             
                end
         | 
| 44 96 |  | 
| 97 | 
            +
                #
         | 
| 98 | 
            +
                # Sets a content of a given frame.
         | 
| 99 | 
            +
                #
         | 
| 100 | 
            +
                # @param [String] html
         | 
| 101 | 
            +
                #
         | 
| 102 | 
            +
                # @example
         | 
| 103 | 
            +
                #   browser.go_to("https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe")
         | 
| 104 | 
            +
                #   frame = browser.frames[1]
         | 
| 105 | 
            +
                #   frame.body # <html lang="en"><head><style>body {transition: opacity ease-in 0.2s; }...
         | 
| 106 | 
            +
                #   frame.content = "<html><head></head><body><p>lol</p></body></html>"
         | 
| 107 | 
            +
                #   frame.body # => <html><head></head><body><p>lol</p></body></html>
         | 
| 108 | 
            +
                #
         | 
| 45 109 | 
             
                def content=(html)
         | 
| 46 110 | 
             
                  evaluate_async(%(
         | 
| 47 111 | 
             
                    document.open();
         | 
| @@ -52,13 +116,36 @@ module Ferrum | |
| 52 116 | 
             
                end
         | 
| 53 117 | 
             
                alias set_content content=
         | 
| 54 118 |  | 
| 55 | 
            -
                 | 
| 119 | 
            +
                #
         | 
| 120 | 
            +
                # Execution context id which is used by JS, each frame has it's own
         | 
| 121 | 
            +
                # context in which JS evaluates. Locks for a page timeout and raises
         | 
| 122 | 
            +
                # an error if an execution id hasn't been set yet, if id is set
         | 
| 123 | 
            +
                # returns immediately.
         | 
| 124 | 
            +
                #
         | 
| 125 | 
            +
                # @return [Integer]
         | 
| 126 | 
            +
                #
         | 
| 127 | 
            +
                # @raise [NoExecutionContextError]
         | 
| 128 | 
            +
                #
         | 
| 129 | 
            +
                def execution_id!
         | 
| 56 130 | 
             
                  value = @execution_id.borrow(@page.timeout, &:itself)
         | 
| 57 131 | 
             
                  raise NoExecutionContextError if value.instance_of?(Object)
         | 
| 58 132 |  | 
| 59 133 | 
             
                  value
         | 
| 60 134 | 
             
                end
         | 
| 61 135 |  | 
| 136 | 
            +
                #
         | 
| 137 | 
            +
                # Execution context id which is used by JS, each frame has it's own
         | 
| 138 | 
            +
                # context in which JS evaluates.
         | 
| 139 | 
            +
                #
         | 
| 140 | 
            +
                # @return [Integer, nil]
         | 
| 141 | 
            +
                #
         | 
| 142 | 
            +
                def execution_id
         | 
| 143 | 
            +
                  value = @execution_id.value
         | 
| 144 | 
            +
                  return if value.instance_of?(Object)
         | 
| 145 | 
            +
             | 
| 146 | 
            +
                  value
         | 
| 147 | 
            +
                end
         | 
| 148 | 
            +
             | 
| 62 149 | 
             
                def execution_id=(value)
         | 
| 63 150 | 
             
                  if value.nil?
         | 
| 64 151 | 
             
                    @execution_id.try_take!
         | 
    
        data/lib/ferrum/headers.rb
    CHANGED
    
    | @@ -7,20 +7,48 @@ module Ferrum | |
| 7 7 | 
             
                  @headers = {}
         | 
| 8 8 | 
             
                end
         | 
| 9 9 |  | 
| 10 | 
            +
                #
         | 
| 11 | 
            +
                # Get all headers.
         | 
| 12 | 
            +
                #
         | 
| 13 | 
            +
                # @return [Hash{String => String}]
         | 
| 14 | 
            +
                #
         | 
| 10 15 | 
             
                def get
         | 
| 11 16 | 
             
                  @headers
         | 
| 12 17 | 
             
                end
         | 
| 13 18 |  | 
| 19 | 
            +
                #
         | 
| 20 | 
            +
                # Set given headers. Eventually clear all headers and set given ones.
         | 
| 21 | 
            +
                #
         | 
| 22 | 
            +
                # @param [Hash{String => String}] headers
         | 
| 23 | 
            +
                #   key-value pairs for example `"User-Agent" => "Browser"`.
         | 
| 24 | 
            +
                #
         | 
| 25 | 
            +
                # @return [true]
         | 
| 26 | 
            +
                #
         | 
| 14 27 | 
             
                def set(headers)
         | 
| 15 28 | 
             
                  clear
         | 
| 16 29 | 
             
                  add(headers)
         | 
| 17 30 | 
             
                end
         | 
| 18 31 |  | 
| 32 | 
            +
                #
         | 
| 33 | 
            +
                # Clear all headers.
         | 
| 34 | 
            +
                #
         | 
| 35 | 
            +
                # @return [true]
         | 
| 36 | 
            +
                #
         | 
| 19 37 | 
             
                def clear
         | 
| 20 38 | 
             
                  @headers = {}
         | 
| 21 39 | 
             
                  true
         | 
| 22 40 | 
             
                end
         | 
| 23 41 |  | 
| 42 | 
            +
                #
         | 
| 43 | 
            +
                # Adds given headers to already set ones.
         | 
| 44 | 
            +
                #
         | 
| 45 | 
            +
                # @param [Hash{String => String}] headers
         | 
| 46 | 
            +
                #   key-value pairs for example `"Referer" => "http://example.com"`.
         | 
| 47 | 
            +
                #
         | 
| 48 | 
            +
                # @param [Boolean] permanent
         | 
| 49 | 
            +
                #
         | 
| 50 | 
            +
                # @return [true]
         | 
| 51 | 
            +
                #
         | 
| 24 52 | 
             
                def add(headers, permanent: true)
         | 
| 25 53 | 
             
                  if headers["Referer"]
         | 
| 26 54 | 
             
                    @page.referrer = headers["Referer"]
         | 
    
        data/lib/ferrum/keyboard.rb
    CHANGED
    
    | @@ -30,19 +30,44 @@ module Ferrum | |
| 30 30 | 
             
                  @page = page
         | 
| 31 31 | 
             
                end
         | 
| 32 32 |  | 
| 33 | 
            +
                #
         | 
| 34 | 
            +
                # Dispatches a `keydown` event.
         | 
| 35 | 
            +
                #
         | 
| 36 | 
            +
                # @param [String, Symbol] key
         | 
| 37 | 
            +
                #   Name of the key, such as `"a"`, `:enter`, or `:backspace`.
         | 
| 38 | 
            +
                #
         | 
| 39 | 
            +
                # @return [self]
         | 
| 40 | 
            +
                #
         | 
| 33 41 | 
             
                def down(key)
         | 
| 34 | 
            -
                  key = normalize_keys(Array(key))
         | 
| 42 | 
            +
                  key = normalize_keys(Array(key)).first
         | 
| 35 43 | 
             
                  type = key[:text] ? "keyDown" : "rawKeyDown"
         | 
| 36 44 | 
             
                  @page.command("Input.dispatchKeyEvent", slowmoable: true, type: type, **key)
         | 
| 37 45 | 
             
                  self
         | 
| 38 46 | 
             
                end
         | 
| 39 47 |  | 
| 48 | 
            +
                #
         | 
| 49 | 
            +
                # Dispatches a `keyup` event.
         | 
| 50 | 
            +
                #
         | 
| 51 | 
            +
                # @param [String, Symbol] key
         | 
| 52 | 
            +
                #   Name of the key, such as `"a"`, `:enter`, or `:backspace`.
         | 
| 53 | 
            +
                #
         | 
| 54 | 
            +
                # @return [self]
         | 
| 55 | 
            +
                #
         | 
| 40 56 | 
             
                def up(key)
         | 
| 41 | 
            -
                  key = normalize_keys(Array(key))
         | 
| 57 | 
            +
                  key = normalize_keys(Array(key)).first
         | 
| 42 58 | 
             
                  @page.command("Input.dispatchKeyEvent", slowmoable: true, type: "keyUp", **key)
         | 
| 43 59 | 
             
                  self
         | 
| 44 60 | 
             
                end
         | 
| 45 61 |  | 
| 62 | 
            +
                #
         | 
| 63 | 
            +
                # Sends a keydown, keypress/input, and keyup event for each character in
         | 
| 64 | 
            +
                # the text.
         | 
| 65 | 
            +
                #
         | 
| 66 | 
            +
                # @param [Array<String, Symbol, (Symbol, String)>] keys
         | 
| 67 | 
            +
                #   The text to type into a focused element, `[:Shift, "s"], "tring"`.
         | 
| 68 | 
            +
                #
         | 
| 69 | 
            +
                # @return [self]
         | 
| 70 | 
            +
                #
         | 
| 46 71 | 
             
                def type(*keys)
         | 
| 47 72 | 
             
                  keys = normalize_keys(Array(keys))
         | 
| 48 73 |  | 
| @@ -55,15 +80,27 @@ module Ferrum | |
| 55 80 | 
             
                  self
         | 
| 56 81 | 
             
                end
         | 
| 57 82 |  | 
| 83 | 
            +
                #
         | 
| 84 | 
            +
                # Returns bitfield for a given keys.
         | 
| 85 | 
            +
                #
         | 
| 86 | 
            +
                # @param [Array<:alt, :ctrl, :command, :shift>] keys
         | 
| 87 | 
            +
                #
         | 
| 88 | 
            +
                # @return [Integer]
         | 
| 89 | 
            +
                #
         | 
| 58 90 | 
             
                def modifiers(keys)
         | 
| 59 91 | 
             
                  keys.map { |k| MODIFIERS[k.to_s] }.compact.reduce(0, :|)
         | 
| 60 92 | 
             
                end
         | 
| 61 93 |  | 
| 62 94 | 
             
                private
         | 
| 63 95 |  | 
| 96 | 
            +
                # TODO: Refactor it, and try to simplify complexity
         | 
| 97 | 
            +
                # rubocop:disable Metrics/PerceivedComplexity
         | 
| 98 | 
            +
                # rubocop:disable Metrics/CyclomaticComplexity
         | 
| 64 99 | 
             
                def normalize_keys(keys, pressed_keys = [], memo = [])
         | 
| 65 100 | 
             
                  case keys
         | 
| 66 101 | 
             
                  when Array
         | 
| 102 | 
            +
                    raise ArgumentError, "empty keys passed" if keys.empty?
         | 
| 103 | 
            +
             | 
| 67 104 | 
             
                    pressed_keys.push([])
         | 
| 68 105 | 
             
                    memo += combine_strings(keys).map do |key|
         | 
| 69 106 | 
             
                      normalize_keys(key, pressed_keys, memo)
         | 
| @@ -82,6 +119,8 @@ module Ferrum | |
| 82 119 | 
             
                      to_options(key)
         | 
| 83 120 | 
             
                    end
         | 
| 84 121 | 
             
                  when String
         | 
| 122 | 
            +
                    raise ArgumentError, "empty keys passed" if keys.empty?
         | 
| 123 | 
            +
             | 
| 85 124 | 
             
                    pressed = pressed_keys.flatten
         | 
| 86 125 | 
             
                    keys.each_char.map do |char|
         | 
| 87 126 | 
             
                      key = KEYS[char] || {}
         | 
| @@ -102,8 +141,12 @@ module Ferrum | |
| 102 141 | 
             
                        modifiers + [to_options(key)]
         | 
| 103 142 | 
             
                      end.flatten
         | 
| 104 143 | 
             
                    end
         | 
| 144 | 
            +
                  else
         | 
| 145 | 
            +
                    raise ArgumentError, "unexpected argument"
         | 
| 105 146 | 
             
                  end
         | 
| 106 147 | 
             
                end
         | 
| 148 | 
            +
                # rubocop:enable Metrics/PerceivedComplexity
         | 
| 149 | 
            +
                # rubocop:enable Metrics/CyclomaticComplexity
         | 
| 107 150 |  | 
| 108 151 | 
             
                def combine_strings(keys)
         | 
| 109 152 | 
             
                  keys
         |