ferrum 0.11 → 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.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +1 -1
  3. data/README.md +174 -30
  4. data/lib/ferrum/browser/binary.rb +46 -0
  5. data/lib/ferrum/browser/client.rb +17 -16
  6. data/lib/ferrum/browser/command.rb +10 -12
  7. data/lib/ferrum/browser/options/base.rb +2 -11
  8. data/lib/ferrum/browser/options/chrome.rb +29 -18
  9. data/lib/ferrum/browser/options/firefox.rb +13 -9
  10. data/lib/ferrum/browser/options.rb +84 -0
  11. data/lib/ferrum/browser/process.rb +45 -40
  12. data/lib/ferrum/browser/subscriber.rb +1 -3
  13. data/lib/ferrum/browser/version_info.rb +71 -0
  14. data/lib/ferrum/browser/web_socket.rb +9 -12
  15. data/lib/ferrum/browser/xvfb.rb +4 -8
  16. data/lib/ferrum/browser.rb +193 -47
  17. data/lib/ferrum/context.rb +9 -4
  18. data/lib/ferrum/contexts.rb +12 -10
  19. data/lib/ferrum/cookies/cookie.rb +126 -0
  20. data/lib/ferrum/cookies.rb +93 -55
  21. data/lib/ferrum/dialog.rb +30 -0
  22. data/lib/ferrum/errors.rb +115 -0
  23. data/lib/ferrum/frame/dom.rb +177 -0
  24. data/lib/ferrum/frame/runtime.rb +58 -75
  25. data/lib/ferrum/frame.rb +118 -23
  26. data/lib/ferrum/headers.rb +30 -2
  27. data/lib/ferrum/keyboard.rb +56 -13
  28. data/lib/ferrum/mouse.rb +92 -7
  29. data/lib/ferrum/network/auth_request.rb +7 -2
  30. data/lib/ferrum/network/exchange.rb +97 -12
  31. data/lib/ferrum/network/intercepted_request.rb +10 -8
  32. data/lib/ferrum/network/request.rb +69 -0
  33. data/lib/ferrum/network/response.rb +85 -3
  34. data/lib/ferrum/network.rb +285 -36
  35. data/lib/ferrum/node.rb +69 -23
  36. data/lib/ferrum/page/animation.rb +16 -1
  37. data/lib/ferrum/page/frames.rb +111 -30
  38. data/lib/ferrum/page/screenshot.rb +142 -65
  39. data/lib/ferrum/page/stream.rb +38 -0
  40. data/lib/ferrum/page/tracing.rb +97 -0
  41. data/lib/ferrum/page.rb +224 -60
  42. data/lib/ferrum/proxy.rb +147 -0
  43. data/lib/ferrum/{rbga.rb → rgba.rb} +4 -2
  44. data/lib/ferrum/target.rb +7 -4
  45. data/lib/ferrum/utils/attempt.rb +20 -0
  46. data/lib/ferrum/utils/elapsed_time.rb +27 -0
  47. data/lib/ferrum/utils/platform.rb +28 -0
  48. data/lib/ferrum/version.rb +1 -1
  49. data/lib/ferrum.rb +4 -146
  50. metadata +63 -51
data/lib/ferrum/frame.rb CHANGED
@@ -5,37 +5,108 @@ require "ferrum/frame/runtime"
5
5
 
6
6
  module Ferrum
7
7
  class Frame
8
- include DOM, Runtime
8
+ include DOM
9
+ include Runtime
9
10
 
10
- attr_reader :page, :parent_id, :state
11
- attr_accessor :id, :name
11
+ STATE_VALUES = %i[
12
+ started_loading
13
+ navigated
14
+ stopped_loading
15
+ ].freeze
16
+
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
12
41
 
13
42
  def initialize(id, page, parent_id = nil)
14
- @execution_id = nil
15
- @id, @page, @parent_id = id, page, parent_id
43
+ @id = id
44
+ @page = page
45
+ @parent_id = parent_id
46
+ @execution_id = Concurrent::MVar.new
16
47
  end
17
48
 
18
- # Can be one of:
19
- # * started_loading
20
- # * navigated
21
- # * stopped_loading
22
49
  def state=(value)
