Skip to content

Commit eef1f40

Browse files
committed
Rework network interception API to allow mutating request/response
This makes it more aligned to Java/.NET and simplifies interface allowing to mutate requests and response in-place. Selenium will then ensure that untouched requests/responses are continued as-is and mutated ones are provided as stubs.
1 parent 319fd1a commit eef1f40

File tree

4 files changed

+124
-150
lines changed

4 files changed

+124
-150
lines changed

rb/lib/selenium/webdriver/common/driver_extensions/has_network_interception.rb

Lines changed: 54 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -28,49 +28,44 @@ module HasNetworkInterception
2828
# a stubbed response instead.
2929
#
3030
# @example Log requests and pass through
31-
# driver.intercept do |request|
31+
# driver.intercept do |request, &continue|
3232
# puts "#{request.method} #{request.url}"
33-
# request.continue
33+
# continue.call(request)
3434
# end
3535
#
3636
# @example Stub requests for images
37-
# driver.intercept do |request|
37+
# driver.intercept do |request, &continue|
3838
# if request.url.match?(/\.png$/)
39-
# request.respond(body: File.read('myfile.png'))
40-
# else
41-
# request.continue
39+
# request.url = 'https://upload.wikimedia.org/wikipedia/commons/d/d5/Selenium_Logo.png'
4240
# end
41+
# continue.call(request)
4342
# end
4443
#
4544
# @example Log responses and pass through
46-
# driver.intercept do |request|
47-
# request.continue do |response|
45+
# driver.intercept do |request, &continue|
46+
# continue.call(request) do |response|
4847
# puts "#{response.code} #{response.body}"
49-
# response.continue
5048
# end
5149
# end
5250
#
5351
# @example Mutate specific response
54-
# driver.intercept do |request|
55-
# request.continue do |response|
56-
# if request.url.include?('/myurl')
57-
# request.respond(body: "#{response.body}, Added by Selenium!")
58-
# else
59-
# response.continue
60-
# end
52+
# driver.intercept do |request, &continue|
53+
# continue.call(request) do |response|
54+
# response.body << 'Added by Selenium!' if request.url.include?('/myurl')
6155
# end
6256
# end
6357
#
64-
# @param [#call] block which is called when request is interecepted
65-
# @yieldparam [DevTools::Request]
58+
# @param [Proc] block which is called when request is intercepted
59+
# @yieldparam [DevTools::Request] request
60+
# @yieldparam [Proc] continue block which proceeds with the request and optionally yields response
6661
#
6762

6863
def intercept(&block)
6964
devtools.network.set_cache_disabled(cache_disabled: true)
7065
devtools.fetch.on(:request_paused) do |params|
7166
id = params['requestId']
7267
if params.key?('responseStatusCode') || params.key?('responseErrorReason')
73-
intercept_response(id, params, &intercepted_requests[id].on_response)
68+
intercept_response(id, params, &pending_response_requests.delete(id))
7469
else
7570
intercept_request(id, params, &block)
7671
end
@@ -80,33 +75,53 @@ def intercept(&block)
8075

8176
private
8277

83-
def intercepted_requests
84-
@intercepted_requests ||= {}
78+
def pending_response_requests
79+
@pending_response_requests ||= {}
8580
end
8681

87-
def intercept_request(id, params)
88-
request = DevTools::Request.new(
89-
devtools: devtools,
90-
id: id,
91-
url: params.dig('request', 'url'),
92-
method: params.dig('request', 'method'),
93-
headers: params.dig('request', 'headers')
94-
)
95-
intercepted_requests[id] = request
82+
def intercept_request(id, params, &block)
83+
original = DevTools::Request.from(id, params)
84+
mutable = DevTools::Request.from(id, params)
9685

97-
yield request
86+
block.call(mutable) do |&continue| # rubocop:disable Performance/RedundantBlockCall
87+
pending_response_requests[id] = continue
88+
89+
if original == mutable
90+
devtools.fetch.continue_request(request_id: id)
91+
else
92+
devtools.fetch.continue_request(
93+
request_id: id,
94+
url: mutable.url,
95+
method: mutable.method,
96+
post_data: mutable.post_data,
97+
headers: mutable.headers.map do |k, v|
98+
{name: k, value: v}
99+
end
100+
)
101+
end
102+
end
98103
end
99104

