Skip to content

Commit e2eafcf

Browse files
committed
Support mutating responses in network interception
1 parent 336fa13 commit e2eafcf

File tree

5 files changed

+182
-16
lines changed

5 files changed

+182
-16
lines changed

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

Lines changed: 58 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ module HasNetworkInterception
3333
# request.continue
3434
# end
3535
#
36-
# @example Stub response for image requests
36+
# @example Stub requests for images
3737
# driver.intercept do |request|
3838
# if request.url.match?(/\.png$/)
3939
# request.respond(body: File.read('myfile.png'))
@@ -42,25 +42,72 @@ module HasNetworkInterception
4242
# end
4343
# end
4444
#
45+
# @example Log responses and pass through
46+
# driver.intercept do |request|
47+
# request.continue do |response|
48+
# puts "#{response.code} #{response.body}"
49+
# response.continue
50+
# end
51+
# end
52+
#
53+
# @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
61+
# end
62+
# end
63+
#
4564
# @param [#call] block which is called when request is interecepted
4665
# @yieldparam [DevTools::Request]
4766
#
4867

49-
def intercept
68+
def intercept(&block)
5069
devtools.network.set_cache_disabled(cache_disabled: true)
5170
devtools.fetch.on(:request_paused) do |params|
52-
request = DevTools::Request.new(
53-
devtools: devtools,
54-
id: params['requestId'],
55-
url: params.dig('request', 'url'),
56-
method: params.dig('request', 'method'),
57-
headers: params.dig('request', 'headers')
58-
)
59-
yield request
71+
id = params['requestId']
72+
if params.key?('responseStatusCode') || params.key?('responseErrorReason')
73+
intercept_response(id, params, &intercepted_requests[id].on_response)
74+
else
75+
intercept_request(id, params, &block)
76+
end
6077
end
61-
devtools.fetch.enable
78+
devtools.fetch.enable(patterns: [{requestStage: 'Request'}, {requestStage: 'Response'}])
6279
end
6380

81+
private
82+
83+
def intercepted_requests
84+
@intercepted_requests ||= {}
85+
end
86+
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
96+
97+
yield request
98+
end
99+
100+
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)
108+
109+
yield response
110+
end
64111
end # HasNetworkInterception
65112
end # DriverExtensions
66113
end # WebDriver

rb/lib/selenium/webdriver/devtools.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ class DevTools
2525
autoload :MutationEvent, 'selenium/webdriver/devtools/mutation_event'
2626
autoload :PinnedScript, 'selenium/webdriver/devtools/pinned_script'
2727
autoload :Request, 'selenium/webdriver/devtools/request'
28+
autoload :Response, 'selenium/webdriver/devtools/response'
2829

2930
def initialize(url:)
3031
@messages = []

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

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,18 +24,39 @@ class Request
2424

2525
attr_reader :url, :method, :headers
2626

27+
# @api private
28+
attr_reader :on_response
29+
2730
def initialize(devtools:, id:, url:, method:, headers:)
2831
@devtools = devtools
2932
@id = id
3033
@url = url
3134
@method = method
3235
@headers = headers
36+
@on_response = Proc.new(&:continue)
3337
end
3438

35-
def continue
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?
3649
@devtools.fetch.continue_request(request_id: @id)
3750
end
3851

52+
#
53+
# Fulfills the request providing the stubbed response.
54+
#
55+
# @param [Integer] code
56+
# @param [Hash] headers
57+
# @param [String] body
58+
#
59+
3960
def respond(code: 200, headers: {}, body: '')
4061
@devtools.fetch.fulfill_request(
4162
request_id: @id,
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# frozen_string_literal: true
2+
3+
# Licensed to the Software Freedom Conservancy (SFC) under one
4+
# or more contributor license agreements. See the NOTICE file
5+
# distributed with this work for additional information
6+
# regarding copyright ownership. The SFC licenses this file
7+
# to you under the Apache License, Version 2.0 (the
8+
# "License"); you may not use this file except in compliance
9+
# with the License. You may obtain a copy of the License at
10+
#
11+
# http://www.apache.org/licenses/LICENSE-2.0
12+
#
13+
# Unless required by applicable law or agreed to in writing,
14+
# software distributed under the License is distributed on an
15+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16+
# KIND, either express or implied. See the License for the
17+
# specific language governing permissions and limitations
18+
# under the License.
19+
20+
module Selenium
21+
module WebDriver
22+
class DevTools
23+
class Response
24+
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
33+
34+
#
35+
# Returns the response body.
36+
# @return [String]
37+
#
38+
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
46+
end
47+
48+
#
49+
# Continues the response unmodified.
50+
#
51+
52+
def continue
53+
@devtools.fetch.continue_request(request_id: @id)
54+
end
55+
56+
def inspect
57+
%(#<#{self.class.name} @code="#{code}")
58+
end
59+
60+
end # Response
61+
end # DevTools
62+
end # WebDriver
63+
end # Selenium

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

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -160,15 +160,12 @@ module WebDriver
160160
expect(requests).not_to be_empty
161161
end
162162

163-
it 'allows to stub responses' do
164-
requests = []
163+
it 'allows to stub requests' do
165164
driver.intercept do |request|
166-
requests << request
167165
request.respond(body: '<title>Intercepted!</title>')
168166
end
169167
driver.navigate.to url_for('html5Page.html')
170168
expect(driver.title).to eq('Intercepted!')
171-
expect(requests).not_to be_empty
172169
end
173170

174171
it 'intercepts specific requests' do
@@ -193,6 +190,43 @@ module WebDriver
193190
expect(driver.title).to eq('Intercepted!')
194191
expect(stubbed).not_to be_empty
195192
end
193+
194+
it 'allows to continue responses' do
195+
responses = []
196+
driver.intercept do |request|
197+
request.continue do |response|
198+
responses << response
199+
response.continue
200+
end
201+
end
202+
driver.navigate.to url_for('html5Page.html')
203+
expect(driver.title).to eq('HTML5')
204+
expect(responses).not_to be_empty
205+
end
206+
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
225+
end
226+
end
227+
driver.navigate.to url_for('html5Page.html')
228+
expect(driver.find_elements(id: "appended")).not_to be_empty
229+
end
196230
end
197231

198232
context 'script pinning' do

0 commit comments

Comments
 (0)