50
+ raise ArgumentError unless STATE_VALUES.include?(value)
51
+
23
52
  @state = value
24
53
  end
25
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
+ #
26
65
  def url
27
66
  evaluate("document.location.href")
28
67
  end
29
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
+ #
30
79
  def title
31
80
  evaluate("document.title")
32
81
  end
33
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
+ #
34
93
  def main?
35
94
  @parent_id.nil?
36
95
  end
37
96
 
38
- def set_content(html)
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
+ #
109
+ def content=(html)
39
110
  evaluate_async(%(
40
111
  document.open();
41
112
  document.write(arguments[0]);
@@ -43,29 +114,53 @@ module Ferrum
43
114
  arguments[1](true);
44
115
  ), @page.timeout, html)
45
116
  end
117
+ alias set_content content=
46
118
 
47
- def execution_id?(execution_id)
48
- @execution_id == execution_id
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!
130
+ value = @execution_id.borrow(@page.timeout, &:itself)
131
+ raise NoExecutionContextError if value.instance_of?(Object)
132
+
133
+ value
49
134
  end
50
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
+ #
51
142
  def execution_id
52
- raise NoExecutionContextError unless @execution_id
53
- @execution_id
54
- rescue NoExecutionContextError
55
- @page.event.reset
56
- @page.event.wait(@page.timeout) ? retry : raise
57
- end
143
+ value = @execution_id.value
144
+ return if value.instance_of?(Object)
58
145
 
59
- def set_execution_id(value)
60
- @execution_id ||= value
146
+ value
61
147
  end
62
148
 
63
- def reset_execution_id
64
- @execution_id = nil
149
+ def execution_id=(value)
150
+ if value.nil?
151
+ @execution_id.try_take!
152
+ else
153
+ @execution_id.try_put!(value)
154
+ end
65
155
  end
66
156
 
67
157
  def inspect
68
- %(#<#{self.class} @id=#{@id.inspect} @parent_id=#{@parent_id.inspect} @name=#{@name.inspect} @state=#{@state.inspect} @execution_id=#{@execution_id.inspect}>)
158
+ "#<#{self.class} " \
159
+ "@id=#{@id.inspect} " \
160
+ "@parent_id=#{@parent_id.inspect} " \
161
+ "@name=#{@name.inspect} " \
162
+ "@state=#{@state.inspect} " \
163
+ "@execution_id=#{@execution_id.inspect}>"
69
164
  end
70
165
  end
71
166
  end
@@ -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"]
@@ -39,12 +67,12 @@ module Ferrum
39
67
  private
40
68
 
41
69
  def set_overrides(user_agent: nil, accept_language: nil, platform: nil)
42
- options = Hash.new
70
+ options = {}
43
71
  options[:userAgent] = user_agent || @page.browser.default_user_agent
44
72
  options[:acceptLanguage] = accept_language if accept_language
45
73
  options[:platform] if platform
46
74
 
47
- @page.command("Network.setUserAgentOverride", **options) if !options.empty?
75
+ @page.command("Network.setUserAgentOverride", **options) unless options.empty?
48
76
  end
49
77
  end
50
78
  end
@@ -4,14 +4,14 @@ require "json"
4
4
 
5
5
  module Ferrum
6
6
  class Keyboard
7
- KEYS = JSON.parse(File.read(File.expand_path("../keyboard.json", __FILE__)))
7
+ KEYS = JSON.parse(File.read(File.expand_path("keyboard.json", __dir__)))
8
8
  MODIFIERS = { "alt" => 1, "ctrl" => 2, "control" => 2,
9
- "meta" => 4, "command" => 4, "shift" => 8 }
9
+ "meta" => 4, "command" => 4, "shift" => 8 }.freeze
10
10
  KEYS_MAPPING = {
11
11
  cancel: "Cancel", help: "Help", backspace: "Backspace", tab: "Tab",
12
12
  clear: "Clear", return: "Enter", enter: "Enter", shift: "Shift",
13
13
  ctrl: "Control", control: "Control", alt: "Alt", pause: "Pause",
14
- escape: "Escape", space: "Space", pageup: "PageUp", page_up: "PageUp",
14
+ escape: "Escape", space: "Space", pageup: "PageUp", page_up: "PageUp",
15
15
  pagedown: "PageDown", page_down: "PageDown", end: "End", home: "Home",
16
16
  left: "ArrowLeft", up: "ArrowUp", right: "ArrowRight",
17
17
  down: "ArrowDown", insert: "Insert", delete: "Delete",
@@ -23,26 +23,51 @@ module Ferrum
23
23
  separator: "NumpadDecimal", subtract: "NumpadSubtract",
24
24
  decimal: "NumpadDecimal", divide: "NumpadDivide", f1: "F1", f2: "F2",
25
25
  f3: "F3", f4: "F4", f5: "F5", f6: "F6", f7: "F7", f8: "F8", f9: "F9",
26
- f10: "F10", f11: "F11", f12: "F12", meta: "Meta", command: "Meta",
27
- }
26
+ f10: "F10", f11: "F11", f12: "F12", meta: "Meta", command: "Meta"
27
+ }.freeze
28
28
 