100105
def intercept_response(id, params)
101-
response = DevTools::Response.new(
102-
devtools: devtools,
103-
id: id,
104-
code: params['responseStatusCode'],
105-
headers: params['responseHeaders']
106-
)
107-
intercepted_requests.delete(id)
106+
return devtools.fetch.continue_request(request_id: id) unless block_given?
107+
108+
body = devtools.fetch.get_response_body(request_id: id).dig('result', 'body')
109+
original = DevTools::Response.from(id, body, params)
110+
mutable = DevTools::Response.from(id, body, params)
111+
yield mutable
108112

109-
yield response
113+
if original == mutable
114+
devtools.fetch.continue_request(request_id: id)
115+
else
116+
devtools.fetch.fulfill_request(
117+
request_id: id,
118+
body: Base64.strict_encode64(mutable.body),
119+
response_code: mutable.code,
120+
response_headers: mutable.headers.map do |k, v|
121+
{name: k, value: v}
122+
end
123+
)
124+
end
110125
end
111126
end # HasNetworkInterception
112127
end # DriverExtensions

rb/lib/selenium/webdriver/devtools/request.rb

Lines changed: 25 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -22,54 +22,43 @@ module WebDriver
2222
class DevTools
2323
class Request
2424

25-
attr_reader :url, :method, :headers
25+
attr_accessor :url, :method, :headers, :post_data
26+
attr_reader :id
2627

28+
#
29+
# Creates request from DevTools message.
2730
# @api private
28-
attr_reader :on_response
31+
#
32+
33+
def self.from(id, params)
34+
new(
35+
id: id,
36+
url: params.dig('request', 'url'),
37+
method: params.dig('request', 'method'),
38+
headers: params.dig('request', 'headers'),
39+
post_data: params.dig('request', 'postData')
40+
)
41+
end
2942

30-
def initialize(devtools:, id:, url:, method:, headers:)
31-
@devtools = devtools
43+
def initialize(id:, url:, method:, headers:, post_data:)
3244
@id = id
3345
@url = url
3446
@method = method
3547
@headers = headers
36-
@on_response = Proc.new(&:continue)
37-
end
38-
39-
#
40-
# Continues the request, optionally yielding
41-
# the response before it reaches the browser.
42-
#
43-
# @param [#call] block which is called when response is intercepted
44-
# @yieldparam [DevTools::Response]
45-
#
46-
47-
def continue(&block)
48-
@on_response = block if block_given?
49-
@devtools.fetch.continue_request(request_id: @id)
48+
@post_data = post_data
5049
end
5150

52-
#
53-
# Fulfills the request providing the stubbed response.
54-
#
55-
# @param [Integer] code
56-
# @param [Hash] headers
57-
# @param [String] body
58-
#
59-
60-
def respond(code: 200, headers: {}, body: '')
61-
@devtools.fetch.fulfill_request(
62-
request_id: @id,
63-
body: Base64.strict_encode64(body),
64-
response_code: code,
65-
response_headers: headers.map do |k, v|
66-
{name: k, value: v}
67-
end
68-
)
51+
def ==(other)
52+
self.class == other.class &&
53+
id == other.id &&
54+
url == other.url &&
55+
method == other.method &&
56+
headers == other.headers &&
57+
post_data == other.post_data
6958
end
7059

