From 24e4c2496c52ede1f6d9404d79335fe7f69b9999 Mon Sep 17 00:00:00 2001 From: Doug McInnes Date: Wed, 6 Jun 2012 10:57:49 -0700 Subject: [PATCH 1/8] keep track of outgoing connections Used PhantomJS's onResourceRequested callback to capture requests. Added driver requested_resources() endpoint that returns an array of outgoing page requests and accepts an optional filter. --- lib/capybara/poltergeist/browser.rb | 4 ++ .../poltergeist/client/browser.coffee | 10 +++++ .../poltergeist/client/compiled/browser.js | 17 ++++++++ .../poltergeist/client/compiled/web_page.js | 9 ++++ .../poltergeist/client/web_page.coffee | 8 ++++ lib/capybara/poltergeist/driver.rb | 4 ++ spec/integration/driver_spec.rb | 42 +++++++++++++++++++ 7 files changed, 94 insertions(+) diff --git a/lib/capybara/poltergeist/browser.rb b/lib/capybara/poltergeist/browser.rb index 4d7b937..e49c7be 100644 --- a/lib/capybara/poltergeist/browser.rb +++ b/lib/capybara/poltergeist/browser.rb @@ -111,6 +111,10 @@ module Capybara::Poltergeist command 'resize', width, height end + def requested_resources(filter=nil) + command 'requestedResources', filter + end + def command(name, *args) message = { 'name' => name, 'args' => args } log message.inspect diff --git a/lib/capybara/poltergeist/client/browser.coffee b/lib/capybara/poltergeist/client/browser.coffee index 5e611c1..2046f48 100644 --- a/lib/capybara/poltergeist/client/browser.coffee +++ b/lib/capybara/poltergeist/client/browser.coffee @@ -191,6 +191,16 @@ class Poltergeist.Browser @page.setViewportSize(width: width, height: height) this.sendResponse(true) + requestedResources: (filter) -> + if filter + matches = [] + for request in @page.requestedResources() + matches.push(request) if request.match(filter) + else + matches = @page.requestedResources() + + this.sendResponse(matches) + exit: -> phantom.exit() diff --git a/lib/capybara/poltergeist/client/compiled/browser.js b/lib/capybara/poltergeist/client/compiled/browser.js index 29060dd..aecacac 100644 --- a/lib/capybara/poltergeist/client/compiled/browser.js +++ b/lib/capybara/poltergeist/client/compiled/browser.js @@ -237,6 +237,23 @@ Poltergeist.Browser = (function() { return this.sendResponse(true); }; + Browser.prototype.requestedResources = function(filter) { + var matches, request, _i, _len, _ref; + if (filter) { + matches = []; + _ref = this.page.requestedResources(); + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + request = _ref[_i]; + if (request.match(filter)) { + matches.push(request); + } + } + } else { + matches = this.page.requestedResources(); + } + return this.sendResponse(matches); + }; + Browser.prototype.exit = function() { return phantom.exit(); }; diff --git a/lib/capybara/poltergeist/client/compiled/web_page.js b/lib/capybara/poltergeist/client/compiled/web_page.js index 8369a80..e13e257 100644 --- a/lib/capybara/poltergeist/client/compiled/web_page.js +++ b/lib/capybara/poltergeist/client/compiled/web_page.js @@ -15,6 +15,7 @@ Poltergeist.WebPage = (function() { this["native"] = require('webpage').create(); this._source = ""; this._errors = []; + this._requestedResources = []; this.setViewportSize({ width: width, height: height @@ -93,6 +94,14 @@ Poltergeist.WebPage = (function() { }); }; + WebPage.prototype.onResourceRequestedNative = function(request) { + return this._requestedResources.push(request.url); + }; + + WebPage.prototype.requestedResources = function() { + return this._requestedResources; + }; + WebPage.prototype.content = function() { return this["native"].content; }; diff --git a/lib/capybara/poltergeist/client/web_page.coffee b/lib/capybara/poltergeist/client/web_page.coffee index 8bd16e8..aba52aa 100644 --- a/lib/capybara/poltergeist/client/web_page.coffee +++ b/lib/capybara/poltergeist/client/web_page.coffee @@ -11,6 +11,7 @@ class Poltergeist.WebPage @native = require('webpage').create() @_source = "" @_errors = [] + @_requestedResources = [] this.setViewportSize(width: width, height: height) @@ -58,6 +59,13 @@ class Poltergeist.WebPage onErrorNative: (message, stack) -> @_errors.push(message: message, stack: stack) + # capture any outgoing requests + onResourceRequestedNative: (request) -> + @_requestedResources.push(request.url) + + requestedResources: -> + @_requestedResources + content: -> @native.content diff --git a/lib/capybara/poltergeist/driver.rb b/lib/capybara/poltergeist/driver.rb index ca91034..742dd18 100644 --- a/lib/capybara/poltergeist/driver.rb +++ b/lib/capybara/poltergeist/driver.rb @@ -110,6 +110,10 @@ module Capybara::Poltergeist browser.resize(width, height) end + def requested_resources(filter=nil) + browser.requested_resources(filter) + end + def debug inspector.open pause diff --git a/spec/integration/driver_spec.rb b/spec/integration/driver_spec.rb index e6c926b..6da2fe8 100644 --- a/spec/integration/driver_spec.rb +++ b/spec/integration/driver_spec.rb @@ -174,5 +174,47 @@ module Capybara::Poltergeist expect { driver.execute_script "" }.to_not raise_error(JavascriptError) end end + + context "requested resources" do + before do + @driver.restart + end + + it "keeps track of outgoing resource requests" do + @driver.visit('/poltergeist/with_js') + urls = @driver.requested_resources + + urls.grep(%r{/poltergeist/jquery-1.6.2.min.js$}).size.should == 1 + urls.grep(%r{/poltergeist/jquery-ui-1.8.14.min.js$}).size.should == 1 + urls.grep(%r{/poltergeist/test.js$}).size.should == 1 + end + + it "keeps a running list between multiple web page views" do + @driver.visit('/poltergeist/with_js') + @driver.requested_resources.length.should equal(4) + + @driver.visit('/poltergeist/with_js') + @driver.requested_resources.length.should equal(8) + end + + it "gets cleared on restart" do + @driver.visit('/poltergeist/with_js') + @driver.requested_resources.length.should equal(4) + + @driver.restart + + @driver.visit('/poltergeist/with_js') + @driver.requested_resources.length.should equal(4) + end + + it "supports filtering" do + @driver.visit('/poltergeist/with_js') + urls = @driver.requested_resources('jquery') + + urls.length.should equal(2) + urls.grep(%r{/poltergeist/jquery-1.6.2.min.js$}).size.should == 1 + urls.grep(%r{/poltergeist/jquery-ui-1.8.14.min.js$}).size.should == 1 + end + end end end From 23dbebfe520e1ab0bc0fbe4e3049dbcb97089b8b Mon Sep 17 00:00:00 2001 From: Doug McInnes Date: Wed, 6 Jun 2012 16:57:06 -0700 Subject: [PATCH 2/8] keep track of responses as well --- lib/capybara/poltergeist/browser.rb | 4 +-- .../poltergeist/client/browser.coffee | 8 +++--- .../poltergeist/client/compiled/browser.js | 14 +++++------ .../poltergeist/client/compiled/web_page.js | 21 +++++++++++++--- .../poltergeist/client/web_page.coffee | 25 +++++++++++++------ lib/capybara/poltergeist/driver.rb | 4 +-- spec/integration/driver_spec.rb | 19 ++++++++------ 7 files changed, 61 insertions(+), 34 deletions(-) diff --git a/lib/capybara/poltergeist/browser.rb b/lib/capybara/poltergeist/browser.rb index e49c7be..925979c 100644 --- a/lib/capybara/poltergeist/browser.rb +++ b/lib/capybara/poltergeist/browser.rb @@ -111,8 +111,8 @@ module Capybara::Poltergeist command 'resize', width, height end - def requested_resources(filter=nil) - command 'requestedResources', filter + def network_traffic(filter=nil) + command 'networkTraffic', filter end def command(name, *args) diff --git a/lib/capybara/poltergeist/client/browser.coffee b/lib/capybara/poltergeist/client/browser.coffee index 2046f48..44bd0f5 100644 --- a/lib/capybara/poltergeist/client/browser.coffee +++ b/lib/capybara/poltergeist/client/browser.coffee @@ -191,13 +191,13 @@ class Poltergeist.Browser @page.setViewportSize(width: width, height: height) this.sendResponse(true) - requestedResources: (filter) -> + networkTraffic: (filter) -> if filter matches = [] - for request in @page.requestedResources() - matches.push(request) if request.match(filter) + for traffic in @page.networkTraffic() + matches.push(traffic) if traffic.request.match(filter) else - matches = @page.requestedResources() + matches = @page.networkTraffic() this.sendResponse(matches) diff --git a/lib/capybara/poltergeist/client/compiled/browser.js b/lib/capybara/poltergeist/client/compiled/browser.js index aecacac..7da4413 100644 --- a/lib/capybara/poltergeist/client/compiled/browser.js +++ b/lib/capybara/poltergeist/client/compiled/browser.js @@ -237,19 +237,19 @@ Poltergeist.Browser = (function() { return this.sendResponse(true); }; - Browser.prototype.requestedResources = function(filter) { - var matches, request, _i, _len, _ref; + Browser.prototype.networkTraffic = function(filter) { + var matches, traffic, _i, _len, _ref; if (filter) { matches = []; - _ref = this.page.requestedResources(); + _ref = this.page.networkTraffic(); for (_i = 0, _len = _ref.length; _i < _len; _i++) { - request = _ref[_i]; - if (request.match(filter)) { - matches.push(request); + traffic = _ref[_i]; + if (traffic.request.match(filter)) { + matches.push(traffic); } } } else { - matches = this.page.requestedResources(); + matches = this.page.networkTraffic(); } return this.sendResponse(matches); }; diff --git a/lib/capybara/poltergeist/client/compiled/web_page.js b/lib/capybara/poltergeist/client/compiled/web_page.js index e13e257..e4eac6d 100644 --- a/lib/capybara/poltergeist/client/compiled/web_page.js +++ b/lib/capybara/poltergeist/client/compiled/web_page.js @@ -15,7 +15,7 @@ Poltergeist.WebPage = (function() { this["native"] = require('webpage').create(); this._source = ""; this._errors = []; - this._requestedResources = []; + this._networkTraffic = []; this.setViewportSize({ width: width, height: height @@ -95,11 +95,24 @@ Poltergeist.WebPage = (function() { }; WebPage.prototype.onResourceRequestedNative = function(request) { - return this._requestedResources.push(request.url); + return this._networkTraffic[request.id] = { + request: request, + startReply: null, + endReply: null + }; }; - WebPage.prototype.requestedResources = function() { - return this._requestedResources; + WebPage.prototype.onResourceReceivedNative = function(response) { + if (response.stage === 'start') { + this._networkTraffic[response.id].startReply = response; + } + if (response.stage === 'end') { + return this._networkTraffic[response.id].endReply = response; + } + }; + + WebPage.prototype.networkTraffic = function() { + return this._networkTraffic; }; WebPage.prototype.content = function() { diff --git a/lib/capybara/poltergeist/client/web_page.coffee b/lib/capybara/poltergeist/client/web_page.coffee index aba52aa..4a1e923 100644 --- a/lib/capybara/poltergeist/client/web_page.coffee +++ b/lib/capybara/poltergeist/client/web_page.coffee @@ -8,10 +8,10 @@ class Poltergeist.WebPage @COMMANDS = ['currentUrl', 'find', 'nodeCall', 'pushFrame', 'popFrame', 'documentSize'] constructor: (width, height) -> - @native = require('webpage').create() - @_source = "" - @_errors = [] - @_requestedResources = [] + @native = require('webpage').create() + @_source = "" + @_errors = [] + @_networkTraffic = [] this.setViewportSize(width: width, height: height) @@ -61,10 +61,21 @@ class Poltergeist.WebPage # capture any outgoing requests onResourceRequestedNative: (request) -> - @_requestedResources.push(request.url) + @_networkTraffic[request.id] = { + request: request, + startReply: null, + endReply: null + } - requestedResources: -> - @_requestedResources + # capture request responses + onResourceReceivedNative: (response) -> + if response.stage == 'start' + @_networkTraffic[response.id].startReply = response + if response.stage == 'end' + @_networkTraffic[response.id].endReply = response + + networkTraffic: -> + @_networkTraffic content: -> @native.content diff --git a/lib/capybara/poltergeist/driver.rb b/lib/capybara/poltergeist/driver.rb index 742dd18..66851e7 100644 --- a/lib/capybara/poltergeist/driver.rb +++ b/lib/capybara/poltergeist/driver.rb @@ -110,8 +110,8 @@ module Capybara::Poltergeist browser.resize(width, height) end - def requested_resources(filter=nil) - browser.requested_resources(filter) + def network_traffic(filter=nil) + browser.network_traffic(filter) end def debug diff --git a/spec/integration/driver_spec.rb b/spec/integration/driver_spec.rb index 6da2fe8..9deba11 100644 --- a/spec/integration/driver_spec.rb +++ b/spec/integration/driver_spec.rb @@ -175,41 +175,44 @@ module Capybara::Poltergeist end end - context "requested resources" do + context "network traffic" do before do @driver.restart end - it "keeps track of outgoing resource requests" do + it "keeps track of network traffic" do @driver.visit('/poltergeist/with_js') - urls = @driver.requested_resources + urls = @driver.network_traffic.map(&:request) urls.grep(%r{/poltergeist/jquery-1.6.2.min.js$}).size.should == 1 urls.grep(%r{/poltergeist/jquery-ui-1.8.14.min.js$}).size.should == 1 urls.grep(%r{/poltergeist/test.js$}).size.should == 1 end + it "captures start and end receive events" do + end + it "keeps a running list between multiple web page views" do @driver.visit('/poltergeist/with_js') - @driver.requested_resources.length.should equal(4) + @driver.network_traffic.length.should equal(4) @driver.visit('/poltergeist/with_js') - @driver.requested_resources.length.should equal(8) + @driver.network_traffic.length.should equal(8) end it "gets cleared on restart" do @driver.visit('/poltergeist/with_js') - @driver.requested_resources.length.should equal(4) + @driver.network_traffic.length.should equal(4) @driver.restart @driver.visit('/poltergeist/with_js') - @driver.requested_resources.length.should equal(4) + @driver.network_traffic.length.should equal(4) end it "supports filtering" do @driver.visit('/poltergeist/with_js') - urls = @driver.requested_resources('jquery') + urls = @driver.network_traffic('jquery').map(&:request) urls.length.should equal(2) urls.grep(%r{/poltergeist/jquery-1.6.2.min.js$}).size.should == 1 From 30360aef922f1cf3fc7e5f2d83076fa6c2e2c693 Mon Sep 17 00:00:00 2001 From: Doug McInnes Date: Thu, 7 Jun 2012 11:49:38 -0700 Subject: [PATCH 3/8] NetworkTraffic model --- lib/capybara/poltergeist.rb | 1 + lib/capybara/poltergeist/browser.rb | 3 +- .../poltergeist/client/browser.coffee | 9 ++-- .../poltergeist/client/compiled/browser.js | 18 +++---- .../poltergeist/client/compiled/web_page.js | 2 +- .../poltergeist/client/web_page.coffee | 2 +- lib/capybara/poltergeist/network_traffic.rb | 49 +++++++++++++++++++ spec/integration/driver_spec.rb | 4 +- 8 files changed, 66 insertions(+), 22 deletions(-) create mode 100644 lib/capybara/poltergeist/network_traffic.rb diff --git a/lib/capybara/poltergeist.rb b/lib/capybara/poltergeist.rb index b41fd55..41dcb0a 100644 --- a/lib/capybara/poltergeist.rb +++ b/lib/capybara/poltergeist.rb @@ -13,6 +13,7 @@ module Capybara autoload :Inspector, 'capybara/poltergeist/inspector' autoload :Spawn, 'capybara/poltergeist/spawn' autoload :JSON, 'capybara/poltergeist/json' + autoload :NetworkTraffic, 'capybara/poltergeist/network_traffic' require 'capybara/poltergeist/errors' end diff --git a/lib/capybara/poltergeist/browser.rb b/lib/capybara/poltergeist/browser.rb index 925979c..b830b29 100644 --- a/lib/capybara/poltergeist/browser.rb +++ b/lib/capybara/poltergeist/browser.rb @@ -112,7 +112,8 @@ module Capybara::Poltergeist end def network_traffic(filter=nil) - command 'networkTraffic', filter + response = command 'networkTraffic', filter + response.map {|event| NetworkTraffic.new(event) } end def command(name, *args) diff --git a/lib/capybara/poltergeist/client/browser.coffee b/lib/capybara/poltergeist/client/browser.coffee index 44bd0f5..e976414 100644 --- a/lib/capybara/poltergeist/client/browser.coffee +++ b/lib/capybara/poltergeist/client/browser.coffee @@ -192,12 +192,9 @@ class Poltergeist.Browser this.sendResponse(true) networkTraffic: (filter) -> - if filter - matches = [] - for traffic in @page.networkTraffic() - matches.push(traffic) if traffic.request.match(filter) - else - matches = @page.networkTraffic() + matches = [] + for id, traffic of @page.networkTraffic() + matches.push(traffic) if !filter || traffic.request.url.match(filter) this.sendResponse(matches) diff --git a/lib/capybara/poltergeist/client/compiled/browser.js b/lib/capybara/poltergeist/client/compiled/browser.js index 7da4413..0fd19c2 100644 --- a/lib/capybara/poltergeist/client/compiled/browser.js +++ b/lib/capybara/poltergeist/client/compiled/browser.js @@ -238,18 +238,14 @@ Poltergeist.Browser = (function() { }; Browser.prototype.networkTraffic = function(filter) { - var matches, traffic, _i, _len, _ref; - if (filter) { - matches = []; - _ref = this.page.networkTraffic(); - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - traffic = _ref[_i]; - if (traffic.request.match(filter)) { - matches.push(traffic); - } + var id, matches, traffic, _ref; + matches = []; + _ref = this.page.networkTraffic(); + for (id in _ref) { + traffic = _ref[id]; + if (!filter || traffic.request.url.match(filter)) { + matches.push(traffic); } - } else { - matches = this.page.networkTraffic(); } return this.sendResponse(matches); }; diff --git a/lib/capybara/poltergeist/client/compiled/web_page.js b/lib/capybara/poltergeist/client/compiled/web_page.js index e4eac6d..3108a07 100644 --- a/lib/capybara/poltergeist/client/compiled/web_page.js +++ b/lib/capybara/poltergeist/client/compiled/web_page.js @@ -15,7 +15,7 @@ Poltergeist.WebPage = (function() { this["native"] = require('webpage').create(); this._source = ""; this._errors = []; - this._networkTraffic = []; + this._networkTraffic = {}; this.setViewportSize({ width: width, height: height diff --git a/lib/capybara/poltergeist/client/web_page.coffee b/lib/capybara/poltergeist/client/web_page.coffee index 4a1e923..3e12723 100644 --- a/lib/capybara/poltergeist/client/web_page.coffee +++ b/lib/capybara/poltergeist/client/web_page.coffee @@ -11,7 +11,7 @@ class Poltergeist.WebPage @native = require('webpage').create() @_source = "" @_errors = [] - @_networkTraffic = [] + @_networkTraffic = {} this.setViewportSize(width: width, height: height) diff --git a/lib/capybara/poltergeist/network_traffic.rb b/lib/capybara/poltergeist/network_traffic.rb new file mode 100644 index 0000000..3f973aa --- /dev/null +++ b/lib/capybara/poltergeist/network_traffic.rb @@ -0,0 +1,49 @@ +module Capybara + module Poltergeist + + # holds information about a single request the browser performed + class NetworkTraffic + + Request = Struct.new(:url, + :method, + :headers, + :time) + + Response = Struct.new(:status, + :status_text, + :headers, + :redirect_url, + :body_size, + :time) + + attr_reader :request, :response + + def initialize request_info + @request = construct_from_hash Request, request_info['request'] + @response = construct_from_hash Response, request_info['endReply'] + @response.body_size = request_info['startReply']['bodySize'] + end + + def url + request.url + end + + private + + def construct_from_hash struct, hash + object = struct.new + hash.each_pair do |key, value| + setter = "#{underscorize(key)}=" + object.send(setter, value) if object.respond_to? setter + end + object + end + + def underscorize string + string.gsub(/(.)([A-Z])/, '\1_\2').downcase + end + + end + + end +end diff --git a/spec/integration/driver_spec.rb b/spec/integration/driver_spec.rb index 9deba11..768acff 100644 --- a/spec/integration/driver_spec.rb +++ b/spec/integration/driver_spec.rb @@ -182,7 +182,7 @@ module Capybara::Poltergeist it "keeps track of network traffic" do @driver.visit('/poltergeist/with_js') - urls = @driver.network_traffic.map(&:request) + urls = @driver.network_traffic.map(&:url) urls.grep(%r{/poltergeist/jquery-1.6.2.min.js$}).size.should == 1 urls.grep(%r{/poltergeist/jquery-ui-1.8.14.min.js$}).size.should == 1 @@ -212,7 +212,7 @@ module Capybara::Poltergeist it "supports filtering" do @driver.visit('/poltergeist/with_js') - urls = @driver.network_traffic('jquery').map(&:request) + urls = @driver.network_traffic('jquery').map(&:url) urls.length.should equal(2) urls.grep(%r{/poltergeist/jquery-1.6.2.min.js$}).size.should == 1 From 81e90abf22658a2377bc9d0efa7687791762c9f5 Mon Sep 17 00:00:00 2001 From: Doug McInnes Date: Thu, 7 Jun 2012 14:02:34 -0700 Subject: [PATCH 4/8] response integration spec --- spec/integration/driver_spec.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/spec/integration/driver_spec.rb b/spec/integration/driver_spec.rb index 768acff..4643838 100644 --- a/spec/integration/driver_spec.rb +++ b/spec/integration/driver_spec.rb @@ -189,7 +189,11 @@ module Capybara::Poltergeist urls.grep(%r{/poltergeist/test.js$}).size.should == 1 end - it "captures start and end receive events" do + it "captures responses" do + @driver.visit('/poltergeist/with_js') + request = @driver.network_traffic.last + + request.response.status.should == 200 end it "keeps a running list between multiple web page views" do From 800fe3de266ca887b65f0fc1639296500c7eeebf Mon Sep 17 00:00:00 2001 From: Doug McInnes Date: Thu, 7 Jun 2012 14:03:04 -0700 Subject: [PATCH 5/8] parse time into Time objects --- lib/capybara/poltergeist/network_traffic.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/capybara/poltergeist/network_traffic.rb b/lib/capybara/poltergeist/network_traffic.rb index 3f973aa..5f8283c 100644 --- a/lib/capybara/poltergeist/network_traffic.rb +++ b/lib/capybara/poltergeist/network_traffic.rb @@ -22,6 +22,8 @@ module Capybara @request = construct_from_hash Request, request_info['request'] @response = construct_from_hash Response, request_info['endReply'] @response.body_size = request_info['startReply']['bodySize'] + @request.time = Time.parse(@request.time) + @response.time = Time.parse(@response.time) end def url From c83b8f75741a6602e8125bb19eb1e41dedb5e31b Mon Sep 17 00:00:00 2001 From: Doug McInnes Date: Thu, 7 Jun 2012 14:03:23 -0700 Subject: [PATCH 6/8] NetworkTraffic unit test --- spec/support/network_traffic.json | 1 + spec/unit/network_traffic_spec.rb | 40 +++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 spec/support/network_traffic.json create mode 100644 spec/unit/network_traffic_spec.rb diff --git a/spec/support/network_traffic.json b/spec/support/network_traffic.json new file mode 100644 index 0000000..d9e42c1 --- /dev/null +++ b/spec/support/network_traffic.json @@ -0,0 +1 @@ +{"request":{"headers":[{"name":"User-Agent","value":"Mozilla/5.0 (Macintosh; Intel Mac OS X) AppleWebKit/534.34 (KHTML, like Gecko) PhantomJS/1.5.0 Safari/534.34"},{"name":"Accept","value":"*/*"},{"name":"Referer","value":"http://127.0.0.1:62762/poltergeist/with_js"}],"id":4,"method":"GET","time":"2012-06-07T00:34:23.897Z","url":"http://127.0.0.1:62762/poltergeist/test.js"},"startReply":{"bodySize":707,"contentType":"text/html;charset=utf-8","headers":[{"name":"X-Frame-Options","value":"sameorigin"},{"name":"X-Xss-Protection","value":"1; mode=block"},{"name":"Content-Type","value":"text/html;charset=utf-8"},{"name":"Content-Length","value":"707"},{"name":"Server","value":"WEBrick/1.3.1 (Ruby/1.9.2/2012-02-14)"},{"name":"Date","value":"Thu, 07 Jun 2012 00:34:23 GMT"},{"name":"Connection","value":"Keep-Alive"}],"id":4,"redirectURL":null,"stage":"start","status":200,"statusText":"OK ","time":"2012-06-07T00:34:23.908Z","url":"http://127.0.0.1:62762/poltergeist/test.js"},"endReply":{"contentType":"text/html;charset=utf-8","headers":[{"name":"X-Frame-Options","value":"sameorigin"},{"name":"X-Xss-Protection","value":"1; mode=block"},{"name":"Content-Type","value":"text/html;charset=utf-8"},{"name":"Content-Length","value":"707"},{"name":"Server","value":"WEBrick/1.3.1 (Ruby/1.9.2/2012-02-14)"},{"name":"Date","value":"Thu, 07 Jun 2012 00:34:23 GMT"},{"name":"Connection","value":"Keep-Alive"}],"id":4,"redirectURL":null,"stage":"end","status":200,"statusText":"OK ","time":"2012-06-07T00:34:23.908Z","url":"http://127.0.0.1:62762/poltergeist/test.js"}} diff --git a/spec/unit/network_traffic_spec.rb b/spec/unit/network_traffic_spec.rb new file mode 100644 index 0000000..54be31e --- /dev/null +++ b/spec/unit/network_traffic_spec.rb @@ -0,0 +1,40 @@ +require 'spec_helper' +require 'multi_json' + +module Capybara::Poltergeist + describe NetworkTraffic do + + before(:all) do + dir = File.dirname(__FILE__) + "/../support" + json = File.read("#{dir}/network_traffic.json") + @hash = MultiJson.load(json) + @traffic = NetworkTraffic.new(@hash) + end + + it "converts network traffic hash into internal request and response objects" do + @traffic.request.url.should match(/test\.js$/) + @traffic.response.status.should == 200 + end + + it "underscorizes the property names" do + @traffic.response.status_text.should match(/OK/) + end + + it "sets body size on the response for the startReply object" do + @traffic.response.body_size.should == 707 + end + + it "has a url method that returns the url from the response" do + @traffic.url.should match(/test\.js$/) + end + + it "parses the time into Time objects" do + @traffic.request.time.should be_a(Time) + @traffic.response.time.should be_a(Time) + @traffic.request.time.year.should == 2012 + @traffic.response.time.year.should == 2012 + @traffic.request.time.should < @traffic.response.time + end + + end +end From 66fdefda2b40288c712d36fa8415f5783f523c09 Mon Sep 17 00:00:00 2001 From: Doug McInnes Date: Fri, 8 Jun 2012 16:16:06 -0700 Subject: [PATCH 7/8] don't choke if there is no network traffic response --- lib/capybara/poltergeist/network_traffic.rb | 16 ++-- .../support/network_traffic_request_only.json | 1 + spec/unit/network_traffic_spec.rb | 73 ++++++++++++------- 3 files changed, 59 insertions(+), 31 deletions(-) create mode 100644 spec/support/network_traffic_request_only.json diff --git a/lib/capybara/poltergeist/network_traffic.rb b/lib/capybara/poltergeist/network_traffic.rb index 5f8283c..cc89586 100644 --- a/lib/capybara/poltergeist/network_traffic.rb +++ b/lib/capybara/poltergeist/network_traffic.rb @@ -21,9 +21,11 @@ module Capybara def initialize request_info @request = construct_from_hash Request, request_info['request'] @response = construct_from_hash Response, request_info['endReply'] - @response.body_size = request_info['startReply']['bodySize'] - @request.time = Time.parse(@request.time) - @response.time = Time.parse(@response.time) + if request_info['startReply'] + @response.body_size = request_info['startReply']['bodySize'] + end + @request.time = Time.parse(@request.time) if @request.time + @response.time = Time.parse(@response.time) if @response.time end def url @@ -34,9 +36,11 @@ module Capybara def construct_from_hash struct, hash object = struct.new - hash.each_pair do |key, value| - setter = "#{underscorize(key)}=" - object.send(setter, value) if object.respond_to? setter + if hash + hash.each_pair do |key, value| + setter = "#{underscorize(key)}=" + object.send(setter, value) if object.respond_to? setter + end end object end diff --git a/spec/support/network_traffic_request_only.json b/spec/support/network_traffic_request_only.json new file mode 100644 index 0000000..d2dd193 --- /dev/null +++ b/spec/support/network_traffic_request_only.json @@ -0,0 +1 @@ +{"request":{"headers":[{"name":"User-Agent","value":"Mozilla/5.0 (Macintosh; Intel Mac OS X) AppleWebKit/534.34 (KHTML, like Gecko) PhantomJS/1.5.0 Safari/534.34"},{"name":"Accept","value":"*/*"},{"name":"Referer","value":"http://127.0.0.1:62762/poltergeist/with_js"}],"id":4,"method":"GET","time":"2012-06-07T00:34:23.897Z","url":"http://127.0.0.1:62762/poltergeist/test.js"},"startReply":null,"endReply":null} diff --git a/spec/unit/network_traffic_spec.rb b/spec/unit/network_traffic_spec.rb index 54be31e..165a649 100644 --- a/spec/unit/network_traffic_spec.rb +++ b/spec/unit/network_traffic_spec.rb @@ -4,36 +4,59 @@ require 'multi_json' module Capybara::Poltergeist describe NetworkTraffic do - before(:all) do - dir = File.dirname(__FILE__) + "/../support" - json = File.read("#{dir}/network_traffic.json") - @hash = MultiJson.load(json) - @traffic = NetworkTraffic.new(@hash) + describe "full request/response" do + before(:all) do + dir = File.dirname(__FILE__) + "/../support" + json = File.read("#{dir}/network_traffic.json") + @hash = MultiJson.load(json) + @traffic = NetworkTraffic.new(@hash) + end + + it "converts network traffic hash into internal request and response objects" do + @traffic.request.url.should match(/test\.js$/) + @traffic.response.status.should == 200 + end + + it "underscorizes the property names" do + @traffic.response.status_text.should match(/OK/) + end + + it "sets body size on the response for the startReply object" do + @traffic.response.body_size.should == 707 + end + + it "has a url method that returns the url from the response" do + @traffic.url.should match(/test\.js$/) + end + + it "parses the time into Time objects" do + @traffic.request.time.should be_a(Time) + @traffic.response.time.should be_a(Time) + @traffic.request.time.year.should == 2012 + @traffic.response.time.year.should == 2012 + @traffic.request.time.should < @traffic.response.time + end end - it "converts network traffic hash into internal request and response objects" do - @traffic.request.url.should match(/test\.js$/) - @traffic.response.status.should == 200 - end + describe "request only" do + before(:all) do + dir = File.dirname(__FILE__) + "/../support" + json = File.read("#{dir}/network_traffic_request_only.json") + @hash = MultiJson.load(json) + @traffic = NetworkTraffic.new(@hash) + end - it "underscorizes the property names" do - @traffic.response.status_text.should match(/OK/) - end + it "still creates the request object" do + @traffic.request.url.should match(/test\.js$/) + end - it "sets body size on the response for the startReply object" do - @traffic.response.body_size.should == 707 - end + it "creates an empty response object" do + @traffic.response.status.should be_nil + end - it "has a url method that returns the url from the response" do - @traffic.url.should match(/test\.js$/) - end - - it "parses the time into Time objects" do - @traffic.request.time.should be_a(Time) - @traffic.response.time.should be_a(Time) - @traffic.request.time.year.should == 2012 - @traffic.response.time.year.should == 2012 - @traffic.request.time.should < @traffic.response.time + it "keeps time as nil" do + @traffic.response.time.should be_nil + end end end From fb9eac669dd1802e9e91837a3953a0188f0cd65e Mon Sep 17 00:00:00 2001 From: Doug McInnes Date: Thu, 14 Jun 2012 11:50:26 -0700 Subject: [PATCH 8/8] use separate request and response objects --- lib/capybara/poltergeist.rb | 3 +- lib/capybara/poltergeist/browser.rb | 13 +++- .../poltergeist/client/browser.coffee | 8 +-- .../poltergeist/client/compiled/browser.js | 13 +--- .../poltergeist/client/compiled/web_page.js | 10 +-- .../poltergeist/client/web_page.coffee | 10 +-- lib/capybara/poltergeist/driver.rb | 4 +- lib/capybara/poltergeist/network_traffic.rb | 55 ---------------- lib/capybara/poltergeist/request.rb | 30 +++++++++ lib/capybara/poltergeist/response.rb | 44 +++++++++++++ spec/integration/driver_spec.rb | 11 +--- spec/support/network_traffic.json | 1 - .../support/network_traffic_request_only.json | 1 - spec/unit/network_traffic_spec.rb | 63 ------------------- 14 files changed, 98 insertions(+), 168 deletions(-) delete mode 100644 lib/capybara/poltergeist/network_traffic.rb create mode 100644 lib/capybara/poltergeist/request.rb create mode 100644 lib/capybara/poltergeist/response.rb delete mode 100644 spec/support/network_traffic.json delete mode 100644 spec/support/network_traffic_request_only.json delete mode 100644 spec/unit/network_traffic_spec.rb diff --git a/lib/capybara/poltergeist.rb b/lib/capybara/poltergeist.rb index 41dcb0a..14034a2 100644 --- a/lib/capybara/poltergeist.rb +++ b/lib/capybara/poltergeist.rb @@ -13,7 +13,8 @@ module Capybara autoload :Inspector, 'capybara/poltergeist/inspector' autoload :Spawn, 'capybara/poltergeist/spawn' autoload :JSON, 'capybara/poltergeist/json' - autoload :NetworkTraffic, 'capybara/poltergeist/network_traffic' + autoload :Request, 'capybara/poltergeist/request' + autoload :Response, 'capybara/poltergeist/response' require 'capybara/poltergeist/errors' end diff --git a/lib/capybara/poltergeist/browser.rb b/lib/capybara/poltergeist/browser.rb index b830b29..63db13a 100644 --- a/lib/capybara/poltergeist/browser.rb +++ b/lib/capybara/poltergeist/browser.rb @@ -111,9 +111,16 @@ module Capybara::Poltergeist command 'resize', width, height end - def network_traffic(filter=nil) - response = command 'networkTraffic', filter - response.map {|event| NetworkTraffic.new(event) } + def network_traffic + response = command 'networkTraffic' + + response.values.map do |event| + request = Request.new(event['request']) + event['responseParts'].each do |response| + request.response_parts.push(Response.new(response)) + end + request + end end def command(name, *args) diff --git a/lib/capybara/poltergeist/client/browser.coffee b/lib/capybara/poltergeist/client/browser.coffee index e976414..3c9d9f6 100644 --- a/lib/capybara/poltergeist/client/browser.coffee +++ b/lib/capybara/poltergeist/client/browser.coffee @@ -191,12 +191,8 @@ class Poltergeist.Browser @page.setViewportSize(width: width, height: height) this.sendResponse(true) - networkTraffic: (filter) -> - matches = [] - for id, traffic of @page.networkTraffic() - matches.push(traffic) if !filter || traffic.request.url.match(filter) - - this.sendResponse(matches) + networkTraffic: -> + this.sendResponse(@page.networkTraffic()) exit: -> phantom.exit() diff --git a/lib/capybara/poltergeist/client/compiled/browser.js b/lib/capybara/poltergeist/client/compiled/browser.js index 0fd19c2..40baa4b 100644 --- a/lib/capybara/poltergeist/client/compiled/browser.js +++ b/lib/capybara/poltergeist/client/compiled/browser.js @@ -237,17 +237,8 @@ Poltergeist.Browser = (function() { return this.sendResponse(true); }; - Browser.prototype.networkTraffic = function(filter) { - var id, matches, traffic, _ref; - matches = []; - _ref = this.page.networkTraffic(); - for (id in _ref) { - traffic = _ref[id]; - if (!filter || traffic.request.url.match(filter)) { - matches.push(traffic); - } - } - return this.sendResponse(matches); + Browser.prototype.networkTraffic = function() { + return this.sendResponse(this.page.networkTraffic()); }; Browser.prototype.exit = function() { diff --git a/lib/capybara/poltergeist/client/compiled/web_page.js b/lib/capybara/poltergeist/client/compiled/web_page.js index 3108a07..61912ea 100644 --- a/lib/capybara/poltergeist/client/compiled/web_page.js +++ b/lib/capybara/poltergeist/client/compiled/web_page.js @@ -97,18 +97,12 @@ Poltergeist.WebPage = (function() { WebPage.prototype.onResourceRequestedNative = function(request) { return this._networkTraffic[request.id] = { request: request, - startReply: null, - endReply: null + responseParts: [] }; }; WebPage.prototype.onResourceReceivedNative = function(response) { - if (response.stage === 'start') { - this._networkTraffic[response.id].startReply = response; - } - if (response.stage === 'end') { - return this._networkTraffic[response.id].endReply = response; - } + return this._networkTraffic[response.id].responseParts.push(response); }; WebPage.prototype.networkTraffic = function() { diff --git a/lib/capybara/poltergeist/client/web_page.coffee b/lib/capybara/poltergeist/client/web_page.coffee index 3e12723..9e33aca 100644 --- a/lib/capybara/poltergeist/client/web_page.coffee +++ b/lib/capybara/poltergeist/client/web_page.coffee @@ -62,17 +62,13 @@ class Poltergeist.WebPage # capture any outgoing requests onResourceRequestedNative: (request) -> @_networkTraffic[request.id] = { - request: request, - startReply: null, - endReply: null + request: request, + responseParts: [] } # capture request responses onResourceReceivedNative: (response) -> - if response.stage == 'start' - @_networkTraffic[response.id].startReply = response - if response.stage == 'end' - @_networkTraffic[response.id].endReply = response + @_networkTraffic[response.id].responseParts.push(response) networkTraffic: -> @_networkTraffic diff --git a/lib/capybara/poltergeist/driver.rb b/lib/capybara/poltergeist/driver.rb index 66851e7..6b88f00 100644 --- a/lib/capybara/poltergeist/driver.rb +++ b/lib/capybara/poltergeist/driver.rb @@ -110,8 +110,8 @@ module Capybara::Poltergeist browser.resize(width, height) end - def network_traffic(filter=nil) - browser.network_traffic(filter) + def network_traffic + browser.network_traffic end def debug diff --git a/lib/capybara/poltergeist/network_traffic.rb b/lib/capybara/poltergeist/network_traffic.rb deleted file mode 100644 index cc89586..0000000 --- a/lib/capybara/poltergeist/network_traffic.rb +++ /dev/null @@ -1,55 +0,0 @@ -module Capybara - module Poltergeist - - # holds information about a single request the browser performed - class NetworkTraffic - - Request = Struct.new(:url, - :method, - :headers, - :time) - - Response = Struct.new(:status, - :status_text, - :headers, - :redirect_url, - :body_size, - :time) - - attr_reader :request, :response - - def initialize request_info - @request = construct_from_hash Request, request_info['request'] - @response = construct_from_hash Response, request_info['endReply'] - if request_info['startReply'] - @response.body_size = request_info['startReply']['bodySize'] - end - @request.time = Time.parse(@request.time) if @request.time - @response.time = Time.parse(@response.time) if @response.time - end - - def url - request.url - end - - private - - def construct_from_hash struct, hash - object = struct.new - if hash - hash.each_pair do |key, value| - setter = "#{underscorize(key)}=" - object.send(setter, value) if object.respond_to? setter - end - end - object - end - - def underscorize string - string.gsub(/(.)([A-Z])/, '\1_\2').downcase - end - - end - - end -end diff --git a/lib/capybara/poltergeist/request.rb b/lib/capybara/poltergeist/request.rb new file mode 100644 index 0000000..8e79276 --- /dev/null +++ b/lib/capybara/poltergeist/request.rb @@ -0,0 +1,30 @@ +module Capybara::Poltergeist + + class Request + + attr_reader :response_parts + + def initialize(data) + @data = data + @response_parts = [] + end + + def url + @data['url'] + end + + def method + @data['method'] + end + + def headers + @data['headers'] + end + + def time + @data['time'] && Time.parse(@data['time']) + end + + end + +end diff --git a/lib/capybara/poltergeist/response.rb b/lib/capybara/poltergeist/response.rb new file mode 100644 index 0000000..1ee39fa --- /dev/null +++ b/lib/capybara/poltergeist/response.rb @@ -0,0 +1,44 @@ +module Capybara::Poltergeist + + class Response + + def initialize(data) + @data = data + end + + def url + @data['url'] + end + + def status + @data['status'] + end + + def status_text + @data['statusText'] + end + + def headers + @data['headers'] + end + + def redirect_url + @data['redirectUrl'] + end + + def body_size + @data['bodySize'] + end + + def content_type + @data['contentType'] + end + + def time + @data['time'] && Time.parse(@data['time']) + end + + end + +end + diff --git a/spec/integration/driver_spec.rb b/spec/integration/driver_spec.rb index 4643838..b4d23d7 100644 --- a/spec/integration/driver_spec.rb +++ b/spec/integration/driver_spec.rb @@ -193,7 +193,7 @@ module Capybara::Poltergeist @driver.visit('/poltergeist/with_js') request = @driver.network_traffic.last - request.response.status.should == 200 + request.response_parts.last.status.should == 200 end it "keeps a running list between multiple web page views" do @@ -213,15 +213,6 @@ module Capybara::Poltergeist @driver.visit('/poltergeist/with_js') @driver.network_traffic.length.should equal(4) end - - it "supports filtering" do - @driver.visit('/poltergeist/with_js') - urls = @driver.network_traffic('jquery').map(&:url) - - urls.length.should equal(2) - urls.grep(%r{/poltergeist/jquery-1.6.2.min.js$}).size.should == 1 - urls.grep(%r{/poltergeist/jquery-ui-1.8.14.min.js$}).size.should == 1 - end end end end diff --git a/spec/support/network_traffic.json b/spec/support/network_traffic.json deleted file mode 100644 index d9e42c1..0000000 --- a/spec/support/network_traffic.json +++ /dev/null @@ -1 +0,0 @@ -{"request":{"headers":[{"name":"User-Agent","value":"Mozilla/5.0 (Macintosh; Intel Mac OS X) AppleWebKit/534.34 (KHTML, like Gecko) PhantomJS/1.5.0 Safari/534.34"},{"name":"Accept","value":"*/*"},{"name":"Referer","value":"http://127.0.0.1:62762/poltergeist/with_js"}],"id":4,"method":"GET","time":"2012-06-07T00:34:23.897Z","url":"http://127.0.0.1:62762/poltergeist/test.js"},"startReply":{"bodySize":707,"contentType":"text/html;charset=utf-8","headers":[{"name":"X-Frame-Options","value":"sameorigin"},{"name":"X-Xss-Protection","value":"1; mode=block"},{"name":"Content-Type","value":"text/html;charset=utf-8"},{"name":"Content-Length","value":"707"},{"name":"Server","value":"WEBrick/1.3.1 (Ruby/1.9.2/2012-02-14)"},{"name":"Date","value":"Thu, 07 Jun 2012 00:34:23 GMT"},{"name":"Connection","value":"Keep-Alive"}],"id":4,"redirectURL":null,"stage":"start","status":200,"statusText":"OK ","time":"2012-06-07T00:34:23.908Z","url":"http://127.0.0.1:62762/poltergeist/test.js"},"endReply":{"contentType":"text/html;charset=utf-8","headers":[{"name":"X-Frame-Options","value":"sameorigin"},{"name":"X-Xss-Protection","value":"1; mode=block"},{"name":"Content-Type","value":"text/html;charset=utf-8"},{"name":"Content-Length","value":"707"},{"name":"Server","value":"WEBrick/1.3.1 (Ruby/1.9.2/2012-02-14)"},{"name":"Date","value":"Thu, 07 Jun 2012 00:34:23 GMT"},{"name":"Connection","value":"Keep-Alive"}],"id":4,"redirectURL":null,"stage":"end","status":200,"statusText":"OK ","time":"2012-06-07T00:34:23.908Z","url":"http://127.0.0.1:62762/poltergeist/test.js"}} diff --git a/spec/support/network_traffic_request_only.json b/spec/support/network_traffic_request_only.json deleted file mode 100644 index d2dd193..0000000 --- a/spec/support/network_traffic_request_only.json +++ /dev/null @@ -1 +0,0 @@ -{"request":{"headers":[{"name":"User-Agent","value":"Mozilla/5.0 (Macintosh; Intel Mac OS X) AppleWebKit/534.34 (KHTML, like Gecko) PhantomJS/1.5.0 Safari/534.34"},{"name":"Accept","value":"*/*"},{"name":"Referer","value":"http://127.0.0.1:62762/poltergeist/with_js"}],"id":4,"method":"GET","time":"2012-06-07T00:34:23.897Z","url":"http://127.0.0.1:62762/poltergeist/test.js"},"startReply":null,"endReply":null} diff --git a/spec/unit/network_traffic_spec.rb b/spec/unit/network_traffic_spec.rb deleted file mode 100644 index 165a649..0000000 --- a/spec/unit/network_traffic_spec.rb +++ /dev/null @@ -1,63 +0,0 @@ -require 'spec_helper' -require 'multi_json' - -module Capybara::Poltergeist - describe NetworkTraffic do - - describe "full request/response" do - before(:all) do - dir = File.dirname(__FILE__) + "/../support" - json = File.read("#{dir}/network_traffic.json") - @hash = MultiJson.load(json) - @traffic = NetworkTraffic.new(@hash) - end - - it "converts network traffic hash into internal request and response objects" do - @traffic.request.url.should match(/test\.js$/) - @traffic.response.status.should == 200 - end - - it "underscorizes the property names" do - @traffic.response.status_text.should match(/OK/) - end - - it "sets body size on the response for the startReply object" do - @traffic.response.body_size.should == 707 - end - - it "has a url method that returns the url from the response" do - @traffic.url.should match(/test\.js$/) - end - - it "parses the time into Time objects" do - @traffic.request.time.should be_a(Time) - @traffic.response.time.should be_a(Time) - @traffic.request.time.year.should == 2012 - @traffic.response.time.year.should == 2012 - @traffic.request.time.should < @traffic.response.time - end - end - - describe "request only" do - before(:all) do - dir = File.dirname(__FILE__) + "/../support" - json = File.read("#{dir}/network_traffic_request_only.json") - @hash = MultiJson.load(json) - @traffic = NetworkTraffic.new(@hash) - end - - it "still creates the request object" do - @traffic.request.url.should match(/test\.js$/) - end - - it "creates an empty response object" do - @traffic.response.status.should be_nil - end - - it "keeps time as nil" do - @traffic.response.time.should be_nil - end - end - - end -end