29
29
  def initialize(page)
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)
@@ -77,33 +114,39 @@ module Ferrum
77
114
  pressed_keys.last.push(key)
78
115
  nil
79
116
  else
80
- _key = KEYS.fetch(KEYS_MAPPING[key.to_sym] || key.to_sym)
81
- _key[:modifiers] = pressed_keys.flatten.map { |k| MODIFIERS[k] }.reduce(0, :|)
82
- to_options(_key)
117
+ key = KEYS.fetch(KEYS_MAPPING[key.to_sym] || key.to_sym)
118
+ key[:modifiers] = pressed_keys.flatten.map { |k| MODIFIERS[k] }.reduce(0, :|)
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|
126
+ key = KEYS[char] || {}
127
+
87
128
  if pressed.empty?
88
- key = KEYS[char] || {}
89
129
  key = key.merge(text: char, unmodifiedText: char)
90
130
  [to_options(key)]
91
131
  else
92
- key = KEYS[char] || {}
93
132
  text = pressed == ["shift"] ? char.upcase : char
94
133
  key = key.merge(
95
134
  text: text,
96
135
  unmodifiedText: text,
97
136
  isKeypad: key["location"] == 3,
98
- modifiers: pressed.map { |k| MODIFIERS[k] }.reduce(0, :|),
137
+ modifiers: pressed.map { |k| MODIFIERS[k] }.reduce(0, :|)
99
138
  )
100
139
 
101
140
  modifiers = pressed.map { |k| to_options(KEYS.fetch(KEYS_MAPPING[k.to_sym])) }
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
data/lib/ferrum/mouse.rb CHANGED
@@ -10,10 +10,50 @@ module Ferrum
10
10
  @x = @y = 0
11
11
  end
12
12
 
13
+ #
14
+ # Scroll page to a given x, y coordinates.
15
+ #
16
+ # @param [Integer] top
17
+ # The pixel along the horizontal axis of the document that you want
18
+ # displayed in the upper left.
19
+ #
20
+ # @param [Integer] left
21
+ # The pixel along the vertical axis of the document that you want
22
+ # displayed in the upper left.
23
+ #
24
+ # @example
25
+ # browser.go_to("https://www.google.com/search?q=Ruby+headless+driver+for+Capybara")
26
+ # browser.mouse.scroll_to(0, 400)
27
+ #
13
28
  def scroll_to(top, left)
14
29
  tap { @page.execute("window.scrollTo(#{top}, #{left})") }
15
30
  end
16
31
 
32
+ #
33
+ # Click given coordinates, fires mouse move, down and up events.
34
+ #
35
+ # @param [Integer] x
36
+ #
37
+ # @param [Integer] y
38
+ #
39
+ # @param [Float] delay
40
+ # Delay between mouse down and mouse up events.
41
+ #
42
+ # @param [Float] wait
43
+ #
44
+ # @param [Hash{Symbol => Object}] options
45
+ # Additional keyword arguments.
46
+ #
47
+ # @option options [:left, :right] :button (:left)
48
+ # The mouse button to click.
49
+ #
50
+ # @option options [Integer] :count (1)
51
+ #
52
+ # @option options [Integer] :modifiers
53
+ # Bitfield for key modifiers. See`keyboard.modifiers`.
54
+ #
55
+ # @return [self]
56
+ #
17
57
  def click(x:, y:, delay: 0, wait: CLICK_WAIT, **options)