7160
def inspect
72-
%(#<#{self.class.name} @method="#{method}" @url="#{url}")
61+
%(#<#{self.class.name} @id="#{id}" @method="#{method}" @url="#{url}")
7362
end
7463

7564
end # Request

rb/lib/selenium/webdriver/devtools/response.rb

Lines changed: 26 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -22,39 +22,42 @@ module WebDriver
2222
class DevTools
2323
class Response
2424

25-
attr_reader :code, :headers
26-
27-
def initialize(devtools:, id:, code:, headers:)
28-
@devtools = devtools
29-
@id = id
30-
@code = code
31-
@headers = headers
32-
end
25+
attr_accessor :code, :body, :headers
26+
attr_reader :id
3327

3428
#
35-
# Returns the response body.
36-
# @return [String]
29+
# Creates response from DevTools message.
30+
# @api private
3731
#
3832

39-
def body
40-
@body ||= begin
41-
result = @devtools.fetch.get_response_body(request_id: @id)
42-
encoded_body = result.dig('result', 'body')
43-
44-
Base64.strict_decode64(encoded_body)
45-
end
33+
def self.from(id, encoded_body, params)
34+
new(
35+
id: id,
36+
code: params['responseStatusCode'],
37+
body: Base64.strict_decode64(encoded_body),
38+
headers: params['responseHeaders'].each_with_object({}) do |header, hash|
39+
hash[header['name']] = header['value']
40+
end
41+
)
4642
end
4743

48-
#
49-
# Continues the response unmodified.
50-
#
44+
def initialize(id:, code:, body:, headers:)
45+
@id = id
46+
@code = code
47+
@body = body
48+
@headers = headers
49+
end
5150

52-
def continue
53-
@devtools.fetch.continue_request(request_id: @id)
51+
def ==(other)
52+
self.class == other.class &&
53+
id == other.id &&
54+
code == other.code &&
55+
body == other.body &&
56+
headers == other.headers
5457
end
5558

5659
def inspect
57-
%(#<#{self.class.name} @code="#{code}")
60+
%(#<#{self.class.name} @id="#{id}" @code="#{code}")
5861
end
5962

6063
end # Response

rb/spec/integration/selenium/webdriver/devtools_spec.rb

Lines changed: 19 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -149,79 +149,46 @@ module WebDriver
149149

150150
context 'network interception', except: {browser: :firefox_nightly,
151151
reason: 'Fetch.enable is not yet supported'} do
152-
it 'allows to continue requests' do
152+
it 'continues requests' do
153153
requests = []
154-
driver.intercept do |request|
154+
driver.intercept do |request, &continue|
155155
requests << request
156-
request.continue
156+
continue.call(request)
157157
end
158158
driver.navigate.to url_for('html5Page.html')
159159
expect(driver.title).to eq('HTML5')
160160
expect(requests).not_to be_empty
161161
end
162162

163-
it 'allows to stub requests' do
164-
driver.intercept do |request|
165-
request.respond(body: '<title>Intercepted!</title>')
166-
end
167-
driver.navigate.to url_for('html5Page.html')
168-
expect(driver.title).to eq('Intercepted!')
169-
end
170-
171-
it 'intercepts specific requests' do
172-
stubbed = []
173-
continued = []
174-
driver.intercept do |request|
175-
if request.method == 'GET' && request.url.include?('resultPage.html')
176-
stubbed << request
177-
request.respond(body: '<title>Intercepted!</title>')
178-
else
179-
continued << request
180-
request.continue
163+
it 'changes requests' do
164+
driver.intercept do |request, &continue|
165+
uri = URI(request.url)
166+
if uri.path.match?(%r{/html5/.*\.jpg})
167+
uri.path = '/beach.jpg'
168+
request.url = uri.to_s
181169
end
170+
continue.call(request)
182171
end
183-
184-
driver.navigate.to url_for('formPage.html')
185-
expect(driver.title).to eq('We Leave From Here')
186-
expect(stubbed).to be_empty
187-
expect(continued).not_to be_empty
188-
189-
driver.find_element(id: 'submitButton').click
190-
expect(driver.title).to eq('Intercepted!')
191-
expect(stubbed).not_to be_empty
172+
driver.navigate.to url_for('html5Page.html')
173+
expect(driver.find_elements(tag_name: 'img').map(&:size).uniq).to eq([Dimension.new(640, 480)])
192174
end
193175

194-
it 'allows to continue responses' do
176+
it 'continues responses' do
195177
responses = []
196-
driver.intercept do |request|
197-
request.continue do |response|
178+
driver.intercept do |request, &continue|
179+
continue.call(request) do |response|
198180
responses << response
199-
response.continue
200181
end
201182
end
202183
driver.navigate.to url_for('html5Page.html')
203184
expect(driver.title).to eq('HTML5')
204185
expect(responses).not_to be_empty
205186
end
206187

207-
it 'allows to stub responses' do
208-
driver.intercept do |request|
209-
request.continue do |response|
210-
request.respond(body: "#{response.body}, '<h4 id=\"appended\">Appended!</h4>'")
211-
end
212-
end
213-
driver.navigate.to url_for('html5Page.html')
214-
expect(driver.find_elements(id: "appended")).not_to be_empty
215-
end
216-
217-
it 'intercepts specific responses' do
218-
driver.intercept do |request|
219-
request.continue do |response|
220-
if request.url.include?('html5Page.html')
221-
request.respond(body: "#{response.body}, '<h4 id=\"appended\">Appended!</h4>'")
222-
else
223-
response.continue
224-
end
188+
it 'changes responses' do
189+
driver.intercept do |request, &continue|
190+
continue.call(request) do |response|
191+
response.body << '<h4 id="appended">Appended!</h4>' if request.url.include?('html5Page.html')
225192
end
226193
end
227194
driver.navigate.to url_for('html5Page.html')

0 commit comments

Comments
 (0)