teampoltergeist--poltergeist/spec/integration/driver_spec.rb

1568 lines
51 KiB
Ruby

# frozen_string_literal: true
require 'spec_helper'
require 'image_size'
require 'pdf/reader'
module Capybara::Poltergeist
describe Driver do
before do
@session = TestSessions::Poltergeist
@driver = @session.driver
end
after { @driver.reset! }
def session_url(path)
server = @session.server
"http://#{server.host}:#{server.port}#{path}"
end
it 'supports a custom phantomjs path' do
begin
file = POLTERGEIST_ROOT + '/spec/support/custom_phantomjs_called'
path = POLTERGEIST_ROOT + '/spec/support/custom_phantomjs'
FileUtils.rm_f file
driver = Capybara::Poltergeist::Driver.new(nil, phantomjs: path)
driver.browser
# If the correct custom path is called, it will touch the file.
# We allow at least 10 secs for this to happen before failing.
tries = 0
until File.exist?(file) || tries == 100
sleep 0.1
tries += 1
end
expect(File.exist?(file)).to be true
ensure
driver&.quit
end
end
context 'output redirection' do
let(:logger) { StringIO.new }
let(:session) { Capybara::Session.new(:poltergeist_with_logger, TestApp) }
before do
Capybara.register_driver :poltergeist_with_logger do |app|
Capybara::Poltergeist::Driver.new(app, phantomjs_logger: logger)
end
end
after do
session.driver.quit
end
it 'supports capturing console.log' do
session.visit('/poltergeist/console_log')
expect(logger.string).to include('Hello world')
end
it 'is threadsafe in how it captures console.log' do
pending('JRuby and Rubinius do not support the :out parameter to Process.spawn, so there is no threadsafe way to redirect output') unless Capybara::Poltergeist.mri?
# Write something to STDOUT right before Process.spawn is called
allow(Process).to receive(:spawn).and_wrap_original do |m, *args|
STDOUT.puts '1'
$stdout.puts '2'
m.call(*args)
end
expect do
session.visit('/poltergeist/console_log')
end.to output("1\n2\n").to_stdout_from_any_process
expect(logger.string).not_to match(/\d/)
end
end
it 'raises an error and restarts the client if the client dies while executing a command' do
expect { @driver.browser.command('exit') }.to raise_error(DeadClient)
@session.visit('/')
expect(@driver.html).to include('Hello world')
end
it 'quits silently before visit call' do
driver = Capybara::Poltergeist::Driver.new(nil)
expect { driver.quit }.not_to raise_error
end
it 'has a viewport size of 1024x768 by default' do
@session.visit('/')
expect(
@driver.evaluate_script('[window.innerWidth, window.innerHeight]')
).to eq([1024, 768])
end
it 'allows the viewport to be resized' do
@session.visit('/')
@driver.resize(200, 400)
expect(
@driver.evaluate_script('[window.innerWidth, window.innerHeight]')
).to eq([200, 400])
end
it 'defaults viewport maximization to 1366x768' do
@session.visit('/')
@session.current_window.maximize
expect(@session.current_window.size).to eq([1366, 768])
end
it 'allows custom maximization size' do
begin
@driver.options[:screen_size] = [1600, 1200]
@session.visit('/')
@session.current_window.maximize
expect(@session.current_window.size).to eq([1600, 1200])
ensure
@driver.options.delete(:screen_size)
end
end
it 'allows the page to be scrolled' do
@session.visit('/poltergeist/long_page')
@driver.resize(10, 10)
@driver.scroll_to(200, 100)
expect(
@driver.evaluate_script('[window.scrollX, window.scrollY]')
).to eq([200, 100])
end
it 'supports specifying viewport size with an option' do
begin
Capybara.register_driver :poltergeist_with_custom_window_size do |app|
Capybara::Poltergeist::Driver.new(
app,
logger: TestSessions.logger,
window_size: [800, 600]
)
end
driver = Capybara::Session.new(:poltergeist_with_custom_window_size, TestApp).driver
driver.visit(session_url('/'))
expect(
driver.evaluate_script('[window.innerWidth, window.innerHeight]')
).to eq([800, 600])
ensure
driver&.quit
end
end
shared_examples 'render screen' do
it 'supports rendering the whole of a page that goes outside the viewport' do
@session.visit('/poltergeist/long_page')
create_screenshot file
File.open(file, 'rb') do |f|
expect(ImageSize.new(f.read).size).to eq(
@driver.evaluate_script('[window.innerWidth, window.innerHeight]')
)
end
create_screenshot file, full: true
File.open(file, 'rb') do |f|
expect(ImageSize.new(f.read).size).to eq(
@driver.evaluate_script('[document.documentElement.clientWidth, document.documentElement.clientHeight]')
)
end
end
it 'supports rendering the entire window when documentElement has no height' do
@session.visit('/poltergeist/fixed_positioning')
create_screenshot file, full: true
File.open(file, 'rb') do |f|
expect(ImageSize.new(f.read).size).to eq(
@driver.evaluate_script('[window.innerWidth, window.innerHeight]')
)
end
end
it 'supports rendering just the selected element' do
@session.visit('/poltergeist/long_page')
create_screenshot file, selector: '#penultimate'
File.open(file, 'rb') do |f|
size = @driver.evaluate_script <<-JS
function() {
var ele = document.getElementById('penultimate');
var rect = ele.getBoundingClientRect();
return [rect.width, rect.height];
}();
JS
expect(ImageSize.new(f.read).size).to eq(size)
end
end
it 'ignores :selector in #save_screenshot if full: true' do
@session.visit('/poltergeist/long_page')
expect(@driver.browser).to receive(:warn).with(/Ignoring :selector/)
create_screenshot file, full: true, selector: '#penultimate'
File.open(file, 'rb') do |f|
expect(ImageSize.new(f.read).size).to eq(
@driver.evaluate_script('[document.documentElement.clientWidth, document.documentElement.clientHeight]')
)
end
end
it 'resets element positions after' do
@session.visit('poltergeist/long_page')
el = @session.find(:css, '#middleish')
# make the page scroll an element into view
el.click
position_script = 'document.querySelector("#middleish").getBoundingClientRect()'
offset = @session.evaluate_script(position_script)
create_screenshot file
expect(@session.evaluate_script(position_script)).to eq offset
end
end
describe '#save_screenshot' do
let(:format) { :png }
let(:file) { POLTERGEIST_ROOT + "/spec/tmp/screenshot.#{format}" }
before(:each) { FileUtils.rm_f file }
def create_screenshot(file, *args)
@driver.save_screenshot(file, *args)
end
it 'supports rendering the page' do
@session.visit('/')
@driver.save_screenshot(file)
expect(File.exist?(file)).to be true
end
it 'supports rendering the page with a nonstring path' do
@session.visit('/')
@driver.save_screenshot(Pathname(file))
expect(File.exist?(file)).to be true
end
it 'supports rendering the page to file without extension when format is specified' do
begin
file = POLTERGEIST_ROOT + '/spec/tmp/screenshot'
FileUtils.rm_f file
@session.visit('/')
@driver.save_screenshot(file, format: 'jpg')
expect(File.exist?(file)).to be true
ensure
FileUtils.rm_f file
end
end
it 'supports rendering the page with different quality settings' do
file2 = POLTERGEIST_ROOT + "/spec/tmp/screenshot2.#{format}"
file3 = POLTERGEIST_ROOT + "/spec/tmp/screenshot3.#{format}"
FileUtils.rm_f [file2, file3]
begin
@session.visit('/')
@driver.save_screenshot(file, quality: 0)
@driver.save_screenshot(file2) # phantomjs defaults to a quality of 75
@driver.save_screenshot(file3, quality: 100)
expect(File.size(file)).to be < File.size(file2)
expect(File.size(file2)).to be < File.size(file3)
ensure
FileUtils.rm_f [file2, file3]
end
end
shared_examples 'when #zoom_factor= is set' do
let(:format) { :xbm }
it 'changes image dimensions' do
@session.visit('/poltergeist/zoom_test')
black_pixels_count = lambda { |file|
File.read(file).to_s[/{.*}/m][1...-1].split(/\W/).map { |n| n.hex.to_s(2).count('1') }.reduce(:+)
}
@driver.save_screenshot(file)
before = black_pixels_count[file]
@driver.zoom_factor = zoom_factor
@driver.save_screenshot(file)
after = black_pixels_count[file]
expect(after.to_f / before.to_f).to eq(zoom_factor**2)
end
end
context 'zoom in' do
let(:zoom_factor) { 2 }
include_examples 'when #zoom_factor= is set'
end
context 'zoom out' do
let(:zoom_factor) { 0.5 }
include_examples 'when #zoom_factor= is set'
end
context 'when #paper_size= is set' do
let(:format) { :pdf }
it 'changes pdf size' do
@session.visit('/poltergeist/long_page')
@driver.paper_size = { width: '1in', height: '1in' }
@driver.save_screenshot(file)
reader = PDF::Reader.new(file)
reader.pages.each do |page|
bbox = page.attributes[:MediaBox]
width = (bbox[2] - bbox[0]) / 72
expect(width).to eq(1)
end
end
end
include_examples 'render screen'
end
describe '#render_base64' do
let(:file) { POLTERGEIST_ROOT + "/spec/tmp/screenshot.#{format}" }
def create_screenshot(file, *args)
image = @driver.render_base64(format, *args)
File.open(file, 'wb') { |f| f.write Base64.decode64(image) }
end
it 'supports rendering the page in base64' do
@session.visit('/')
screenshot = @driver.render_base64
expect(screenshot.length).to be > 100
end
context 'png' do
let(:format) { :png }
include_examples 'render screen'
end
context 'jpeg' do
let(:format) { :jpeg }
include_examples 'render screen'
end
end
context 'setting headers' do
it 'allows headers to be set' do
@driver.headers = {
'Cookie' => 'foo=bar',
'Host' => 'foo.com'
}
@session.visit('/poltergeist/headers')
expect(@driver.body).to include('COOKIE: foo=bar')
expect(@driver.body).to include('HOST: foo.com')
end
it 'allows headers to be read' do
expect(@driver.headers).to eq({})
@driver.headers = { 'User-Agent' => 'PhantomJS', 'Host' => 'foo.com' }
expect(@driver.headers).to eq('User-Agent' => 'PhantomJS', 'Host' => 'foo.com')
end
it 'supports User-Agent' do
@driver.headers = { 'User-Agent' => 'foo' }
@session.visit '/'
expect(@driver.evaluate_script('window.navigator.userAgent')).to eq('foo')
end
it 'sets headers for all HTTP requests' do
@driver.headers = { 'X-Omg' => 'wat' }
@session.visit '/'
@driver.execute_script <<-JS
var request = new XMLHttpRequest();
request.open('GET', '/poltergeist/headers', false);
request.send();
if (request.status === 200) {
document.body.innerHTML = request.responseText;
}
JS
expect(@driver.body).to include('X_OMG: wat')
end
it 'adds new headers' do
@driver.headers = { 'User-Agent' => 'PhantomJS', 'Host' => 'foo.com' }
@driver.add_headers('User-Agent' => 'Poltergeist', 'Appended' => 'true')
@session.visit('/poltergeist/headers')
expect(@driver.body).to include('USER_AGENT: Poltergeist')
expect(@driver.body).to include('HOST: foo.com')
expect(@driver.body).to include('APPENDED: true')
end
it 'sets headers on the initial request' do
@driver.headers = { 'PermanentA' => 'a' }
@driver.add_headers('PermanentB' => 'b')
@driver.add_header('Referer', 'http://google.com', permanent: false)
@driver.add_header('TempA', 'a', permanent: false)
@session.visit('/poltergeist/headers_with_ajax')
initial_request = @session.find(:css, '#initial_request').text
ajax_request = @session.find(:css, '#ajax_request').text
expect(initial_request).to include('PERMANENTA: a')
expect(initial_request).to include('PERMANENTB: b')
expect(initial_request).to include('REFERER: http://google.com')
expect(initial_request).to include('TEMPA: a')
expect(ajax_request).to include('PERMANENTA: a')
expect(ajax_request).to include('PERMANENTB: b')
expect(ajax_request).to_not include('REFERER: http://google.com')
expect(ajax_request).to_not include('TEMPA: a')
end
it 'keeps added headers on redirects by default' do
@driver.add_header('X-Custom-Header', '1', permanent: false)
@session.visit('/poltergeist/redirect_to_headers')
expect(@driver.body).to include('X_CUSTOM_HEADER: 1')
end
it 'does not keep added headers on redirect when ' \
'permanent is no_redirect' do
@driver.add_header('X-Custom-Header', '1', permanent: :no_redirect)
@session.visit('/poltergeist/redirect_to_headers')
expect(@driver.body).not_to include('X_CUSTOM_HEADER: 1')
end
context 'multiple windows' do
it 'persists headers across popup windows' do
@driver.headers = {
'Cookie' => 'foo=bar',
'Host' => 'foo.com',
'User-Agent' => 'foo'
}
@session.visit('/poltergeist/popup_headers')
@session.click_link 'pop up'
@session.switch_to_window @session.windows.last
expect(@driver.body).to include('USER_AGENT: foo')
expect(@driver.body).to include('COOKIE: foo=bar')
expect(@driver.body).to include('HOST: foo.com')
end
it 'sets headers in existing windows' do
@session.open_new_window
@driver.headers = {
'Cookie' => 'foo=bar',
'Host' => 'foo.com',
'User-Agent' => 'foo'
}
@session.visit('/poltergeist/headers')
expect(@driver.body).to include('USER_AGENT: foo')
expect(@driver.body).to include('COOKIE: foo=bar')
expect(@driver.body).to include('HOST: foo.com')
@session.switch_to_window @session.windows.last
@session.visit('/poltergeist/headers')
expect(@driver.body).to include('USER_AGENT: foo')
expect(@driver.body).to include('COOKIE: foo=bar')
expect(@driver.body).to include('HOST: foo.com')
end
it 'keeps temporary headers local to the current window' do
@session.open_new_window
@driver.add_header('X-Custom-Header', '1', permanent: false)
@session.switch_to_window @session.windows.last
@session.visit('/poltergeist/headers')
expect(@driver.body).not_to include('X_CUSTOM_HEADER: 1')
@session.switch_to_window @session.windows.first
@session.visit('/poltergeist/headers')
expect(@driver.body).to include('X_CUSTOM_HEADER: 1')
end
it 'does not mix temporary headers with permanent ones when propagating to other windows' do
@session.open_new_window
@driver.add_header('X-Custom-Header', '1', permanent: false)
@driver.add_header('Host', 'foo.com')
@session.switch_to_window @session.windows.last
@session.visit('/poltergeist/headers')
expect(@driver.body).to include('HOST: foo.com')
expect(@driver.body).not_to include('X_CUSTOM_HEADER: 1')
@session.switch_to_window @session.windows.first
@session.visit('/poltergeist/headers')
expect(@driver.body).to include('HOST: foo.com')
expect(@driver.body).to include('X_CUSTOM_HEADER: 1')
end
it 'does not propagate temporary headers to new windows' do
@session.visit '/'
@driver.add_header('X-Custom-Header', '1', permanent: false)
@session.open_new_window
@session.switch_to_window @session.windows.last
@session.visit('/poltergeist/headers')
expect(@driver.body).not_to include('X_CUSTOM_HEADER: 1')
@session.switch_to_window @session.windows.first
@session.visit('/poltergeist/headers')
expect(@driver.body).to include('X_CUSTOM_HEADER: 1')
end
end
end
it 'supports clicking precise coordinates' do
@session.visit('/poltergeist/click_coordinates')
@driver.click(100, 150)
expect(@driver.body).to include('x: 100, y: 150')
end
it 'supports executing multiple lines of javascript' do
@driver.execute_script <<-JS
var a = 1
var b = 2
window.result = a + b
JS
expect(@driver.evaluate_script('window.result')).to eq(3)
end
it 'operates a timeout when communicating with phantomjs' do
begin
prev_timeout = @driver.timeout
@driver.timeout = 0.001
expect { @driver.browser.command 'noop' }.to raise_error(TimeoutError)
ensure
@driver.timeout = prev_timeout
end
end
unless Capybara::Poltergeist.windows?
# Not sure how to do this on Windows, so skipping
it 'supports quitting the session' do
driver = Capybara::Poltergeist::Driver.new(nil)
pid = driver.client_pid
expect(Process.kill(0, pid)).to eq(1)
driver.quit
expect { Process.kill(0, pid) }.to raise_error(Errno::ESRCH)
end
end
context 'extending browser javascript' do
before do
@extended_driver = Capybara::Poltergeist::Driver.new(
@session.app,
logger: TestSessions.logger,
inspector: !ENV['DEBUG'].nil?,
extensions: %W[#{File.expand_path '../support/geolocation.js', __dir__}]
)
end
after do
@extended_driver.quit
end
it 'supports extending the phantomjs world' do
@extended_driver.visit session_url('/poltergeist/requiring_custom_extension')
expect(
@extended_driver.body
).to include(%(Location: <span id="location">1,-1</span>))
expect(
@extended_driver.evaluate_script("document.getElementById('location').innerHTML")
).to eq('1,-1')
expect(
@extended_driver.evaluate_script('navigator.geolocation')
).to_not eq(nil)
end
it 'errors when extension is unavailable' do
begin
@failing_driver = Capybara::Poltergeist::Driver.new(
@session.app,
logger: TestSessions.logger,
inspector: !ENV['DEBUG'].nil?,
extensions: %W[#{File.expand_path '../support/non_existent.js', __dir__}]
)
expect { @failing_driver.visit '/' }.to raise_error(Capybara::Poltergeist::BrowserError, /Unable to load extension: .*non_existent\.js/)
ensure
@failing_driver.quit
end
end
end
context 'javascript errors' do
it 'propagates a Javascript error inside Poltergeist to a ruby exception' do
expect do
@driver.browser.command 'browser_error'
end.to raise_error(BrowserError) { |e|
expect(e.message).to include('Error: zomg')
# PhantomJS 2.1 refers to files as being in code subdirectory
expect(e.message).to include('compiled/browser.js').or include('code/browser.js')
}
end
it 'propagates an asynchronous Javascript error on the page to a ruby exception' do
expect do
@driver.execute_script 'setTimeout(function() { omg }, 0)'
sleep 0.01
@driver.execute_script ''
end.to raise_error(JavascriptError, /ReferenceError.*omg/)
end
it 'propagates a synchronous Javascript error on the page to a ruby exception' do
expect do
@driver.execute_script 'omg'
end.to raise_error(JavascriptError, /ReferenceError.*omg/)
end
it 'does not re-raise a Javascript error if it is rescued' do
expect do
@driver.execute_script 'setTimeout(function() { omg }, 0)'
sleep 0.01
@driver.execute_script ''
end.to raise_error(JavascriptError)
# should not raise again
expect(@driver.evaluate_script('1+1')).to eq(2)
end
it 'propagates a Javascript error during page load to a ruby exception' do
expect { @session.visit '/poltergeist/js_error' }.to raise_error(JavascriptError)
end
it 'does not propagate a Javascript error to ruby if error raising disabled' do
begin
driver = Capybara::Poltergeist::Driver.new(@session.app, js_errors: false, logger: TestSessions.logger)
driver.visit session_url('/poltergeist/js_error')
driver.execute_script 'setTimeout(function() { omg }, 0)'
sleep 0.1
expect(driver.body).to include('hello')
ensure
driver&.quit
end
end
it 'does not propagate a Javascript error to ruby if error raising disabled and client restarted' do
begin
driver = Capybara::Poltergeist::Driver.new(@session.app, js_errors: false, logger: TestSessions.logger)
driver.restart
driver.visit session_url('/poltergeist/js_error')
driver.execute_script 'setTimeout(function() { omg }, 0)'
sleep 0.1
expect(driver.body).to include('hello')
ensure
driver&.quit
end
end
context 'PhantomJS page settings' do
it 'can override defaults' do
begin
driver = Capybara::Poltergeist::Driver.new(@session.app, page_settings: { userAgent: 'PageSettingsOverride' }, logger: TestSessions.logger)
driver.visit session_url('/poltergeist/headers')
expect(driver.body).to include('USER_AGENT: PageSettingsOverride')
ensure
driver&.quit
end
end
it 'can set resource timeout' do
begin
# If PJS resource timeout is less than drivers timeout it should ignore resources not loading in time
driver = Capybara::Poltergeist::Driver.new(@session.app, page_settings: { resourceTimeout: 1000 }, logger: TestSessions.logger)
driver.timeout = 3
expect do
driver.visit session_url('/poltergeist/visit_timeout')
end.not_to raise_error
ensure
driver&.quit
end
end
end
end
context "phantomjs {'status': 'fail'} responses" do
before { @port = @session.server.port }
it 'do not occur when DNS correct' do
expect { @session.visit("http://localhost:#{@port}/") }.not_to raise_error
end
it 'handles when DNS incorrect' do
expect { @session.visit("http://nope:#{@port}/") }.to raise_error(StatusFailError)
end
it 'has a descriptive message when DNS incorrect' do
url = "http://nope:#{@port}/"
expect do
@session.visit(url)
end.to raise_error(StatusFailError, "Request to '#{url}' failed to reach server, check DNS and/or server status")
end
it 'reports open resource requests' do
old_timeout = @session.driver.timeout
begin
@session.driver.timeout = 2
expect do
@session.visit('/poltergeist/visit_timeout')
end.to raise_error(StatusFailError, %r{resources still waiting http://.*/poltergeist/really_slow})
ensure
@session.driver.timeout = old_timeout
end
end
it 'doesnt report open resources where there are none' do
old_timeout = @session.driver.timeout
begin
@session.driver.timeout = 2
expect do
@session.visit('/poltergeist/really_slow')
end.to raise_error(StatusFailError) { |error|
expect(error.message).not_to include('resources still waiting')
}
ensure
@session.driver.timeout = old_timeout
end
end
end
context 'network traffic' do
before do
@driver.restart
end
it 'keeps track of network traffic' do
@session.visit('/poltergeist/with_js')
urls = @driver.network_traffic.map(&:url)
expect(urls.grep(%r{/poltergeist/jquery.min.js$}).size).to eq(1)
expect(urls.grep(%r{/poltergeist/jquery-ui.min.js$}).size).to eq(1)
expect(urls.grep(%r{/poltergeist/test.js$}).size).to eq(1)
end
it 'keeps track of blocked network traffic' do
@driver.browser.url_blacklist = ['unwanted']
@session.visit '/poltergeist/url_blacklist'
blocked_urls = @driver.network_traffic(:blocked).map(&:url)
expect(blocked_urls).to include(/unwanted/)
end
it 'captures responses' do
@session.visit('/poltergeist/with_js')
request = @driver.network_traffic.last
expect(request.response_parts.last.status).to eq(200)
end
it 'captures errors' do
@session.visit('/poltergeist/with_ajax_fail')
expect(@session).to have_css('h1', text: 'Done')
error = @driver.network_traffic.last.error
expect(error).to be
end
it 'keeps a running list between multiple web page views' do
@session.visit('/poltergeist/with_js')
expect(@driver.network_traffic.length).to eq(4)
@session.visit('/poltergeist/with_js')
expect(@driver.network_traffic.length).to eq(8)
end
it 'gets cleared on restart' do
@session.visit('/poltergeist/with_js')
expect(@driver.network_traffic.length).to eq(4)
@driver.restart
@session.visit('/poltergeist/with_js')
expect(@driver.network_traffic.length).to eq(4)
end
it 'gets cleared when being cleared' do
@session.visit('/poltergeist/with_js')
expect(@driver.network_traffic.length).to eq(4)
@driver.clear_network_traffic
expect(@driver.network_traffic.length).to eq(0)
end
it 'blocked requests get cleared along with network traffic' do
@driver.browser.url_blacklist = ['unwanted']
@session.visit '/poltergeist/url_blacklist'
expect(@driver.network_traffic(:blocked).length).to eq(2)
@driver.clear_network_traffic
expect(@driver.network_traffic(:blocked).length).to eq(0)
end
end
context 'memory cache clearing' do
before do
@driver.restart
end
it 'can clear memory cache when supported (phantomjs >=2.0.0)' do
skip 'clear_memory_cache is not supported by tested PhantomJS' unless phantom_version_is? '>= 2.0.0', @driver
@driver.clear_memory_cache
@session.visit('/poltergeist/cacheable')
first_request = @driver.network_traffic.last
expect(@driver.network_traffic.length).to eq(1)
expect(first_request.response_parts.last.status).to eq(200)
@session.visit('/poltergeist/cacheable')
expect(@driver.network_traffic.length).to eq(1)
@driver.clear_memory_cache
@session.visit('/poltergeist/cacheable')
another_request = @driver.network_traffic.last
expect(@driver.network_traffic.length).to eq(2)
expect(another_request.response_parts.last.status).to eq(200)
end
it 'raises error when it is unsupported (phantomjs <2.0.0)' do
skip 'clear_memory_cache is supported by tested PhantomJS' if phantom_version_is? '>= 2.0.0', @driver
@session.visit('/poltergeist/cacheable')
first_request = @driver.network_traffic.last
expect(@driver.network_traffic.length).to eq(1)
expect(first_request.response_parts.last.status).to eq(200)
expect { @driver.clear_memory_cache }.to raise_error(Capybara::Poltergeist::UnsupportedFeature)
@session.visit('/poltergeist/cacheable')
expect(@driver.network_traffic.length).to eq(2)
end
end
context 'status code support' do
it 'determines status from the simple response' do
@session.visit('/poltergeist/status/500')
expect(@driver.status_code).to eq(500)
end
it 'determines status code when the page has a few resources' do
@session.visit('/poltergeist/with_different_resources')
expect(@driver.status_code).to eq(200)
end
it 'determines status code even after redirect' do
@session.visit('/poltergeist/redirect')
expect(@driver.status_code).to eq(200)
end
end
context 'cookies support' do
it 'returns set cookies' do
@session.visit('/set_cookie')
cookie = @driver.cookies['capybara']
expect(cookie.name).to eq('capybara')
expect(cookie.value).to eq('test_cookie')
expect(cookie.domain).to eq('127.0.0.1')
expect(cookie.path).to eq('/')
expect(cookie.secure?).to be false
expect(cookie.httponly?).to be false
expect(cookie.samesite).to be_nil
expect(cookie.expires).to be_nil
end
it 'can set cookies' do
@driver.set_cookie 'capybara', 'omg'
@session.visit('/get_cookie')
expect(@driver.body).to include('omg')
end
it 'can set cookies with custom settings' do
@driver.set_cookie 'capybara', 'omg', path: '/poltergeist'
@session.visit('/get_cookie')
expect(@driver.body).to_not include('omg')
@session.visit('/poltergeist/get_cookie')
expect(@driver.body).to include('omg')
expect(@driver.cookies['capybara'].path).to eq('/poltergeist')
end
it 'can remove a cookie' do
@session.visit('/set_cookie')
@session.visit('/get_cookie')
expect(@driver.body).to include('test_cookie')
@driver.remove_cookie 'capybara'
@session.visit('/get_cookie')
expect(@driver.body).to_not include('test_cookie')
end
it 'can clear cookies' do
@session.visit('/set_cookie')
@session.visit('/get_cookie')
expect(@driver.body).to include('test_cookie')
@driver.clear_cookies
@session.visit('/get_cookie')
expect(@driver.body).to_not include('test_cookie')
end
it 'can set cookies with an expires time' do
time = Time.at(Time.now.to_i + 10000)
@session.visit '/'
@driver.set_cookie 'foo', 'bar', expires: time
expect(@driver.cookies['foo'].expires).to eq(time)
end
it 'can set cookies for given domain' do
port = @session.server.port
@driver.set_cookie 'capybara', '127.0.0.1'
@driver.set_cookie 'capybara', 'localhost', domain: 'localhost'
@session.visit("http://localhost:#{port}/poltergeist/get_cookie")
expect(@driver.body).to include('localhost')
@session.visit("http://127.0.0.1:#{port}/poltergeist/get_cookie")
expect(@driver.body).to include('127.0.0.1')
end
it 'can enable and disable cookies' do
@driver.cookies_enabled = false
@session.visit('/set_cookie')
expect(@driver.cookies).to be_empty
@driver.cookies_enabled = true
@session.visit('/set_cookie')
expect(@driver.cookies).to_not be_empty
end
it 'sets cookies correctly when Capybara.app_host is set' do
old_app_host = Capybara.app_host
begin
Capybara.app_host = 'http://localhost/poltergeist'
@driver.set_cookie 'capybara', 'app_host'
port = @session.server.port
@session.visit("http://localhost:#{port}/poltergeist/get_cookie")
expect(@driver.body).to include('app_host')
@session.visit("http://127.0.0.1:#{port}/poltergeist/get_cookie")
expect(@driver.body).not_to include('app_host')
ensure
Capybara.app_host = old_app_host
end
end
end
it 'allows the driver to have a fixed port' do
begin
driver = Capybara::Poltergeist::Driver.new(@driver.app, port: 12345)
driver.visit session_url('/')
expect { TCPServer.new('127.0.0.1', 12345) }.to raise_error(Errno::EADDRINUSE)
ensure
driver.quit
end
end
it 'allows the driver to have a custom host' do
begin
# Use custom host "pointing" to localhost, specified by POLTERGEIST_TEST_HOST env var.
# Use /etc/hosts or iptables for this: https://superuser.com/questions/516208/how-to-change-ip-address-to-point-to-localhost
# A custom host and corresponding env var for Travis is specified in .travis.yml
# If var is unspecified, skip test
host = ENV['POLTERGEIST_TEST_HOST']
skip 'POLTERGEIST_TEST_HOST not set' if host.nil?
driver = Capybara::Poltergeist::Driver.new(@driver.app, host: host, port: 12345)
driver.visit session_url('/')
expect { TCPServer.new(host, 12345) }.to raise_error(Errno::EADDRINUSE)
ensure
driver&.quit
end
end
it 'lists the open windows' do
@session.visit '/'
@session.execute_script <<-JS
window.open('/poltergeist/simple', 'popup')
JS
expect(@driver.window_handles).to eq(%w[0 1])
popup2 = @session.window_opened_by do
@session.execute_script <<-JS
window.open('/poltergeist/simple', 'popup2')
JS
end
expect(@driver.window_handles).to eq(%w[0 1 2])
@session.within_window(popup2) do
expect(@session.html).to include('Test')
@session.execute_script('window.close()')
end
sleep 0.1
expect(@driver.window_handles).to eq(%w[0 1])
end
context 'a new window inherits settings' do
it 'inherits size' do
@session.visit '/'
@session.current_window.resize_to(1200, 800)
new_tab = @session.open_new_window
expect(new_tab.size).to eq [1200, 800]
end
it 'inherits url_blacklist' do
@driver.browser.url_blacklist = ['unwanted']
@session.visit '/'
new_tab = @session.open_new_window
@session.within_window(new_tab) do
@session.visit '/poltergeist/url_blacklist'
expect(@session).to have_content('We are loading some unwanted action here')
@session.within_frame 'framename' do
expect(@session.html).not_to include('We shouldn\'t see this.')
end
end
end
it 'inherits url_whitelist' do
@session.visit '/'
@driver.browser.url_whitelist = ['url_whitelist', '/poltergeist/wanted']
new_tab = @session.open_new_window
@session.within_window(new_tab) do
@session.visit '/poltergeist/url_whitelist'
expect(@session).to have_content('We are loading some wanted action here')
@session.within_frame 'framename' do
expect(@session).to have_content('We should see this.')
end
@session.within_frame 'unwantedframe' do
# make sure non whitelisted urls are blocked
expect(@session).not_to have_content("We shouldn't see this.")
end
end
end
end
it 'resizes windows' do
@session.visit '/'
popup1 = @session.window_opened_by do
@session.execute_script <<-JS
window.open('/poltergeist/simple', 'popup1')
JS
end
popup2 = @session.window_opened_by do
@session.execute_script <<-JS
window.open('/poltergeist/simple', 'popup2')
JS
end
popup1.resize_to(100, 200)
popup2.resize_to(200, 100)
expect(popup1.size).to eq([100, 200])
expect(popup2.size).to eq([200, 100])
end
it 'clears local storage between tests' do
@session.visit '/'
@session.execute_script <<-JS
localStorage.setItem('key', 'value');
JS
value = @session.evaluate_script <<-JS
localStorage.getItem('key');
JS
expect(value).to eq('value')
@driver.reset!
@session.visit '/'
value = @session.evaluate_script <<-JS
localStorage.getItem('key');
JS
expect(value).to be_nil
end
context 'basic http authentication' do
it 'denies without credentials' do
@session.visit '/poltergeist/basic_auth'
expect(@session.status_code).to eq(401)
expect(@session).not_to have_content('Welcome, authenticated client')
end
it 'allows with given credentials' do
@driver.basic_authorize('login', 'pass')
@session.visit '/poltergeist/basic_auth'
expect(@session.status_code).to eq(200)
expect(@session).to have_content('Welcome, authenticated client')
end
it 'allows even overwriting headers' do
@driver.basic_authorize('login', 'pass')
@driver.headers = [{ 'Poltergeist' => 'true' }]
@session.visit '/poltergeist/basic_auth'
expect(@session.status_code).to eq(200)
expect(@session).to have_content('Welcome, authenticated client')
end
it 'denies with wrong credentials' do
@driver.basic_authorize('user', 'pass!')
@session.visit '/poltergeist/basic_auth'
expect(@session.status_code).to eq(401)
expect(@session).not_to have_content('Welcome, authenticated client')
end
it 'allows on POST request' do
@driver.basic_authorize('login', 'pass')
@session.visit '/poltergeist/basic_auth'
@session.click_button('Submit')
expect(@session.status_code).to eq(200)
expect(@session).to have_content('Authorized POST request')
end
end
context 'blacklisting urls for resource requests' do
it 'blocks unwanted urls' do
@driver.browser.url_blacklist = ['unwanted']
@session.visit '/poltergeist/url_blacklist'
expect(@session.status_code).to eq(200)
expect(@session).to have_content('We are loading some unwanted action here')
@session.within_frame 'framename' do
expect(@session.html).not_to include('We shouldn\'t see this.')
end
end
it 'supports wildcards' do
@driver.browser.url_blacklist = ['*wanted']
@session.visit '/poltergeist/url_whitelist'
expect(@session.status_code).to eq(200)
expect(@session).to have_content('We are loading some wanted action here')
@session.within_frame 'framename' do
expect(@session).not_to have_content('We should see this.')
end
@session.within_frame 'unwantedframe' do
expect(@session).not_to have_content("We shouldn't see this.")
end
end
it 'can be configured in the driver and survive reset' do
Capybara.register_driver :poltergeist_blacklist do |app|
Capybara::Poltergeist::Driver.new(app, @driver.options.merge(url_blacklist: ['unwanted']))
end
session = Capybara::Session.new(:poltergeist_blacklist, @session.app)
session.visit '/poltergeist/url_blacklist'
expect(session).to have_content('We are loading some unwanted action here')
session.within_frame 'framename' do
expect(session.html).not_to include('We shouldn\'t see this.')
end
session.reset!
session.visit '/poltergeist/url_blacklist'
expect(session).to have_content('We are loading some unwanted action here')
session.within_frame 'framename' do
expect(session.html).not_to include('We shouldn\'t see this.')
end
end
end
context 'whitelisting urls for resource requests' do
it 'allows whitelisted urls' do
@driver.browser.url_whitelist = ['url_whitelist', '/wanted']
@session.visit '/poltergeist/url_whitelist'
expect(@session.status_code).to eq(200)
expect(@session).to have_content('We are loading some wanted action here')
@session.within_frame 'framename' do
expect(@session).to have_content('We should see this.')
end
@session.within_frame 'unwantedframe' do
expect(@session).not_to have_content("We shouldn't see this.")
end
end
it 'supports wildcards' do
@driver.browser.url_whitelist = ['url_whitelist', '/*wanted']
@session.visit '/poltergeist/url_whitelist'
expect(@session.status_code).to eq(200)
expect(@session).to have_content('We are loading some wanted action here')
@session.within_frame 'framename' do
expect(@session).to have_content('We should see this.')
end
@session.within_frame 'unwantedframe' do
expect(@session).to have_content("We shouldn't see this.")
end
end
it 'blocks overruled urls' do
@driver.browser.url_whitelist = ['url_whitelist']
@driver.browser.url_blacklist = ['url_whitelist']
@session.visit '/poltergeist/url_whitelist'
expect(@session.status_code).to eq(nil)
expect(@session).not_to have_content('We are loading some wanted action here')
end
it 'allows urls when the whitelist is empty' do
@driver.browser.url_whitelist = []
@session.visit '/poltergeist/url_whitelist'
expect(@session.status_code).to eq(200)
expect(@session).to have_content('We are loading some wanted action here')
@session.within_frame 'framename' do
expect(@session).to have_content('We should see this.')
end
end
it 'can be configured in the driver and survive reset' do
Capybara.register_driver :poltergeist_whitelist do |app|
Capybara::Poltergeist::Driver.new(app, @driver.options.merge(url_whitelist: ['url_whitelist', '/poltergeist/wanted']))
end
session = Capybara::Session.new(:poltergeist_whitelist, @session.app)
session.visit '/poltergeist/url_whitelist'
expect(session).to have_content('We are loading some wanted action here')
session.within_frame 'framename' do
expect(session).to have_content('We should see this.')
end
session.within_frame 'unwantedframe' do
# make sure non whitelisted urls are blocked
expect(session).not_to have_content("We shouldn't see this.")
end
session.reset!
session.visit '/poltergeist/url_whitelist'
expect(session).to have_content('We are loading some wanted action here')
session.within_frame 'framename' do
expect(session).to have_content('We should see this.')
end
session.within_frame 'unwantedframe' do
# make sure non whitelisted urls are blocked
expect(session).not_to have_content("We shouldn't see this.")
end
end
end
context 'has ability to send keys' do
before { @session.visit('/poltergeist/send_keys') }
it 'sends keys to empty input' do
input = @session.find(:css, '#empty_input')
input.native.send_keys('Input')
expect(input.value).to eq('Input')
end
it 'sends keys to filled input' do
input = @session.find(:css, '#filled_input')
input.native.send_keys(' appended')
expect(input.value).to eq('Text appended')
end
it 'sends keys to empty textarea' do
input = @session.find(:css, '#empty_textarea')
input.native.send_keys('Input')
expect(input.value).to eq('Input')
end
it 'sends keys to filled textarea' do
input = @session.find(:css, '#filled_textarea')
input.native.send_keys(' appended')
expect(input.value).to eq('Description appended')
end
it 'sends keys to empty contenteditable div' do
input = @session.find(:css, '#empty_div')
input.native.send_keys('Input')
expect(input.text).to eq('Input')
end
it 'persists focus across calls' do
input = @session.find(:css, '#empty_div')
input.native.send_keys('helo')
input.native.send_keys(:Left)
input.native.send_keys('l')
expect(input.text).to eq('hello')
end
it 'sends keys to filled contenteditable div' do
input = @session.find(:css, '#filled_div')
input.native.send_keys(' appended')
expect(input.text).to eq('Content appended')
end
it 'sends sequences' do
input = @session.find(:css, '#empty_input')
input.native.send_keys([:Shift], 'S', [:Alt], 't', 'r', 'i', 'g', :Left, 'n')
expect(input.value).to eq('String')
end
it 'submits the form with sequence' do
input = @session.find(:css, '#without_submit_button input')
input.native.send_keys(:Enter)
expect(input.value).to eq('Submitted')
end
it 'sends sequences with modifiers and letters' do
input = @session.find(:css, '#empty_input')
input.native.send_keys([:Shift, 's'], 't', 'r', 'i', 'n', 'g')
expect(input.value).to eq('String')
end
it 'sends sequences with modifiers and symbols' do
input = @session.find(:css, '#empty_input')
input.native.send_keys('t', 'r', 'i', 'n', 'g', %i[Ctrl Left], 's')
expect(input.value).to eq('string')
end
it 'sends sequences with multiple modifiers and symbols' do
input = @session.find(:css, '#empty_input')
input.native.send_keys('t', 'r', 'i', 'n', 'g', %i[Ctrl Shift Left], 's')
expect(input.value).to eq('s')
end
it 'sends modifiers with sequences' do
input = @session.find(:css, '#empty_input')
input.native.send_keys('s', [:Shift, 'tring'])
expect(input.value).to eq('sTRING')
end
it 'sends modifiers with multiple keys' do
input = @session.find(:css, '#empty_input')
input.native.send_keys('poltre', %i[Shift Left Left], 'ergeist')
expect(input.value).to eq('poltergeist')
end
it 'has an alias' do
input = @session.find(:css, '#empty_input')
input.native.send_key('S')
expect(input.value).to eq('S')
end
it 'generates correct events with keyCodes for modified puncation' do
input = @session.find(:css, '#empty_input')
input.send_keys([:shift, '.'], [:shift, 't'])
expect(@session.find(:css, '#key-events-output')).to have_text('keydown:16 keydown:190 keydown:16 keydown:84')
end
it 'suuports snake_case sepcified keys (Capybara standard)' do
input = @session.find(:css, '#empty_input')
input.send_keys(:PageUp, :page_up)
expect(@session.find(:css, '#key-events-output')).to have_text('keydown:33', count: 2)
end
it 'supports :control alias for :Ctrl' do
input = @session.find(:css, '#empty_input')
input.send_keys([:Ctrl, 'a'], [:control, 'a'])
expect(@session.find(:css, '#key-events-output')).to have_text('keydown:17 keydown:65', count: 2)
end
it 'supports :command alias for :Meta' do
input = @session.find(:css, '#empty_input')
input.send_keys([:Meta, 'z'], [:command, 'z'])
expect(@session.find(:css, '#key-events-output')).to have_text('keydown:91 keydown:90', count: 2)
end
it 'supports Capybara specified numpad keys' do
input = @session.find(:css, '#empty_input')
input.send_keys(:numpad2, :numpad8, :divide, :decimal)
expect(@session.find(:css, '#key-events-output')).to have_text('keydown:98 keydown:104 keydown:111 keydown:110')
end
it 'raises error for unknown keys' do
input = @session.find(:css, '#empty_input')
expect do
input.send_keys('abc', :blah)
end.to raise_error Capybara::Poltergeist::KeyError, 'Unknown key: Blah'
end
end
context 'set' do
before { @session.visit('/poltergeist/set') }
it "sets a contenteditable's content" do
input = @session.find(:css, '#filled_div')
input.set('new text')
expect(input.text).to eq('new text')
end
it "sets multiple contenteditables' content" do
input = @session.find(:css, '#empty_div')
input.set('new text')
expect(input.text).to eq('new text')
input = @session.find(:css, '#filled_div')
input.set('replacement text')
expect(input.text).to eq('replacement text')
end
it 'sets a content editable childs content' do
@session.visit('/with_js')
@session.find(:css, '#existing_content_editable_child').set('WYSIWYG')
expect(@session.find(:css, '#existing_content_editable_child').text).to eq('WYSIWYG')
end
end
context 'date_fields' do
before { @session.visit('/poltergeist/date_fields') }
it 'sets a date' do
input = @session.find(:css, '#date_field')
input.set('2016-02-14')
expect(input.value).to eq('2016-02-14')
end
it 'fills a date' do
@session.fill_in 'date_field', with: '2016-02-14'
expect(@session.find(:css, '#date_field').value).to eq('2016-02-14')
end
end
context 'evaluate_script' do
it 'can return an element' do
@session.visit('/poltergeist/send_keys')
element = @session.driver.evaluate_script('document.getElementById("empty_input")')
expect(element).to eq(@session.find(:id, 'empty_input').native)
end
it 'can return structures with elements' do
@session.visit('/poltergeist/send_keys')
result = @session.driver.evaluate_script('{ a: document.getElementById("empty_input"), b: { c: document.querySelectorAll("#empty_textarea, #filled_textarea") } }')
expect(result).to eq(
'a' => @session.driver.find_css('#empty_input').first,
'b' => {
'c' => @session.driver.find_css('#empty_textarea, #filled_textarea')
}
)
end
end
context 'evaluate_async_script' do
it 'handles evaluate_async_script value properly' do
@session.using_wait_time(5) do
expect(@session.driver.evaluate_async_script('arguments[0](null)')).to be_nil
expect(@session.driver.evaluate_async_script('arguments[0](false)')).to be false
expect(@session.driver.evaluate_async_script('arguments[0](true)')).to be true
expect(@session.driver.evaluate_async_script("arguments[0]({foo: 'bar'})")).to eq('foo' => 'bar')
end
end
it 'will timeout' do
@session.using_wait_time(1) do
expect do
@session.driver.evaluate_async_script('var callback=arguments[0]; setTimeout(function(){callback(true)}, 4000)')
end.to raise_error Capybara::Poltergeist::ScriptTimeoutError
end
end
end
context 'URL' do
it 'can get the frames url' do
@session.visit '/poltergeist/frames'
@session.within_frame 0 do
expect(@session.driver.frame_url).to end_with('/poltergeist/slow')
if Capybara::VERSION.to_f < 3.0
expect(@session.driver.current_url).to end_with('/poltergeist/slow')
else
# current_url is required to return the top level browsing context in Capybara 3
expect(@session.driver.current_url).to end_with('/poltergeist/frames')
end
end
expect(@session.driver.frame_url).to end_with('/poltergeist/frames')
expect(@session.driver.current_url).to end_with('/poltergeist/frames')
end
end
end
end