18
58
  move(x: x, y: y)
19
59
  down(**options)
@@ -24,21 +64,67 @@ module Ferrum
24
64
  self
25
65
  end
26
66
 
67
+ #
68
+ # Mouse down for given coordinates.
69
+ #
70
+ # @param [Hash{Symbol => Object}] options
71
+ # Additional keyword arguments.
72
+ #
73
+ # @option options [:left, :right] :button (:left)
74
+ # The mouse button to click.
75
+ #
76
+ # @option options [Integer] :count (1)
77
+ #
78
+ # @option options [Integer] :modifiers
79
+ # Bitfield for key modifiers. See`keyboard.modifiers`.
80
+ #
81
+ # @return [self]
82
+ #
27
83
  def down(**options)
28
84
  tap { mouse_event(type: "mousePressed", **options) }
29
85
  end
30
86
 
87
+ #
88
+ # Mouse up for given coordinates.
89
+ #
90
+ # @param [Hash{Symbol => Object}] options
91
+ # Additional keyword arguments.
92
+ #
93
+ # @option options [:left, :right] :button (:left)
94
+ # The mouse button to click.
95
+ #
96
+ # @option options [Integer] :count (1)
97
+ #
98
+ # @option options [Integer] :modifiers
99
+ # Bitfield for key modifiers. See`keyboard.modifiers`.
100
+ #
101
+ # @return [self]
102
+ #
31
103
  def up(**options)
32
104
  tap { mouse_event(type: "mouseReleased", **options) }
33
105
  end
34
106
 
107
+ #
108
+ # Mouse move to given x and y.
109
+ #
110
+ # @param [Integer] x
111
+ #
112
+ # @param [Integer] y
113
+ #
114
+ # @param [Integer] steps
115
+ # Sends intermediate mousemove events.
116
+ #
117
+ # @return [self]
118
+ #
35
119
  def move(x:, y:, steps: 1)
36
- from_x, from_y = @x, @y
37
- @x, @y = x, y
120
+ from_x = @x
121
+ from_y = @y
122
+ @x = x
123
+ @y = y
38
124
 
39
125
  steps.times do |i|
40
- new_x = from_x + (@x - from_x) * ((i + 1) / steps.to_f)
41
- new_y = from_y + (@y - from_y) * ((i + 1) / steps.to_f)
126
+ new_x = from_x + ((@x - from_x) * ((i + 1) / steps.to_f))
127
+ new_y = from_y + ((@y - from_y) * ((i + 1) / steps.to_f))
42
128
 
