ferrum 0.11 → 0.13

Sign up to get free protection for your applications and to get access to all the features.
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