ferrum 0.1.2 → 0.2

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.
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ferrum
4
+ class Page
5
+ module Screenshot
6
+ def screenshot(**opts)
7
+ path, encoding = common_options(**opts)
8
+ options = screenshot_options(path, **opts)
9
+ data = command("Page.captureScreenshot", **options).fetch("data")
10
+ return data if encoding == :base64
11
+ save_file(path, data)
12
+ end
13
+
14
+ def pdf(**opts)
15
+ path, encoding = common_options(**opts)
16
+ options = pdf_options(**opts)
17
+ data = command("Page.printToPDF", **options).fetch("data")
18
+ return data if encoding == :base64
19
+ save_file(path, data)
20
+ end
21
+
22
+ private
23
+
24
+ def save_file(path, data)
25
+ bin = Base64.decode64(data)
26
+ File.open(path.to_s, "wb") { |f| f.write(bin) }
27
+ end
28
+
29
+ def common_options(encoding: :base64, path: nil, **_)
30
+ encoding = encoding.to_sym
31
+ encoding = :binary if path
32
+
33
+ if encoding == :binary && !path
34
+ raise "You have to provide `:path` for `:binary` encoding"
35
+ end
36
+
37
+ [path, encoding]
38
+ end
39
+
40
+ def pdf_options(landscape: false, paper_width: 8.5, paper_height: 11, scale: 1.0, **opts)
41
+ options = {}
42
+ options[:landscape] = landscape
43
+ options[:paperWidth] = paper_width.to_f
44
+ options[:paperHeight] = paper_height.to_f
45
+ options[:scale] = scale.to_f
46
+ options.merge(opts)
47
+ end
48
+
49
+ def screenshot_options(path = nil, format: nil, scale: 1.0, **opts)
50
+ options = {}
51
+
52
+ format ||= path ? File.extname(path).delete(".") : "png"
53
+ format = "jpeg" if format == "jpg"
54
+ raise "Not supported options `:format` #{format}. jpeg | png" if format !~ /jpeg|png/i
55
+ options.merge!(format: format)
56
+
57
+ options.merge!(quality: opts[:quality] ? opts[:quality] : 75) if format == "jpeg"
58
+
59
+ if !!opts[:full] && opts[:selector]
60
+ warn "Ignoring :selector in #screenshot since full: true was given at #{caller(1..1).first}"
61
+ end
62
+
63
+ if !!opts[:full]
64
+ width, height = evaluate("[document.documentElement.offsetWidth, document.documentElement.offsetHeight]")
65
+ options.merge!(clip: { x: 0, y: 0, width: width, height: height, scale: scale }) if width > 0 && height > 0
66
+ elsif opts[:selector]
67
+ rect = evaluate("document.querySelector('#{opts[:selector]}').getBoundingClientRect()")
68
+ options.merge!(clip: { x: rect["x"], y: rect["y"], width: rect["width"], height: rect["height"], scale: scale })
69
+ end
70
+
71
+ if scale != 1.0
72
+ if !options[:clip]
73
+ width, height = evaluate("[document.documentElement.clientWidth, document.documentElement.clientHeight]")
74
+ options[:clip] = { x: 0, y: 0, width: width, height: height }
75
+ end
76
+
77
+ options[:clip].merge!(scale: scale)
78
+ end
79
+
80
+ options
81
+ end
82
+ end
83
+ end
84
+ end
@@ -102,17 +102,13 @@ module Ferrum
102
102
  end
103
103
 
104
104
  def targets
105
- attempts = 1
106
- begin
105
+ Ferrum.with_attempts(errors: EmptyTargetsError,
106
+ max: TARGETS_RETRY_ATTEMPTS,
107
+ wait: TARGETS_RETRY_WAIT) do
107
108
  # Targets cannot be empty the must be at least one default target.
108
109
  targets = @browser.command("Target.getTargets")["targetInfos"]
109
- raise EmptyTargetsError.new("No target browser available") if targets.empty?
110
+ raise EmptyTargetsError if targets.empty?
110
111
  targets