43
129
  @page.command("Input.dispatchMouseEvent",
44
130
  slowmoable: true,
@@ -61,9 +147,8 @@ module Ferrum
61
147
 
62
148
  def validate_button(button)
63
149
  button = button.to_s
64
- unless VALID_BUTTONS.include?(button)
65
- raise "Invalid button: #{button}"
66
- end
150
+ raise "Invalid button: #{button}" unless VALID_BUTTONS.include?(button)
151
+
67
152
  button
68
153
  end
69
154
  end
@@ -6,7 +6,8 @@ module Ferrum
6
6
  attr_accessor :request_id, :frame_id, :resource_type
7
7
 
8
8
  def initialize(page, params)
9
- @page, @params = page, params
9
+ @page = page
10
+ @params = params
10
11
  @request_id = params["requestId"]
11
12
  @frame_id = params["frameId"]
12
13
  @resource_type = params["resourceType"]
@@ -55,7 +56,11 @@ module Ferrum
55
56
  end
56
57
 
57
58
  def inspect
58
- %(#<#{self.class} @request_id=#{@request_id.inspect} @frame_id=#{@frame_id.inspect} @resource_type=#{@resource_type.inspect} @request=#{@request.inspect}>)
59
+ "#<#{self.class} " \
60
+ "@request_id=#{@request_id.inspect} " \
61
+ "@frame_id=#{@frame_id.inspect} " \
62
+ "@resource_type=#{@resource_type.inspect} " \
63
+ "@request=#{@request.inspect}>"
59
64
  end
60
65
  end
61
66
  end
@@ -3,48 +3,133 @@
3
3
  module Ferrum
4
4
  class Network
5
5
  class Exchange
6
+ # ID of the request.
7
+ #
8
+ # @return String
6
9
  attr_reader :id
10
+
11
+ # The intercepted request.
12
+ #
13
+ # @return [InterceptedRequest, nil]
7
14
  attr_accessor :intercepted_request
8
- attr_accessor :request, :response, :error
9
15
 
16
+ # The request object.
17
+ #
18
+ # @return [Request, nil]
19
+ attr_accessor :request
20
+
21
+ # The response object.
22
+ #
23
+ # @return [Response, nil]
24
+ attr_accessor :response
25
+
26
+ # The error object.
27
+ #
28
+ # @return [Error, nil]
29
+ attr_accessor :error
30
+
31
+ #
32
+ # Initializes the network exchange.
33
+ #
34
+ # @param [Page] page
35
+ #
36
+ # @param [String] id
37
+ #
10
38
  def initialize(page, id)
11
- @page, @id = page, id
39
+ @id = id
40
+ @page = page
12
41
  @intercepted_request = nil
13
42
  @request = @response = @error = nil
14
43
  end
15
44
 
45
+ #
46
+ # Determines if the network exchange was caused by a page navigation
47
+ # event.
48
+ #
49
+ # @param [String] frame_id
50
+ #
51
+ # @return [Boolean]
52
+ #
16
53
  def navigation_request?(frame_id)
17
- request.type?(:document) &&
18
- request.frame_id == frame_id
54
+ request.type?(:document) && request&.frame_id == frame_id
19
55
  end
20
56
 
57
+ #
58
+ # Determines if the network exchange has a request.
59
+ #
60
+ # @return [Boolean]
61
+ #
21
62
  def blank?
22
63
  !request
23
64
  end
24
65
 
66
+ #
67
+ # Determines if the request was intercepted and blocked.
68
+ #
69
+ # @return [Boolean]
70
+ #
25
71
  def blocked?
26
- intercepted_request && intercepted_request.status?(:aborted)
72
+ intercepted? && intercepted_request.status?(:aborted)
27
73
  end
28
74
 
75
+ #
76
+ # Determines if the request was blocked, a response was returned, or if an
77
+ # error occurred.
78
+ #
79
+ # @return [Boolean]
80
+ #
29
81
  def finished?
30
- blocked? || response || error
82
+ blocked? || !response.nil? || !error.nil?
31
83
  end
32
84
 
85
+ #
86
+ # Determines if the network exchange is still not finished.
87
+ #
88
+ # @return [Boolean]
89
+ #
33
90
  def pending?
34
91
  !finished?
35
92
  end
36
93
 
94
+ #
95
+ # Determines if the exchange's request was intercepted.
96
+ #
97
+ # @return [Boolean]
98
+ #
99
+ def intercepted?
100
+ !intercepted_request.nil?
101
+ end
102
+
103
+ #
104
+ # Returns request's URL.
105
+ #
106
+ # @return [String, nil]
107
+ #
108
+ def url
109
+ request&.url
110
+ end
111
+
112
+ #
113
+ # Converts the network exchange into a request, response, and error tuple.
114
+ #
115
+ # @return [Array]
116
+ #
37
117
  def to_a
38
118
  [request, response, error]
39
119
  end
40
120
 
121
+ #
122
+ # Inspects the network exchange.
123
+ #
124
+ # @return [String]
125
+ #
41
126
  def inspect
42
- "#<#{self.class} "\
43
- "@id=#{@id.inspect} "\
44
- "@intercepted_request=#{@intercepted_request.inspect} "\
45
- "@request=#{@request.inspect} "\
46
- "@response=#{@response.inspect} "\
47
- "@error=#{@error.inspect}>"
127
+ "#<#{self.class} " \
128
+ "@id=#{@id.inspect} " \
129
+ "@intercepted_request=#{@intercepted_request.inspect} " \
130
+ "@request=#{@request.inspect} " \
131
+ "@response=#{@response.inspect} " \
132
+ "@error=#{@error.inspect}>"
48
133
  end
49
134
  end
50
135
  end