require 'spec_helper' describe Arachni::Browser do before( :all ) do @url = Arachni::Utilities.normalize_url( web_server_url_for( :browser ) ) end before( :each ) do clear_hit_count @browser = described_class.new end after( :each ) do Arachni::Options.reset Arachni::Framework.reset @browser.shutdown clear_hit_count end let(:subject) { @browser } let(:ua) { Arachni::Options.http.user_agent } def transitions_from_array( transitions ) transitions.map do |t| element, event = t.first.to_a options = {} if element == :page && event == :load options.merge!( url: @browser.watir.url, cookies: {} ) end if element.is_a? Hash element = described_class::ElementLocator.new( element ) end Arachni::Page::DOM::Transition.new( element, event, options ).complete end end def hit_count Typhoeus::Request.get( "#{@url}/hit-count" ).body.to_i end def image_hit_count Typhoeus::Request.get( "#{@url}/image-hit-count" ).body.to_i end def clear_hit_count Typhoeus::Request.get( "#{@url}/clear-hit-count" ) end it 'supports HTTPS' do url = web_server_url_for( :browser_https ) @browser.start_capture pages = @browser.load( url ).flush_pages pages_should_have_form_with_input( pages, 'ajax-token' ) pages_should_have_form_with_input( pages, 'by-ajax' ) end describe '.has_executable?' do context 'when there is no executable browser' do it 'returns false' do Selenium::WebDriver::PhantomJS.stub(:path){ false } described_class.has_executable?.should be_false end end context 'when there is an executable browser' do it 'returns true' do Selenium::WebDriver::PhantomJS.stub(:path){ __FILE__ } described_class.has_executable?.should be_true end end end describe '.executable' do it 'returns the path to the browser executable' do stub = __FILE__ Selenium::WebDriver::PhantomJS.stub(:path){ stub } described_class.executable.should == stub end end describe '#initialize' do describe :concurrency do it 'sets the HTTP request concurrency' end describe :ignore_scope do context true do it 'ignores scope restrictions' do @browser.shutdown @browser = described_class.new( ignore_scope: true ) Arachni::Options.scope.exclude_path_patterns << /sleep/ subject.load @url + '/ajax_sleep' subject.to_page.should be_true end end context false do it 'enforces scope restrictions' do @browser.shutdown @browser = described_class.new( ignore_scope: false ) Arachni::Options.scope.exclude_path_patterns << /sleep/ subject.load @url + '/ajax_sleep' subject.to_page.code.should == 0 end end context :default do it 'enforces scope restrictions' do @browser.shutdown @browser = described_class.new( ignore_scope: false ) Arachni::Options.scope.exclude_path_patterns << /sleep/ subject.load @url + '/ajax_sleep' subject.to_page.code.should == 0 end end end describe :width do it 'sets the window width' do @browser.shutdown width = 100 @browser = described_class.new( width: width ) subject.javascript.run('return window.innerWidth').should == width end it 'defaults to 1600' do subject.javascript.run('return window.innerWidth').should == 1600 end end describe :height do it 'sets the window height' do @browser.shutdown height = 100 @browser = described_class.new( height: height ) subject.javascript.run('return window.innerHeight').should == height end it 'defaults to 1200' do subject.javascript.run('return window.innerHeight').should == 1200 end end describe :store_pages do describe 'default' do it 'stores snapshot pages' do @browser.shutdown @browser = described_class.new @browser.load( @url + '/explore' ).flush_pages.should be_any end it 'stores captured pages' do @browser.shutdown @browser = described_class.new @browser.start_capture @browser.load( @url + '/with-ajax' ).flush_pages.should be_any end end describe true do it 'stores snapshot pages' do @browser.shutdown @browser = described_class.new( store_pages: true ) @browser.load( @url + '/explore' ).trigger_events.flush_pages.should be_any end it 'stores captured pages' do @browser.shutdown @browser = described_class.new( store_pages: true ) @browser.start_capture @browser.load( @url + '/with-ajax' ).flush_pages.should be_any end end describe false do it 'stores snapshot pages' do @browser.shutdown @browser = described_class.new( store_pages: false ) @browser.load( @url + '/explore' ).trigger_events.flush_pages.should be_empty end it 'stores captured pages' do @browser.shutdown @browser = described_class.new( store_pages: false ) @browser.start_capture @browser.load( @url + '/with-ajax' ).flush_pages.should be_empty end end end context 'when browser process spawn fails' do it "raises #{described_class::Error::Spawn}" do described_class.any_instance.stub(:spawn_phantomjs) { nil } expect { described_class.new }.to raise_error described_class::Error::Spawn end end end describe '#source_with_line_numbers' do it 'prefixes each source code line with a number' do subject.load @url lines = subject.source.lines.to_a lines.should be_any subject.source_with_line_numbers.lines.each.with_index do |l, i| l.should == "#{i+1} - #{lines[i]}" end end end describe '#load_delay' do it 'returns nil' do subject.load @url subject.load_delay.should be_nil end context 'when the page has JS timeouts' do it 'returns the maximum time the browser should wait for the page based on Timeout' do subject.load( "#{@url}load_delay" ) subject.load_delay.should == 2000 end end end describe '#wait_for_timers' do it 'returns' do subject.load @url subject.wait_for_timers.should be_nil end context 'when the page has JS timeouts' do it 'waits for them to complete' do subject.load( "#{@url}load_delay" ) seconds = subject.load_delay / 1000 time = Time.now subject.wait_for_timers (Time.now - time).should > seconds end it "caps them at #{Arachni::OptionGroups::HTTP}#request_timeout" do subject.load( "#{@url}load_delay" ) Arachni::Options.http.request_timeout = 100 time = Time.now subject.wait_for_timers (Time.now - time).should < 0.2 end end end describe '#capture_snapshot' do let(:sink_url) do "#{@url}script_sink?input=#{@browser.javascript.log_execution_flow_sink_stub(1)}" end let(:ajax_url) do "#{@url}with-ajax" end let(:captured) { subject.capture_snapshot } context 'when a snapshot has not been previously seen' do before :each do subject.load( @url + '/with-ajax', take_snapshot: false ) end it 'calls #on_new_page callbacks' do received = [] subject.on_new_page do |page| received << page end captured.should == received end context '#store_pages?' do context true do subject { @browser.shutdown; @browser = described_class.new( store_pages: true )} it 'stores it in #page_snapshots' do captured = subject.capture_snapshot subject.page_snapshots.should == captured end it 'returns it' do captured.size.should == 1 captured.first.should == subject.to_page end end context false do subject { @browser.shutdown; @browser = described_class.new( store_pages: false ) } it 'does not store it' do subject.capture_snapshot subject.page_snapshots.should be_empty end it 'returns an empty array' do captured.should be_empty end end end end context 'when a snapshot has already been seen' do before :each do subject.load( @url + '/with-ajax', take_snapshot: false ) end it 'ignores it' do subject.capture_snapshot.should be_any subject.capture_snapshot.should be_empty end end context 'when a snapshot has sink data' do before :each do subject.load sink_url, take_snapshot: false end it 'calls #on_new_page_with_sink callbacks' do sinks = [] subject.on_new_page_with_sink do |page| sinks << page.dom.execution_flow_sinks end subject.capture_snapshot sinks.size.should == 1 end context 'and has already been seen' do it 'calls #on_new_page_with_sink callbacks' do sinks = [] subject.on_new_page_with_sink do |page| sinks << page.dom.execution_flow_sinks end subject.capture_snapshot subject.capture_snapshot sinks.size.should == 2 end end context '#store_pages?' do context true do subject { @browser.shutdown; @browser = described_class.new( store_pages: true )} it 'stores it in #page_snapshots_with_sinks' do subject.capture_snapshot subject.page_snapshots_with_sinks.should be_any end end context false do subject { @browser.shutdown; @browser = described_class.new( store_pages: false )} it 'does not store it in #page_snapshots_with_sinks' do subject.capture_snapshot subject.page_snapshots_with_sinks.should be_empty end end end end context 'when a transition has been given' do before :each do subject.load( ajax_url, take_snapshot: false ) end it 'pushes it to the existing transitions' do transition = { stuff: :here } captured = subject.capture_snapshot( stuff: :here ) captured.first.dom.transitions.should include transition end end context 'when there are multiple windows open' do before :each do subject.load( ajax_url, take_snapshot: false ) end it 'captures snapshots from all windows' do subject.javascript.run( 'window.open()' ) subject.watir.windows.last.use subject.load sink_url, take_snapshot: false subject.capture_snapshot.map(&:url).sort.should == [ajax_url, sink_url].sort end end context 'when an error occurs' do it 'ignores it' do subject.watir.stub(:windows) { raise } subject.capture_snapshot( blah: :stuff ).should be_empty end end end describe '#flush_page_snapshots_with_sinks' do it 'returns pages with data-flow sink data' do @browser.load "#{@url}/lots_of_sinks?input=#{@browser.javascript.log_data_flow_sink_stub( function: { name: 'blah' } )}" @browser.explore_and_flush @browser.page_snapshots_with_sinks.map(&:dom).map(&:data_flow_sinks).should == @browser.flush_page_snapshots_with_sinks.map(&:dom).map(&:data_flow_sinks) end it 'returns pages with execution-flow sink data' do @browser.load "#{@url}/lots_of_sinks?input=#{@browser.javascript.log_execution_flow_sink_stub( function: { name: 'blah' } )}" @browser.explore_and_flush @browser.page_snapshots_with_sinks.map(&:dom).map(&:execution_flow_sinks).should == @browser.flush_page_snapshots_with_sinks.map(&:dom).map(&:execution_flow_sinks) end it 'empties the data-flow sink page buffer' do @browser.load "#{@url}/lots_of_sinks?input=#{@browser.javascript.log_data_flow_sink_stub( function: { name: 'blah' } )}" @browser.explore_and_flush @browser.flush_page_snapshots_with_sinks.map(&:dom).map(&:data_flow_sinks) @browser.page_snapshots_with_sinks.should be_empty end it 'empties the execution-flow sink page buffer' do @browser.load "#{@url}/lots_of_sinks?input=#{@browser.javascript.log_execution_flow_sink_stub( function: { name: 'blah' } )}" @browser.explore_and_flush @browser.flush_page_snapshots_with_sinks.map(&:dom).map(&:execution_flow_sinks) @browser.page_snapshots_with_sinks.should be_empty end end describe '#on_new_page_with_sink' do it 'assigns blocks to handle each page with execution-flow sink data' do @browser.load "#{@url}/lots_of_sinks?input=#{@browser.javascript.log_execution_flow_sink_stub( function: { name: 'blah' } )}" sinks = [] @browser.on_new_page_with_sink do |page| sinks << page.dom.execution_flow_sinks end @browser.explore_and_flush sinks.size.should == 2 sinks.should == @browser.page_snapshots_with_sinks.map(&:dom). map(&:execution_flow_sinks) end it 'assigns blocks to handle each page with data-flow sink data' do @browser.javascript.taint = 'taint' @browser.load "#{@url}/lots_of_sinks?input=#{@browser.javascript.log_data_flow_sink_stub( @browser.javascript.taint, function: { name: 'blah' } )}" sinks = [] @browser.on_new_page_with_sink do |page| sinks << page.dom.data_flow_sinks end @browser.explore_and_flush sinks.size.should == 2 sinks.should == @browser.page_snapshots_with_sinks.map(&:dom). map(&:data_flow_sinks) end end describe '#on_fire_event' do it 'gets called before each event is triggered' do @browser.load "#{@url}/trigger_events" calls = [] @browser.on_fire_event do |element, event| calls << [element.opening_tag, event] end @browser.fire_event @browser.watir.div( id: 'my-div' ), :click @browser.fire_event @browser.watir.div( id: 'my-div' ), :mouseover calls.should == [ [ "
", :click ], [ "
", :mouseover ] ] end end describe '#on_new_page' do it 'is passed each snapshot' do pages = [] @browser.on_new_page { |page| pages << page } @browser.load( @url + '/explore' ).trigger_events. page_snapshots.should == pages end it 'is passed each request capture' do pages = [] @browser.on_new_page { |page| pages << page } @browser.start_capture # Last page will be the root snapshot so ignore it. @browser.load( @url + '/with-ajax' ).captured_pages.should == pages[0...2] end end describe '#on_response' do context 'when a response is preloaded' do it 'is passed each response' do responses = [] @browser.on_response { |response| responses << response } @browser.preload Arachni::HTTP::Client.get( @url, mode: :sync ) @browser.goto @url response = responses.first response.should be_kind_of Arachni::HTTP::Response response.url.should == @url end end context 'when a response is cached' do it 'is passed each response' do responses = [] @browser.on_response { |response| responses << response } @browser.cache Arachni::HTTP::Client.get( @url, mode: :sync ) @browser.goto @url response = responses.first response.should be_kind_of Arachni::HTTP::Response response.url.should == @url end end context 'when a request is performed by the browser' do it 'is passed each response' do responses = [] @browser.on_response { |response| responses << response } @browser.goto @url response = responses.first response.should be_kind_of Arachni::HTTP::Response response.url.should == @url end end end describe '#explore_and_flush' do it 'handles deep DOM/page transitions' do url = @url + '/deep-dom' pages = @browser.load( url ).explore_and_flush pages_should_have_form_with_input pages, 'by-ajax' pages.map(&:dom).map(&:transitions).should == [ [ { :page => :load }, { "#{@url}deep-dom" => :request }, { "#{@url}level2" => :request } ], [ { :page => :load }, { "#{@url}deep-dom" => :request }, { "#{@url}level2" => :request }, { { tag_name: 'a', attributes: { 'onmouseover' => 'writeButton();', 'href' => 'javascript:level3();' } } => :mouseover } ], [ { :page => :load }, { "#{@url}deep-dom" => :request }, { "#{@url}level2" => :request }, { { tag_name: 'a', attributes: { 'onmouseover' => 'writeButton();', 'href' => 'javascript:level3();' } } => :click }, { "#{@url}level4" => :request } ], [ { :page => :load }, { "#{@url}deep-dom" => :request }, { "#{@url}level2" => :request }, { { tag_name: 'a', attributes: { 'onmouseover' => 'writeButton();', 'href' => 'javascript:level3();' } } => :mouseover }, { { tag_name: 'button', attributes: { 'onclick' => 'writeUserAgent();', } } => :click } ], [ { :page => :load }, { "#{@url}deep-dom" => :request }, { "#{@url}level2" => :request }, { { tag_name: 'a', attributes: { 'onmouseover' => 'writeButton();', 'href' => 'javascript:level3();' } } => :click }, { "#{@url}level4" => :request }, { { tag_name: 'div', attributes: { 'onclick' => 'level6();', 'id' => 'level5' } } => :click }, { "#{@url}level6" => :request } ] ].map { |transitions| transitions_from_array( transitions ) } end context 'with a depth argument' do it 'does not go past the given DOM depth' do pages = @browser.load( @url + '/deep-dom' ).explore_and_flush(2) pages.map(&:dom).map(&:transitions).should == [ [ { :page => :load }, { "#{@url}deep-dom" => :request }, { "#{@url}level2" => :request } ], [ { :page => :load }, { "#{@url}deep-dom" => :request }, { "#{@url}level2" => :request }, { { tag_name: 'a', attributes: { 'onmouseover' => 'writeButton();', 'href' => 'javascript:level3();' } } => :mouseover } ], [ { :page => :load }, { "#{@url}deep-dom" => :request }, { "#{@url}level2" => :request }, { { tag_name: 'a', attributes: { 'onmouseover' => 'writeButton();', 'href' => 'javascript:level3();' } } => :click }, { "#{@url}level4" => :request } ] ].map { |transitions| transitions_from_array( transitions ) } end end end describe '#page_snapshots_with_sinks' do it 'returns execution-flow sink data' do @browser.load "#{@url}/lots_of_sinks?input=#{@browser.javascript.log_execution_flow_sink_stub(1)}" @browser.explore_and_flush pages = @browser.page_snapshots_with_sinks doms = pages.map(&:dom) doms.size.should == 2 doms[0].transitions.should == transitions_from_array([ { page: :load }, { "#{@url}lots_of_sinks?input=#{@browser.javascript.log_execution_flow_sink_stub(1)}" => :request }, { { tag_name: 'a', attributes: { 'href' => '#', 'onmouseover' => "onClick2('blah1', 'blah2', 'blah3');" } } => :mouseover } ]) doms[0].execution_flow_sinks.size.should == 2 entry = doms[0].execution_flow_sinks[0] entry.data.should == [1] entry.trace.size.should == 3 entry.trace[0].function.name.should == 'onClick' entry.trace[0].function.source.should start_with 'function onClick' @browser.source.split("\n")[entry.trace[0].line].should include 'log_execution_flow_sink(1)' entry.trace[0].function.arguments.should == [1, 2] entry.trace[1].function.name.should == 'onClick2' entry.trace[1].function.source.should start_with 'function onClick2' @browser.source.split("\n")[entry.trace[1].line].should include 'onClick' entry.trace[1].function.arguments.should == %w(blah1 blah2 blah3) entry.trace[2].function.name.should == 'onmouseover' entry.trace[2].function.source.should start_with 'function onmouseover' event = entry.trace[2].function.arguments.first link = "Blah" event['target'].should == link event['srcElement'].should == link event['type'].should == 'mouseover' entry = doms[0].execution_flow_sinks[1] entry.data.should == [1] entry.trace.size.should == 4 entry.trace[0].function.name.should == 'onClick3' entry.trace[0].function.source.should start_with 'function onClick3' @browser.source.split("\n")[entry.trace[0].line].should include 'log_execution_flow_sink(1)' entry.trace[0].function.arguments.should be_empty entry.trace[1].function.name.should == 'onClick' entry.trace[1].function.source.should start_with 'function onClick' @browser.source.split("\n")[entry.trace[1].line].should include 'onClick3' entry.trace[1].function.arguments.should == [1, 2] entry.trace[2].function.name.should == 'onClick2' entry.trace[2].function.source.should start_with 'function onClick2' @browser.source.split("\n")[entry.trace[2].line].should include 'onClick' entry.trace[2].function.arguments.should == %w(blah1 blah2 blah3) entry.trace[3].function.name.should == 'onmouseover' entry.trace[3].function.source.should start_with 'function onmouseover' event = entry.trace[3].function.arguments.first link = "Blah" event['target'].should == link event['srcElement'].should == link event['type'].should == 'mouseover' doms[1].transitions.should == transitions_from_array([ { page: :load }, { "#{@url}lots_of_sinks?input=#{@browser.javascript.log_execution_flow_sink_stub(1)}" => :request }, { { tag_name: 'form', attributes: { 'id' => 'my_form', 'onsubmit' => "onClick('some-arg', 'arguments-arg', 'here-arg'); return false;" } } => :submit } ]) doms[1].execution_flow_sinks.size.should == 2 entry = doms[1].execution_flow_sinks[0] entry.data.should == [1] entry.trace.size.should == 2 entry.trace[0].function.name.should == 'onClick' entry.trace[0].function.source.should start_with 'function onClick' @browser.source.split("\n")[entry.trace[0].line].should include 'log_execution_flow_sink(1)' entry.trace[0].function.arguments.should == %w(some-arg arguments-arg here-arg) entry.trace[1].function.name.should == 'onsubmit' entry.trace[1].function.source.should start_with 'function onsubmit' @browser.source.split("\n")[entry.trace[1].line].should include 'onClick' event = entry.trace[1].function.arguments.first form = "
\n
" event['target'].should == form event['srcElement'].should == form event['type'].should == 'submit' entry = doms[1].execution_flow_sinks[1] entry.data.should == [1] entry.trace.size.should == 3 entry.trace[0].function.name.should == 'onClick3' entry.trace[0].function.source.should start_with 'function onClick3' @browser.source.split("\n")[entry.trace[0].line].should include 'log_execution_flow_sink(1)' entry.trace[0].function.arguments.should be_empty entry.trace[1].function.name.should == 'onClick' entry.trace[1].function.source.should start_with 'function onClick' @browser.source.split("\n")[entry.trace[1].line].should include 'onClick3()' entry.trace[1].function.arguments.should == %w(some-arg arguments-arg here-arg) entry.trace[2].function.name.should == 'onsubmit' entry.trace[2].function.source.should start_with 'function onsubmit' @browser.source.split("\n")[entry.trace[2].line].should include 'onClick(' event = entry.trace[2].function.arguments.first form = "
\n
" event['target'].should == form event['srcElement'].should == form event['type'].should == 'submit' end it 'returns data-flow sink data' do @browser.javascript.taint = 'taint' @browser.load "#{@url}/lots_of_sinks?input=#{@browser.javascript.log_data_flow_sink_stub( @browser.javascript.taint, function: 'blah' )}" @browser.explore_and_flush pages = @browser.page_snapshots_with_sinks doms = pages.map(&:dom) doms.size.should == 2 doms[0].data_flow_sinks.size.should == 2 entry = doms[0].data_flow_sinks[0] entry.function.should == 'blah' entry.trace.size.should == 3 entry.trace[0].function.name.should == 'onClick' entry.trace[0].function.source.should start_with 'function onClick' @browser.source.split("\n")[entry.trace[0].line].should include 'log_data_flow_sink(' entry.trace[0].function.arguments.should == [1, 2] entry.trace[1].function.name.should == 'onClick2' entry.trace[1].function.source.should start_with 'function onClick2' @browser.source.split("\n")[entry.trace[1].line].should include 'onClick' entry.trace[1].function.arguments.should == %w(blah1 blah2 blah3) entry.trace[2].function.name.should == 'onmouseover' entry.trace[2].function.source.should start_with 'function onmouseover' event = entry.trace[2].function.arguments.first link = "Blah" event['target'].should == link event['srcElement'].should == link event['type'].should == 'mouseover' entry = doms[0].data_flow_sinks[1] entry.function.should == 'blah' entry.trace.size.should == 4 entry.trace[0].function.name.should == 'onClick3' entry.trace[0].function.source.should start_with 'function onClick3' @browser.source.split("\n")[entry.trace[0].line].should include 'log_data_flow_sink(' entry.trace[0].function.arguments.should be_empty entry.trace[1].function.name.should == 'onClick' entry.trace[1].function.source.should start_with 'function onClick' @browser.source.split("\n")[entry.trace[1].line].should include 'onClick3' entry.trace[1].function.arguments.should == [1, 2] entry.trace[2].function.name.should == 'onClick2' entry.trace[2].function.source.should start_with 'function onClick2' @browser.source.split("\n")[entry.trace[2].line].should include 'onClick' entry.trace[2].function.arguments.should == %w(blah1 blah2 blah3) entry.trace[3].function.name.should == 'onmouseover' entry.trace[3].function.source.should start_with 'function onmouseover' event = entry.trace[3].function.arguments.first link = "Blah" event['target'].should == link event['srcElement'].should == link event['type'].should == 'mouseover' doms[1].data_flow_sinks.size.should == 2 entry = doms[1].data_flow_sinks[0] entry.function.should == 'blah' entry.trace.size.should == 2 entry.trace[0].function.name.should == 'onClick' entry.trace[0].function.source.should start_with 'function onClick' @browser.source.split("\n")[entry.trace[0].line].should include 'log_data_flow_sink(' entry.trace[0].function.arguments.should == %w(some-arg arguments-arg here-arg) entry.trace[1].function.name.should == 'onsubmit' entry.trace[1].function.source.should start_with 'function onsubmit' @browser.source.split("\n")[entry.trace[1].line].should include 'onClick' event = entry.trace[1].function.arguments.first form = "
\n
" event['target'].should == form event['srcElement'].should == form event['type'].should == 'submit' entry = doms[1].data_flow_sinks[1] entry.function.should == 'blah' entry.trace.size.should == 3 entry.trace[0].function.name.should == 'onClick3' entry.trace[0].function.source.should start_with 'function onClick3' @browser.source.split("\n")[entry.trace[0].line].should include 'log_data_flow_sink(' entry.trace[0].function.arguments.should be_empty entry.trace[1].function.name.should == 'onClick' entry.trace[1].function.source.should start_with 'function onClick' @browser.source.split("\n")[entry.trace[1].line].should include 'onClick3()' entry.trace[1].function.arguments.should == %w(some-arg arguments-arg here-arg) entry.trace[2].function.name.should == 'onsubmit' entry.trace[2].function.source.should start_with 'function onsubmit' @browser.source.split("\n")[entry.trace[2].line].should include 'onClick(' event = entry.trace[2].function.arguments.first form = "
\n
" event['target'].should == form event['srcElement'].should == form event['type'].should == 'submit' end describe 'when store_pages: false' do it 'does not store pages' do @browser.shutdown @browser = @browser.class.new( store_pages: false ) @browser.load "#{@url}/lots_of_sinks?input=#{@browser.javascript.log_execution_flow_sink_stub(1)}" @browser.explore_and_flush @browser.page_snapshots_with_sinks.should be_empty end end end describe '#response' do it "returns the #{Arachni::HTTP::Response} for the loaded page" do @browser.load @url browser_response = @browser.response browser_request = browser_response.request raw_response = Arachni::HTTP::Client.get( @url, mode: :sync ) raw_request = raw_response.request browser_response.url.should == raw_response.url [:url, :method].each do |attribute| browser_request.send(attribute).should == raw_request.send(attribute) end end context "when the response takes more than #{Arachni::OptionGroups::HTTP}#request_timeout" do it 'returns nil' end context 'when the resource is out-of-scope' do it 'returns nil' do Arachni::Options.url = @url @browser.load 'http://google.com/' @browser.response.should be_nil end end end describe '#to_page' do it "converts the working window to an #{Arachni::Page}" do ua = Arachni::Options.http.user_agent @browser.load( @url ) page = @browser.to_page page.should be_kind_of Arachni::Page ua.should_not be_empty page.response.body.should_not include( ua ) page.body.should include( ua ) end it "assigns the proper #{Arachni::Page::DOM}#digest" do @browser.load( @url ) @browser.to_page.dom.instance_variable_get(:@digest).should == '