111
- rescue EmptyTargetsError
112
- raise if attempts > TARGETS_RETRY_ATTEMPTS
113
- attempts += 1
114
- sleep TARGETS_RETRY_WAIT
115
- retry
116
112
  end
117
113
  end
118
114
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Ferrum
4
- VERSION = "0.1.2"
4
+ VERSION = "0.2"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ferrum
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: '0.2'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dmitry Vorotilin
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-08-27 00:00:00.000000000 Z
11
+ date: 2019-09-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: websocket-driver
@@ -195,28 +195,27 @@ files:
195
195
  - README.md
196
196
  - lib/ferrum.rb
197
197
  - lib/ferrum/browser.rb
198
- - lib/ferrum/browser/api.rb
199
- - lib/ferrum/browser/api/cookie.rb
200
- - lib/ferrum/browser/api/header.rb
201
- - lib/ferrum/browser/api/intercept.rb
202
- - lib/ferrum/browser/api/screenshot.rb
203
198
  - lib/ferrum/browser/client.rb
204
199
  - lib/ferrum/browser/process.rb
205
200
  - lib/ferrum/browser/subscriber.rb
206
201
  - lib/ferrum/browser/web_socket.rb
207
- - lib/ferrum/cookie.rb
208
- - lib/ferrum/errors.rb
202
+ - lib/ferrum/cookies.rb
203
+ - lib/ferrum/headers.rb
204
+ - lib/ferrum/keyboard.json
205
+ - lib/ferrum/keyboard.rb
206
+ - lib/ferrum/mouse.rb
209
207
  - lib/ferrum/network/error.rb
208
+ - lib/ferrum/network/intercepted_request.rb
210
209
  - lib/ferrum/network/request.rb
211
210
  - lib/ferrum/network/response.rb
212
211
  - lib/ferrum/node.rb
213
212
  - lib/ferrum/page.rb
214
213
  - lib/ferrum/page/dom.rb
215
214
  - lib/ferrum/page/frame.rb
216
- - lib/ferrum/page/input.json
217
215
  - lib/ferrum/page/input.rb
218
216
  - lib/ferrum/page/net.rb
219
217
  - lib/ferrum/page/runtime.rb
218
+ - lib/ferrum/page/screenshot.rb
220
219
  - lib/ferrum/targets.rb
221
220
  - lib/ferrum/version.rb
222
221
  homepage: https://github.com/route/ferrum
