poltergeist-1.10.0/0000755000175000017500000000000012766546165013200 5ustar pravipravipoltergeist-1.10.0/README.md0000644000175000017500000004220212766546165014457 0ustar pravipravi# Poltergeist - A PhantomJS driver for Capybara # [![Build Status](https://secure.travis-ci.org/teampoltergeist/poltergeist.png)](http://travis-ci.org/teampoltergeist/poltergeist) Poltergeist is a driver for [Capybara](https://github.com/jnicklas/capybara). It allows you to run your Capybara tests on a headless [WebKit](http://webkit.org) browser, provided by [PhantomJS](http://phantomjs.org/). **If you're viewing this at https://github.com/teampoltergeist/poltergeist, you're reading the documentation for the master branch. [View documentation for the latest release (1.9.0).](https://github.com/teampoltergeist/poltergeist/tree/v1.9.0)** ## Getting help ## Questions should be posted [on Stack Overflow, using the 'poltergeist' tag](http://stackoverflow.com/questions/tagged/poltergeist). Bug reports should be posted [on GitHub](https://github.com/teampoltergeist/poltergeist/issues) (and be sure to read the bug reporting guidance below). ## Installation ## Add this line to your Gemfile and run `bundle install`: ``` ruby gem 'poltergeist' ``` In your test setup add: ``` ruby require 'capybara/poltergeist' Capybara.javascript_driver = :poltergeist ``` If you were previously using the `:rack_test` driver, be aware that your app will now run in a separate thread and this can have consequences for transactional tests. [See the Capybara README for more detail](https://github.com/jnicklas/capybara/blob/master/README.md#transactions-and-database-setup). ## Installing PhantomJS ## You need at least PhantomJS 1.8.1. There are *no other external dependencies* (you don't need Qt, or a running X server, etc.) ### Mac ### * *Homebrew*: `brew install phantomjs` * *MacPorts*: `sudo port install phantomjs` * *Manual install*: [Download this](https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-1.9.8-macosx.zip) ### Linux ### * Download the [32 bit](https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-1.9.8-linux-i686.tar.bz2) or [64 bit](https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-1.9.8-linux-x86_64.tar.bz2) binary. * Extract the tarball and copy `bin/phantomjs` into your `PATH` ### Windows ### * Download the [precompiled binary](https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-1.9.8-windows.zip) for Windows ### Manual compilation ### Do this as a last resort if the binaries don't work for you. It will take quite a long time as it has to build WebKit. * Download [the source tarball](https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-1.9.8-source.zip) * Extract and cd in * `./build.sh` (See also the [PhantomJS building guide](http://phantomjs.org/build.html).) ## Compatibility ## Poltergeist runs on MRI 1.9, JRuby 1.9 and Rubinius 1.9. Poltergeist and PhantomJS are currently supported on Mac OS X, Linux, and Windows platforms. Ruby 1.8 is no longer supported. The last release to support Ruby 1.8 was 1.0.2, so you should use that if you still need Ruby 1.8 support. ## Running on a CI ## There are no special steps to take. You don't need Xvfb or any running X server at all. [Travis CI](https://travis-ci.org/) and [Codeship](https://codeship.com/) has PhantomJS pre-installed. Depending on your tests, one thing that you may need is some fonts. If you're getting errors on a CI that don't occur during development then try taking some screenshots - it may well be missing fonts throwing things off kilter. Your distro will have various font packages available to install. ## What's supported? ## Poltergeist supports all the mandatory features for a Capybara driver, and the following optional features: * `page.evaluate_script` and `page.execute_script` * `page.within_frame` * `page.status_code` * `page.response_headers` * `page.save_screenshot` * `page.driver.render_base64(format, options)` * `page.driver.scroll_to(left, top)` * `page.driver.basic_authorize(user, password)` * `element.native.send_keys(*keys)` * `page.driver.set_proxy(ip, port, type, user, password)` * window API * cookie handling * drag-and-drop There are some additional features: ### Taking screenshots with some extensions ### You can grab screenshots of the page at any point by calling `save_screenshot('/path/to/file.png')` (this works the same way as the PhantomJS render feature, so you can specify other extensions like `.pdf`, `.gif`, etc.) Just in case you render pdf it's might be worth to set `driver.paper_size=` with settings provided by PhantomJS in [here](https://github.com/ariya/phantomjs/wiki/API-Reference-WebPage#wiki-webpage-paperSize) By default, only the viewport will be rendered (the part of the page that is in view). To render the entire page, use `save_screenshot('/path/to/file.png', :full => true)`. You also have an ability to render selected element. Pass option `selector` with any valid element selector to make a screenshot bounded by that element `save_screenshot('/path/to/file.png', :selector => '#id')`. If you need for some reasons base64 encoded screenshot you can simply call `render_base64` that will return you encoded image. Additional options are the same as for `save_screenshot` except the first argument which is format (:png by default, acceptable :png, :gif, :jpeg). ### Clicking precise coordinates ### Sometimes its desirable to click a very specific area of the screen. You can accomplish this with `page.driver.click(x, y)`, where x and y are the screen coordinates. ### Remote debugging (experimental) ### If you use the `:inspector => true` option (see below), remote debugging will be enabled. When this option is enabled, you can insert `page.driver.debug` into your tests to pause the test and launch a browser which gives you the WebKit inspector to view your test run with. You can register this debugger driver with a different name and set it as the current javascript driver. By example, in your helper file: ```ruby Capybara.register_driver :poltergeist_debug do |app| Capybara::Poltergeist::Driver.new(app, :inspector => true) end # Capybara.javascript_driver = :poltergeist Capybara.javascript_driver = :poltergeist_debug ``` [Read more here](http://jonathanleighton.com/articles/2012/poltergeist-0-6-0/) ### Manipulating request headers ### You can manipulate HTTP request headers with these methods: ``` ruby page.driver.headers # => {} page.driver.headers = { "User-Agent" => "Poltergeist" } page.driver.add_headers("Referer" => "https://example.com") page.driver.headers # => { "User-Agent" => "Poltergeist", "Referer" => "https://example.com" } ``` Notice that `headers=` will overwrite already set headers. You should use `add_headers` if you want to add a few more. These headers will apply to all subsequent HTTP requests (including requests for assets, AJAX, etc). They will be automatically cleared at the end of the test. You have ability to set headers only for the initial request: ``` ruby page.driver.headers = { "User-Agent" => "Poltergeist" } page.driver.add_header("Referer", "http://example.com", permanent: false) page.driver.headers # => { "User-Agent" => "Poltergeist", "Referer" => "http://example.com" } visit(login_path) page.driver.headers # => { "User-Agent" => "Poltergeist" } ``` This way your temporary headers will be sent only for the initial request, all subsequent request will only contain your permanent headers. ### Inspecting network traffic ### You can inspect the network traffic (i.e. what resources have been loaded) on the current page by calling `page.driver.network_traffic`. This returns an array of request objects. A request object has a `response_parts` method containing data about the response chunks. Please note that network traffic is not cleared when you visit new page. You can manually clear the network traffic by calling `page.driver.clear_network_traffic` or `page.driver.reset` ### Manipulating cookies ### The following methods are used to inspect and manipulate cookies: * `page.driver.cookies` - a hash of cookies accessible to the current page. The keys are cookie names. The values are `Cookie` objects, with the following methods: `name`, `value`, `domain`, `path`, `secure?`, `httponly?`, `samesite`, `expires`. * `page.driver.set_cookie(name, value, options = {})` - set a cookie. The options hash can take the following keys: `:domain`, `:path`, `:secure`, `:httponly`, `:samesite`, `:expires`. `:expires` should be a `Time` object. * `page.driver.remove_cookie(name)` - remove a cookie * `page.driver.clear_cookies` - clear all cookies ### Sending keys ### There's an ability to send arbitrary keys to the element: ``` ruby element = find('input#id') element.native.send_key('String') ``` or even more complicated: ``` ruby element.native.send_keys('H', 'elo', :Left, 'l') # => 'Hello' element.native.send_key(:Enter) # triggers Enter key ``` Since it's implemented natively in PhantomJS this will exactly imitate user behavior. See more about [sendEvent](http://phantomjs.org/api/webpage/method/send-event.html) and [PhantomJS keys](https://github.com/ariya/phantomjs/commit/cab2635e66d74b7e665c44400b8b20a8f225153a) ## Customization ## You can customize the way that Capybara sets up Poltergeist via the following code in your test setup: ``` ruby Capybara.register_driver :poltergeist do |app| Capybara::Poltergeist::Driver.new(app, options) end ``` `options` is a hash of options. The following options are supported: * `:phantomjs` (String) - A custom path to the phantomjs executable * `:debug` (Boolean) - When true, debug output is logged to `STDERR`. Some debug info from the PhantomJS portion of Poltergeist is also output, but this goes to `STDOUT` due to technical limitations. * `:logger` (Object responding to `puts`) - When present, debug output is written to this object * `:phantomjs_logger` (`IO` object) - Where the `STDOUT` from PhantomJS is written to. This is where your `console.log` statements will show up. Default: `STDOUT` * `:timeout` (Numeric) - The number of seconds we'll wait for a response when communicating with PhantomJS. Default is 30. * `:inspector` (Boolean, String) - See 'Remote Debugging', above. * `:js_errors` (Boolean) - When false, JavaScript errors do not get re-raised in Ruby. * `:window_size` (Array) - The dimensions of the browser window in which to test, expressed as a 2-element array, e.g. [1024, 768]. Default: [1024, 768] * `:phantomjs_options` (Array) - Additional [command line options](http://phantomjs.org/api/command-line.html) to be passed to PhantomJS, e.g. `['--load-images=no', '--ignore-ssl-errors=yes']` * `:extensions` (Array) - An array of JS files to be preloaded into the phantomjs browser. Useful for faking unsupported APIs. * `:port` (Fixnum) - The port which should be used to communicate with the PhantomJS process. Defaults to a random open port. * `:url_blacklist` (Array) - Default session url blacklist - expressed as an array of strings to match against requested URLs. * `:url_whitelist` (Array) - Default session url whitelist - expressed as an array of strings to match against requested URLs. ### URL Blacklisting & Whitelisting ### Poltergeist supports URL blacklisting, which allows you to prevent scripts from running on designated domains: ```ruby page.driver.browser.url_blacklist = ['http://www.example.com'] ``` and also URL whitelisting, which allows scripts to only run on designated domains: ```ruby page.driver.browser.url_whitelist = ['http://www.example.com'] ``` If you are experiencing slower run times, consider creating a URL whitelist of domains that are essential or a blacklist of domains that are not essential, such as ad networks or analytics, to your testing environment. ## Troubleshooting ## Unfortunately, the nature of full-stack testing is that things can and do go wrong from time to time. This section aims to highlight a number of common problems and provide ideas about how you can work around them. ### DeadClient errors ### Sometimes PhantomJS crashes during a test. There are basically two kinds of crashes: those that can be reproduced every time, and those that occur sporadically and are not easily reproduced. If your crash happens every time, you should read the [PhantomJS crash reporting guide](http://phantomjs.org/crash-reporting.html) and file a bug against PhantomJS. Feel free to also file a bug against Poltergeist in case there are workarounds that can be implemented within Poltergeist. Also, if lots of Poltergeist users are experiencing the same crash then fixing it will move up the priority list. If your crash is sporadic, there is less that can be done. Often these issues are very complicated and difficult to track down. It may be that the crash has already been fixed in a newer version of WebKit that will eventually find its way into PhantomJS. It's still worth reporting your bug against PhantomJS, but it's probably not worth filing a bug against Poltergeist as there's not much we can do. If you experience sporadic crashes a lot, it may be worth configuring your CI to automatically re-run failing tests before reporting a failed build. ### MouseEventFailed errors ### When Poltergeist clicks on an element, rather than generating a DOM click event, it actually generates a "proper" click. This is much closer to what happens when a real user clicks on the page - but it means that Poltergeist must scroll the page to where the element is, and work out the correct co-ordinates to click. If the element is covered up by another element, the click will fail (this is a good thing - because your user won't be able to click a covered up element either). Sometimes there can be issues with this behavior. If you have problems, it's worth taking screenshots of the page and trying to work out what's going on. If your click is failing, but you're not getting a `MouseEventFailed` error, then you can turn on the `:debug` option and look in the output to see what co-ordinates Poltergeist is using for the click. You can then cross-reference this with a screenshot to see if something is obviously wrong. If you can't figure out what's going on and just want to work around the problem so you can get on with life, consider using a DOM click event. For example, if this code is failing: ``` ruby click_button "Save" ``` Then try: ``` ruby find_button("Save").trigger('click') ``` ### Timing problems ### Sometimes tests pass and fail sporadically. This is often because there is some problem synchronising events properly. It's often straightforward to verify this by adding `sleep` statements into your test to allow sufficient time for the page to settle. If you have these types of problems, read through the [Capybara documentation on asynchronous JavaScript](https://github.com/jnicklas/capybara#asynchronous-javascript-ajax-and-friends) which explains the tools that Capybara provides for dealing with this. ### Memory leak ### If you run a few capybara sessions manually please make sure you've called `session.driver.quit` when you don't need session anymore. Forgetting about this causes memory leakage and your system's resources can be exhausted earlier than you may expect. ### General troubleshooting hints ### * Configure Poltergeist with `:debug` turned on so you can see its communication with PhantomJS. * Take screenshots to figure out what the state of your page is when the problem occurs. * Use the remote web inspector in case it provides any useful insight * Consider downloading the Poltergeist source and using `console.log` debugging to figure out what's going on inside PhantomJS. (This will require an understanding of the Poltergeist source code and PhantomJS, so it's only for the committed!) ### Filing a bug ### If you can provide specific steps to reproduce your problem, or have specific information that might help other help you track down the problem, then please file a bug on Github. Include as much information as possible. For example: * Specific steps to reproduce where possible (failing tests are even better) * The output obtained from running Poltergeist with `:debug` turned on * Screenshots * Stack traces if there are any Ruby on JavaScript exceptions generated * The Poltergeist and PhantomJS version numbers used * The operating system name and version used ## Changes ## Version history and a list of next-release features and fixes can be found in the [changelog](CHANGELOG.md). ## License ## Copyright (c) 2011-2015 Jonathan Leighton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. poltergeist-1.10.0/lib/0000755000175000017500000000000012766546165013746 5ustar pravipravipoltergeist-1.10.0/lib/capybara/0000755000175000017500000000000012766546165015530 5ustar pravipravipoltergeist-1.10.0/lib/capybara/poltergeist/0000755000175000017500000000000012766546165020071 5ustar pravipravipoltergeist-1.10.0/lib/capybara/poltergeist/cookie.rb0000644000175000017500000000107612766546165021673 0ustar pravipravimodule Capybara::Poltergeist class Cookie def initialize(attributes) @attributes = attributes end def name @attributes['name'] end def value @attributes['value'] end def domain @attributes['domain'] end def path @attributes['path'] end def secure? @attributes['secure'] end def httponly? @attributes['httponly'] end def samesite @attributes['samesite'] end def expires Time.at @attributes['expiry'] if @attributes['expiry'] end end end poltergeist-1.10.0/lib/capybara/poltergeist/browser.rb0000644000175000017500000002252712766546165022111 0ustar pravipravirequire "capybara/poltergeist/errors" require "capybara/poltergeist/command" require 'json' require 'time' module Capybara::Poltergeist class Browser ERROR_MAPPINGS = { 'Poltergeist.JavascriptError' => JavascriptError, 'Poltergeist.FrameNotFound' => FrameNotFound, 'Poltergeist.InvalidSelector' => InvalidSelector, 'Poltergeist.StatusFailError' => StatusFailError, 'Poltergeist.NoSuchWindowError' => NoSuchWindowError, 'Poltergeist.UnsupportedFeature' => UnsupportedFeature } attr_reader :server, :client, :logger def initialize(server, client, logger = nil) @server = server @client = client @logger = logger end def restart server.restart client.restart self.debug = @debug if defined?(@debug) self.js_errors = @js_errors if defined?(@js_errors) self.extensions = @extensions if @extensions end def visit(url) command 'visit', url end def current_url command 'current_url' end def status_code command 'status_code' end def body command 'body' end def source command 'source' end def title command 'title' end def parents(page_id, id) command 'parents', page_id, id end def find(method, selector) result = command('find', method, selector) result['ids'].map { |id| [result['page_id'], id] } end def find_within(page_id, id, method, selector) command 'find_within', page_id, id, method, selector end def all_text(page_id, id) command 'all_text', page_id, id end def visible_text(page_id, id) command 'visible_text', page_id, id end def delete_text(page_id, id) command 'delete_text', page_id, id end def property(page_id, id, name) command 'property', page_id, id, name.to_s end def attributes(page_id, id) command 'attributes', page_id, id end def attribute(page_id, id, name) command 'attribute', page_id, id, name.to_s end def value(page_id, id) command 'value', page_id, id end def set(page_id, id, value) command 'set', page_id, id, value end def select_file(page_id, id, value) command 'select_file', page_id, id, value end def tag_name(page_id, id) command('tag_name', page_id, id).downcase end def visible?(page_id, id) command 'visible', page_id, id end def disabled?(page_id, id) command 'disabled', page_id, id end def click_coordinates(x, y) command 'click_coordinates', x, y end def evaluate(script) command 'evaluate', script end def execute(script) command 'execute', script end def within_frame(handle, &block) if handle.is_a?(Capybara::Node::Base) command 'push_frame', [handle.native.page_id, handle.native.id] else command 'push_frame', handle end yield ensure command 'pop_frame' end def window_handle command 'window_handle' end def window_handles command 'window_handles' end def switch_to_window(handle) command 'switch_to_window', handle end def open_new_window command 'open_new_window' end def close_window(handle) command 'close_window', handle end def find_window_handle(locator) return locator if window_handles.include? locator handle = command 'window_handle', locator raise NoSuchWindowError unless handle return handle end def within_window(locator, &block) original = window_handle handle = find_window_handle(locator) switch_to_window(handle) yield ensure switch_to_window(original) end def click(page_id, id) command 'click', page_id, id end def right_click(page_id, id) command 'right_click', page_id, id end def double_click(page_id, id) command 'double_click', page_id, id end def hover(page_id, id) command 'hover', page_id, id end def drag(page_id, id, other_id) command 'drag', page_id, id, other_id end def drag_by(page_id, id, x, y) command 'drag_by', page_id, id, x, y end def select(page_id, id, value) command 'select', page_id, id, value end def trigger(page_id, id, event) command 'trigger', page_id, id, event.to_s end def reset command 'reset' end def scroll_to(left, top) command 'scroll_to', left, top end def render(path, options = {}) check_render_options!(options) options[:full] = !!options[:full] command 'render', path.to_s, options end def render_base64(format, options = {}) check_render_options!(options) options[:full] = !!options[:full] command 'render_base64', format.to_s, options end def set_zoom_factor(zoom_factor) command 'set_zoom_factor', zoom_factor end def set_paper_size(size) command 'set_paper_size', size end def resize(width, height) command 'resize', width, height end def send_keys(page_id, id, keys) command 'send_keys', page_id, id, normalize_keys(keys) end def path(page_id, id) command 'path', page_id, id end def network_traffic command('network_traffic').values.map do |event| NetworkTraffic::Request.new( event['request'], event['responseParts'].map { |response| NetworkTraffic::Response.new(response) }, event['error'] ? NetworkTraffic::Error.new(event['error']) : nil ) end end def clear_network_traffic command('clear_network_traffic') end def set_proxy(ip, port, type, user, password) args = [ip, port, type] args << user if user args << password if password command('set_proxy', *args) end def equals(page_id, id, other_id) command('equals', page_id, id, other_id) end def get_headers command 'get_headers' end def set_headers(headers) command 'set_headers', headers end def add_headers(headers) command 'add_headers', headers end def add_header(header, permanent) command 'add_header', header, permanent end def response_headers command 'response_headers' end def cookies Hash[command('cookies').map { |cookie| [cookie['name'], Cookie.new(cookie)] }] end def set_cookie(cookie) if cookie[:expires] cookie[:expires] = cookie[:expires].to_i * 1000 end command 'set_cookie', cookie end def remove_cookie(name) command 'remove_cookie', name end def clear_cookies command 'clear_cookies' end def cookies_enabled=(flag) command 'cookies_enabled', !!flag end def set_http_auth(user, password) command 'set_http_auth', user, password end def js_errors=(val) @js_errors = val command 'set_js_errors', !!val end def extensions=(names) @extensions = names Array(names).each do |name| command 'add_extension', name end end def url_whitelist=(whitelist) command 'set_url_whitelist', *whitelist end def url_blacklist=(blacklist) command 'set_url_blacklist', *blacklist end def debug=(val) @debug = val command 'set_debug', !!val end def clear_memory_cache command 'clear_memory_cache' end def command(name, *args) cmd = Command.new(name, *args) log cmd.message response = server.send(cmd) log response json = JSON.load(response) if json['error'] klass = ERROR_MAPPINGS[json['error']['name']] || BrowserError raise klass.new(json['error']) else json['response'] end rescue DeadClient restart raise end def go_back command 'go_back' end def go_forward command 'go_forward' end def accept_confirm command 'set_confirm_process', true end def dismiss_confirm command 'set_confirm_process', false end # # press "OK" with text (response) or default value # def accept_prompt(response) command 'set_prompt_response', response || false end # # press "Cancel" # def dismiss_prompt command 'set_prompt_response', nil end def modal_message command 'modal_message' end private def log(message) logger.puts message if logger end def check_render_options!(options) if !!options[:full] && options.has_key?(:selector) warn "Ignoring :selector in #render since :full => true was given at #{caller.first}" options.delete(:selector) end end def normalize_keys(keys) keys.map do |key| case key when Array # [:Shift, "s"] => { modifier: "shift", key: "S" } # [:Ctrl, :Left] => { modifier: "ctrl", key: :Left } # [:Ctrl, :Shift, :Left] => { modifier: "ctrl,shift", key: :Left } letter = key.last symbol = key[0...-1].map { |k| k.to_s.downcase }.join(',') { modifier: symbol.to_s.downcase, key: letter.capitalize } when Symbol { key: key.capitalize } # Return a known sequence for PhantomJS when String key # Plain string, nothing to do end end end end end poltergeist-1.10.0/lib/capybara/poltergeist/server.rb0000644000175000017500000000161412766546165021726 0ustar pravipravimodule Capybara::Poltergeist class Server attr_reader :socket, :fixed_port, :timeout def initialize(fixed_port = nil, timeout = nil) @fixed_port = fixed_port @timeout = timeout start end def port @socket.port end def timeout=(sec) @timeout = @socket.timeout = sec end def start @socket = WebSocketServer.new(fixed_port, timeout) end def stop @socket.close end def restart stop start end def send(command) receive_timeout = nil # default if command.name == 'visit' command.args.push(timeout) # set the client set visit timeout parameter receive_timeout = timeout + 5 # Add a couple of seconds to let the client timeout first end @socket.send(command.id, command.message, receive_timeout) or raise DeadClient.new(command.message) end end end poltergeist-1.10.0/lib/capybara/poltergeist/node.rb0000644000175000017500000000676312766546165021357 0ustar pravipravimodule Capybara::Poltergeist class Node < Capybara::Driver::Node attr_reader :page_id, :id def initialize(driver, page_id, id) super(driver, self) @page_id = page_id @id = id end def browser driver.browser end def command(name, *args) browser.send(name, page_id, id, *args) rescue BrowserError => error case error.name when 'Poltergeist.ObsoleteNode' raise ObsoleteNode.new(self, error.response) when 'Poltergeist.MouseEventFailed' raise MouseEventFailed.new(self, error.response) else raise end end def parents command(:parents).map { |parent_id| self.class.new(driver, page_id, parent_id) } end def find(method, selector) command(:find_within, method, selector).map { |id| self.class.new(driver, page_id, id) } end def find_xpath(selector) find :xpath, selector end def find_css(selector) find :css, selector end def all_text filter_text command(:all_text) end def visible_text filter_text command(:visible_text) end def property(name) command :property, name end def [](name) # Although the attribute matters, the property is consistent. Return that in # preference to the attribute for links and images. if (tag_name == 'img' and name == 'src') or (tag_name == 'a' and name == 'href' ) #if attribute exists get the property value = command(:attribute, name) && command(:property, name) return value end value = property(name) value = command(:attribute, name) if value.nil? || value.is_a?(Hash) value end def attributes command :attributes end def value command :value end def set(value) if tag_name == 'input' case self[:type] when 'radio' click when 'checkbox' click if value != checked? when 'file' files = value.respond_to?(:to_ary) ? value.to_ary.map(&:to_s) : value.to_s command :select_file, files else command :set, value.to_s end elsif tag_name == 'textarea' command :set, value.to_s elsif self[:contenteditable] == 'true' command :delete_text send_keys(value.to_s) end end def select_option command :select, true end def unselect_option command(:select, false) or raise(Capybara::UnselectNotAllowed, "Cannot unselect option from single select box.") end def tag_name @tag_name ||= command(:tag_name) end def visible? command :visible? end def checked? self[:checked] end def selected? !!self[:selected] end def disabled? command :disabled? end def click command :click end def right_click command :right_click end def double_click command :double_click end def hover command :hover end def drag_to(other) command :drag, other.id end def drag_by(x, y) command :drag_by, x, y end def trigger(event) command :trigger, event end def ==(other) command :equals, other.id end def send_keys(*keys) command :send_keys, keys end alias_method :send_key, :send_keys def path command :path end private def filter_text(text) Capybara::Helpers.normalize_whitespace(text.to_s) end end end poltergeist-1.10.0/lib/capybara/poltergeist/client/0000755000175000017500000000000012766546165021347 5ustar pravipravipoltergeist-1.10.0/lib/capybara/poltergeist/client/connection.coffee0000644000175000017500000000053412766546165024661 0ustar pravipraviclass Poltergeist.Connection constructor: (@owner, @port) -> @socket = new WebSocket "ws://127.0.0.1:#{@port}/" @socket.onmessage = this.commandReceived @socket.onclose = -> phantom.exit() commandReceived: (message) => @owner.runCommand(JSON.parse(message.data)) send: (message) -> @socket.send(JSON.stringify(message)) poltergeist-1.10.0/lib/capybara/poltergeist/client/compiled/0000755000175000017500000000000012766546165023143 5ustar pravipravipoltergeist-1.10.0/lib/capybara/poltergeist/client/compiled/cmd.js0000644000175000017500000000140212766546165024241 0ustar pravipraviPoltergeist.Cmd = (function() { function Cmd(owner, id, name, args) { this.owner = owner; this.id = id; this.name = name; this.args = args; } Cmd.prototype.sendResponse = function(response) { var errors; errors = this.browser.currentPage.errors; this.browser.currentPage.clearErrors(); if (errors.length > 0 && this.browser.js_errors) { return this.sendError(new Poltergeist.JavascriptError(errors)); } else { return this.owner.sendResponse(this.id, response); } }; Cmd.prototype.sendError = function(errors) { return this.owner.sendError(this.id, errors); }; Cmd.prototype.run = function(browser) { this.browser = browser; return this.browser.runCommand(this); }; return Cmd; })(); poltergeist-1.10.0/lib/capybara/poltergeist/client/compiled/node.js0000644000175000017500000001340412766546165024430 0ustar pravipravivar slice = [].slice; Poltergeist.Node = (function() { var fn, j, len, name, ref; Node.DELEGATES = ['allText', 'visibleText', 'getAttribute', 'value', 'set', 'setAttribute', 'isObsolete', 'removeAttribute', 'isMultiple', 'select', 'tagName', 'find', 'getAttributes', 'isVisible', 'isInViewport', 'position', 'trigger', 'parentId', 'parentIds', 'mouseEventTest', 'scrollIntoView', 'isDOMEqual', 'isDisabled', 'deleteText', 'containsSelection', 'path', 'getProperty']; function Node(page, id) { this.page = page; this.id = id; } Node.prototype.parent = function() { return new Poltergeist.Node(this.page, this.parentId()); }; ref = Node.DELEGATES; fn = function(name) { return Node.prototype[name] = function() { var args; args = 1 <= arguments.length ? slice.call(arguments, 0) : []; return this.page.nodeCall(this.id, name, args); }; }; for (j = 0, len = ref.length; j < len; j++) { name = ref[j]; fn(name); } Node.prototype.mouseEventPosition = function() { var area_offset, image, middle, pos, res, viewport; viewport = this.page.viewportSize(); if (image = this._getAreaImage()) { pos = image.position(); if (area_offset = this._getAreaOffsetRect()) { pos.left = pos.left + area_offset.x; pos.right = pos.left + area_offset.width; pos.top = pos.top + area_offset.y; pos.bottom = pos.top + area_offset.height; } } else { pos = this.position(); } middle = function(start, end, size) { return start + ((Math.min(end, size) - start) / 2); }; return res = { x: middle(pos.left, pos.right, viewport.width), y: middle(pos.top, pos.bottom, viewport.height) }; }; Node.prototype.mouseEvent = function(name) { var area_image, pos, test; if (area_image = this._getAreaImage()) { area_image.scrollIntoView(); } else { this.scrollIntoView(); } pos = this.mouseEventPosition(); test = this.mouseEventTest(pos.x, pos.y); if (test.status === 'success') { if (name === 'rightclick') { this.page.mouseEvent('click', pos.x, pos.y, 'right'); this.trigger('contextmenu'); } else { this.page.mouseEvent(name, pos.x, pos.y); } return pos; } else { throw new Poltergeist.MouseEventFailed(name, test.selector, pos); } }; Node.prototype.dragTo = function(other) { var otherPosition, position; this.scrollIntoView(); position = this.mouseEventPosition(); otherPosition = other.mouseEventPosition(); this.page.mouseEvent('mousedown', position.x, position.y); return this.page.mouseEvent('mouseup', otherPosition.x, otherPosition.y); }; Node.prototype.dragBy = function(x, y) { var final_pos, position; this.scrollIntoView(); position = this.mouseEventPosition(); final_pos = { x: position.x + x, y: position.y + y }; this.page.mouseEvent('mousedown', position.x, position.y); return this.page.mouseEvent('mouseup', final_pos.x, final_pos.y); }; Node.prototype.isEqual = function(other) { return this.page === other.page && this.isDOMEqual(other.id); }; Node.prototype._getAreaOffsetRect = function() { var centerX, centerY, coord, coords, i, maxX, maxY, minX, minY, radius, rect, shape, x, xs, y, ys; shape = this.getAttribute('shape').toLowerCase(); coords = (function() { var k, len1, ref1, results; ref1 = this.getAttribute('coords').split(','); results = []; for (k = 0, len1 = ref1.length; k < len1; k++) { coord = ref1[k]; results.push(parseInt(coord, 10)); } return results; }).call(this); return rect = (function() { switch (shape) { case 'rect': case 'rectangle': x = coords[0], y = coords[1]; return { x: x, y: y, width: coords[2] - x, height: coords[3] - y }; case 'circ': case 'circle': centerX = coords[0], centerY = coords[1], radius = coords[2]; return { x: centerX - radius, y: centerY - radius, width: 2 * radius, height: 2 * radius }; case 'poly': case 'polygon': xs = (function() { var k, ref1, results; results = []; for (i = k = 0, ref1 = coords.length; k < ref1; i = k += 2) { results.push(coords[i]); } return results; })(); ys = (function() { var k, ref1, results; results = []; for (i = k = 1, ref1 = coords.length; k < ref1; i = k += 2) { results.push(coords[i]); } return results; })(); minX = Math.min.apply(Math, xs); maxX = Math.max.apply(Math, xs); minY = Math.min.apply(Math, ys); maxY = Math.max.apply(Math, ys); return { x: minX, y: minY, width: maxX - minX, height: maxY - minY }; } })(); }; Node.prototype._getAreaImage = function() { var image_node_id, map, mapName; if ('area' === this.tagName().toLowerCase()) { map = this.parent(); if (map.tagName().toLowerCase() !== 'map') { throw new Error('the area is not within a map'); } mapName = map.getAttribute('name'); if (mapName == null) { throw new Error("area's parent map must have a name"); } mapName = '#' + mapName.toLowerCase(); image_node_id = this.page.find('css', "img[usemap='" + mapName + "']")[0]; if (image_node_id == null) { throw new Error("no image matches the map"); } return this.page.get(image_node_id); } }; return Node; })(); poltergeist-1.10.0/lib/capybara/poltergeist/client/compiled/agent.js0000644000175000017500000003757012766546165024613 0ustar pravipravivar PoltergeistAgent; PoltergeistAgent = (function() { PoltergeistAgent.JSON || (PoltergeistAgent.JSON = { parse: JSON.parse, stringify: JSON.stringify }); function PoltergeistAgent() { this.elements = []; this.nodes = {}; } PoltergeistAgent.prototype.externalCall = function(name, args) { var error, error1; try { return { value: this[name].apply(this, args) }; } catch (error1) { error = error1; return { error: { message: error.toString(), stack: error.stack } }; } }; PoltergeistAgent.stringify = function(object) { var error, error1; try { return PoltergeistAgent.JSON.stringify(object, function(key, value) { if (Array.isArray(this[key])) { return this[key]; } else { return value; } }); } catch (error1) { error = error1; if (error instanceof TypeError) { return '"(cyclic structure)"'; } else { throw error; } } }; PoltergeistAgent.prototype.currentUrl = function() { return window.location.href.replace(/\ /g, '%20'); }; PoltergeistAgent.prototype.find = function(method, selector, within) { var el, error, error1, i, j, len, results, results1, xpath; if (within == null) { within = document; } try { if (method === "xpath") { xpath = document.evaluate(selector, within, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); results = (function() { var j, ref, results1; results1 = []; for (i = j = 0, ref = xpath.snapshotLength; 0 <= ref ? j < ref : j > ref; i = 0 <= ref ? ++j : --j) { results1.push(xpath.snapshotItem(i)); } return results1; })(); } else { results = within.querySelectorAll(selector); } results1 = []; for (j = 0, len = results.length; j < len; j++) { el = results[j]; results1.push(this.register(el)); } return results1; } catch (error1) { error = error1; if (error.code === DOMException.SYNTAX_ERR || error.code === 51) { throw new PoltergeistAgent.InvalidSelector; } else { throw error; } } }; PoltergeistAgent.prototype.register = function(element) { this.elements.push(element); return this.elements.length - 1; }; PoltergeistAgent.prototype.documentSize = function() { return { height: document.documentElement.scrollHeight || document.documentElement.clientHeight, width: document.documentElement.scrollWidth || document.documentElement.clientWidth }; }; PoltergeistAgent.prototype.get = function(id) { var base; return (base = this.nodes)[id] || (base[id] = new PoltergeistAgent.Node(this, this.elements[id])); }; PoltergeistAgent.prototype.nodeCall = function(id, name, args) { var node; node = this.get(id); if (node.isObsolete()) { throw new PoltergeistAgent.ObsoleteNode; } return node[name].apply(node, args); }; PoltergeistAgent.prototype.beforeUpload = function(id) { return this.get(id).setAttribute('_poltergeist_selected', ''); }; PoltergeistAgent.prototype.afterUpload = function(id) { return this.get(id).removeAttribute('_poltergeist_selected'); }; PoltergeistAgent.prototype.clearLocalStorage = function() { return localStorage.clear(); }; return PoltergeistAgent; })(); PoltergeistAgent.ObsoleteNode = (function() { function ObsoleteNode() {} ObsoleteNode.prototype.toString = function() { return "PoltergeistAgent.ObsoleteNode"; }; return ObsoleteNode; })(); PoltergeistAgent.InvalidSelector = (function() { function InvalidSelector() {} InvalidSelector.prototype.toString = function() { return "PoltergeistAgent.InvalidSelector"; }; return InvalidSelector; })(); PoltergeistAgent.Node = (function() { Node.EVENTS = { FOCUS: ['blur', 'focus', 'focusin', 'focusout'], MOUSE: ['click', 'dblclick', 'mousedown', 'mouseenter', 'mouseleave', 'mousemove', 'mouseover', 'mouseout', 'mouseup', 'contextmenu'], FORM: ['submit'] }; function Node(agent, element1) { this.agent = agent; this.element = element1; } Node.prototype.parentId = function() { return this.agent.register(this.element.parentNode); }; Node.prototype.parentIds = function() { var ids, parent; ids = []; parent = this.element.parentNode; while (parent !== document) { ids.push(this.agent.register(parent)); parent = parent.parentNode; } return ids; }; Node.prototype.find = function(method, selector) { return this.agent.find(method, selector, this.element); }; Node.prototype.isObsolete = function() { var obsolete; obsolete = (function(_this) { return function(element) { var parent; if ((parent = element.parentNode) != null) { if (parent === document) { return false; } else { return obsolete(parent); } } else { return true; } }; })(this); return obsolete(this.element); }; Node.prototype.changed = function() { var element, event; event = document.createEvent('HTMLEvents'); event.initEvent('change', true, false); if (this.element.nodeName === 'OPTION') { element = this.element.parentNode; if (element.nodeName === 'OPTGROUP') { element = element.parentNode; } element; } else { element = this.element; } return element.dispatchEvent(event); }; Node.prototype.input = function() { var event; event = document.createEvent('HTMLEvents'); event.initEvent('input', true, false); return this.element.dispatchEvent(event); }; Node.prototype.keyupdowned = function(eventName, keyCode) { var event; event = document.createEvent('UIEvents'); event.initEvent(eventName, true, true); event.keyCode = keyCode; event.which = keyCode; event.charCode = 0; return this.element.dispatchEvent(event); }; Node.prototype.keypressed = function(altKey, ctrlKey, shiftKey, metaKey, keyCode, charCode) { var event; event = document.createEvent('UIEvents'); event.initEvent('keypress', true, true); event.window = this.agent.window; event.altKey = altKey; event.ctrlKey = ctrlKey; event.shiftKey = shiftKey; event.metaKey = metaKey; event.keyCode = keyCode; event.charCode = charCode; event.which = keyCode; return this.element.dispatchEvent(event); }; Node.prototype.insideBody = function() { return this.element === document.body || document.evaluate('ancestor::body', this.element, null, XPathResult.BOOLEAN_TYPE, null).booleanValue; }; Node.prototype.allText = function() { return this.element.textContent; }; Node.prototype.visibleText = function() { if (this.isVisible()) { if (this.element.nodeName === "TEXTAREA") { return this.element.textContent; } else { if (this.element instanceof SVGElement) { return this.element.textContent; } else { return this.element.innerText; } } } }; Node.prototype.deleteText = function() { var range; range = document.createRange(); range.selectNodeContents(this.element); window.getSelection().removeAllRanges(); window.getSelection().addRange(range); return window.getSelection().deleteFromDocument(); }; Node.prototype.getProperty = function(name) { return this.element[name]; }; Node.prototype.getAttributes = function() { var attr, attrs, j, len, ref; attrs = {}; ref = this.element.attributes; for (j = 0, len = ref.length; j < len; j++) { attr = ref[j]; attrs[attr.name] = attr.value.replace("\n", "\\n"); } return attrs; }; Node.prototype.getAttribute = function(name) { if (name === 'checked' || name === 'selected') { return this.element[name]; } else { return this.element.getAttribute(name); } }; Node.prototype.scrollIntoView = function() { this.element.scrollIntoViewIfNeeded(); if (!this.isInViewport()) { return this.element.scrollIntoView(); } }; Node.prototype.value = function() { var j, len, option, ref, results1; if (this.element.tagName === 'SELECT' && this.element.multiple) { ref = this.element.children; results1 = []; for (j = 0, len = ref.length; j < len; j++) { option = ref[j]; if (option.selected) { results1.push(option.value); } } return results1; } else { return this.element.value; } }; Node.prototype.set = function(value) { var char, j, keyCode, len; if (this.element.readOnly) { return; } if (this.element.maxLength >= 0) { value = value.substr(0, this.element.maxLength); } this.trigger('focus'); this.element.value = ''; if (this.element.type === 'number') { this.element.value = value; } else { for (j = 0, len = value.length; j < len; j++) { char = value[j]; keyCode = this.characterToKeyCode(char); this.keyupdowned('keydown', keyCode); this.element.value += char; this.keypressed(false, false, false, false, char.charCodeAt(0), char.charCodeAt(0)); this.keyupdowned('keyup', keyCode); } } this.changed(); this.input(); return this.trigger('blur'); }; Node.prototype.isMultiple = function() { return this.element.multiple; }; Node.prototype.setAttribute = function(name, value) { return this.element.setAttribute(name, value); }; Node.prototype.removeAttribute = function(name) { return this.element.removeAttribute(name); }; Node.prototype.select = function(value) { if (this.isDisabled()) { return false; } else if (value === false && !this.element.parentNode.multiple) { return false; } else { this.trigger('focus', this.element.parentNode); this.element.selected = value; this.changed(); this.trigger('blur', this.element.parentNode); return true; } }; Node.prototype.tagName = function() { return this.element.tagName; }; Node.prototype.isVisible = function(element) { var map_name, style; if (element == null) { element = this.element; } if (element.tagName === 'AREA') { map_name = document.evaluate('./ancestor::map/@name', element, null, XPathResult.STRING_TYPE, null).stringValue; element = document.querySelector("img[usemap='#" + map_name + "']"); if (element == null) { return false; } } while (element) { style = window.getComputedStyle(element); if (style.display === 'none' || style.visibility === 'hidden' || parseFloat(style.opacity) === 0) { return false; } element = element.parentElement; } return true; }; Node.prototype.isInViewport = function() { var rect; rect = this.element.getBoundingClientRect(); return rect.top >= 0 && rect.left >= 0 && rect.bottom <= window.innerHeight && rect.right <= window.innerWidth; }; Node.prototype.isDisabled = function() { return this.element.disabled || this.element.tagName === 'OPTION' && this.element.parentNode.disabled; }; Node.prototype.path = function() { var elements, selectors; elements = this.parentIds().reverse().map((function(_this) { return function(id) { return _this.agent.get(id); }; })(this)); elements.push(this); selectors = elements.map(function(el) { var prev_siblings; prev_siblings = el.find('xpath', "./preceding-sibling::" + (el.tagName())); return (el.tagName()) + "[" + (prev_siblings.length + 1) + "]"; }); return "//" + selectors.join('/'); }; Node.prototype.containsSelection = function() { var selectedNode; selectedNode = document.getSelection().focusNode; if (!selectedNode) { return false; } if (selectedNode.nodeType === 3) { selectedNode = selectedNode.parentNode; } return this.element.contains(selectedNode); }; Node.prototype.frameOffset = function() { var offset, rect, style, win; win = window; offset = { top: 0, left: 0 }; while (win.frameElement) { rect = win.frameElement.getClientRects()[0]; style = win.getComputedStyle(win.frameElement); win = win.parent; offset.top += rect.top + parseInt(style.getPropertyValue("padding-top"), 10); offset.left += rect.left + parseInt(style.getPropertyValue("padding-left"), 10); } return offset; }; Node.prototype.position = function() { var frameOffset, pos, rect; rect = this.element.getClientRects()[0] || this.element.getBoundingClientRect(); if (!rect) { throw new PoltergeistAgent.ObsoleteNode; } frameOffset = this.frameOffset(); pos = { top: rect.top + frameOffset.top, right: rect.right + frameOffset.left, left: rect.left + frameOffset.left, bottom: rect.bottom + frameOffset.top, width: rect.width, height: rect.height }; return pos; }; Node.prototype.trigger = function(name, element) { var event; if (element == null) { element = this.element; } if (Node.EVENTS.MOUSE.indexOf(name) !== -1) { event = document.createEvent('MouseEvent'); event.initMouseEvent(name, true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null); } else if (Node.EVENTS.FOCUS.indexOf(name) !== -1) { event = this.obtainEvent(name); } else if (Node.EVENTS.FORM.indexOf(name) !== -1) { event = this.obtainEvent(name); } else { throw "Unknown event"; } return element.dispatchEvent(event); }; Node.prototype.obtainEvent = function(name) { var event; event = document.createEvent('HTMLEvents'); event.initEvent(name, true, true); return event; }; Node.prototype.mouseEventTest = function(x, y) { var el, frameOffset, origEl; frameOffset = this.frameOffset(); x -= frameOffset.left; y -= frameOffset.top; el = origEl = document.elementFromPoint(x, y); while (el) { if (el === this.element) { return { status: 'success' }; } else { el = el.parentNode; } } return { status: 'failure', selector: origEl && this.getSelector(origEl) }; }; Node.prototype.getSelector = function(el) { var className, classes, j, len, ref, ref1, selector; selector = el.tagName !== 'HTML' ? this.getSelector(el.parentNode) + ' ' : ''; selector += el.tagName.toLowerCase(); if (el.id) { selector += "#" + el.id; } classes = el.classList || ((ref = el.getAttribute('class')) != null ? (ref1 = ref.trim()) != null ? ref1.split(/\s+/) : void 0 : void 0) || []; for (j = 0, len = classes.length; j < len; j++) { className = classes[j]; if (className !== '') { selector += "." + className; } } return selector; }; Node.prototype.characterToKeyCode = function(character) { var code, specialKeys; code = character.toUpperCase().charCodeAt(0); specialKeys = { 96: 192, 45: 189, 61: 187, 91: 219, 93: 221, 92: 220, 59: 186, 39: 222, 44: 188, 46: 190, 47: 191, 127: 46, 126: 192, 33: 49, 64: 50, 35: 51, 36: 52, 37: 53, 94: 54, 38: 55, 42: 56, 40: 57, 41: 48, 95: 189, 43: 187, 123: 219, 125: 221, 124: 220, 58: 186, 34: 222, 60: 188, 62: 190, 63: 191 }; return specialKeys[code] || code; }; Node.prototype.isDOMEqual = function(other_id) { return this.element === this.agent.get(other_id).element; }; return Node; })(); window.__poltergeist = new PoltergeistAgent; document.addEventListener('DOMContentLoaded', function() { return console.log('__DOMContentLoaded'); }); poltergeist-1.10.0/lib/capybara/poltergeist/client/compiled/main.js0000644000175000017500000001356312766546165024435 0ustar pravipravivar Poltergeist, system, extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, hasProp = {}.hasOwnProperty; Poltergeist = (function() { function Poltergeist(port, width, height) { var that; this.browser = new Poltergeist.Browser(width, height); this.connection = new Poltergeist.Connection(this, port); that = this; phantom.onError = function(message, stack) { return that.onError(message, stack); }; this.running = false; } Poltergeist.prototype.runCommand = function(command) { var error, error1; this.running = true; command = new Poltergeist.Cmd(this, command.id, command.name, command.args); try { return command.run(this.browser); } catch (error1) { error = error1; if (error instanceof Poltergeist.Error) { return this.sendError(command.id, error); } else { return this.sendError(command.id, new Poltergeist.BrowserError(error.toString(), error.stack)); } } }; Poltergeist.prototype.sendResponse = function(command_id, response) { return this.send({ command_id: command_id, response: response }); }; Poltergeist.prototype.sendError = function(command_id, error) { return this.send({ command_id: command_id, error: { name: error.name || 'Generic', args: error.args && error.args() || [error.toString()] } }); }; Poltergeist.prototype.send = function(data) { if (this.running) { this.connection.send(data); this.running = false; return true; } return false; }; return Poltergeist; })(); window.Poltergeist = Poltergeist; Poltergeist.Error = (function() { function Error() {} return Error; })(); Poltergeist.ObsoleteNode = (function(superClass) { extend(ObsoleteNode, superClass); function ObsoleteNode() { return ObsoleteNode.__super__.constructor.apply(this, arguments); } ObsoleteNode.prototype.name = "Poltergeist.ObsoleteNode"; ObsoleteNode.prototype.args = function() { return []; }; ObsoleteNode.prototype.toString = function() { return this.name; }; return ObsoleteNode; })(Poltergeist.Error); Poltergeist.InvalidSelector = (function(superClass) { extend(InvalidSelector, superClass); function InvalidSelector(method, selector) { this.method = method; this.selector = selector; } InvalidSelector.prototype.name = "Poltergeist.InvalidSelector"; InvalidSelector.prototype.args = function() { return [this.method, this.selector]; }; return InvalidSelector; })(Poltergeist.Error); Poltergeist.FrameNotFound = (function(superClass) { extend(FrameNotFound, superClass); function FrameNotFound(frameName) { this.frameName = frameName; } FrameNotFound.prototype.name = "Poltergeist.FrameNotFound"; FrameNotFound.prototype.args = function() { return [this.frameName]; }; return FrameNotFound; })(Poltergeist.Error); Poltergeist.MouseEventFailed = (function(superClass) { extend(MouseEventFailed, superClass); function MouseEventFailed(eventName, selector, position) { this.eventName = eventName; this.selector = selector; this.position = position; } MouseEventFailed.prototype.name = "Poltergeist.MouseEventFailed"; MouseEventFailed.prototype.args = function() { return [this.eventName, this.selector, this.position]; }; return MouseEventFailed; })(Poltergeist.Error); Poltergeist.JavascriptError = (function(superClass) { extend(JavascriptError, superClass); function JavascriptError(errors) { this.errors = errors; } JavascriptError.prototype.name = "Poltergeist.JavascriptError"; JavascriptError.prototype.args = function() { return [this.errors]; }; return JavascriptError; })(Poltergeist.Error); Poltergeist.BrowserError = (function(superClass) { extend(BrowserError, superClass); function BrowserError(message1, stack1) { this.message = message1; this.stack = stack1; } BrowserError.prototype.name = "Poltergeist.BrowserError"; BrowserError.prototype.args = function() { return [this.message, this.stack]; }; return BrowserError; })(Poltergeist.Error); Poltergeist.StatusFailError = (function(superClass) { extend(StatusFailError, superClass); function StatusFailError(url, details) { this.url = url; this.details = details; } StatusFailError.prototype.name = "Poltergeist.StatusFailError"; StatusFailError.prototype.args = function() { return [this.url, this.details]; }; return StatusFailError; })(Poltergeist.Error); Poltergeist.NoSuchWindowError = (function(superClass) { extend(NoSuchWindowError, superClass); function NoSuchWindowError() { return NoSuchWindowError.__super__.constructor.apply(this, arguments); } NoSuchWindowError.prototype.name = "Poltergeist.NoSuchWindowError"; NoSuchWindowError.prototype.args = function() { return []; }; return NoSuchWindowError; })(Poltergeist.Error); Poltergeist.UnsupportedFeature = (function(superClass) { extend(UnsupportedFeature, superClass); function UnsupportedFeature(message1) { this.message = message1; } UnsupportedFeature.prototype.name = "Poltergeist.UnsupportedFeature"; UnsupportedFeature.prototype.args = function() { return [this.message, phantom.version]; }; return UnsupportedFeature; })(Poltergeist.Error); phantom.injectJs(phantom.libraryPath + "/web_page.js"); phantom.injectJs(phantom.libraryPath + "/node.js"); phantom.injectJs(phantom.libraryPath + "/connection.js"); phantom.injectJs(phantom.libraryPath + "/cmd.js"); phantom.injectJs(phantom.libraryPath + "/browser.js"); system = require('system'); new Poltergeist(system.args[1], system.args[2], system.args[3]); poltergeist-1.10.0/lib/capybara/poltergeist/client/compiled/web_page.js0000644000175000017500000004170112766546165025255 0ustar pravipravivar slice = [].slice, indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }, hasProp = {}.hasOwnProperty; Poltergeist.WebPage = (function() { var command, delegate, fn1, fn2, i, j, len, len1, ref, ref1; WebPage.CALLBACKS = ['onConsoleMessage', 'onError', 'onLoadFinished', 'onInitialized', 'onLoadStarted', 'onResourceRequested', 'onResourceReceived', 'onResourceError', 'onNavigationRequested', 'onUrlChanged', 'onPageCreated', 'onClosing']; WebPage.DELEGATES = ['open', 'sendEvent', 'uploadFile', 'release', 'render', 'renderBase64', 'goBack', 'goForward']; WebPage.COMMANDS = ['currentUrl', 'find', 'nodeCall', 'documentSize', 'beforeUpload', 'afterUpload', 'clearLocalStorage']; WebPage.EXTENSIONS = []; function WebPage(_native) { var callback, i, len, ref; this._native = _native; this._native || (this._native = require('webpage').create()); this.id = 0; this.source = null; this.closed = false; this.state = 'default'; this.urlWhitelist = []; this.urlBlacklist = []; this.frames = []; this.errors = []; this._networkTraffic = {}; this._tempHeaders = {}; this._blockedUrls = []; this._requestedResources = {}; ref = WebPage.CALLBACKS; for (i = 0, len = ref.length; i < len; i++) { callback = ref[i]; this.bindCallback(callback); } } ref = WebPage.COMMANDS; fn1 = function(command) { return WebPage.prototype[command] = function() { var args; args = 1 <= arguments.length ? slice.call(arguments, 0) : []; return this.runCommand(command, args); }; }; for (i = 0, len = ref.length; i < len; i++) { command = ref[i]; fn1(command); } ref1 = WebPage.DELEGATES; fn2 = function(delegate) { return WebPage.prototype[delegate] = function() { return this._native[delegate].apply(this._native, arguments); }; }; for (j = 0, len1 = ref1.length; j < len1; j++) { delegate = ref1[j]; fn2(delegate); } WebPage.prototype.onInitializedNative = function() { this.id += 1; this.source = null; this.injectAgent(); this.removeTempHeaders(); return this.setScrollPosition({ left: 0, top: 0 }); }; WebPage.prototype.onClosingNative = function() { this.handle = null; return this.closed = true; }; WebPage.prototype.onConsoleMessageNative = function(message) { if (message === '__DOMContentLoaded') { this.source = this._native.content; return false; } else { return console.log(message); } }; WebPage.prototype.onLoadStartedNative = function() { this.state = 'loading'; this.requestId = this.lastRequestId; return this._requestedResources = {}; }; WebPage.prototype.onLoadFinishedNative = function(status) { this.status = status; this.state = 'default'; return this.source || (this.source = this._native.content); }; WebPage.prototype.onErrorNative = function(message, stack) { var stackString; stackString = message; stack.forEach(function(frame) { stackString += "\n"; stackString += " at " + frame.file + ":" + frame.line; if (frame["function"] && frame["function"] !== '') { return stackString += " in " + frame["function"]; } }); this.errors.push({ message: message, stack: stackString }); return true; }; WebPage.prototype.onResourceRequestedNative = function(request, net) { var abort, blacklisted, ref2, useWhitelist, whitelisted; useWhitelist = this.urlWhitelist.length > 0; whitelisted = this.urlWhitelist.some(function(whitelisted_regex) { return whitelisted_regex.test(request.url); }); blacklisted = this.urlBlacklist.some(function(blacklisted_regex) { return blacklisted_regex.test(request.url); }); abort = false; if (useWhitelist && !whitelisted) { abort = true; } if (blacklisted) { abort = true; } if (abort) { if (ref2 = request.url, indexOf.call(this._blockedUrls, ref2) < 0) { this._blockedUrls.push(request.url); } net.abort(); } else { this.lastRequestId = request.id; if (this.normalizeURL(request.url) === this.redirectURL) { this.redirectURL = null; this.requestId = request.id; } this._networkTraffic[request.id] = { request: request, responseParts: [], error: null }; this._requestedResources[request.id] = request.url; } return true; }; WebPage.prototype.onResourceReceivedNative = function(response) { var ref2; if ((ref2 = this._networkTraffic[response.id]) != null) { ref2.responseParts.push(response); } if (response.stage === 'end') { delete this._requestedResources[response.id]; } if (this.requestId === response.id) { if (response.redirectURL) { this.redirectURL = this.normalizeURL(response.redirectURL); } else { this.statusCode = response.status; this._responseHeaders = response.headers; } } return true; }; WebPage.prototype.onResourceErrorNative = function(errorResponse) { var ref2; if ((ref2 = this._networkTraffic[errorResponse.id]) != null) { ref2.error = errorResponse; } delete this._requestedResources[errorResponse.id]; return true; }; WebPage.prototype.injectAgent = function() { var extension, k, len2, ref2; if (this["native"]().evaluate(function() { return typeof __poltergeist; }) === "undefined") { this["native"]().injectJs(phantom.libraryPath + "/agent.js"); ref2 = WebPage.EXTENSIONS; for (k = 0, len2 = ref2.length; k < len2; k++) { extension = ref2[k]; this["native"]().injectJs(extension); } return true; } return false; }; WebPage.prototype.injectExtension = function(file) { WebPage.EXTENSIONS.push(file); return this["native"]().injectJs(file); }; WebPage.prototype["native"] = function() { if (this.closed) { throw new Poltergeist.NoSuchWindowError; } else { return this._native; } }; WebPage.prototype.windowName = function() { return this["native"]().windowName; }; WebPage.prototype.keyCode = function(name) { return this["native"]().event.key[name]; }; WebPage.prototype.keyModifierCode = function(names) { var modifiers; modifiers = this["native"]().event.modifier; names = names.split(',').map((function(name) { return modifiers[name]; })); return names[0] | names[1]; }; WebPage.prototype.keyModifierKeys = function(names) { return names.split(',').map((function(_this) { return function(name) { return _this.keyCode(name.charAt(0).toUpperCase() + name.substring(1)); }; })(this)); }; WebPage.prototype._waitState_until = function(state, callback, timeout, timeout_callback) { if (this.state === state) { return callback.call(this); } else { if (new Date().getTime() > timeout) { return timeout_callback.call(this); } else { return setTimeout(((function(_this) { return function() { return _this._waitState_until(state, callback, timeout, timeout_callback); }; })(this)), 100); } } }; WebPage.prototype.waitState = function(state, callback, max_wait, timeout_callback) { var timeout; if (max_wait == null) { max_wait = 0; } if (this.state === state) { return callback.call(this); } else { if (max_wait !== 0) { timeout = new Date().getTime() + (max_wait * 1000); return setTimeout(((function(_this) { return function() { return _this._waitState_until(state, callback, timeout, timeout_callback); }; })(this)), 100); } else { return setTimeout(((function(_this) { return function() { return _this.waitState(state, callback); }; })(this)), 100); } } }; WebPage.prototype.setHttpAuth = function(user, password) { this["native"]().settings.userName = user; this["native"]().settings.password = password; return true; }; WebPage.prototype.networkTraffic = function() { return this._networkTraffic; }; WebPage.prototype.clearNetworkTraffic = function() { this._networkTraffic = {}; return true; }; WebPage.prototype.blockedUrls = function() { return this._blockedUrls; }; WebPage.prototype.clearBlockedUrls = function() { this._blockedUrls = []; return true; }; WebPage.prototype.openResourceRequests = function() { var id, ref2, results, url; ref2 = this._requestedResources; results = []; for (id in ref2) { if (!hasProp.call(ref2, id)) continue; url = ref2[id]; results.push(url); } return results; }; WebPage.prototype.content = function() { return this["native"]().frameContent; }; WebPage.prototype.title = function() { return this["native"]().frameTitle; }; WebPage.prototype.frameUrl = function(frameNameOrId) { var query; query = function(frameNameOrId) { var ref2; return (ref2 = document.querySelector("iframe[name='" + frameNameOrId + "'], iframe[id='" + frameNameOrId + "']")) != null ? ref2.src : void 0; }; return this.evaluate(query, frameNameOrId); }; WebPage.prototype.clearErrors = function() { this.errors = []; return true; }; WebPage.prototype.responseHeaders = function() { var headers; headers = {}; this._responseHeaders.forEach(function(item) { return headers[item.name] = item.value; }); return headers; }; WebPage.prototype.cookies = function() { return this["native"]().cookies; }; WebPage.prototype.deleteCookie = function(name) { return this["native"]().deleteCookie(name); }; WebPage.prototype.viewportSize = function() { return this["native"]().viewportSize; }; WebPage.prototype.setViewportSize = function(size) { return this["native"]().viewportSize = size; }; WebPage.prototype.setZoomFactor = function(zoom_factor) { return this["native"]().zoomFactor = zoom_factor; }; WebPage.prototype.setPaperSize = function(size) { return this["native"]().paperSize = size; }; WebPage.prototype.scrollPosition = function() { return this["native"]().scrollPosition; }; WebPage.prototype.setScrollPosition = function(pos) { return this["native"]().scrollPosition = pos; }; WebPage.prototype.clipRect = function() { return this["native"]().clipRect; }; WebPage.prototype.setClipRect = function(rect) { return this["native"]().clipRect = rect; }; WebPage.prototype.elementBounds = function(selector) { return this["native"]().evaluate(function(selector) { return document.querySelector(selector).getBoundingClientRect(); }, selector); }; WebPage.prototype.setUserAgent = function(userAgent) { return this["native"]().settings.userAgent = userAgent; }; WebPage.prototype.getCustomHeaders = function() { return this["native"]().customHeaders; }; WebPage.prototype.setCustomHeaders = function(headers) { return this["native"]().customHeaders = headers; }; WebPage.prototype.addTempHeader = function(header) { var name, value; for (name in header) { value = header[name]; this._tempHeaders[name] = value; } return this._tempHeaders; }; WebPage.prototype.removeTempHeaders = function() { var allHeaders, name, ref2, value; allHeaders = this.getCustomHeaders(); ref2 = this._tempHeaders; for (name in ref2) { value = ref2[name]; delete allHeaders[name]; } return this.setCustomHeaders(allHeaders); }; WebPage.prototype.pushFrame = function(name) { var frame_no; if (this["native"]().switchToFrame(name)) { this.frames.push(name); return true; } else { frame_no = this["native"]().evaluate(function(frame_name) { var f, frames, idx; frames = document.querySelectorAll("iframe, frame"); return ((function() { var k, len2, results; results = []; for (idx = k = 0, len2 = frames.length; k < len2; idx = ++k) { f = frames[idx]; if ((f != null ? f['name'] : void 0) === frame_name || (f != null ? f['id'] : void 0) === frame_name) { results.push(idx); } } return results; })())[0]; }, name); if ((frame_no != null) && this["native"]().switchToFrame(frame_no)) { this.frames.push(name); return true; } else { return false; } } }; WebPage.prototype.popFrame = function() { this.frames.pop(); return this["native"]().switchToParentFrame(); }; WebPage.prototype.dimensions = function() { var scroll, viewport; scroll = this.scrollPosition(); viewport = this.viewportSize(); return { top: scroll.top, bottom: scroll.top + viewport.height, left: scroll.left, right: scroll.left + viewport.width, viewport: viewport, document: this.documentSize() }; }; WebPage.prototype.validatedDimensions = function() { var dimensions, document; dimensions = this.dimensions(); document = dimensions.document; if (dimensions.right > document.width) { dimensions.left = Math.max(0, dimensions.left - (dimensions.right - document.width)); dimensions.right = document.width; } if (dimensions.bottom > document.height) { dimensions.top = Math.max(0, dimensions.top - (dimensions.bottom - document.height)); dimensions.bottom = document.height; } this.setScrollPosition({ left: dimensions.left, top: dimensions.top }); return dimensions; }; WebPage.prototype.get = function(id) { return new Poltergeist.Node(this, id); }; WebPage.prototype.mouseEvent = function(name, x, y, button) { if (button == null) { button = 'left'; } this.sendEvent('mousemove', x, y); return this.sendEvent(name, x, y, button); }; WebPage.prototype.evaluate = function() { var args, fn; fn = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : []; this.injectAgent(); return JSON.parse(this.sanitize(this["native"]().evaluate("function() { return PoltergeistAgent.stringify(" + (this.stringifyCall(fn, args)) + ") }"))); }; WebPage.prototype.sanitize = function(potential_string) { if (typeof potential_string === "string") { return potential_string.replace("\n", "\\n").replace("\r", "\\r"); } else { return potential_string; } }; WebPage.prototype.execute = function() { var args, fn; fn = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : []; return this["native"]().evaluate("function() { " + (this.stringifyCall(fn, args)) + " }"); }; WebPage.prototype.stringifyCall = function(fn, args) { if (args.length === 0) { return "(" + (fn.toString()) + ")()"; } else { return "(" + (fn.toString()) + ").apply(this, PoltergeistAgent.JSON.parse(" + (JSON.stringify(JSON.stringify(args))) + "))"; } }; WebPage.prototype.bindCallback = function(name) { var that; that = this; this["native"]()[name] = function() { var result; if (that[name + 'Native'] != null) { result = that[name + 'Native'].apply(that, arguments); } if (result !== false && (that[name] != null)) { return that[name].apply(that, arguments); } }; return true; }; WebPage.prototype.runCommand = function(name, args) { var method, result, selector; result = this.evaluate(function(name, args) { return __poltergeist.externalCall(name, args); }, name, args); if (result !== null) { if (result.error != null) { switch (result.error.message) { case 'PoltergeistAgent.ObsoleteNode': throw new Poltergeist.ObsoleteNode; break; case 'PoltergeistAgent.InvalidSelector': method = args[0], selector = args[1]; throw new Poltergeist.InvalidSelector(method, selector); break; default: throw new Poltergeist.BrowserError(result.error.message, result.error.stack); } } else { return result.value; } } }; WebPage.prototype.canGoBack = function() { return this["native"]().canGoBack; }; WebPage.prototype.canGoForward = function() { return this["native"]().canGoForward; }; WebPage.prototype.normalizeURL = function(url) { var parser; parser = document.createElement('a'); parser.href = url; return parser.href; }; WebPage.prototype.clearMemoryCache = function() { var clearMemoryCache; clearMemoryCache = this["native"]().clearMemoryCache; if (typeof clearMemoryCache === "function") { return clearMemoryCache(); } else { throw new Poltergeist.UnsupportedFeature("clearMemoryCache is supported since PhantomJS 2.0.0"); } }; return WebPage; })(); poltergeist-1.10.0/lib/capybara/poltergeist/client/compiled/browser.js0000644000175000017500000005731112766546165025173 0ustar pravipravivar indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }, slice = [].slice; Poltergeist.Browser = (function() { function Browser(width, height) { this.width = width || 1024; this.height = height || 768; this.pages = []; this.js_errors = true; this._debug = false; this._counter = 0; this.processed_modal_messages = []; this.confirm_processes = []; this.prompt_responses = []; this.resetPage(); } Browser.prototype.resetPage = function() { var ref; ref = [0, []], this._counter = ref[0], this.pages = ref[1]; if (this.page != null) { if (!this.page.closed) { if (this.page.currentUrl() !== 'about:blank') { this.page.clearLocalStorage(); } this.page.release(); } phantom.clearCookies(); } this.page = this.currentPage = new Poltergeist.WebPage; this.page.setViewportSize({ width: this.width, height: this.height }); this.page.handle = "" + (this._counter++); this.pages.push(this.page); this.processed_modal_messages = []; this.confirm_processes = []; this.prompt_responses = []; this.page["native"]().onAlert = (function(_this) { return function(msg) { _this.setModalMessage(msg); }; })(this); this.page["native"]().onConfirm = (function(_this) { return function(msg) { var process; process = _this.confirm_processes.pop(); if (process === void 0) { process = true; } _this.setModalMessage(msg); return process; }; })(this); this.page["native"]().onPrompt = (function(_this) { return function(msg, defaultVal) { var response; response = _this.prompt_responses.pop(); if (response === void 0 || response === false) { response = defaultVal; } _this.setModalMessage(msg); return response; }; })(this); this.page.onPageCreated = (function(_this) { return function(newPage) { var page; page = new Poltergeist.WebPage(newPage); page.handle = "" + (_this._counter++); page.urlBlacklist = _this.page.urlBlacklist; page.urlWhitelist = _this.page.urlWhitelist; page.setViewportSize(_this.page.viewportSize()); return _this.pages.push(page); }; })(this); }; Browser.prototype.getPageByHandle = function(handle) { return this.pages.filter(function(p) { return !p.closed && p.handle === handle; })[0]; }; Browser.prototype.runCommand = function(command) { this.current_command = command; this.currentPage.state = 'default'; return this[command.name].apply(this, command.args); }; Browser.prototype.debug = function(message) { if (this._debug) { return console.log("poltergeist [" + (new Date().getTime()) + "] " + message); } }; Browser.prototype.setModalMessage = function(msg) { this.processed_modal_messages.push(msg); }; Browser.prototype.add_extension = function(extension) { this.currentPage.injectExtension(extension); return this.current_command.sendResponse('success'); }; Browser.prototype.node = function(page_id, id) { if (this.currentPage.id === page_id) { return this.currentPage.get(id); } else { throw new Poltergeist.ObsoleteNode; } }; Browser.prototype.visit = function(url, max_wait) { var command, loading_page, prevUrl; if (max_wait == null) { max_wait = 0; } this.currentPage.state = 'loading'; this.processed_modal_messages = []; this.confirm_processes = []; this.prompt_responses = []; prevUrl = this.currentPage.source === null ? 'about:blank' : this.currentPage.currentUrl(); this.currentPage.open(url); if (/#/.test(url) && prevUrl.split('#')[0] === url.split('#')[0]) { this.currentPage.state = 'default'; return this.current_command.sendResponse({ status: 'success' }); } else { command = this.current_command; loading_page = this.currentPage; this.currentPage.waitState('default', function() { if (this.statusCode === null && this.status === 'fail') { return command.sendError(new Poltergeist.StatusFailError(url)); } else { return command.sendResponse({ status: this.status }); } }, max_wait, function() { var msg, resources; resources = this.openResourceRequests(); msg = resources.length ? "Timed out with the following resources still waiting " + (resources.join(',')) : void 0; return command.sendError(new Poltergeist.StatusFailError(url, msg)); }); } }; Browser.prototype.current_url = function() { return this.current_command.sendResponse(this.currentPage.currentUrl()); }; Browser.prototype.status_code = function() { return this.current_command.sendResponse(this.currentPage.statusCode); }; Browser.prototype.body = function() { return this.current_command.sendResponse(this.currentPage.content()); }; Browser.prototype.source = function() { return this.current_command.sendResponse(this.currentPage.source); }; Browser.prototype.title = function() { return this.current_command.sendResponse(this.currentPage.title()); }; Browser.prototype.find = function(method, selector) { return this.current_command.sendResponse({ page_id: this.currentPage.id, ids: this.currentPage.find(method, selector) }); }; Browser.prototype.find_within = function(page_id, id, method, selector) { return this.current_command.sendResponse(this.node(page_id, id).find(method, selector)); }; Browser.prototype.all_text = function(page_id, id) { return this.current_command.sendResponse(this.node(page_id, id).allText()); }; Browser.prototype.visible_text = function(page_id, id) { return this.current_command.sendResponse(this.node(page_id, id).visibleText()); }; Browser.prototype.delete_text = function(page_id, id) { return this.current_command.sendResponse(this.node(page_id, id).deleteText()); }; Browser.prototype.property = function(page_id, id, name) { return this.current_command.sendResponse(this.node(page_id, id).getProperty(name)); }; Browser.prototype.attribute = function(page_id, id, name) { return this.current_command.sendResponse(this.node(page_id, id).getAttribute(name)); }; Browser.prototype.attributes = function(page_id, id, name) { return this.current_command.sendResponse(this.node(page_id, id).getAttributes()); }; Browser.prototype.parents = function(page_id, id) { return this.current_command.sendResponse(this.node(page_id, id).parentIds()); }; Browser.prototype.value = function(page_id, id) { return this.current_command.sendResponse(this.node(page_id, id).value()); }; Browser.prototype.set = function(page_id, id, value) { this.node(page_id, id).set(value); return this.current_command.sendResponse(true); }; Browser.prototype.select_file = function(page_id, id, value) { var node; node = this.node(page_id, id); this.currentPage.beforeUpload(node.id); this.currentPage.uploadFile('[_poltergeist_selected]', value); this.currentPage.afterUpload(node.id); if (phantom.version.major === 2 && phantom.version.minor === 0) { return this.click(page_id, id); } else { return this.current_command.sendResponse(true); } }; Browser.prototype.select = function(page_id, id, value) { return this.current_command.sendResponse(this.node(page_id, id).select(value)); }; Browser.prototype.tag_name = function(page_id, id) { return this.current_command.sendResponse(this.node(page_id, id).tagName()); }; Browser.prototype.visible = function(page_id, id) { return this.current_command.sendResponse(this.node(page_id, id).isVisible()); }; Browser.prototype.disabled = function(page_id, id) { return this.current_command.sendResponse(this.node(page_id, id).isDisabled()); }; Browser.prototype.path = function(page_id, id) { return this.current_command.sendResponse(this.node(page_id, id).path()); }; Browser.prototype.evaluate = function(script) { return this.current_command.sendResponse(this.currentPage.evaluate("function() { return " + script + " }")); }; Browser.prototype.execute = function(script) { this.currentPage.execute("function() { " + script + " }"); return this.current_command.sendResponse(true); }; Browser.prototype.frameUrl = function(frame_name) { return this.currentPage.frameUrl(frame_name); }; Browser.prototype.pushFrame = function(command, name, timeout) { var frame, frame_url; if (Array.isArray(name)) { frame = this.node.apply(this, name); name = frame.getAttribute('name') || frame.getAttribute('id'); if (!name) { frame.setAttribute('name', "_random_name_" + (new Date().getTime())); name = frame.getAttribute('name'); } } frame_url = this.frameUrl(name); if (indexOf.call(this.currentPage.blockedUrls(), frame_url) >= 0) { return command.sendResponse(true); } else if (this.currentPage.pushFrame(name)) { if (frame_url && (frame_url !== 'about:blank') && (this.currentPage.currentUrl() === 'about:blank')) { this.currentPage.state = 'awaiting_frame_load'; return this.currentPage.waitState('default', function() { return command.sendResponse(true); }); } else { return command.sendResponse(true); } } else { if (new Date().getTime() < timeout) { return setTimeout(((function(_this) { return function() { return _this.pushFrame(command, name, timeout); }; })(this)), 50); } else { return command.sendError(new Poltergeist.FrameNotFound(name)); } } }; Browser.prototype.push_frame = function(name, timeout) { if (timeout == null) { timeout = (new Date().getTime()) + 2000; } return this.pushFrame(this.current_command, name, timeout); }; Browser.prototype.pop_frame = function() { return this.current_command.sendResponse(this.currentPage.popFrame()); }; Browser.prototype.window_handles = function() { var handles; handles = this.pages.filter(function(p) { return !p.closed; }).map(function(p) { return p.handle; }); return this.current_command.sendResponse(handles); }; Browser.prototype.window_handle = function(name) { var handle, page; if (name == null) { name = null; } handle = name ? (page = this.pages.filter(function(p) { return !p.closed && p.windowName() === name; })[0], page ? page.handle : null) : this.currentPage.handle; return this.current_command.sendResponse(handle); }; Browser.prototype.switch_to_window = function(handle) { var command, new_page; command = this.current_command; new_page = this.getPageByHandle(handle); if (new_page) { if (new_page !== this.currentPage) { return new_page.waitState('default', (function(_this) { return function() { _this.currentPage = new_page; return command.sendResponse(true); }; })(this)); } else { return command.sendResponse(true); } } else { throw new Poltergeist.NoSuchWindowError; } }; Browser.prototype.open_new_window = function() { this.execute('window.open()'); return this.current_command.sendResponse(true); }; Browser.prototype.close_window = function(handle) { var page; page = this.getPageByHandle(handle); if (page) { page.release(); return this.current_command.sendResponse(true); } else { return this.current_command.sendResponse(false); } }; Browser.prototype.mouse_event = function(page_id, id, name) { var command, event_page, last_mouse_event, node; node = this.node(page_id, id); this.currentPage.state = 'mouse_event'; last_mouse_event = node.mouseEvent(name); event_page = this.currentPage; command = this.current_command; return setTimeout(function() { if (event_page.state === 'mouse_event') { event_page.state = 'default'; return command.sendResponse({ position: last_mouse_event }); } else { return event_page.waitState('default', function() { return command.sendResponse({ position: last_mouse_event }); }); } }, 5); }; Browser.prototype.click = function(page_id, id) { return this.mouse_event(page_id, id, 'click'); }; Browser.prototype.right_click = function(page_id, id) { return this.mouse_event(page_id, id, 'rightclick'); }; Browser.prototype.double_click = function(page_id, id) { return this.mouse_event(page_id, id, 'doubleclick'); }; Browser.prototype.hover = function(page_id, id) { return this.mouse_event(page_id, id, 'mousemove'); }; Browser.prototype.click_coordinates = function(x, y) { this.currentPage.sendEvent('click', x, y); return this.current_command.sendResponse({ click: { x: x, y: y } }); }; Browser.prototype.drag = function(page_id, id, other_id) { this.node(page_id, id).dragTo(this.node(page_id, other_id)); return this.current_command.sendResponse(true); }; Browser.prototype.drag_by = function(page_id, id, x, y) { this.node(page_id, id).dragBy(x, y); return this.current_command.sendResponse(true); }; Browser.prototype.trigger = function(page_id, id, event) { this.node(page_id, id).trigger(event); return this.current_command.sendResponse(event); }; Browser.prototype.equals = function(page_id, id, other_id) { return this.current_command.sendResponse(this.node(page_id, id).isEqual(this.node(page_id, other_id))); }; Browser.prototype.reset = function() { this.resetPage(); return this.current_command.sendResponse(true); }; Browser.prototype.scroll_to = function(left, top) { this.currentPage.setScrollPosition({ left: left, top: top }); return this.current_command.sendResponse(true); }; Browser.prototype.send_keys = function(page_id, id, keys) { var i, j, k, key, len, len1, len2, modifier_code, modifier_key, modifier_keys, sequence, target; target = this.node(page_id, id); if (!target.containsSelection()) { target.mouseEvent('click'); } for (i = 0, len = keys.length; i < len; i++) { sequence = keys[i]; key = sequence.key != null ? this.currentPage.keyCode(sequence.key) : sequence; if (sequence.modifier != null) { modifier_keys = this.currentPage.keyModifierKeys(sequence.modifier); modifier_code = this.currentPage.keyModifierCode(sequence.modifier); for (j = 0, len1 = modifier_keys.length; j < len1; j++) { modifier_key = modifier_keys[j]; this.currentPage.sendEvent('keydown', modifier_key); } this.currentPage.sendEvent('keypress', key, null, null, modifier_code); for (k = 0, len2 = modifier_keys.length; k < len2; k++) { modifier_key = modifier_keys[k]; this.currentPage.sendEvent('keyup', modifier_key); } } else { this.currentPage.sendEvent('keypress', key); } } return this.current_command.sendResponse(true); }; Browser.prototype.render_base64 = function(format, arg) { var dimensions, encoded_image, full, ref, ref1, ref2, ref3, selector, window_scroll_position; ref = arg != null ? arg : {}, full = (ref1 = ref.full) != null ? ref1 : false, selector = (ref2 = ref.selector) != null ? ref2 : null; window_scroll_position = this.currentPage["native"]().evaluate("function(){ return [window.pageXOffset, window.pageYOffset] }"); dimensions = this.set_clip_rect(full, selector); encoded_image = this.currentPage.renderBase64(format); this.currentPage.setScrollPosition({ left: dimensions.left, top: dimensions.top }); (ref3 = this.currentPage["native"]()).evaluate.apply(ref3, ["window.scrollTo"].concat(slice.call(window_scroll_position))); return this.current_command.sendResponse(encoded_image); }; Browser.prototype.render = function(path, arg) { var dimensions, format, full, options, quality, ref, ref1, ref2, ref3, ref4, ref5, selector, window_scroll_position; ref = arg != null ? arg : {}, full = (ref1 = ref.full) != null ? ref1 : false, selector = (ref2 = ref.selector) != null ? ref2 : null, format = (ref3 = ref.format) != null ? ref3 : null, quality = (ref4 = ref.quality) != null ? ref4 : null; window_scroll_position = this.currentPage["native"]().evaluate("function(){ return [window.pageXOffset, window.pageYOffset] }"); dimensions = this.set_clip_rect(full, selector); options = {}; if (format != null) { options["format"] = format; } if (quality != null) { options["quality"] = quality; } this.currentPage.setScrollPosition({ left: 0, top: 0 }); this.currentPage.render(path, options); this.currentPage.setScrollPosition({ left: dimensions.left, top: dimensions.top }); (ref5 = this.currentPage["native"]()).evaluate.apply(ref5, ["window.scrollTo"].concat(slice.call(window_scroll_position))); return this.current_command.sendResponse(true); }; Browser.prototype.set_clip_rect = function(full, selector) { var dimensions, document, rect, ref, viewport; dimensions = this.currentPage.validatedDimensions(); ref = [dimensions.document, dimensions.viewport], document = ref[0], viewport = ref[1]; rect = full ? { left: 0, top: 0, width: document.width, height: document.height } : selector != null ? this.currentPage.elementBounds(selector) : { left: 0, top: 0, width: viewport.width, height: viewport.height }; this.currentPage.setClipRect(rect); return dimensions; }; Browser.prototype.set_paper_size = function(size) { this.currentPage.setPaperSize(size); return this.current_command.sendResponse(true); }; Browser.prototype.set_zoom_factor = function(zoom_factor) { this.currentPage.setZoomFactor(zoom_factor); return this.current_command.sendResponse(true); }; Browser.prototype.resize = function(width, height) { this.currentPage.setViewportSize({ width: width, height: height }); return this.current_command.sendResponse(true); }; Browser.prototype.network_traffic = function() { return this.current_command.sendResponse(this.currentPage.networkTraffic()); }; Browser.prototype.clear_network_traffic = function() { this.currentPage.clearNetworkTraffic(); return this.current_command.sendResponse(true); }; Browser.prototype.set_proxy = function(ip, port, type, user, password) { phantom.setProxy(ip, port, type, user, password); return this.current_command.sendResponse(true); }; Browser.prototype.get_headers = function() { return this.current_command.sendResponse(this.currentPage.getCustomHeaders()); }; Browser.prototype.set_headers = function(headers) { if (headers['User-Agent']) { this.currentPage.setUserAgent(headers['User-Agent']); } this.currentPage.setCustomHeaders(headers); return this.current_command.sendResponse(true); }; Browser.prototype.add_headers = function(headers) { var allHeaders, name, value; allHeaders = this.currentPage.getCustomHeaders(); for (name in headers) { value = headers[name]; allHeaders[name] = value; } return this.set_headers(allHeaders); }; Browser.prototype.add_header = function(header, permanent) { if (!permanent) { this.currentPage.addTempHeader(header); } return this.add_headers(header); }; Browser.prototype.response_headers = function() { return this.current_command.sendResponse(this.currentPage.responseHeaders()); }; Browser.prototype.cookies = function() { return this.current_command.sendResponse(this.currentPage.cookies()); }; Browser.prototype.set_cookie = function(cookie) { phantom.addCookie(cookie); return this.current_command.sendResponse(true); }; Browser.prototype.remove_cookie = function(name) { this.currentPage.deleteCookie(name); return this.current_command.sendResponse(true); }; Browser.prototype.clear_cookies = function() { phantom.clearCookies(); return this.current_command.sendResponse(true); }; Browser.prototype.cookies_enabled = function(flag) { phantom.cookiesEnabled = flag; return this.current_command.sendResponse(true); }; Browser.prototype.set_http_auth = function(user, password) { this.currentPage.setHttpAuth(user, password); return this.current_command.sendResponse(true); }; Browser.prototype.set_js_errors = function(value) { this.js_errors = value; return this.current_command.sendResponse(true); }; Browser.prototype.set_debug = function(value) { this._debug = value; return this.current_command.sendResponse(true); }; Browser.prototype.exit = function() { return phantom.exit(); }; Browser.prototype.noop = function() {}; Browser.prototype.browser_error = function() { throw new Error('zomg'); }; Browser.prototype.go_back = function() { var command; command = this.current_command; if (this.currentPage.canGoBack) { this.currentPage.state = 'loading'; this.currentPage.goBack(); return this.currentPage.waitState('default', function() { return command.sendResponse(true); }); } else { return command.sendResponse(false); } }; Browser.prototype.go_forward = function() { var command; command = this.current_command; if (this.currentPage.canGoForward) { this.currentPage.state = 'loading'; this.currentPage.goForward(); return this.currentPage.waitState('default', function() { return command.sendResponse(true); }); } else { return command.sendResponse(false); } }; Browser.prototype.set_url_whitelist = function() { var wc, wildcards; wildcards = 1 <= arguments.length ? slice.call(arguments, 0) : []; this.currentPage.urlWhitelist = (function() { var i, len, results; results = []; for (i = 0, len = wildcards.length; i < len; i++) { wc = wildcards[i]; results.push(this._wildcardToRegexp(wc)); } return results; }).call(this); return this.current_command.sendResponse(true); }; Browser.prototype.set_url_blacklist = function() { var wc, wildcards; wildcards = 1 <= arguments.length ? slice.call(arguments, 0) : []; this.currentPage.urlBlacklist = (function() { var i, len, results; results = []; for (i = 0, len = wildcards.length; i < len; i++) { wc = wildcards[i]; results.push(this._wildcardToRegexp(wc)); } return results; }).call(this); return this.current_command.sendResponse(true); }; Browser.prototype.set_confirm_process = function(process) { this.confirm_processes.push(process); return this.current_command.sendResponse(true); }; Browser.prototype.set_prompt_response = function(response) { this.prompt_responses.push(response); return this.current_command.sendResponse(true); }; Browser.prototype.modal_message = function() { return this.current_command.sendResponse(this.processed_modal_messages.shift()); }; Browser.prototype.clear_memory_cache = function() { this.currentPage.clearMemoryCache(); return this.current_command.sendResponse(true); }; Browser.prototype._wildcardToRegexp = function(wildcard) { wildcard = wildcard.replace(/[\-\[\]\/\{\}\(\)\+\.\\\^\$\|]/g, "\\$&"); wildcard = wildcard.replace(/\*/g, ".*"); wildcard = wildcard.replace(/\?/g, "."); return new RegExp(wildcard, "i"); }; return Browser; })(); poltergeist-1.10.0/lib/capybara/poltergeist/client/compiled/connection.js0000644000175000017500000000133712766546165025644 0ustar pravipravivar bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; Poltergeist.Connection = (function() { function Connection(owner, port) { this.owner = owner; this.port = port; this.commandReceived = bind(this.commandReceived, this); this.socket = new WebSocket("ws://127.0.0.1:" + this.port + "/"); this.socket.onmessage = this.commandReceived; this.socket.onclose = function() { return phantom.exit(); }; } Connection.prototype.commandReceived = function(message) { return this.owner.runCommand(JSON.parse(message.data)); }; Connection.prototype.send = function(message) { return this.socket.send(JSON.stringify(message)); }; return Connection; })(); poltergeist-1.10.0/lib/capybara/poltergeist/client/agent.coffee0000644000175000017500000002747712766546165023637 0ustar pravipravi# This is injected into each page that is loaded class PoltergeistAgent # Since this code executes in the sites browser space - copy needed JSON functions # in case user code messes with JSON (early mootools for instance) @.JSON ||= { parse: JSON.parse, stringify: JSON.stringify } constructor: -> @elements = [] @nodes = {} externalCall: (name, args) -> try { value: this[name].apply(this, args) } catch error { error: { message: error.toString(), stack: error.stack } } @stringify: (object) -> try PoltergeistAgent.JSON.stringify object, (key, value) -> if Array.isArray(this[key]) return this[key] else return value catch error if error instanceof TypeError '"(cyclic structure)"' else throw error # Somehow PhantomJS returns all characters(brackets, etc) properly encoded # except whitespace character in pathname part of the location. This hack # is intended to fix this up. currentUrl: -> window.location.href.replace(/\ /g, '%20') find: (method, selector, within = document) -> try if method == "xpath" xpath = document.evaluate(selector, within, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null) results = (xpath.snapshotItem(i) for i in [0...xpath.snapshotLength]) else results = within.querySelectorAll(selector) this.register(el) for el in results catch error # DOMException.INVALID_EXPRESSION_ERR is undefined, using pure code if error.code == DOMException.SYNTAX_ERR || error.code == 51 throw new PoltergeistAgent.InvalidSelector else throw error register: (element) -> @elements.push(element) @elements.length - 1 documentSize: -> height: document.documentElement.scrollHeight || document.documentElement.clientHeight, width: document.documentElement.scrollWidth || document.documentElement.clientWidth get: (id) -> @nodes[id] or= new PoltergeistAgent.Node(this, @elements[id]) nodeCall: (id, name, args) -> node = this.get(id) throw new PoltergeistAgent.ObsoleteNode if node.isObsolete() node[name].apply(node, args) beforeUpload: (id) -> this.get(id).setAttribute('_poltergeist_selected', '') afterUpload: (id) -> this.get(id).removeAttribute('_poltergeist_selected') clearLocalStorage: -> localStorage.clear() class PoltergeistAgent.ObsoleteNode toString: -> "PoltergeistAgent.ObsoleteNode" class PoltergeistAgent.InvalidSelector toString: -> "PoltergeistAgent.InvalidSelector" class PoltergeistAgent.Node @EVENTS = { FOCUS: ['blur', 'focus', 'focusin', 'focusout'], MOUSE: ['click', 'dblclick', 'mousedown', 'mouseenter', 'mouseleave', 'mousemove', 'mouseover', 'mouseout', 'mouseup', 'contextmenu'], FORM: ['submit'] } constructor: (@agent, @element) -> parentId: -> @agent.register(@element.parentNode) parentIds: -> ids = [] parent = @element.parentNode while parent != document ids.push @agent.register(parent) parent = parent.parentNode ids find: (method, selector) -> @agent.find(method, selector, @element) isObsolete: -> obsolete = (element) => if (parent = element.parentNode)? if parent == document return false else obsolete parent else return true obsolete @element changed: -> event = document.createEvent('HTMLEvents') event.initEvent('change', true, false) # In the case of an OPTION tag, the change event should come # from the parent SELECT if @element.nodeName == 'OPTION' element = @element.parentNode element = element.parentNode if element.nodeName == 'OPTGROUP' element else element = @element element.dispatchEvent(event) input: -> event = document.createEvent('HTMLEvents') event.initEvent('input', true, false) @element.dispatchEvent(event) keyupdowned: (eventName, keyCode) -> event = document.createEvent('UIEvents') event.initEvent(eventName, true, true) event.keyCode = keyCode event.which = keyCode event.charCode = 0 @element.dispatchEvent(event) keypressed: (altKey, ctrlKey, shiftKey, metaKey, keyCode, charCode) -> event = document.createEvent('UIEvents') event.initEvent('keypress', true, true) event.window = @agent.window event.altKey = altKey event.ctrlKey = ctrlKey event.shiftKey = shiftKey event.metaKey = metaKey event.keyCode = keyCode event.charCode = charCode event.which = keyCode @element.dispatchEvent(event) insideBody: -> @element == document.body || document.evaluate('ancestor::body', @element, null, XPathResult.BOOLEAN_TYPE, null).booleanValue allText: -> @element.textContent visibleText: -> if this.isVisible() if @element.nodeName == "TEXTAREA" @element.textContent else if @element instanceof SVGElement @element.textContent else @element.innerText deleteText: -> range = document.createRange() range.selectNodeContents(@element) window.getSelection().removeAllRanges() window.getSelection().addRange(range) window.getSelection().deleteFromDocument() getProperty: (name) -> @element[name] getAttributes: -> attrs = {} for attr in @element.attributes attrs[attr.name] = attr.value.replace("\n","\\n"); attrs getAttribute: (name) -> if name == 'checked' || name == 'selected' @element[name] else @element.getAttribute(name) scrollIntoView: -> @element.scrollIntoViewIfNeeded() #Sometimes scrollIntoViewIfNeeded doesn't seem to work, not really sure why. #Just calling scrollIntoView doesnt work either, however calling scrollIntoView #after scrollIntoViewIfNeeded when element is not in the viewport does appear to work @element.scrollIntoView() unless this.isInViewport() value: -> if @element.tagName == 'SELECT' && @element.multiple option.value for option in @element.children when option.selected else @element.value set: (value) -> return if @element.readOnly if (@element.maxLength >= 0) value = value.substr(0, @element.maxLength) this.trigger('focus') @element.value = '' if @element.type == 'number' @element.value = value else for char in value keyCode = this.characterToKeyCode(char) this.keyupdowned('keydown', keyCode) @element.value += char this.keypressed(false, false, false, false, char.charCodeAt(0), char.charCodeAt(0)) this.keyupdowned('keyup', keyCode) this.changed() this.input() this.trigger('blur') isMultiple: -> @element.multiple setAttribute: (name, value) -> @element.setAttribute(name, value) removeAttribute: (name) -> @element.removeAttribute(name) select: (value) -> if @isDisabled() false else if value == false && !@element.parentNode.multiple false else this.trigger('focus', @element.parentNode) @element.selected = value this.changed() this.trigger('blur', @element.parentNode) true tagName: -> @element.tagName isVisible: (element = @element) -> #if an area element, check visibility of relevant image if element.tagName == 'AREA' map_name = document.evaluate('./ancestor::map/@name', element, null, XPathResult.STRING_TYPE, null).stringValue element = document.querySelector("img[usemap='##{map_name}']") return false unless element? while (element) style = window.getComputedStyle(element) return false if style.display == 'none' or style.visibility == 'hidden' or parseFloat(style.opacity) == 0 element = element.parentElement return true isInViewport: -> rect = @element.getBoundingClientRect(); rect.top >= 0 && rect.left >= 0 && rect.bottom <= window.innerHeight && rect.right <= window.innerWidth isDisabled: -> @element.disabled || @element.tagName == 'OPTION' && @element.parentNode.disabled path: -> elements = @parentIds().reverse().map((id) => @agent.get(id)) elements.push(this) selectors = elements.map (el)-> prev_siblings = el.find('xpath', "./preceding-sibling::#{el.tagName()}") "#{el.tagName()}[#{prev_siblings.length + 1}]" "//" + selectors.join('/') containsSelection: -> selectedNode = document.getSelection().focusNode return false if !selectedNode if selectedNode.nodeType == 3 selectedNode = selectedNode.parentNode @element.contains(selectedNode) frameOffset: -> win = window offset = { top: 0, left: 0 } while win.frameElement rect = win.frameElement.getClientRects()[0] style = win.getComputedStyle(win.frameElement) win = win.parent offset.top += rect.top + parseInt(style.getPropertyValue("padding-top"), 10) offset.left += rect.left + parseInt(style.getPropertyValue("padding-left"), 10) offset position: -> # Elements inside an SVG return underfined for getClientRects??? rect = @element.getClientRects()[0] || @element.getBoundingClientRect() throw new PoltergeistAgent.ObsoleteNode unless rect frameOffset = this.frameOffset() pos = { top: rect.top + frameOffset.top, right: rect.right + frameOffset.left, left: rect.left + frameOffset.left, bottom: rect.bottom + frameOffset.top, width: rect.width, height: rect.height } pos trigger: (name, element = @element) -> if Node.EVENTS.MOUSE.indexOf(name) != -1 event = document.createEvent('MouseEvent') event.initMouseEvent( name, true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null ) else if Node.EVENTS.FOCUS.indexOf(name) != -1 event = this.obtainEvent(name) else if Node.EVENTS.FORM.indexOf(name) != -1 event = this.obtainEvent(name) else throw "Unknown event" element.dispatchEvent(event) obtainEvent: (name) -> event = document.createEvent('HTMLEvents') event.initEvent(name, true, true) event mouseEventTest: (x, y) -> frameOffset = this.frameOffset() x -= frameOffset.left y -= frameOffset.top el = origEl = document.elementFromPoint(x, y) while el if el == @element return { status: 'success' } else el = el.parentNode { status: 'failure', selector: origEl && this.getSelector(origEl) } getSelector: (el) -> selector = if el.tagName != 'HTML' then this.getSelector(el.parentNode) + ' ' else '' selector += el.tagName.toLowerCase() selector += "##{el.id}" if el.id #PhantomJS < 2.0 doesn't support classList for SVG elements - so get classes manually classes = el.classList || (el.getAttribute('class')?.trim()?.split(/\s+/)) || [] for className in classes when className != '' selector += ".#{className}" selector characterToKeyCode: (character) -> code = character.toUpperCase().charCodeAt(0) specialKeys = 96: 192 #` 45: 189 #- 61: 187 #= 91: 219 #[ 93: 221 #] 92: 220 #\ 59: 186 #; 39: 222 #' 44: 188 #, 46: 190 #. 47: 191 #/ 127: 46 #delete 126: 192 #~ 33: 49 #! 64: 50 #@ 35: 51 ## 36: 52 #$ 37: 53 #% 94: 54 #^ 38: 55 #& 42: 56 #* 40: 57 #( 41: 48 #) 95: 189 #_ 43: 187 #+ 123: 219 #{ 125: 221 #} 124: 220 #| 58: 186 #: 34: 222 #" 60: 188 #< 62: 190 #> 63: 191 #? specialKeys[code] || code isDOMEqual: (other_id) -> @element == @agent.get(other_id).element window.__poltergeist = new PoltergeistAgent document.addEventListener( 'DOMContentLoaded', -> console.log('__DOMContentLoaded') ) poltergeist-1.10.0/lib/capybara/poltergeist/client/cmd.coffee0000644000175000017500000000067612766546165023274 0ustar pravipraviclass Poltergeist.Cmd constructor: (@owner, @id, @name, @args)-> sendResponse: (response) -> errors = @browser.currentPage.errors @browser.currentPage.clearErrors() if errors.length > 0 && @browser.js_errors @sendError(new Poltergeist.JavascriptError(errors)) else @owner.sendResponse(@id, response) sendError: (errors) -> @owner.sendError(@id, errors) run: (@browser) -> @browser.runCommand(this) poltergeist-1.10.0/lib/capybara/poltergeist/client/main.coffee0000644000175000017500000000646412766546165023456 0ustar pravipraviclass Poltergeist constructor: (port, width, height) -> @browser = new Poltergeist.Browser(width, height) @connection = new Poltergeist.Connection(this, port) # The QtWebKit bridge doesn't seem to like Function.prototype.bind that = this phantom.onError = (message, stack) -> that.onError(message, stack) @running = false runCommand: (command) -> @running = true command = new Poltergeist.Cmd(this, command.id, command.name, command.args) try command.run(@browser) catch error if error instanceof Poltergeist.Error this.sendError(command.id, error) else this.sendError(command.id, new Poltergeist.BrowserError(error.toString(), error.stack)) sendResponse: (command_id, response) -> this.send(command_id: command_id, response: response) sendError: (command_id, error) -> this.send( command_id: command_id, error: name: error.name || 'Generic', args: error.args && error.args() || [error.toString()] ) send: (data) -> # Prevents more than one response being sent for a single # command. This can happen in some scenarios where an error # is raised but the script can still continue. if @running @connection.send(data) @running = false return true return false # This is necessary because the remote debugger will wrap the # script in a function, causing the Poltergeist variable to # become local. window.Poltergeist = Poltergeist class Poltergeist.Error class Poltergeist.ObsoleteNode extends Poltergeist.Error name: "Poltergeist.ObsoleteNode" args: -> [] toString: -> this.name class Poltergeist.InvalidSelector extends Poltergeist.Error constructor: (@method, @selector) -> name: "Poltergeist.InvalidSelector" args: -> [@method, @selector] class Poltergeist.FrameNotFound extends Poltergeist.Error constructor: (@frameName) -> name: "Poltergeist.FrameNotFound" args: -> [@frameName] class Poltergeist.MouseEventFailed extends Poltergeist.Error constructor: (@eventName, @selector, @position) -> name: "Poltergeist.MouseEventFailed" args: -> [@eventName, @selector, @position] class Poltergeist.JavascriptError extends Poltergeist.Error constructor: (@errors) -> name: "Poltergeist.JavascriptError" args: -> [@errors] class Poltergeist.BrowserError extends Poltergeist.Error constructor: (@message, @stack) -> name: "Poltergeist.BrowserError" args: -> [@message, @stack] class Poltergeist.StatusFailError extends Poltergeist.Error constructor: (@url, @details) -> name: "Poltergeist.StatusFailError" args: -> [@url, @details] class Poltergeist.NoSuchWindowError extends Poltergeist.Error name: "Poltergeist.NoSuchWindowError" args: -> [] class Poltergeist.UnsupportedFeature extends Poltergeist.Error constructor: (@message) -> name: "Poltergeist.UnsupportedFeature" args: -> [@message, phantom.version] # We're using phantom.libraryPath so that any stack traces # report the full path. phantom.injectJs("#{phantom.libraryPath}/web_page.js") phantom.injectJs("#{phantom.libraryPath}/node.js") phantom.injectJs("#{phantom.libraryPath}/connection.js") phantom.injectJs("#{phantom.libraryPath}/cmd.js") phantom.injectJs("#{phantom.libraryPath}/browser.js") system = require 'system' new Poltergeist(system.args[1], system.args[2], system.args[3]) poltergeist-1.10.0/lib/capybara/poltergeist/client/browser.coffee0000644000175000017500000004165312766546165024214 0ustar pravipraviclass Poltergeist.Browser constructor: (width, height) -> @width = width || 1024 @height = height || 768 @pages = [] @js_errors = true @_debug = false @_counter = 0 @processed_modal_messages = [] @confirm_processes = [] @prompt_responses = [] this.resetPage() resetPage: -> [@_counter, @pages] = [0, []] if @page? unless @page.closed @page.clearLocalStorage() if @page.currentUrl() != 'about:blank' @page.release() phantom.clearCookies() @page = @currentPage = new Poltergeist.WebPage @page.setViewportSize(width: @width, height: @height) @page.handle = "#{@_counter++}" @pages.push(@page) @processed_modal_messages = [] @confirm_processes = [] @prompt_responses = [] @page.native().onAlert = (msg) => @setModalMessage msg return @page.native().onConfirm = (msg) => process = @confirm_processes.pop() process = true if process == undefined @setModalMessage msg return process @page.native().onPrompt = (msg, defaultVal) => response = @prompt_responses.pop() response = defaultVal if (response == undefined || response == false) @setModalMessage msg return response @page.onPageCreated = (newPage) => page = new Poltergeist.WebPage(newPage) page.handle = "#{@_counter++}" page.urlBlacklist = @page.urlBlacklist page.urlWhitelist = @page.urlWhitelist page.setViewportSize(@page.viewportSize()) @pages.push(page) return getPageByHandle: (handle) -> @pages.filter((p) -> !p.closed && p.handle == handle)[0] runCommand: (command) -> @current_command = command @currentPage.state = 'default' this[command.name].apply(this, command.args) debug: (message) -> console.log "poltergeist [#{new Date().getTime()}] #{message}" if @_debug setModalMessage: (msg) -> @processed_modal_messages.push(msg) return add_extension: (extension) -> @currentPage.injectExtension extension @current_command.sendResponse 'success' node: (page_id, id) -> if @currentPage.id == page_id @currentPage.get(id) else throw new Poltergeist.ObsoleteNode visit: (url, max_wait=0) -> @currentPage.state = 'loading' #reset modal processing state when changing page @processed_modal_messages = [] @confirm_processes = [] @prompt_responses = [] # Prevent firing `page.onInitialized` event twice. Calling currentUrl # method before page is actually opened fires this event for the first time. # The second time will be in the right place after `page.open` prevUrl = if @currentPage.source is null then 'about:blank' else @currentPage.currentUrl() @currentPage.open(url) if /#/.test(url) && prevUrl.split('#')[0] == url.split('#')[0] # Hash change occurred, so there will be no onLoadFinished @currentPage.state = 'default' @current_command.sendResponse(status: 'success') else command = @current_command loading_page = @currentPage @currentPage.waitState 'default', -> if @statusCode == null && @status == 'fail' command.sendError(new Poltergeist.StatusFailError(url)) else command.sendResponse(status: @status) , max_wait, -> resources = @openResourceRequests() msg = if resources.length "Timed out with the following resources still waiting #{resources.join(',')}" command.sendError(new Poltergeist.StatusFailError(url,msg)) return current_url: -> @current_command.sendResponse @currentPage.currentUrl() status_code: -> @current_command.sendResponse @currentPage.statusCode body: -> @current_command.sendResponse @currentPage.content() source: -> @current_command.sendResponse @currentPage.source title: -> @current_command.sendResponse @currentPage.title() find: (method, selector) -> @current_command.sendResponse(page_id: @currentPage.id, ids: @currentPage.find(method, selector)) find_within: (page_id, id, method, selector) -> @current_command.sendResponse this.node(page_id, id).find(method, selector) all_text: (page_id, id) -> @current_command.sendResponse this.node(page_id, id).allText() visible_text: (page_id, id) -> @current_command.sendResponse this.node(page_id, id).visibleText() delete_text: (page_id, id) -> @current_command.sendResponse this.node(page_id, id).deleteText() property: (page_id, id, name) -> @current_command.sendResponse this.node(page_id, id).getProperty(name) attribute: (page_id, id, name) -> @current_command.sendResponse this.node(page_id, id).getAttribute(name) attributes: (page_id, id, name) -> @current_command.sendResponse this.node(page_id, id).getAttributes() parents: (page_id, id) -> @current_command.sendResponse this.node(page_id, id).parentIds() value: (page_id, id) -> @current_command.sendResponse this.node(page_id, id).value() set: (page_id, id, value) -> this.node(page_id, id).set(value) @current_command.sendResponse(true) # PhantomJS only allows us to reference the element by CSS selector, not XPath, # so we have to add an attribute to the element to identify it, then remove it # afterwards. select_file: (page_id, id, value) -> node = this.node(page_id, id) @currentPage.beforeUpload(node.id) @currentPage.uploadFile('[_poltergeist_selected]', value) @currentPage.afterUpload(node.id) if phantom.version.major == 2 && phantom.version.minor == 0 # In phantomjs 2.0.x - uploadFile only fully works if executed within a user action # It does however setup the filenames to be uploaded, so if we then click on the # file input element the filenames will get set @click(page_id, id) else @current_command.sendResponse(true) select: (page_id, id, value) -> @current_command.sendResponse this.node(page_id, id).select(value) tag_name: (page_id, id) -> @current_command.sendResponse this.node(page_id, id).tagName() visible: (page_id, id) -> @current_command.sendResponse this.node(page_id, id).isVisible() disabled: (page_id, id) -> @current_command.sendResponse this.node(page_id, id).isDisabled() path: (page_id, id) -> @current_command.sendResponse this.node(page_id, id).path() evaluate: (script) -> @current_command.sendResponse @currentPage.evaluate("function() { return #{script} }") execute: (script) -> @currentPage.execute("function() { #{script} }") @current_command.sendResponse(true) frameUrl: (frame_name) -> @currentPage.frameUrl(frame_name) pushFrame: (command, name, timeout) -> if Array.isArray(name) frame = this.node(name...) name = frame.getAttribute('name') || frame.getAttribute('id') unless name frame.setAttribute('name', "_random_name_#{new Date().getTime()}") name = frame.getAttribute('name') frame_url = @frameUrl(name) if frame_url in @currentPage.blockedUrls() command.sendResponse(true) else if @currentPage.pushFrame(name) if frame_url && (frame_url != 'about:blank') && (@currentPage.currentUrl() == 'about:blank') @currentPage.state = 'awaiting_frame_load' @currentPage.waitState 'default', -> command.sendResponse(true) else command.sendResponse(true) else if new Date().getTime() < timeout setTimeout((=> @pushFrame(command, name, timeout)), 50) else command.sendError(new Poltergeist.FrameNotFound(name)) push_frame: (name, timeout = (new Date().getTime()) + 2000) -> @pushFrame(@current_command, name, timeout) pop_frame: -> @current_command.sendResponse(@currentPage.popFrame()) window_handles: -> handles = @pages.filter((p) -> !p.closed).map((p) -> p.handle) @current_command.sendResponse(handles) window_handle: (name = null) -> handle = if name page = @pages.filter((p) -> !p.closed && p.windowName() == name)[0] if page then page.handle else null else @currentPage.handle @current_command.sendResponse(handle) switch_to_window: (handle) -> command = @current_command new_page = @getPageByHandle(handle) if new_page if new_page != @currentPage new_page.waitState 'default', => @currentPage = new_page command.sendResponse(true) else command.sendResponse(true) else throw new Poltergeist.NoSuchWindowError open_new_window: -> this.execute 'window.open()' @current_command.sendResponse(true) close_window: (handle) -> page = @getPageByHandle(handle) if page page.release() @current_command.sendResponse(true) else @current_command.sendResponse(false) mouse_event: (page_id, id, name) -> # Get the node before changing state, in case there is an exception node = this.node(page_id, id) # If the event triggers onNavigationRequested, we will transition to the 'loading' # state and wait for onLoadFinished before sending a response. @currentPage.state = 'mouse_event' last_mouse_event = node.mouseEvent(name) event_page = @currentPage command = @current_command setTimeout -> # If the state is still the same then navigation event won't happen if event_page.state == 'mouse_event' event_page.state = 'default' command.sendResponse(position: last_mouse_event) else event_page.waitState 'default', -> command.sendResponse(position: last_mouse_event) , 5 click: (page_id, id) -> this.mouse_event page_id, id, 'click' right_click: (page_id, id) -> this.mouse_event page_id, id, 'rightclick' double_click: (page_id, id) -> this.mouse_event page_id, id, 'doubleclick' hover: (page_id, id) -> this.mouse_event page_id, id, 'mousemove' click_coordinates: (x, y) -> @currentPage.sendEvent('click', x, y) @current_command.sendResponse(click: { x: x, y: y }) drag: (page_id, id, other_id) -> this.node(page_id, id).dragTo this.node(page_id, other_id) @current_command.sendResponse(true) drag_by: (page_id, id, x, y) -> this.node(page_id, id).dragBy(x, y) @current_command.sendResponse(true) trigger: (page_id, id, event) -> this.node(page_id, id).trigger(event) @current_command.sendResponse(event) equals: (page_id, id, other_id) -> @current_command.sendResponse this.node(page_id, id).isEqual(this.node(page_id, other_id)) reset: -> this.resetPage() @current_command.sendResponse(true) scroll_to: (left, top) -> @currentPage.setScrollPosition(left: left, top: top) @current_command.sendResponse(true) send_keys: (page_id, id, keys) -> target = this.node(page_id, id) # Programmatically generated focus doesn't work for `sendKeys`. # That's why we need something more realistic like user behavior. if !target.containsSelection() target.mouseEvent('click') for sequence in keys key = if sequence.key? then @currentPage.keyCode(sequence.key) else sequence if sequence.modifier? modifier_keys = @currentPage.keyModifierKeys(sequence.modifier) modifier_code = @currentPage.keyModifierCode(sequence.modifier) @currentPage.sendEvent('keydown', modifier_key) for modifier_key in modifier_keys @currentPage.sendEvent('keypress', key, null, null, modifier_code) @currentPage.sendEvent('keyup', modifier_key) for modifier_key in modifier_keys else @currentPage.sendEvent('keypress', key) @current_command.sendResponse(true) render_base64: (format, { full = false, selector = null } = {})-> window_scroll_position = @currentPage.native().evaluate("function(){ return [window.pageXOffset, window.pageYOffset] }") dimensions = this.set_clip_rect(full, selector) encoded_image = @currentPage.renderBase64(format) @currentPage.setScrollPosition(left: dimensions.left, top: dimensions.top) @currentPage.native().evaluate("window.scrollTo", window_scroll_position...) @current_command.sendResponse(encoded_image) render: (path, { full = false, selector = null, format = null, quality = null } = {} ) -> window_scroll_position = @currentPage.native().evaluate("function(){ return [window.pageXOffset, window.pageYOffset] }") dimensions = this.set_clip_rect(full, selector) options = {} options["format"] = format if format? options["quality"] = quality if quality? @currentPage.setScrollPosition(left: 0, top: 0) @currentPage.render(path, options) @currentPage.setScrollPosition(left: dimensions.left, top: dimensions.top) @currentPage.native().evaluate("window.scrollTo", window_scroll_position...) @current_command.sendResponse(true) set_clip_rect: (full, selector) -> dimensions = @currentPage.validatedDimensions() [document, viewport] = [dimensions.document, dimensions.viewport] rect = if full left: 0, top: 0, width: document.width, height: document.height else if selector? @currentPage.elementBounds(selector) else left: 0, top: 0, width: viewport.width, height: viewport.height @currentPage.setClipRect(rect) dimensions set_paper_size: (size) -> @currentPage.setPaperSize(size) @current_command.sendResponse(true) set_zoom_factor: (zoom_factor) -> @currentPage.setZoomFactor(zoom_factor) @current_command.sendResponse(true) resize: (width, height) -> @currentPage.setViewportSize(width: width, height: height) @current_command.sendResponse(true) network_traffic: -> @current_command.sendResponse(@currentPage.networkTraffic()) clear_network_traffic: -> @currentPage.clearNetworkTraffic() @current_command.sendResponse(true) set_proxy: (ip, port, type, user, password) -> phantom.setProxy(ip, port, type, user, password) @current_command.sendResponse(true) get_headers: -> @current_command.sendResponse(@currentPage.getCustomHeaders()) set_headers: (headers) -> # Workaround for https://code.google.com/p/phantomjs/issues/detail?id=745 @currentPage.setUserAgent(headers['User-Agent']) if headers['User-Agent'] @currentPage.setCustomHeaders(headers) @current_command.sendResponse(true) add_headers: (headers) -> allHeaders = @currentPage.getCustomHeaders() for name, value of headers allHeaders[name] = value this.set_headers(allHeaders) add_header: (header, permanent) -> @currentPage.addTempHeader(header) unless permanent this.add_headers(header) response_headers: -> @current_command.sendResponse(@currentPage.responseHeaders()) cookies: -> @current_command.sendResponse(@currentPage.cookies()) # We're using phantom.addCookie so that cookies can be set # before the first page load has taken place. set_cookie: (cookie) -> phantom.addCookie(cookie) @current_command.sendResponse(true) remove_cookie: (name) -> @currentPage.deleteCookie(name) @current_command.sendResponse(true) clear_cookies: () -> phantom.clearCookies() @current_command.sendResponse(true) cookies_enabled: (flag) -> phantom.cookiesEnabled = flag @current_command.sendResponse(true) set_http_auth: (user, password) -> @currentPage.setHttpAuth(user, password) @current_command.sendResponse(true) set_js_errors: (value) -> @js_errors = value @current_command.sendResponse(true) set_debug: (value) -> @_debug = value @current_command.sendResponse(true) exit: -> phantom.exit() noop: -> # NOOOOOOP! # This command is purely for testing error handling browser_error: -> throw new Error('zomg') go_back: -> command = @current_command if @currentPage.canGoBack @currentPage.state = 'loading' @currentPage.goBack() @currentPage.waitState 'default', -> command.sendResponse(true) else command.sendResponse(false) go_forward: -> command = @current_command if @currentPage.canGoForward @currentPage.state = 'loading' @currentPage.goForward() @currentPage.waitState 'default', -> command.sendResponse(true) else command.sendResponse(false) set_url_whitelist: (wildcards...)-> @currentPage.urlWhitelist = (@_wildcardToRegexp(wc) for wc in wildcards) @current_command.sendResponse(true) set_url_blacklist: (wildcards...)-> @currentPage.urlBlacklist = (@_wildcardToRegexp(wc) for wc in wildcards) @current_command.sendResponse(true) set_confirm_process: (process) -> @confirm_processes.push process @current_command.sendResponse(true) set_prompt_response: (response) -> @prompt_responses.push response @current_command.sendResponse(true) modal_message: -> @current_command.sendResponse(@processed_modal_messages.shift()) clear_memory_cache: -> @currentPage.clearMemoryCache() @current_command.sendResponse(true) _wildcardToRegexp: (wildcard)-> wildcard = wildcard.replace(/[\-\[\]\/\{\}\(\)\+\.\\\^\$\|]/g, "\\$&") wildcard = wildcard.replace(/\*/g, ".*") wildcard = wildcard.replace(/\?/g, ".") new RegExp(wildcard, "i") poltergeist-1.10.0/lib/capybara/poltergeist/client/node.coffee0000644000175000017500000001002712766546165023445 0ustar pravipravi# Proxy object for forwarding method calls to the node object inside the page. class Poltergeist.Node @DELEGATES = ['allText', 'visibleText', 'getAttribute', 'value', 'set', 'setAttribute', 'isObsolete', 'removeAttribute', 'isMultiple', 'select', 'tagName', 'find', 'getAttributes', 'isVisible', 'isInViewport', 'position', 'trigger', 'parentId', 'parentIds', 'mouseEventTest', 'scrollIntoView', 'isDOMEqual', 'isDisabled', 'deleteText', 'containsSelection', 'path', 'getProperty'] constructor: (@page, @id) -> parent: -> new Poltergeist.Node(@page, this.parentId()) for name in @DELEGATES do (name) => this.prototype[name] = (args...) -> @page.nodeCall(@id, name, args) mouseEventPosition: -> viewport = @page.viewportSize() if image = @_getAreaImage() pos = image.position() if area_offset = @_getAreaOffsetRect() pos.left = pos.left + area_offset.x pos.right = pos.left + area_offset.width pos.top = pos.top + area_offset.y pos.bottom = pos.top + area_offset.height else pos = this.position() middle = (start, end, size) -> start + ((Math.min(end, size) - start) / 2) res = { x: middle(pos.left, pos.right, viewport.width), y: middle(pos.top, pos.bottom, viewport.height) } mouseEvent: (name) -> if area_image = @_getAreaImage() area_image.scrollIntoView() else @scrollIntoView() pos = this.mouseEventPosition() test = this.mouseEventTest(pos.x, pos.y) if test.status == 'success' if name == 'rightclick' @page.mouseEvent('click', pos.x, pos.y, 'right') this.trigger('contextmenu') else @page.mouseEvent(name, pos.x, pos.y) pos else throw new Poltergeist.MouseEventFailed(name, test.selector, pos) dragTo: (other) -> this.scrollIntoView() position = this.mouseEventPosition() otherPosition = other.mouseEventPosition() @page.mouseEvent('mousedown', position.x, position.y) @page.mouseEvent('mouseup', otherPosition.x, otherPosition.y) dragBy: (x, y) -> this.scrollIntoView() position = this.mouseEventPosition() final_pos = x: position.x + x y: position.y + y @page.mouseEvent('mousedown', position.x, position.y) @page.mouseEvent('mouseup', final_pos.x, final_pos.y) isEqual: (other) -> @page == other.page && this.isDOMEqual(other.id) _getAreaOffsetRect: -> # get the offset of the center of selected area shape = @getAttribute('shape').toLowerCase(); coords = (parseInt(coord,10) for coord in @getAttribute('coords').split(',')) rect = switch shape when 'rect', 'rectangle' #coords.length == 4 [x,y] = coords { x: x, y: y, width: coords[2] - x, height: coords[3] - y } when 'circ', 'circle' # coords.length == 3 [centerX, centerY, radius] = coords { x: centerX - radius, y: centerY - radius, width: 2 * radius, height: 2 * radius } when 'poly', 'polygon' # coords.length > 2 # This isn't correct for highly concave polygons but is probably good enough for # use in a testing tool xs = (coords[i] for i in [0...coords.length] by 2) ys = (coords[i] for i in [1...coords.length] by 2) minX = Math.min xs... maxX = Math.max xs... minY = Math.min ys... maxY = Math.max ys... { x: minX, y: minY, width: maxX-minX, height: maxY-minY } _getAreaImage: -> if 'area' == @tagName().toLowerCase() map = @parent() if map.tagName().toLowerCase() != 'map' throw new Error('the area is not within a map') mapName = map.getAttribute('name') if not mapName? throw new Error ("area's parent map must have a name") mapName = '#' + mapName.toLowerCase() image_node_id = @page.find('css', "img[usemap='#{mapName}']")[0] if not image_node_id? throw new Error ("no image matches the map") @page.get(image_node_id) poltergeist-1.10.0/lib/capybara/poltergeist/client/web_page.coffee0000644000175000017500000002711212766546165024274 0ustar pravipraviclass Poltergeist.WebPage @CALLBACKS = ['onConsoleMessage','onError', 'onLoadFinished', 'onInitialized', 'onLoadStarted', 'onResourceRequested', 'onResourceReceived', 'onResourceError', 'onNavigationRequested', 'onUrlChanged', 'onPageCreated', 'onClosing'] @DELEGATES = ['open', 'sendEvent', 'uploadFile', 'release', 'render', 'renderBase64', 'goBack', 'goForward'] @COMMANDS = ['currentUrl', 'find', 'nodeCall', 'documentSize', 'beforeUpload', 'afterUpload', 'clearLocalStorage'] @EXTENSIONS = [] constructor: (@_native) -> @_native or= require('webpage').create() @id = 0 @source = null @closed = false @state = 'default' @urlWhitelist = [] @urlBlacklist = [] @frames = [] @errors = [] @_networkTraffic = {} @_tempHeaders = {} @_blockedUrls = [] @_requestedResources = {} for callback in WebPage.CALLBACKS this.bindCallback(callback) for command in @COMMANDS do (command) => this.prototype[command] = (args...) -> this.runCommand(command, args) for delegate in @DELEGATES do (delegate) => this.prototype[delegate] = -> @_native[delegate].apply(@_native, arguments) onInitializedNative: -> @id += 1 @source = null @injectAgent() this.removeTempHeaders() this.setScrollPosition(left: 0, top: 0) onClosingNative: -> @handle = null @closed = true onConsoleMessageNative: (message) -> if message == '__DOMContentLoaded' @source = @_native.content false else console.log(message) onLoadStartedNative: -> @state = 'loading' @requestId = @lastRequestId @_requestedResources = {} onLoadFinishedNative: (@status) -> @state = 'default' @source or= @_native.content onErrorNative: (message, stack) -> stackString = message stack.forEach (frame) -> stackString += "\n" stackString += " at #{frame.file}:#{frame.line}" stackString += " in #{frame.function}" if frame.function && frame.function != '' @errors.push(message: message, stack: stackString) return true onResourceRequestedNative: (request, net) -> useWhitelist = @urlWhitelist.length > 0 whitelisted = @urlWhitelist.some (whitelisted_regex) -> whitelisted_regex.test request.url blacklisted = @urlBlacklist.some (blacklisted_regex) -> blacklisted_regex.test request.url abort = false if useWhitelist && !whitelisted abort = true if blacklisted abort = true if abort @_blockedUrls.push request.url unless request.url in @_blockedUrls net.abort() else @lastRequestId = request.id if @normalizeURL(request.url) == @redirectURL @redirectURL = null @requestId = request.id @_networkTraffic[request.id] = { request: request, responseParts: [] error: null } @_requestedResources[request.id] = request.url return true onResourceReceivedNative: (response) -> @_networkTraffic[response.id]?.responseParts.push(response) if response.stage == 'end' delete @_requestedResources[response.id] if @requestId == response.id if response.redirectURL @redirectURL = @normalizeURL(response.redirectURL) else @statusCode = response.status @_responseHeaders = response.headers return true onResourceErrorNative: (errorResponse) -> @_networkTraffic[errorResponse.id]?.error = errorResponse delete @_requestedResources[errorResponse.id] return true injectAgent: -> if this.native().evaluate(-> typeof __poltergeist) == "undefined" this.native().injectJs "#{phantom.libraryPath}/agent.js" for extension in WebPage.EXTENSIONS this.native().injectJs extension return true return false injectExtension: (file) -> WebPage.EXTENSIONS.push file this.native().injectJs file native: -> if @closed throw new Poltergeist.NoSuchWindowError else @_native windowName: -> this.native().windowName keyCode: (name) -> this.native().event.key[name] keyModifierCode: (names) -> modifiers = this.native().event.modifier names = names.split(',').map ((name) -> modifiers[name]) names[0] | names[1] # return codes for 1 or 2 modifiers keyModifierKeys: (names) -> names.split(',').map (name) => this.keyCode(name.charAt(0).toUpperCase() + name.substring(1)) _waitState_until: (state, callback, timeout, timeout_callback) -> if (@state == state) callback.call(this) else if new Date().getTime() > timeout timeout_callback.call(this) else setTimeout (=> @_waitState_until(state, callback, timeout, timeout_callback)), 100 waitState: (state, callback, max_wait=0, timeout_callback) -> # callback and timeout_callback will be called with this == the current page if @state == state callback.call(this) else if max_wait != 0 timeout = new Date().getTime() + (max_wait*1000) setTimeout (=> @_waitState_until(state, callback, timeout, timeout_callback)), 100 else setTimeout (=> @waitState(state, callback)), 100 setHttpAuth: (user, password) -> this.native().settings.userName = user this.native().settings.password = password return true networkTraffic: -> @_networkTraffic clearNetworkTraffic: -> @_networkTraffic = {} return true blockedUrls: -> @_blockedUrls clearBlockedUrls: -> @_blockedUrls = [] return true openResourceRequests: -> url for own id, url of @_requestedResources content: -> this.native().frameContent title: -> this.native().frameTitle frameUrl: (frameNameOrId) -> query = (frameNameOrId) -> document.querySelector("iframe[name='#{frameNameOrId}'], iframe[id='#{frameNameOrId}']")?.src this.evaluate(query, frameNameOrId) clearErrors: -> @errors = [] return true responseHeaders: -> headers = {} @_responseHeaders.forEach (item) -> headers[item.name] = item.value headers cookies: -> this.native().cookies deleteCookie: (name) -> this.native().deleteCookie(name) viewportSize: -> this.native().viewportSize setViewportSize: (size) -> this.native().viewportSize = size setZoomFactor: (zoom_factor) -> this.native().zoomFactor = zoom_factor setPaperSize: (size) -> this.native().paperSize = size scrollPosition: -> this.native().scrollPosition setScrollPosition: (pos) -> this.native().scrollPosition = pos clipRect: -> this.native().clipRect setClipRect: (rect) -> this.native().clipRect = rect elementBounds: (selector) -> this.native().evaluate( (selector) -> document.querySelector(selector).getBoundingClientRect() , selector ) setUserAgent: (userAgent) -> this.native().settings.userAgent = userAgent getCustomHeaders: -> this.native().customHeaders setCustomHeaders: (headers) -> this.native().customHeaders = headers addTempHeader: (header) -> for name, value of header @_tempHeaders[name] = value @_tempHeaders removeTempHeaders: -> allHeaders = this.getCustomHeaders() for name, value of @_tempHeaders delete allHeaders[name] this.setCustomHeaders(allHeaders) pushFrame: (name) -> if this.native().switchToFrame(name) @frames.push(name) return true else frame_no = this.native().evaluate( (frame_name) -> frames = document.querySelectorAll("iframe, frame") (idx for f, idx in frames when f?['name'] == frame_name or f?['id'] == frame_name)[0] , name) if frame_no? and this.native().switchToFrame(frame_no) @frames.push(name) return true else return false popFrame: -> @frames.pop() this.native().switchToParentFrame() dimensions: -> scroll = this.scrollPosition() viewport = this.viewportSize() top: scroll.top, bottom: scroll.top + viewport.height, left: scroll.left, right: scroll.left + viewport.width, viewport: viewport document: this.documentSize() # A work around for http://code.google.com/p/phantomjs/issues/detail?id=277 validatedDimensions: -> dimensions = this.dimensions() document = dimensions.document if dimensions.right > document.width dimensions.left = Math.max(0, dimensions.left - (dimensions.right - document.width)) dimensions.right = document.width if dimensions.bottom > document.height dimensions.top = Math.max(0, dimensions.top - (dimensions.bottom - document.height)) dimensions.bottom = document.height this.setScrollPosition(left: dimensions.left, top: dimensions.top) dimensions get: (id) -> new Poltergeist.Node(this, id) # Before each mouse event we make sure that the mouse is moved to where the # event will take place. This deals with e.g. :hover changes. mouseEvent: (name, x, y, button = 'left') -> this.sendEvent('mousemove', x, y) this.sendEvent(name, x, y, button) evaluate: (fn, args...) -> this.injectAgent() JSON.parse this.sanitize(this.native().evaluate("function() { return PoltergeistAgent.stringify(#{this.stringifyCall(fn, args)}) }")) sanitize: (potential_string) -> if typeof(potential_string) == "string" # JSON doesn't like \r or \n in strings unless escaped potential_string.replace("\n","\\n").replace("\r","\\r") else potential_string execute: (fn, args...) -> this.native().evaluate("function() { #{this.stringifyCall(fn, args)} }") stringifyCall: (fn, args) -> if args.length == 0 "(#{fn.toString()})()" else # The JSON.stringify happens twice because the second time we are essentially # escaping the string. "(#{fn.toString()}).apply(this, PoltergeistAgent.JSON.parse(#{JSON.stringify(JSON.stringify(args))}))" # For some reason phantomjs seems to have trouble with doing 'fat arrow' binding here, # hence the 'that' closure. bindCallback: (name) -> that = this this.native()[name] = -> if that[name + 'Native']? # For internal callbacks result = that[name + 'Native'].apply(that, arguments) if result != false && that[name]? # For externally set callbacks that[name].apply(that, arguments) return true # Any error raised here or inside the evaluate will get reported to # phantom.onError. If result is null, that means there was an error # inside the agent. runCommand: (name, args) -> result = this.evaluate( (name, args) -> __poltergeist.externalCall(name, args), name, args ) if result != null if result.error? switch result.error.message when 'PoltergeistAgent.ObsoleteNode' throw new Poltergeist.ObsoleteNode when 'PoltergeistAgent.InvalidSelector' [method, selector] = args throw new Poltergeist.InvalidSelector(method, selector) else throw new Poltergeist.BrowserError(result.error.message, result.error.stack) else result.value canGoBack: -> this.native().canGoBack canGoForward: -> this.native().canGoForward normalizeURL: (url) -> parser = document.createElement('a') parser.href = url return parser.href clearMemoryCache: -> clearMemoryCache = this.native().clearMemoryCache if typeof clearMemoryCache == "function" clearMemoryCache() else throw new Poltergeist.UnsupportedFeature("clearMemoryCache is supported since PhantomJS 2.0.0") poltergeist-1.10.0/lib/capybara/poltergeist/client.rb0000644000175000017500000001136012766546165021675 0ustar pravipravirequire "timeout" require "capybara/poltergeist/utility" require 'cliver' module Capybara::Poltergeist class Client PHANTOMJS_SCRIPT = File.expand_path('../client/compiled/main.js', __FILE__) PHANTOMJS_VERSION = ['>= 1.8.1', '< 3.0'] PHANTOMJS_NAME = 'phantomjs' KILL_TIMEOUT = 2 # seconds def self.start(*args) client = new(*args) client.start client end # Returns a proc, that when called will attempt to kill the given process. # This is because implementing ObjectSpace.define_finalizer is tricky. # Hat-Tip to @mperham for describing in detail: # http://www.mikeperham.com/2010/02/24/the-trouble-with-ruby-finalizers/ def self.process_killer(pid) proc do begin Process.kill('KILL', pid) rescue Errno::ESRCH, Errno::ECHILD end end end attr_reader :pid, :server, :path, :window_size, :phantomjs_options def initialize(server, options = {}) @server = server @path = Cliver::detect((options[:path] || PHANTOMJS_NAME), *['>=2.1.0', '< 3.0']) @path ||= Cliver::detect!((options[:path] || PHANTOMJS_NAME), *PHANTOMJS_VERSION).tap do warn "You're running an old version of PhantomJS, update to >= 2.1.1 for a better experience." end @window_size = options[:window_size] || [1024, 768] @phantomjs_options = options[:phantomjs_options] || [] @phantomjs_logger = options[:phantomjs_logger] || $stdout pid = Process.pid at_exit do # do the work in a separate thread, to avoid stomping on $!, # since other libraries depend on it directly. Thread.new do stop if Process.pid == pid end.join end end def start @read_io, @write_io = IO.pipe @out_thread = Thread.new { while !@read_io.eof? && data = @read_io.readpartial(1024) @phantomjs_logger.write(data) end } process_options = {} process_options[:pgroup] = true unless Capybara::Poltergeist.windows? redirect_stdout do @pid = Process.spawn(*command.map(&:to_s), process_options) ObjectSpace.define_finalizer(self, self.class.process_killer(@pid)) end end def stop if pid kill_phantomjs @out_thread.kill close_io ObjectSpace.undefine_finalizer(self) end end def restart stop start end def command parts = [path] parts.concat phantomjs_options parts << PHANTOMJS_SCRIPT parts << server.port parts.concat window_size parts end private # This abomination is because JRuby doesn't support the :out option of # Process.spawn. To be honest it works pretty bad with pipes too, because # we ought close writing end in parent process immediately but JRuby will # lose all the output from child. Process.popen can be used here and seems # it works with JRuby but I've experienced strange mistakes on Rubinius. def redirect_stdout prev = STDOUT.dup $stdout = @write_io STDOUT.reopen(@write_io) yield ensure STDOUT.reopen(prev) $stdout = STDOUT prev.close end def kill_phantomjs begin if Capybara::Poltergeist.windows? Process.kill('KILL', pid) else Process.kill('TERM', pid) begin Timeout.timeout(KILL_TIMEOUT) { Process.wait(pid) } rescue Timeout::Error Process.kill('KILL', pid) Process.wait(pid) end end rescue Errno::ESRCH, Errno::ECHILD # Zed's dead, baby end @pid = nil end # We grab all the output from PhantomJS like console.log in another thread # and when PhantomJS crashes we try to restart it. In order to do it we stop # server and client and on JRuby see this error `IOError: Stream closed`. # It happens because JRuby tries to close pipe and it is blocked on `eof?` # or `readpartial` call. The error is raised in the related thread and it's # not actually main thread but the thread that listens to the output. That's # why if you put some debug code after `rescue IOError` it won't be shown. # In fact the main thread will continue working after the error even if we # don't use `rescue`. The first attempt to fix it was a try not to block on # IO, but looks like similar issue appers after JRuby upgrade. Perhaps the # only way to fix it is catching the exception what this method overall does. def close_io [@write_io, @read_io].each do |io| begin io.close unless io.closed? rescue IOError raise unless RUBY_ENGINE == 'jruby' end end end end end poltergeist-1.10.0/lib/capybara/poltergeist/driver.rb0000644000175000017500000002321412766546165021713 0ustar pravipravirequire 'uri' module Capybara::Poltergeist class Driver < Capybara::Driver::Base DEFAULT_TIMEOUT = 30 attr_reader :app, :options def initialize(app, options = {}) @app = app @options = options @browser = nil @inspector = nil @server = nil @client = nil @started = false end def needs_server? true end def browser @browser ||= begin browser = Browser.new(server, client, logger) browser.js_errors = options[:js_errors] if options.key?(:js_errors) browser.extensions = options.fetch(:extensions, []) browser.debug = true if options[:debug] browser.url_blacklist = options[:url_blacklist] if options.key?(:url_blacklist) browser.url_whitelist = options[:url_whitelist] if options.key?(:url_whitelist) browser end end def inspector @inspector ||= options[:inspector] && Inspector.new(options[:inspector]) end def server @server ||= Server.new(options[:port], options.fetch(:timeout) { DEFAULT_TIMEOUT }) end def client @client ||= Client.start(server, :path => options[:phantomjs], :window_size => options[:window_size], :phantomjs_options => phantomjs_options, :phantomjs_logger => phantomjs_logger ) end def phantomjs_options list = options[:phantomjs_options] || [] # PhantomJS defaults to only using SSLv3, which since POODLE (Oct 2014) # many sites have dropped from their supported protocols (eg PayPal, # Braintree). list += ["--ignore-ssl-errors=yes"] unless list.grep(/ignore-ssl-errors/).any? list += ["--ssl-protocol=TLSv1"] unless list.grep(/ssl-protocol/).any? list += ["--remote-debugger-port=#{inspector.port}", "--remote-debugger-autorun=yes"] if inspector list end def client_pid client.pid end def timeout server.timeout end def timeout=(sec) server.timeout = sec end def restart browser.restart end def quit server.stop client.stop end # logger should be an object that responds to puts, or nil def logger options[:logger] || (options[:debug] && STDERR) end # logger should be an object that behaves like IO or nil def phantomjs_logger options.fetch(:phantomjs_logger, nil) end def visit(url) @started = true browser.visit(url) end def current_url browser.current_url end def status_code browser.status_code end def html browser.body end alias_method :body, :html def source browser.source.to_s end def title browser.title end def find(method, selector) browser.find(method, selector).map { |page_id, id| Capybara::Poltergeist::Node.new(self, page_id, id) } end def find_xpath(selector) find :xpath, selector end def find_css(selector) find :css, selector end def click(x, y) browser.click_coordinates(x, y) end def evaluate_script(script) browser.evaluate(script) end def execute_script(script) browser.execute(script) nil end def within_frame(name, &block) browser.within_frame(name, &block) end def current_window_handle browser.window_handle end def window_handles browser.window_handles end def close_window(handle) browser.close_window(handle) end def open_new_window browser.open_new_window end def switch_to_window(handle) browser.switch_to_window(handle) end def within_window(name, &block) browser.within_window(name, &block) end def no_such_window_error NoSuchWindowError end def reset! browser.reset browser.url_blacklist = options[:url_blacklist] if options.key?(:url_blacklist) browser.url_whitelist = options[:url_whitelist] if options.key?(:url_whitelist) @started = false end def save_screenshot(path, options = {}) browser.render(path, options) end alias_method :render, :save_screenshot def render_base64(format = :png, options = {}) browser.render_base64(format, options) end def paper_size=(size = {}) browser.set_paper_size(size) end def zoom_factor=(zoom_factor) browser.set_zoom_factor(zoom_factor) end def resize(width, height) browser.resize(width, height) end alias_method :resize_window, :resize def resize_window_to(handle, width, height) within_window(handle) do resize(width, height) end end def window_size(handle) within_window(handle) do evaluate_script('[window.innerWidth, window.innerHeight]') end end def scroll_to(left, top) browser.scroll_to(left, top) end def network_traffic browser.network_traffic end def clear_network_traffic browser.clear_network_traffic end def set_proxy(ip, port, type = "http", user = nil, password = nil) browser.set_proxy(ip, port, type, user, password) end def headers browser.get_headers end def headers=(headers) browser.set_headers(headers) end def add_headers(headers) browser.add_headers(headers) end def add_header(name, value, options = {}) permanent = options.fetch(:permanent, true) browser.add_header({ name => value }, permanent) end def response_headers browser.response_headers end def cookies browser.cookies end def set_cookie(name, value, options = {}) options[:name] ||= name options[:value] ||= value options[:domain] ||= begin if @started URI.parse(browser.current_url).host else URI.parse(Capybara.app_host || '').host || "127.0.0.1" end end browser.set_cookie(options) end def remove_cookie(name) browser.remove_cookie(name) end def clear_cookies browser.clear_cookies end def cookies_enabled=(flag) browser.cookies_enabled = flag end def clear_memory_cache browser.clear_memory_cache end # * PhantomJS with set settings doesn't send `Authorize` on POST request # * With manually set header PhantomJS makes next request with # `Authorization: Basic Og==` header when settings are empty and the # response was `401 Unauthorized` (which means Base64.encode64(':')). # Combining both methods to reach proper behavior. def basic_authorize(user, password) browser.set_http_auth(user, password) credentials = ["#{user}:#{password}"].pack('m*').strip add_header('Authorization', "Basic #{credentials}") end def debug if @options[:inspector] # Fall back to default scheme scheme = URI.parse(browser.current_url).scheme rescue nil scheme = 'http' if scheme != 'https' inspector.open(scheme) pause else raise Error, "To use the remote debugging, you have to launch the driver " \ "with `:inspector => true` configuration option" end end def pause # STDIN is not necessarily connected to a keyboard. It might even be closed. # So we need a method other than keypress to continue. # In jRuby - STDIN returns immediately from select # see https://github.com/jruby/jruby/issues/1783 read, write = IO.pipe Thread.new { IO.copy_stream(STDIN, write); write.close } STDERR.puts "Poltergeist execution paused. Press enter (or run 'kill -CONT #{Process.pid}') to continue." signal = false old_trap = trap('SIGCONT') { signal = true; STDERR.puts "\nSignal SIGCONT received" } keyboard = IO.select([read], nil, nil, 1) until keyboard || signal # wait for data on STDIN or signal SIGCONT received begin input = read.read_nonblock(80) # clear out the read buffer puts unless input && input =~ /\n\z/ rescue EOFError, IO::WaitReadable # Ignore problems reading from STDIN. end unless signal trap('SIGCONT', old_trap) # Restore the previuos signal handler, if there was one. STDERR.puts 'Continuing' end def wait? true end def invalid_element_errors [Capybara::Poltergeist::ObsoleteNode, Capybara::Poltergeist::MouseEventFailed] end def go_back browser.go_back end def go_forward browser.go_forward end def accept_modal(type, options = {}) case type when :confirm browser.accept_confirm when :prompt browser.accept_prompt options[:with] end yield if block_given? find_modal(options) end def dismiss_modal(type, options = {}) case type when :confirm browser.dismiss_confirm when :prompt browser.dismiss_prompt end yield if block_given? find_modal(options) end private def find_modal(options) start_time = Time.now timeout_sec = options[:wait] || begin Capybara.default_max_wait_time rescue Capybara.default_wait_time end expect_text = options[:text] not_found_msg = 'Unable to find modal dialog' not_found_msg += " with #{expect_text}" if expect_text begin modal_text = browser.modal_message raise Capybara::ModalNotFound if modal_text.nil? raise Capybara::ModalNotFound if (expect_text && (modal_text != expect_text)) rescue Capybara::ModalNotFound => e raise e, not_found_msg if (Time.now - start_time) >= timeout_sec sleep(0.05) retry end modal_text end end end poltergeist-1.10.0/lib/capybara/poltergeist/command.rb0000644000175000017500000000053112766546165022033 0ustar pravipravirequire 'securerandom' module Capybara::Poltergeist class Command attr_reader :id attr_reader :name attr_accessor :args def initialize(name, *args) @id = SecureRandom.uuid @name = name @args = args end def message JSON.dump({ 'id' => @id, 'name' => @name, 'args' => @args }) end end endpoltergeist-1.10.0/lib/capybara/poltergeist/utility.rb0000644000175000017500000000025112766546165022117 0ustar pravipravimodule Capybara module Poltergeist class << self def windows? RbConfig::CONFIG["host_os"] =~ /mingw|mswin|cygwin/ end end end endpoltergeist-1.10.0/lib/capybara/poltergeist/web_socket_server.rb0000644000175000017500000000621512766546165024135 0ustar pravipravirequire 'socket' require 'websocket/driver' module Capybara::Poltergeist # This is a 'custom' Web Socket server that is designed to be synchronous. What # this means is that it sends a message, and then waits for a response. It does # not expect to receive a message at any other time than right after it has sent # a message. So it is basically operating a request/response cycle (which is not # how Web Sockets are usually used, but it's what we want here, as we want to # send a message to PhantomJS and then wait for it to respond). class WebSocketServer # How much to try to read from the socket at once (it's kinda arbitrary because we # just keep reading until we've received a full frame) RECV_SIZE = 1024 # How many seconds to try to bind to the port for before failing BIND_TIMEOUT = 5 HOST = '127.0.0.1' attr_reader :port, :driver, :socket, :server attr_accessor :timeout def initialize(port = nil, timeout = nil) @timeout = timeout @server = start_server(port) @receive_mutex = Mutex.new end def start_server(port) time = Time.now begin TCPServer.open(HOST, port || 0).tap do |server| @port = server.addr[1] end rescue Errno::EADDRINUSE if (Time.now - time) < BIND_TIMEOUT sleep(0.01) retry else raise end end end def connected? !socket.nil? end # Accept a client on the TCP server socket, then receive its initial HTTP request # and use that to initialize a Web Socket. def accept @socket = server.accept @messages = {} @driver = ::WebSocket::Driver.server(self) @driver.on(:connect) { |event| @driver.start } @driver.on(:message) do |event| command_id = JSON.load(event.data)['command_id'] @messages[command_id] = event.data end end def write(data) @socket.write(data) end # Block until the next message is available from the Web Socket. # Raises Errno::EWOULDBLOCK if timeout is reached. def receive(cmd_id, receive_timeout=nil) receive_timeout ||= timeout start = Time.now until @messages.has_key?(cmd_id) raise Errno::EWOULDBLOCK if (Time.now - start) >= receive_timeout if @receive_mutex.try_lock begin IO.select([socket], [], [], receive_timeout) or raise Errno::EWOULDBLOCK data = socket.recv(RECV_SIZE) break if data.empty? driver.parse(data) ensure @receive_mutex.unlock end else sleep(0.05) end end @messages.delete(cmd_id) end # Send a message and block until there is a response def send(cmd_id, message, accept_timeout=nil) accept unless connected? driver.text(message) receive(cmd_id, accept_timeout) rescue Errno::EWOULDBLOCK raise TimeoutError.new(message) end # Closing sockets separately as `close_read`, `close_write` # causes IO mistakes on JRuby, using just `close` fixes that. def close [server, socket].compact.each(&:close) end end end poltergeist-1.10.0/lib/capybara/poltergeist/network_traffic/0000755000175000017500000000000012766546165023260 5ustar pravipravipoltergeist-1.10.0/lib/capybara/poltergeist/network_traffic/response.rb0000644000175000017500000000104612766546165025444 0ustar pravipravimodule Capybara::Poltergeist::NetworkTraffic 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 poltergeist-1.10.0/lib/capybara/poltergeist/network_traffic/request.rb0000644000175000017500000000073112766546165025276 0ustar pravipravimodule Capybara::Poltergeist::NetworkTraffic class Request attr_reader :response_parts, :error def initialize(data, response_parts = [], error = nil) @data = data @response_parts = response_parts @error = error 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 poltergeist-1.10.0/lib/capybara/poltergeist/network_traffic/error.rb0000644000175000017500000000041012766546165024731 0ustar pravipravimodule Capybara::Poltergeist::NetworkTraffic class Error def initialize(data) @data = data end def url @data['url'] end def code @data['errorCode'] end def description @data['errorString'] end end end poltergeist-1.10.0/lib/capybara/poltergeist/version.rb0000644000175000017500000000010612766546165022100 0ustar pravipravimodule Capybara module Poltergeist VERSION = "1.10.0" end end poltergeist-1.10.0/lib/capybara/poltergeist/inspector.rb0000644000175000017500000000236712766546165022434 0ustar pravipravimodule Capybara::Poltergeist class Inspector BROWSERS = %w(chromium chromium-browser google-chrome open) DEFAULT_PORT = 9664 def self.detect_browser @browser ||= BROWSERS.find { |name| browser_binary_exists?(name) } end attr_reader :port def initialize(browser = nil, port = DEFAULT_PORT) @browser = browser.respond_to?(:to_str) ? browser : nil @port = port end def browser @browser ||= self.class.detect_browser end def url(scheme) "#{scheme}://localhost:#{port}/" end def open(scheme) if browser Process.spawn(browser, url(scheme)) else raise Error, "Could not find a browser executable to open #{url(scheme)}. " \ "You can specify one manually using e.g. `:inspector => 'chromium'` " \ "as a configuration option for Poltergeist." end end def self.browser_binary_exists?(browser) exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : [''] ENV['PATH'].split(File::PATH_SEPARATOR).each do |path| exts.each { |ext| exe = "#{path}#{File::SEPARATOR}#{browser}#{ext}" return exe if File.executable? exe } end return nil end end end poltergeist-1.10.0/lib/capybara/poltergeist/network_traffic.rb0000644000175000017500000000035712766546165023612 0ustar pravipravimodule Capybara::Poltergeist module NetworkTraffic require 'capybara/poltergeist/network_traffic/request' require 'capybara/poltergeist/network_traffic/response' require 'capybara/poltergeist/network_traffic/error' end end poltergeist-1.10.0/lib/capybara/poltergeist/errors.rb0000644000175000017500000001276712766546165021747 0ustar pravipravimodule Capybara module Poltergeist class Error < StandardError; end class NoSuchWindowError < Error; end class ClientError < Error attr_reader :response def initialize(response) @response = response end end class JSErrorItem attr_reader :message, :stack def initialize(message, stack) @message = message @stack = stack end def to_s [message, stack].join("\n") end end class BrowserError < ClientError def name response['name'] end def error_parameters response['args'].join("\n") end def message "There was an error inside the PhantomJS portion of Poltergeist. " \ "If this is the error returned, and not the cause of a more detailed error response, " \ "this is probably a bug, so please report it. " \ "\n\n#{name}: #{error_parameters}" end end class JavascriptError < ClientError def javascript_errors response['args'].first.map { |data| JSErrorItem.new(data['message'], data['stack']) } end def message "One or more errors were raised in the Javascript code on the page. " \ "If you don't care about these errors, you can ignore them by " \ "setting js_errors: false in your Poltergeist configuration (see " \ "documentation for details)." \ "\n\n#{javascript_errors.map(&:to_s).join("\n")}" end end class StatusFailError < ClientError def url response['args'].first end def details response['args'][1] end def message msg = "Request to '#{url}' failed to reach server, check DNS and/or server status" msg += " - #{details}" if details msg end end class FrameNotFound < ClientError def name response['args'].first end def message "The frame '#{name}' was not found." end end class InvalidSelector < ClientError def method response['args'][0] end def selector response['args'][1] end def message "The browser raised a syntax error while trying to evaluate " \ "#{method} selector #{selector.inspect}" end end class NodeError < ClientError attr_reader :node def initialize(node, response) @node = node super(response) end end class ObsoleteNode < NodeError def message "The element you are trying to interact with is either not part of the DOM, or is " \ "not currently visible on the page (perhaps display: none is set). " \ "It's possible the element has been replaced by another element and you meant to interact with " \ "the new element. If so you need to do a new 'find' in order to get a reference to the " \ "new element." end end class UnsupportedFeature < ClientError def name response['name'] end def unsupported_message response['args'][0] end def version response['args'][1].values_at(*%w(major minor patch)).join '.' end def message "Running version of PhantomJS #{version} does not support some feature: #{unsupported_message}" end end class MouseEventFailed < NodeError def name response['args'][0] end def selector response['args'][1] end def position [response['args'][2]['x'], response['args'][2]['y']] end def message "Firing a #{name} at co-ordinates [#{position.join(', ')}] failed. Poltergeist detected " \ "another element with CSS selector '#{selector}' at this position. " \ "It may be overlapping the element you are trying to interact with. " \ "If you don't care about overlapping elements, try using node.trigger('#{name}')." end end class TimeoutError < Error def initialize(message) @message = message end def message "Timed out waiting for response to #{@message}. It's possible that this happened " \ "because something took a very long time (for example a page load was slow). " \ "If so, setting the Poltergeist :timeout option to a higher value will help " \ "(see the docs for details). If increasing the timeout does not help, this is " \ "probably a bug in Poltergeist - please report it to the issue tracker." end end class DeadClient < Error def initialize(message) @message = message end def message "PhantomJS client died while processing #{@message}" end end class PhantomJSTooOld < Error def self.===(other) if Cliver::Dependency::VersionMismatch === other warn "#{name} exception has been deprecated in favor of using the " + "cliver gem for command-line dependency detection. Please " + "handle Cliver::Dependency::VersionMismatch instead." true else super end end end class PhantomJSFailed < Error def self.===(other) if Cliver::Dependency::NotMet === other warn "#{name} exception has been deprecated in favor of using the " + "cliver gem for command-line dependency detection. Please " + "handle Cliver::Dependency::NotMet instead." true else super end end end end end poltergeist-1.10.0/lib/capybara/poltergeist.rb0000644000175000017500000000142012766546165020413 0ustar pravipraviif RUBY_VERSION < "1.9.2" raise "This version of Capybara/Poltergeist does not support Ruby versions " \ "less than 1.9.2." end require 'capybara' module Capybara module Poltergeist require 'capybara/poltergeist/utility' require 'capybara/poltergeist/driver' require 'capybara/poltergeist/browser' require 'capybara/poltergeist/node' require 'capybara/poltergeist/server' require 'capybara/poltergeist/web_socket_server' require 'capybara/poltergeist/client' require 'capybara/poltergeist/inspector' require 'capybara/poltergeist/network_traffic' require 'capybara/poltergeist/errors' require 'capybara/poltergeist/cookie' end end Capybara.register_driver :poltergeist do |app| Capybara::Poltergeist::Driver.new(app) end poltergeist-1.10.0/LICENSE0000644000175000017500000000205212766546165014204 0ustar pravipraviCopyright (c) 2011-2014 Jonathan Leighton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. poltergeist-1.10.0/poltergeist.gemspec0000644000175000017500000001170412766546165017111 0ustar pravipravi######################################################### # This file has been automatically generated by gem2tgz # ######################################################### # -*- encoding: utf-8 -*- # stub: poltergeist 1.10.0 ruby lib Gem::Specification.new do |s| s.name = "poltergeist" s.version = "1.10.0" s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= s.require_paths = ["lib"] s.authors = ["Jon Leighton"] s.date = "2016-06-27" s.description = "Poltergeist is a driver for Capybara that allows you to run your tests on a headless WebKit browser, provided by PhantomJS." s.email = ["j@jonathanleighton.com"] s.files = ["LICENSE", "README.md", "lib/capybara/poltergeist.rb", "lib/capybara/poltergeist/browser.rb", "lib/capybara/poltergeist/client.rb", "lib/capybara/poltergeist/client/agent.coffee", "lib/capybara/poltergeist/client/browser.coffee", "lib/capybara/poltergeist/client/cmd.coffee", "lib/capybara/poltergeist/client/compiled/agent.js", "lib/capybara/poltergeist/client/compiled/browser.js", "lib/capybara/poltergeist/client/compiled/cmd.js", "lib/capybara/poltergeist/client/compiled/connection.js", "lib/capybara/poltergeist/client/compiled/main.js", "lib/capybara/poltergeist/client/compiled/node.js", "lib/capybara/poltergeist/client/compiled/web_page.js", "lib/capybara/poltergeist/client/connection.coffee", "lib/capybara/poltergeist/client/main.coffee", "lib/capybara/poltergeist/client/node.coffee", "lib/capybara/poltergeist/client/web_page.coffee", "lib/capybara/poltergeist/command.rb", "lib/capybara/poltergeist/cookie.rb", "lib/capybara/poltergeist/driver.rb", "lib/capybara/poltergeist/errors.rb", "lib/capybara/poltergeist/inspector.rb", "lib/capybara/poltergeist/network_traffic.rb", "lib/capybara/poltergeist/network_traffic/error.rb", "lib/capybara/poltergeist/network_traffic/request.rb", "lib/capybara/poltergeist/network_traffic/response.rb", "lib/capybara/poltergeist/node.rb", "lib/capybara/poltergeist/server.rb", "lib/capybara/poltergeist/utility.rb", "lib/capybara/poltergeist/version.rb", "lib/capybara/poltergeist/web_socket_server.rb"] s.homepage = "https://github.com/teampoltergeist/poltergeist" s.licenses = ["MIT"] s.required_ruby_version = Gem::Requirement.new(">= 1.9.3") s.rubygems_version = "2.5.1" s.summary = "PhantomJS driver for Capybara" if s.respond_to? :specification_version then s.specification_version = 4 if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then s.add_runtime_dependency(%q, ["~> 2.1"]) s.add_runtime_dependency(%q, ["~> 0.3.1"]) s.add_development_dependency(%q, ["~> 2.2"]) s.add_development_dependency(%q, ["~> 1.10.0"]) s.add_development_dependency(%q, [">= 0"]) s.add_development_dependency(%q, ["~> 2.0.0"]) s.add_development_dependency(%q, ["~> 1.0"]) s.add_development_dependency(%q, ["~> 2.0"]) s.add_development_dependency(%q, ["~> 3.0.6"]) s.add_development_dependency(%q, [">= 1.3.3", "~> 1.3"]) s.add_development_dependency(%q, [">= 0"]) s.add_development_dependency(%q, ["~> 3.4.0"]) s.add_development_dependency(%q, ["!= 3.4.0"]) s.add_development_dependency(%q, ["~> 1.0"]) s.add_runtime_dependency(%q, [">= 0.2.0"]) else s.add_dependency(%q, ["~> 2.1"]) s.add_dependency(%q, ["~> 0.3.1"]) s.add_dependency(%q, ["~> 2.2"]) s.add_dependency(%q, ["~> 1.10.0"]) s.add_dependency(%q, [">= 0"]) s.add_dependency(%q, ["~> 2.0.0"]) s.add_dependency(%q, ["~> 1.0"]) s.add_dependency(%q, ["~> 2.0"]) s.add_dependency(%q, ["~> 3.0.6"]) s.add_dependency(%q, [">= 1.3.3", "~> 1.3"]) s.add_dependency(%q, [">= 0"]) s.add_dependency(%q, ["~> 3.4.0"]) s.add_dependency(%q, ["!= 3.4.0"]) s.add_dependency(%q, ["~> 1.0"]) s.add_dependency(%q, [">= 0.2.0"]) end else s.add_dependency(%q, ["~> 2.1"]) s.add_dependency(%q, ["~> 0.3.1"]) s.add_dependency(%q, ["~> 2.2"]) s.add_dependency(%q, ["~> 1.10.0"]) s.add_dependency(%q, [">= 0"]) s.add_dependency(%q, ["~> 2.0.0"]) s.add_dependency(%q, ["~> 1.0"]) s.add_dependency(%q, ["~> 2.0"]) s.add_dependency(%q, ["~> 3.0.6"]) s.add_dependency(%q, [">= 1.3.3", "~> 1.3"]) s.add_dependency(%q, [">= 0"]) s.add_dependency(%q, ["~> 3.4.0"]) s.add_dependency(%q, ["!= 3.4.0"]) s.add_dependency(%q, ["~> 1.0"]) s.add_dependency(%q, [">= 0.2.0"]) end end