@@ -1,14 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "ferrum/browser/api/cookie"
4
- require "ferrum/browser/api/header"
5
- require "ferrum/browser/api/screenshot"
6
- require "ferrum/browser/api/intercept"
7
-
8
- module Ferrum
9
- class Browser
10
- module API
11
- include Cookie, Header, Screenshot, Intercept
12
- end
13
- end
14
- end
@@ -1,46 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Ferrum
4
- class Browser
5
- module API
6
- module Cookie
7
- def cookies
8
- cookies = page.command("Network.getAllCookies")["cookies"]
9
- cookies.map { |c| [c["name"], ::Ferrum::Cookie.new(c)] }.to_h
10
- end
11
-
12
- def set_cookie(name: nil, value: nil, cookie: nil, **options)
13
- cookie = options.dup
14
- cookie[:name] ||= name
15
- cookie[:value] ||= value
16
- cookie[:domain] ||= default_domain
17
-
18
- expires = cookie.delete(:expires).to_i
19
- cookie[:expires] = expires if expires > 0
20
-
21
- page.command("Network.setCookie", **cookie)
22
- end
23
-
24
- # Supports :url, :domain and :path options
25
- def remove_cookie(name:, **options)
26
- raise "Specify :domain or :url option" if !options[:domain] && !options[:url] && !default_domain
27
-
28
- options = options.merge(name: name)
29
- options[:domain] ||= default_domain
30
-
31
- page.command("Network.deleteCookies", **options)
32
- end
33
-
34
- def clear_cookies
35
- page.command("Network.clearBrowserCookies")
36
- end
37
-
38
- private
39
-
40
- def default_domain
41
- URI.parse(base_url).host if base_url
42
- end
43
- end
44
- end
45
- end
46
- end
@@ -1,32 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Ferrum
4
- class Browser
5
- module API
6
- module Header
7
- def headers=(headers)
8
- @headers = {}
9
- add_headers(headers)
10
- end
11
-
12
- def add_headers(headers, permanent: true)
13
- if headers["Referer"]
14
- page.referrer = headers["Referer"]
15
- headers.delete("Referer") unless permanent
16
- end
17
-
18
- @headers.merge!(headers)
19
- user_agent = @headers["User-Agent"]
20
- accept_language = @headers["Accept-Language"]
21
-
22
- set_overrides(user_agent: user_agent, accept_language: accept_language)
23
- page.command("Network.setExtraHTTPHeaders", headers: @headers)
24
- end
25
-
26
- def add_header(header, permanent: true)
27
- add_headers(header, permanent: permanent)
28
- end
29
- end
30
- end
31
- end
32
- end
@@ -1,32 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Ferrum
4
- class Browser
5
- module API
6
- module Intercept
7
- def url_whitelist=(wildcards)
8
- @url_whitelist = prepare_wildcards(wildcards)
9
- page.intercept_request("*") if @client && !@url_whitelist.empty?
10
- end
11
-
12
- def url_blacklist=(wildcards)
13
- @url_blacklist = prepare_wildcards(wildcards)
14
- page.intercept_request("*") if @client && !@url_blacklist.empty?
15
- end
16
-
17
- private
18
-
19
- def prepare_wildcards(wc)
20
- Array(wc).map do |wildcard|
21
- if wildcard.is_a?(Regexp)
22
- wildcard
23
- else
24
- wildcard = wildcard.gsub("*", ".*")
25
- Regexp.new(wildcard, Regexp::IGNORECASE)
26
- end
27
- end
28
- end
29
- end
30
- end
31
- end
32
- end
@@ -1,78 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Ferrum
4
- class Browser
5
- module API
6
- module Screenshot
7
- def screenshot(**opts)
8
- encoding, path, options = screenshot_options(**opts)
9
-
10
- data = if options[:format].to_s == "pdf"
11
- options = {}
12
- options[:paperWidth] = @paper_size[:width].to_f if @paper_size
13
- options[:paperHeight] = @paper_size[:height].to_f if @paper_size
14
- options[:scale] = @zoom_factor if @zoom_factor
15
- page.command("Page.printToPDF", **options)
16
- else
17
- page.command("Page.captureScreenshot", **options)
18
- end.fetch("data")
19
-
20
- return data if encoding == :base64
21
-
22
- bin = Base64.decode64(data)
23
- File.open(path.to_s, "wb") { |f| f.write(bin) }
24
- end
25
-
26
- def zoom_factor=(value)
27
- @zoom_factor = value.to_f
28
- end
29
-
30
- def paper_size=(value)
31
- @paper_size = value
32
- end
33
-
34
- private
35
-
36
- def screenshot_options(encoding: :base64, format: nil, path: nil, **opts)
37
- options = {}
38
-
39
- encoding = :binary if path
40
-
41
- if encoding == :binary && !path
42
- raise "Not supported option `:path` #{path}. Should be path to file"
43
- end
44
-
45
- format ||= path ? File.extname(path).delete(".") : "png"
46
- format = "jpeg" if format == "jpg"
47
- raise "Not supported options `:format` #{format}. jpeg | png | pdf" if format !~ /jpeg|png|pdf/i
48
- options.merge!(format: format)
49
-
50
- options.merge!(quality: opts[:quality] ? opts[:quality] : 75) if format == "jpeg"
51
-
52
- if !!opts[:full] && opts[:selector]
53
- warn "Ignoring :selector in #screenshot since full: true was given at #{caller(1..1).first}"
54
- end
55
-
56
- if !!opts[:full]
57
- width, height = page.evaluate("[document.documentElement.offsetWidth, document.documentElement.offsetHeight]")
58
- options.merge!(clip: { x: 0, y: 0, width: width, height: height, scale: @zoom_factor || 1.0 }) if width > 0 && height > 0
59
- elsif opts[:selector]
60
- rect = page.evaluate("document.querySelector('#{opts[:selector]}').getBoundingClientRect()")
61
- options.merge!(clip: { x: rect["x"], y: rect["y"], width: rect["width"], height: rect["height"], scale: @zoom_factor || 1.0 })
62
- end
63
-
64
- if @zoom_factor
65
- if !options[:clip]
66
- width, height = page.evaluate("[document.documentElement.clientWidth, document.documentElement.clientHeight]")
67
- options[:clip] = { x: 0, y: 0, width: width, height: height }
68
- end
69
-
70
- options[:clip].merge!(scale: @zoom_factor)
71
- end
72
-
73
- [encoding, path, options]
74
- end
75
- end
76
- end
77
- end
78
- end
@@ -1,47 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Ferrum
4
- class Cookie
5
- def initialize(attributes)
6
- @attributes = attributes
7
- end
8
-
9
- def name
10
- @attributes["name"]
11
- end
12
-
13
- def value
14
- @attributes["value"]
15
- end
16
-
17
- def domain
18
- @attributes["domain"]
19
- end
20
-
21
- def path
22
- @attributes["path"]
23
- end
24
-
25
- def size
26
- @attributes["size"]
27
- end
28
-
29
- def secure?
30
- @attributes["secure"]
31
- end
32
-
33
- def httponly?
34
- @attributes["httpOnly"]
35
- end
36
-
37
- def session?
38
- @attributes["session"]
39
- end
40
-
41
- def expires
42
- if @attributes["expires"] > 0
43
- Time.at(@attributes["expires"])
44
- end
45
- end
46
- end
47
- end
@@ -1,94 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Ferrum
4
- class NotImplemented < StandardError; end
5
- class ModalNotFound < StandardError; end
6
- class Error < StandardError; end
7
- class NoSuchWindowError < Error; end
8
- class EmptyTargetsError < Error; end
9
- class NoExecutionContext < Error; end
10
-
11
- class ClientError < Error
12
- attr_reader :response
13
-
14
- def initialize(response)
15
- @response = response
16
- super(response["message"])
17
- end
18
- end
19
-
20
- class BrowserError < ClientError
21
- def code
22
- response["code"]
23
- end
24
-
25
- def data
26
- response["data"]
27
- end
28
- end
29
-
30
- class JavaScriptError < ClientError
31
- attr_reader :class_name, :message
32
-
33
- def initialize(response)
34
- super
35
- @class_name, @message = response.values_at("className", "description")
36
- end
37
- end
38
-
39
- class StatusFailError < ClientError
40
- def message
41
- "Request to #{response["url"]} failed to reach server, check DNS and/or server status"
42
- end
43
- end
44
-
45
- class FrameNotFound < ClientError
46
- def name
47
- response["args"].first
48
- end
49
-
50
- def message
51
- "The frame \"#{name}\" was not found."
52
- end
53
- end
54
-
55
- class NodeError < ClientError
56
- attr_reader :node
57
-
58
- def initialize(node, response)
59
- @node = node
60
- super(response)
61
- end
62
- end
63
-
64
- class ObsoleteNode < NodeError
65
- def message
66
- "The element you are trying to interact with is either not part of the DOM, or is " \
67
- "not currently visible on the page (perhaps display: none is set). " \
68
- "It is possible the element has been replaced by another element and you meant to interact with " \
69
- "the new element. If so you need to do a new find in order to get a reference to the " \
70
- "new element."
71
- end
72
- end
73
-
74
- class TimeoutError < Error
75
- def message
76
- "Timed out waiting for response. It's possible that this happened " \
77
- "because something took a very long time (for example a page load " \
78
- "was slow). If so, setting the :timeout option to a higher value might " \
79
- "help."
80
- end
81
- end
82
-
83
- class ScriptTimeoutError < Error
84
- def message
85
- "Timed out waiting for evaluated script to return a value"
86
- end
87
- end
88
-
89
- class DeadBrowser < Error
90
- def initialize(message = "Browser is dead")
91
- super
92
- end
93
- end
94
- end