poltergeist-1.18.1/0000755000004100000410000000000013347774706014215 5ustar www-datawww-datapoltergeist-1.18.1/README.md0000644000004100000410000004504513347774706015504 0ustar www-datawww-data# Poltergeist - A PhantomJS driver for Capybara # [![Build Status](https://secure.travis-ci.org/teampoltergeist/poltergeist.svg)](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.18.1).](https://github.com/teampoltergeist/poltergeist/tree/v1.18.1)** ## 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-2.1.1-linux-i686.tar.bz2) or [64 bit](https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-2.1.1-linux-x86_64.tar.bz2) binary. * Extract the tarball and copy `bin/phantomjs` into your `PATH` _DO NOT_ use `phantomjs` from the official Ubuntu repositories, since it doesn't work well with `poltergeist`. More information [here](https://github.com/teampoltergeist/poltergeist/issues/866). ### 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. PhantomJS does not support ES6 features at the time of writing this document. Setting `js_errors` to `true` can help determine if failing tests require Polyfills, although a bug in PhantomJS can cause silent failures if using ES6 features like `let`, `const`, etc. ## 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/), [CircleCI](https://circleci.com/), [Codeship](https://codeship.com/) and [Semaphore](https://semaphoreci.com/) have 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.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 CSS 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, and related 30x redirects. All subsequent request will only contain your permanent headers. If the temporary headers should not be sent on related 30x redirects, specify `permanent: :no_redirect`. Headers set with any of these methods will be set within all windows in the session, with the exception of temporary headers, which are only set within the current window. ### 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. You can inspect requests that were blocked by a whitelist or blacklist by calling `page.driver.network_traffic(:blocked)`. This returns an array of request objects. The `response_parts` portion of these requests will always be empty. 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.send_keys('String') ``` or even more complicated: ``` ruby element.send_keys('H', 'elo', :left, 'l') # => 'Hello' element.send_keys(: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] * `:screen_size` (Array) - The dimensions the window size will be set to when Window#maximize is called. Expressed as a 2-element array, e.g. [1600, 1200]. Default: [1366, 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. * `:host` (String) - The name or IP of the PhantomJS host. Default is '127.0.0.1'. * `: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. * `:page_settings` (Hash) - PhantomJS web page settings (http://phantomjs.org/api/webpage/property/settings.html). ### 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.18.1/LICENSE0000644000004100000410000000205213347774706015221 0ustar www-datawww-dataCopyright (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.18.1/poltergeist.gemspec0000644000004100000410000001260013347774706020122 0ustar www-datawww-data######################################################### # This file has been automatically generated by gem2tgz # ######################################################### # -*- encoding: utf-8 -*- # stub: poltergeist 1.18.1 ruby lib Gem::Specification.new do |s| s.name = "poltergeist".freeze s.version = "1.18.1" s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version= s.require_paths = ["lib".freeze] s.authors = ["Jon Leighton".freeze] s.date = "2018-05-24" s.description = "Poltergeist is a driver for Capybara that allows you to run your tests on a headless WebKit browser, provided by PhantomJS.".freeze s.email = ["j@jonathanleighton.com".freeze] s.files = ["LICENSE".freeze, "README.md".freeze, "lib/capybara/poltergeist.rb".freeze, "lib/capybara/poltergeist/browser.rb".freeze, "lib/capybara/poltergeist/client.rb".freeze, "lib/capybara/poltergeist/client/agent.coffee".freeze, "lib/capybara/poltergeist/client/browser.coffee".freeze, "lib/capybara/poltergeist/client/cmd.coffee".freeze, "lib/capybara/poltergeist/client/compiled/agent.js".freeze, "lib/capybara/poltergeist/client/compiled/browser.js".freeze, "lib/capybara/poltergeist/client/compiled/cmd.js".freeze, "lib/capybara/poltergeist/client/compiled/connection.js".freeze, "lib/capybara/poltergeist/client/compiled/main.js".freeze, "lib/capybara/poltergeist/client/compiled/node.js".freeze, "lib/capybara/poltergeist/client/compiled/web_page.js".freeze, "lib/capybara/poltergeist/client/connection.coffee".freeze, "lib/capybara/poltergeist/client/main.coffee".freeze, "lib/capybara/poltergeist/client/node.coffee".freeze, "lib/capybara/poltergeist/client/web_page.coffee".freeze, "lib/capybara/poltergeist/command.rb".freeze, "lib/capybara/poltergeist/cookie.rb".freeze, "lib/capybara/poltergeist/driver.rb".freeze, "lib/capybara/poltergeist/errors.rb".freeze, "lib/capybara/poltergeist/inspector.rb".freeze, "lib/capybara/poltergeist/network_traffic.rb".freeze, "lib/capybara/poltergeist/network_traffic/error.rb".freeze, "lib/capybara/poltergeist/network_traffic/request.rb".freeze, "lib/capybara/poltergeist/network_traffic/response.rb".freeze, "lib/capybara/poltergeist/node.rb".freeze, "lib/capybara/poltergeist/server.rb".freeze, "lib/capybara/poltergeist/utility.rb".freeze, "lib/capybara/poltergeist/version.rb".freeze, "lib/capybara/poltergeist/web_socket_server.rb".freeze] s.homepage = "https://github.com/teampoltergeist/poltergeist".freeze s.licenses = ["MIT".freeze] s.required_ruby_version = Gem::Requirement.new(">= 1.9.3".freeze) s.rubygems_version = "2.5.2.1".freeze s.summary = "PhantomJS driver for Capybara".freeze 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.freeze, ["< 4", ">= 2.1"]) s.add_runtime_dependency(%q.freeze, ["~> 0.3.1"]) s.add_development_dependency(%q.freeze, ["~> 2.2"]) s.add_development_dependency(%q.freeze, ["~> 1.12.2"]) s.add_development_dependency(%q.freeze, [">= 0"]) s.add_development_dependency(%q.freeze, ["~> 2.0.0"]) s.add_development_dependency(%q.freeze, ["~> 1.0"]) s.add_development_dependency(%q.freeze, ["~> 2.0"]) s.add_development_dependency(%q.freeze, ["~> 3.0.6"]) s.add_development_dependency(%q.freeze, ["< 3.0", ">= 1.3.3"]) s.add_development_dependency(%q.freeze, [">= 0"]) s.add_development_dependency(%q.freeze, ["~> 3.7"]) s.add_development_dependency(%q.freeze, ["<= 3.0"]) s.add_runtime_dependency(%q.freeze, [">= 0.2.0"]) else s.add_dependency(%q.freeze, ["< 4", ">= 2.1"]) s.add_dependency(%q.freeze, ["~> 0.3.1"]) s.add_dependency(%q.freeze, ["~> 2.2"]) s.add_dependency(%q.freeze, ["~> 1.12.2"]) s.add_dependency(%q.freeze, [">= 0"]) s.add_dependency(%q.freeze, ["~> 2.0.0"]) s.add_dependency(%q.freeze, ["~> 1.0"]) s.add_dependency(%q.freeze, ["~> 2.0"]) s.add_dependency(%q.freeze, ["~> 3.0.6"]) s.add_dependency(%q.freeze, ["< 3.0", ">= 1.3.3"]) s.add_dependency(%q.freeze, [">= 0"]) s.add_dependency(%q.freeze, ["~> 3.7"]) s.add_dependency(%q.freeze, ["<= 3.0"]) s.add_dependency(%q.freeze, [">= 0.2.0"]) end else s.add_dependency(%q.freeze, ["< 4", ">= 2.1"]) s.add_dependency(%q.freeze, ["~> 0.3.1"]) s.add_dependency(%q.freeze, ["~> 2.2"]) s.add_dependency(%q.freeze, ["~> 1.12.2"]) s.add_dependency(%q.freeze, [">= 0"]) s.add_dependency(%q.freeze, ["~> 2.0.0"]) s.add_dependency(%q.freeze, ["~> 1.0"]) s.add_dependency(%q.freeze, ["~> 2.0"]) s.add_dependency(%q.freeze, ["~> 3.0.6"]) s.add_dependency(%q.freeze, ["< 3.0", ">= 1.3.3"]) s.add_dependency(%q.freeze, [">= 0"]) s.add_dependency(%q.freeze, ["~> 3.7"]) s.add_dependency(%q.freeze, ["<= 3.0"]) s.add_dependency(%q.freeze, [">= 0.2.0"]) end end poltergeist-1.18.1/lib/0000755000004100000410000000000013347774706014763 5ustar www-datawww-datapoltergeist-1.18.1/lib/capybara/0000755000004100000410000000000013347774706016545 5ustar www-datawww-datapoltergeist-1.18.1/lib/capybara/poltergeist.rb0000644000004100000410000000145713347774706021442 0ustar www-datawww-data# frozen_string_literal: true if 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.18.1/lib/capybara/poltergeist/0000755000004100000410000000000013347774706021106 5ustar www-datawww-datapoltergeist-1.18.1/lib/capybara/poltergeist/driver.rb0000644000004100000410000002713413347774706022735 0ustar www-datawww-data# frozen_string_literal: true require '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.page_settings = options[:page_settings] if options.key?(:page_settings) 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 }, options[:host]) 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 if Capybara::VERSION.to_f < 3.0 frame_url else browser.current_url.gsub(' ', '%20') # PhantomJS < 2.1 doesn't escape spaces end end def frame_url browser.frame_url.gsub(' ', '%20') # PhantomJS < 2.1 doesn't escape spaces 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 if Capybara::VERSION.to_f < 3.0 frame_title else browser.title end end def frame_title browser.frame_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, *args) result = browser.evaluate(script, *args.map { |arg| arg.is_a?(Capybara::Poltergeist::Node) ? arg.native : arg}) unwrap_script_result(result) end def evaluate_async_script(script, *args) result = browser.evaluate_async(script, session_wait_time, *args.map { |arg| arg.is_a?(Capybara::Poltergeist::Node) ? arg.native : arg}) unwrap_script_result(result) end def execute_script(script, *args) browser.execute(script, *args.map { |arg| arg.is_a?(Capybara::Poltergeist::Node) ? arg.native : arg}) nil end def within_frame(name, &block) browser.within_frame(name, &block) end def switch_to_frame(locator) browser.switch_to_frame(locator) 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 maximize_window(handle) resize_window_to(handle, *screen_size) 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(type = nil) browser.network_traffic(type) 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 = {}) browser.add_header({ name => value }, { permanent: true }.merge(options)) 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(default_cookie_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 ensure trap('SIGCONT', old_trap) # Restore the previous 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 refresh browser.refresh 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 screen_size options[:screen_size] || [1366,768] end def find_modal(options) start_time = Time.now timeout_sec = options.fetch(:wait) { session_wait_time } expect_text = options[:text] expect_regexp = expect_text.is_a?(Regexp) ? expect_text : Regexp.escape(expect_text.to_s) 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? || (expect_text && !modal_text.match(expect_regexp)) rescue Capybara::ModalNotFound => e raise e, not_found_msg if (Time.now - start_time) >= timeout_sec sleep(0.05) retry end modal_text end def session_wait_time if respond_to?(:session_options) session_options.default_max_wait_time else begin Capybara.default_max_wait_time rescue Capybara.default_wait_time end end end def default_cookie_host if respond_to?(:session_options) session_options.app_host else Capybara.app_host end || '' end def unwrap_script_result(arg) case arg when Array arg.map { |e| unwrap_script_result(e) } when Hash return Capybara::Poltergeist::Node.new(self, arg['ELEMENT']['page_id'], arg['ELEMENT']['id']) if arg['ELEMENT'] arg.each { |k, v| arg[k] = unwrap_script_result(v) } else arg end end end end poltergeist-1.18.1/lib/capybara/poltergeist/client/0000755000004100000410000000000013347774706022364 5ustar www-datawww-datapoltergeist-1.18.1/lib/capybara/poltergeist/client/node.coffee0000644000004100000410000001165113347774706024466 0ustar www-datawww-data# 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: (offset = {})-> 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) if offset['x']? && offset['y']? x: pos.left + offset['x'], y: pos.top + offset['y'] else x: middle(pos.left, pos.right, viewport.width), y: middle(pos.top, pos.bottom, viewport.height) mouseEvent: (name, keys, offset) -> if area_image = @_getAreaImage() area_image.scrollIntoView() else @scrollIntoView() pos = this.mouseEventPosition(offset) test = this.mouseEventTest(pos.x, pos.y) if test.status == 'success' modifier_keys = (keys || []).join(',').replace('control', 'ctrl') modifiers_code = @page.keyModifierCode(modifier_keys) if name == 'rightclick' @page.mouseEvent('click', pos.x, pos.y, 'right', modifiers_code) if phantom.version.major == 2 && phantom.version.minor >= 1 @page.sendEvent('contextmenu', pos.x, pos.y, 'right', modifiers_code) else scroll_pos = @page.scrollPosition() @trigger('contextmenu', screenX: pos.x screenY: pos.y clientX: pos.x + scroll_pos['left'] clientY: pos.y + scroll_pos['top'] ctrlKey: modifier_keys.indexOf('ctrl') != -1 altKey: modifier_keys.indexOf('alt') != -1 metaKey: modifier_keys.indexOf('meta') != -1 shiftKey: modifier_keys.indexOf('shift') != -1 button: 2 ) else @page.mouseEvent(name, pos.x, pos.y, 'left', modifiers_code) 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.18.1/lib/capybara/poltergeist/client/cmd.coffee0000644000004100000410000000141713347774706024303 0ustar www-datawww-dataclass Poltergeist.Cmd constructor: (@owner, @id, @name, @args)-> @_response_sent = false sendResponse: (response) -> if !@_response_sent errors = @browser.currentPage.errors @browser.currentPage.clearErrors() if errors.length > 0 && @browser.js_errors @sendError(new Poltergeist.JavascriptError(errors)) else @owner.sendResponse(@id, response) @_response_sent = true sendError: (errors) -> if !@_response_sent @owner.sendError(@id, errors) @_response_sent = true run: (@browser) -> try @browser.runCommand(this) catch error if error instanceof Poltergeist.Error @sendError(error) else @sendError(new Poltergeist.BrowserError(error.toString(), error.stack)) poltergeist-1.18.1/lib/capybara/poltergeist/client/main.coffee0000644000004100000410000000575413347774706024474 0ustar www-datawww-dataclass Poltergeist constructor: (port, width, height, host) -> @browser = new Poltergeist.Browser(width, height) @connection = new Poltergeist.Connection(this, port, host) phantom.onError = (message, stack) => @onError(message, stack) runCommand: (command) -> new Poltergeist.Cmd(this, command.id, command.name, command.args).run(@browser) 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) -> @connection.send(data) return true # 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.KeyError extends Poltergeist.Error constructor: (@message) -> name: "Poltergeist.KeyError" args: -> [@message] 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.ScriptTimeoutError extends Poltergeist.Error name: "Poltergeist.ScriptTimeoutError" 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], system.args[4]) poltergeist-1.18.1/lib/capybara/poltergeist/client/connection.coffee0000644000004100000410000000056013347774706025675 0ustar www-datawww-dataclass Poltergeist.Connection constructor: (@owner, @port, @host = "127.0.0.1") -> @socket = new WebSocket "ws://#{@host}:#{@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.18.1/lib/capybara/poltergeist/client/compiled/0000755000004100000410000000000013347774706024160 5ustar www-datawww-datapoltergeist-1.18.1/lib/capybara/poltergeist/client/compiled/web_page.js0000644000004100000410000006351213347774706026276 0ustar www-datawww-datavar bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, 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', 'onResourceTimeout', 'onNavigationRequested', 'onUrlChanged', 'onPageCreated', 'onClosing', 'onCallback']; WebPage.DELEGATES = ['url', 'open', 'sendEvent', 'uploadFile', 'render', 'close', 'renderBase64', 'goBack', 'goForward', 'reload']; WebPage.COMMANDS = ['find', 'nodeCall', 'documentSize', 'beforeUpload', 'afterUpload', 'clearLocalStorage']; WebPage.EXTENSIONS = []; function WebPage(_native, settings) { var callback, i, len, ref; this._native = _native; this._checkForAsyncResult = bind(this._checkForAsyncResult, this); this._native || (this._native = require('webpage').create()); this.id = 0; this.source = null; this.closed = false; this.state = 'default'; this.urlWhitelist = []; this.urlBlacklist = []; this.errors = []; this._networkTraffic = {}; this._tempHeaders = {}; this._blockedUrls = []; this._requestedResources = {}; this._responseHeaders = []; this._tempHeadersToRemoveOnRedirect = {}; this._asyncResults = {}; this._asyncEvaluationId = 0; this.setSettings(settings); ref = WebPage.CALLBACKS; for (i = 0, len = ref.length; i < len; i++) { callback = ref[i]; this.bindCallback(callback); } if (phantom.version.major < 2) { this._overrideNativeEvaluate(); } } 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.setSettings = function(settings) { var results, setting, value; if (settings == null) { settings = {}; } results = []; for (setting in settings) { value = settings[setting]; results.push(this._native.settings[setting] = value); } return results; }; WebPage.prototype.onInitializedNative = function() { this.id += 1; this.source = null; this.injectAgent(); this.removeTempHeaders(); this.removeTempHeadersForRedirect(); 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.onCallbackNative = function(data) { this._asyncResults[data['command_id']] = data['command_result']; return true; }; WebPage.prototype.onResourceRequestedNative = function(request, net) { var ref2; this._networkTraffic[request.id] = { request: request, responseParts: [], error: null }; if (this._blockRequest(request.url)) { this._networkTraffic[request.id].blocked = true; 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.removeTempHeadersForRedirect(); this.redirectURL = null; this.requestId = request.id; } 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.removeTempHeadersForRedirect(); 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.onResourceTimeoutNative = function(request) { return console.log("Resource request timed out for " + request.url); }; 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) { if (name === "Ctrl") { name = "Control"; } return this["native"]().event.key[name]; }; WebPage.prototype.keyModifierCode = function(names) { var modifiers; modifiers = this["native"]().event.modifier; return names.split(',').map(function(name) { return modifiers[name]; }).reduce(function(n1, n2) { return n1 | n2; }); }; WebPage.prototype.keyModifierKeys = function(names) { var k, len2, name, ref2, results; ref2 = names.split(','); results = []; for (k = 0, len2 = ref2.length; k < len2; k++) { name = ref2[k]; if (!(name !== 'keypad')) { continue; } name = name.charAt(0).toUpperCase() + name.substring(1); results.push(this.keyCode(name)); } return results; }; WebPage.prototype._waitState_until = function(states, callback, timeout, timeout_callback) { var ref2; if ((ref2 = this.state, indexOf.call(states, ref2) >= 0)) { return callback.call(this, this.state); } else { if (new Date().getTime() > timeout) { return timeout_callback.call(this); } else { return setTimeout(((function(_this) { return function() { return _this._waitState_until(states, callback, timeout, timeout_callback); }; })(this)), 100); } } }; WebPage.prototype.waitState = function(states, callback, max_wait, timeout_callback) { var ref2, timeout; if (max_wait == null) { max_wait = 0; } states = [].concat(states); if (ref2 = this.state, indexOf.call(states, ref2) >= 0) { return callback.call(this, this.state); } else { if (max_wait !== 0) { timeout = new Date().getTime() + (max_wait * 1000); return setTimeout(((function(_this) { return function() { return _this._waitState_until(states, callback, timeout, timeout_callback); }; })(this)), 100); } else { return setTimeout(((function(_this) { return function() { return _this.waitState(states, 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(type) { var id, ref2, ref3, ref4, request, results, results1, results2; switch (type) { case 'all': ref2 = this._networkTraffic; results = []; for (id in ref2) { if (!hasProp.call(ref2, id)) continue; request = ref2[id]; results.push(request); } return results; break; case 'blocked': ref3 = this._networkTraffic; results1 = []; for (id in ref3) { if (!hasProp.call(ref3, id)) continue; request = ref3[id]; if (request.blocked) { results1.push(request); } } return results1; break; default: ref4 = this._networkTraffic; results2 = []; for (id in ref4) { if (!hasProp.call(ref4, id)) continue; request = ref4[id]; if (!request.blocked) { results2.push(request); } } return results2; } }; 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"]().title; }; WebPage.prototype.frameTitle = function() { return this["native"]().frameTitle; }; WebPage.prototype.currentUrl = function() { return this["native"]().url || this.runCommand('frameUrl'); }; WebPage.prototype.frameUrl = function() { if (phantom.version.major > 2 || (phantom.version.major === 2 && phantom.version.minor >= 1)) { return this["native"]().frameUrl || this.runCommand('frameUrl'); } else { return this.runCommand('frameUrl'); } }; WebPage.prototype.frameUrlFor = 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.getUserAgent = function() { return this["native"]().settings.userAgent; }; WebPage.prototype.setUserAgent = function(userAgent) { return this["native"]().settings.userAgent = userAgent; }; WebPage.prototype.getCustomHeaders = function() { return this["native"]().customHeaders; }; WebPage.prototype.getPermanentCustomHeaders = function() { var allHeaders, name, ref2, ref3, value; allHeaders = this.getCustomHeaders(); ref2 = this._tempHeaders; for (name in ref2) { value = ref2[name]; delete allHeaders[name]; } ref3 = this._tempHeadersToRemoveOnRedirect; for (name in ref3) { value = ref3[name]; delete allHeaders[name]; } return allHeaders; }; 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.addTempHeaderToRemoveOnRedirect = function(header) { var name, value; for (name in header) { value = header[name]; this._tempHeadersToRemoveOnRedirect[name] = value; } return this._tempHeadersToRemoveOnRedirect; }; WebPage.prototype.removeTempHeadersForRedirect = function() { var allHeaders, name, ref2, value; allHeaders = this.getCustomHeaders(); ref2 = this._tempHeadersToRemoveOnRedirect; for (name in ref2) { value = ref2[name]; delete allHeaders[name]; } return this.setCustomHeaders(allHeaders); }; 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)) { return true; } 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); return (frame_no != null) && this["native"]().switchToFrame(frame_no); }; WebPage.prototype.popFrame = function(pop_all) { if (pop_all == null) { pop_all = false; } if (pop_all) { return this["native"]().switchToMainFrame(); } else { 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, modifiers) { if (button == null) { button = 'left'; } if (modifiers == null) { modifiers = 0; } this.sendEvent('mousemove', x, y); return this.sendEvent(name, x, y, button, modifiers); }; WebPage.prototype.evaluate = function() { var args, fn, ref2, result; fn = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : []; this.injectAgent(); result = (ref2 = this["native"]()).evaluate.apply(ref2, ["function() { var page_id = arguments[0]; var args = []; for(var i=1; i < arguments.length; i++){ if ((typeof(arguments[i]) == 'object') && (typeof(arguments[i]['ELEMENT']) == 'object')){ args.push(window.__poltergeist.get(arguments[i]['ELEMENT']['id']).element); } else { args.push(arguments[i]) } } var _result = " + (this.stringifyCall(fn, "args")) + "; return window.__poltergeist.wrapResults(_result, page_id); }", this.id].concat(slice.call(args))); return result; }; WebPage.prototype.evaluate_async = function() { var args, callback, cb, command_id, fn, ref2; fn = arguments[0], callback = arguments[1], args = 3 <= arguments.length ? slice.call(arguments, 2) : []; command_id = ++this._asyncEvaluationId; cb = callback; this.injectAgent(); (ref2 = this["native"]()).evaluate.apply(ref2, ["function(){ var page_id = arguments[0]; var args = []; for(var i=1; i < arguments.length; i++){ if ((typeof(arguments[i]) == 'object') && (typeof(arguments[i]['ELEMENT']) == 'object')){ args.push(window.__poltergeist.get(arguments[i]['ELEMENT']['id']).element); } else { args.push(arguments[i]) } } args.push(function(result){ result = window.__poltergeist.wrapResults(result, page_id); window.callPhantom( { command_id: " + command_id + ", command_result: result } ); }); " + (this.stringifyCall(fn, "args")) + "; return}", this.id].concat(slice.call(args))); setTimeout((function(_this) { return function() { return _this._checkForAsyncResult(command_id, cb); }; })(this), 10); }; WebPage.prototype.execute = function() { var args, fn, ref2; fn = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : []; return (ref2 = this["native"]()).evaluate.apply(ref2, ["function() { for(var i=0; i < arguments.length; i++){ if ((typeof(arguments[i]) == 'object') && (typeof(arguments[i]['ELEMENT']) == 'object')){ arguments[i] = window.__poltergeist.get(arguments[i]['ELEMENT']['id']).element; } } " + (this.stringifyCall(fn)) + " }"].concat(slice.call(args))); }; WebPage.prototype.stringifyCall = function(fn, args_name) { if (args_name == null) { args_name = "arguments"; } return "(" + (fn.toString()) + ").apply(this, " + args_name + ")"; }; WebPage.prototype.bindCallback = function(name) { this["native"]()[name] = (function(_this) { return function() { var result; if (_this[name + 'Native'] != null) { result = _this[name + 'Native'].apply(_this, arguments); } if (result !== false && (_this[name] != null)) { return _this[name].apply(_this, arguments); } }; })(this); 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 ? result.error : void 0) != 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 != null ? result.value : void 0; } }; 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"); } }; WebPage.prototype._checkForAsyncResult = function(command_id, callback) { if (this._asyncResults.hasOwnProperty(command_id)) { callback(this._asyncResults[command_id]); delete this._asyncResults[command_id]; } else { setTimeout((function(_this) { return function() { return _this._checkForAsyncResult(command_id, callback); }; })(this), 50); } }; WebPage.prototype._blockRequest = function(url) { var blacklisted, useWhitelist, whitelisted; useWhitelist = this.urlWhitelist.length > 0; whitelisted = this.urlWhitelist.some(function(whitelisted_regex) { return whitelisted_regex.test(url); }); blacklisted = this.urlBlacklist.some(function(blacklisted_regex) { return blacklisted_regex.test(url); }); if (useWhitelist && !whitelisted) { return true; } if (blacklisted) { return true; } return false; }; WebPage.prototype._overrideNativeEvaluate = function() { return this._native.evaluate = function (func, args) { function quoteString(str) { var c, i, l = str.length, o = '"'; for (i = 0; i < l; i += 1) { c = str.charAt(i); if (c >= ' ') { if (c === '\\' || c === '"') { o += '\\'; } o += c; } else { switch (c) { case '\b': o += '\\b'; break; case '\f': o += '\\f'; break; case '\n': o += '\\n'; break; case '\r': o += '\\r'; break; case '\t': o += '\\t'; break; default: c = c.charCodeAt(); o += '\\u00' + Math.floor(c / 16).toString(16) + (c % 16).toString(16); } } } return o + '"'; } function detectType(value) { var s = typeof value; if (s === 'object') { if (value) { if (value instanceof Array) { s = 'array'; } else if (value instanceof RegExp) { s = 'regexp'; } else if (value instanceof Date) { s = 'date'; } } else { s = 'null'; } } return s; } var str, arg, argType, i, l; if (!(func instanceof Function || typeof func === 'string' || func instanceof String)) { throw "Wrong use of WebPage#evaluate"; } str = 'function() { return (' + func.toString() + ')('; for (i = 1, l = arguments.length; i < l; i++) { arg = arguments[i]; argType = detectType(arg); switch (argType) { case "object": //< for type "object" case "array": //< for type "array" str += JSON.stringify(arg) + "," break; case "date": //< for type "date" str += "new Date(" + JSON.stringify(arg) + ")," break; case "string": //< for type "string" str += quoteString(arg) + ','; break; default: // for types: "null", "number", "function", "regexp", "undefined" str += arg + ','; break; } } str = str.replace(/,$/, '') + '); }'; return this.evaluateJavaScript(str); };; }; return WebPage; })(); poltergeist-1.18.1/lib/capybara/poltergeist/client/compiled/browser.js0000644000004100000410000007053313347774706026211 0ustar www-datawww-datavar 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; }; 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._page_settings = null; 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.frameUrl() !== 'about:blank') { this.page.clearLocalStorage(); } this.page.close(); } phantom.clearCookies(); } this.page = this.currentPage = new Poltergeist.WebPage(null, this._page_settings); 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.setupPageHandlers(this.page); }; Browser.prototype.setupPageHandlers = function(page) { page["native"]().onAlert = (function(_this) { return function(msg) { _this.setModalMessage(msg); }; })(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); 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); page.onPageCreated = (function(_this) { return function(newPage) { var _page; _page = new Poltergeist.WebPage(newPage, _this._page_settings); _page.handle = "" + (_this._counter++); _page.urlBlacklist = page.urlBlacklist; _page.urlWhitelist = page.urlWhitelist; _page.setViewportSize(page.viewportSize()); _page.setUserAgent(page.getUserAgent()); _page.setCustomHeaders(page.getPermanentCustomHeaders()); _this.setupPageHandlers(_page); 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) { if (this.currentPage.injectExtension(extension)) { return this.current_command.sendResponse('success'); } else { return this.current_command.sendError(new Poltergeist.BrowserError("Unable to load extension: " + extension)); } }; 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 ? this.currentPage.currentUrl() : 'about:blank'; 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(',')) : "Timed out with no open resource requests"; return command.sendError(new Poltergeist.StatusFailError(url, msg)); }); } }; Browser.prototype.current_url = function() { return this.current_command.sendResponse(this.currentPage.currentUrl()); }; Browser.prototype.frame_url = function() { return this.current_command.sendResponse(this.currentPage.frameUrl()); }; 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.frame_title = function() { return this.current_command.sendResponse(this.currentPage.frameTitle()); }; 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() { var arg, args, i, len, ref, script; script = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : []; for (i = 0, len = args.length; i < len; i++) { arg = args[i]; if (this._isElementArgument(arg)) { if (arg["ELEMENT"]["page_id"] !== this.currentPage.id) { throw new Poltergeist.ObsoleteNode; } } } return this.current_command.sendResponse((ref = this.currentPage).evaluate.apply(ref, ["function() { return " + script + " }"].concat(slice.call(args)))); }; Browser.prototype.evaluate_async = function() { var arg, args, cb, command, i, len, max_wait, ref, script; script = arguments[0], max_wait = arguments[1], args = 3 <= arguments.length ? slice.call(arguments, 2) : []; for (i = 0, len = args.length; i < len; i++) { arg = args[i]; if (this._isElementArgument(arg)) { if (arg["ELEMENT"]["page_id"] !== this.currentPage.id) { throw new Poltergeist.ObsoleteNode; } } } command = this.current_command; cb = (function(_this) { return function(result) { return command.sendResponse(result); }; })(this); (ref = this.currentPage).evaluate_async.apply(ref, ["function() { " + script + " }", cb].concat(slice.call(args))); return setTimeout((function(_this) { return function() { return command.sendError(new Poltergeist.ScriptTimeoutError); }; })(this), max_wait * 1000); }; Browser.prototype.execute = function() { var arg, args, i, len, ref, script; script = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : []; for (i = 0, len = args.length; i < len; i++) { arg = args[i]; if (this._isElementArgument(arg)) { if (arg["ELEMENT"]["page_id"] !== this.currentPage.id) { throw new Poltergeist.ObsoleteNode; } } } (ref = this.currentPage).execute.apply(ref, ["function() { " + script + " }"].concat(slice.call(args))); return this.current_command.sendResponse(true); }; Browser.prototype.frameUrlFor = function(frame_name) { return this.currentPage.frameUrlFor(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.frameUrlFor(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.frameUrl() === '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(pop_all) { if (pop_all == null) { pop_all = false; } return this.current_command.sendResponse(this.currentPage.popFrame(pop_all)); }; 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.close(); return this.current_command.sendResponse(true); } else { return this.current_command.sendResponse(false); } }; Browser.prototype.mouse_event = function(page_id, id, name, keys, offset) { var command, event_page, last_mouse_event, node; if (keys == null) { keys = []; } if (offset == null) { offset = {}; } node = this.node(page_id, id); this.currentPage.state = 'mouse_event'; last_mouse_event = node.mouseEvent(name, keys, offset); 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, keys, offset) { return this.mouse_event(page_id, id, 'click', keys, offset); }; Browser.prototype.right_click = function(page_id, id, keys, offset) { return this.mouse_event(page_id, id, 'rightclick', keys, offset); }; Browser.prototype.double_click = function(page_id, id, keys, offset) { return this.mouse_event(page_id, id, 'doubleclick', keys, offset); }; 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 target; target = this.node(page_id, id); if (!target.containsSelection()) { target.mouseEvent('click'); } this._send_keys_with_modifiers(keys); return this.current_command.sendResponse(true); }; Browser.prototype._send_keys_with_modifiers = function(keys, current_modifier_code) { var i, j, k, key, len, len1, len2, modifier_code, modifier_key, modifier_keys, sequence; if (current_modifier_code == null) { current_modifier_code = 0; } for (i = 0, len = keys.length; i < len; i++) { sequence = keys[i]; if (sequence.key != null) { if (!(key = this.currentPage.keyCode(sequence.key))) { this.current_command.sendError(new Poltergeist.KeyError("Unknown key: " + sequence.key)); return; } } else if (sequence.keys != null) { key = sequence.keys; } else { key = sequence; } if (sequence.modifier != null) { modifier_keys = this.currentPage.keyModifierKeys(sequence.modifier); modifier_code = this.currentPage.keyModifierCode(sequence.modifier) | current_modifier_code; for (j = 0, len1 = modifier_keys.length; j < len1; j++) { modifier_key = modifier_keys[j]; this.currentPage.sendEvent('keydown', modifier_key); } this._send_keys_with_modifiers([].concat(key), 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, null, null, current_modifier_code); } } return true; }; Browser.prototype.render_base64 = function(format, arg1) { var dimensions, encoded_image, full, ref, ref1, ref2, ref3, selector, window_scroll_position; ref = arg1 != null ? arg1 : {}, 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, arg1) { var dimensions, format, full, options, quality, ref, ref1, ref2, ref3, ref4, ref5, selector, window_scroll_position; ref = arg1 != null ? arg1 : {}, 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(type) { return this.current_command.sendResponse(this.currentPage.networkTraffic(type)); }; 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) { return this.add_headers(headers, false, false); }; Browser.prototype.add_headers = function(headers, local, keepExisting) { var pages; if (local == null) { local = false; } if (keepExisting == null) { keepExisting = true; } pages = local ? [this.currentPage] : this.pages; pages.forEach((function(_this) { return function(page) { var allHeaders, name, value; allHeaders = keepExisting ? page.getCustomHeaders() : {}; for (name in headers) { value = headers[name]; allHeaders[name] = value; } if (allHeaders['User-Agent']) { page.setUserAgent(allHeaders['User-Agent']); } return page.setCustomHeaders(allHeaders); }; })(this)); return this.current_command.sendResponse(true); }; Browser.prototype.add_header = function(header, arg1) { var permanent, ref; permanent = (ref = arg1.permanent) != null ? ref : true; if (permanent !== true) { this.currentPage.addTempHeader(header); if (permanent === "no_redirect") { this.currentPage.addTempHeaderToRemoveOnRedirect(header); } } return this.add_headers(header, permanent !== true); }; 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.set_page_settings = function(settings) { this._page_settings = settings; this.page.setSettings(this._page_settings); 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() { if (this.currentPage.canGoBack) { this.currentPage.state = 'wait_for_loading'; this.currentPage.goBack(); return this._waitForHistoryChange(); } else { return this.current_command.sendResponse(false); } }; Browser.prototype.go_forward = function() { if (this.currentPage.canGoForward) { this.currentPage.state = 'wait_for_loading'; this.currentPage.goForward(); return this._waitForHistoryChange(); } else { return this.current_command.sendResponse(false); } }; Browser.prototype.refresh = function() { this.currentPage.state = 'wait_for_loading'; this.currentPage.reload(); return this._waitForHistoryChange(); }; 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._waitForHistoryChange = function() { var command; command = this.current_command; return this.currentPage.waitState(['loading', 'default'], function(cur_state) { if (cur_state === 'loading') { return this.waitState('default', function() { return command.sendResponse(true); }); } else { return command.sendResponse(true); } }, 0.5, function() { this.state = 'default'; return 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"); }; Browser.prototype._isElementArgument = function(arg) { return typeof arg === "object" && typeof arg['ELEMENT'] === "object"; }; return Browser; })(); poltergeist-1.18.1/lib/capybara/poltergeist/client/compiled/agent.js0000644000004100000410000004262513347774706025625 0ustar www-datawww-datavar PoltergeistAgent, 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; PoltergeistAgent = (function() { function PoltergeistAgent() { this.elements = []; this.nodes = {}; } PoltergeistAgent.prototype.externalCall = function(name, args) { var error; try { return { value: this[name].apply(this, args) }; } catch (error1) { error = error1; return { error: { message: error.toString(), stack: error.stack } }; } }; PoltergeistAgent.prototype.frameUrl = function() { return window.location.href; }; PoltergeistAgent.prototype.find = function(method, selector, within) { var el, error, 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() { var error; try { return typeof localStorage !== "undefined" && localStorage !== null ? localStorage.clear() : void 0; } catch (error1) { error = error1; } }; PoltergeistAgent.prototype.wrapResults = function(result, page_id) { var j, key, len, obj, res, results1, val; this._visitedObjects || (this._visitedObjects = []); switch (false) { case indexOf.call(this._visitedObjects, result) < 0: return '(cyclic structure)'; case !(Array.isArray(result) || (result instanceof NodeList)): results1 = []; for (j = 0, len = result.length; j < len; j++) { res = result[j]; results1.push(this.wrapResults(res, page_id)); } return results1; break; case !(result && result.nodeType === 1 && result['tagName']): return { 'ELEMENT': { id: this.register(result), page_id: page_id } }; case !(result == null): return void 0; case typeof result !== 'object': this._visitedObjects.push(result); obj = {}; for (key in result) { if (!hasProp.call(result, key)) continue; val = result[key]; obj[key] = this.wrapResults(val, page_id); } this._visitedObjects.pop(); return obj; default: return result; } }; 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 != null ? element.parentNode : void 0) != 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) { var ref; if (name === 'checked' || name === 'selected') { return this.element[name]; } else { return (ref = this.element.getAttribute(name)) != null ? ref : void 0; } }; 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() { var xpath; xpath = 'parent::optgroup[@disabled] | ancestor::select[@disabled] | parent::fieldset[@disabled] | ancestor::*[not(self::legend) or preceding-sibling::legend][parent::fieldset[@disabled]]'; return this.element.disabled || document.evaluate(xpath, this.element, null, XPathResult.BOOLEAN_TYPE, null).booleanValue; }; 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, options, element) { var event; if (options == null) { options = {}; } if (element == null) { element = this.element; } if (Node.EVENTS.MOUSE.indexOf(name) !== -1) { event = document.createEvent('MouseEvent'); event.initMouseEvent(name, true, true, window, 0, options['screenX'] || 0, options['screenY'] || 0, options['clientX'] || 0, options['clientY'] || 0, options['ctrlKey'] || false, options['altKey'] || false, options['shiftKey'] || false, options['metaKey'] || false, options['button'] || 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'); }); if (document.readyState === 'complete') { console.log('__DOMContentLoaded'); } poltergeist-1.18.1/lib/capybara/poltergeist/client/compiled/main.js0000644000004100000410000001425513347774706025451 0ustar www-datawww-datavar 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, host) { this.browser = new Poltergeist.Browser(width, height); this.connection = new Poltergeist.Connection(this, port, host); phantom.onError = (function(_this) { return function(message, stack) { return _this.onError(message, stack); }; })(this); } Poltergeist.prototype.runCommand = function(command) { return new Poltergeist.Cmd(this, command.id, command.name, command.args).run(this.browser); }; 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) { this.connection.send(data); return true; }; 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.KeyError = (function(superClass) { extend(KeyError, superClass); function KeyError(message1) { this.message = message1; } KeyError.prototype.name = "Poltergeist.KeyError"; KeyError.prototype.args = function() { return [this.message]; }; return KeyError; })(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.ScriptTimeoutError = (function(superClass) { extend(ScriptTimeoutError, superClass); function ScriptTimeoutError() { return ScriptTimeoutError.__super__.constructor.apply(this, arguments); } ScriptTimeoutError.prototype.name = "Poltergeist.ScriptTimeoutError"; ScriptTimeoutError.prototype.args = function() { return []; }; return ScriptTimeoutError; })(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], system.args[4]); poltergeist-1.18.1/lib/capybara/poltergeist/client/compiled/node.js0000644000004100000410000001555413347774706025455 0ustar www-datawww-datavar 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(offset) { var area_offset, image, middle, pos, viewport; if (offset == null) { offset = {}; } 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); }; if ((offset['x'] != null) && (offset['y'] != null)) { return { x: pos.left + offset['x'], y: pos.top + offset['y'] }; } else { return { x: middle(pos.left, pos.right, viewport.width), y: middle(pos.top, pos.bottom, viewport.height) }; } }; Node.prototype.mouseEvent = function(name, keys, offset) { var area_image, modifier_keys, modifiers_code, pos, scroll_pos, test; if (area_image = this._getAreaImage()) { area_image.scrollIntoView(); } else { this.scrollIntoView(); } pos = this.mouseEventPosition(offset); test = this.mouseEventTest(pos.x, pos.y); if (test.status === 'success') { modifier_keys = (keys || []).join(',').replace('control', 'ctrl'); modifiers_code = this.page.keyModifierCode(modifier_keys); if (name === 'rightclick') { this.page.mouseEvent('click', pos.x, pos.y, 'right', modifiers_code); if (phantom.version.major === 2 && phantom.version.minor >= 1) { this.page.sendEvent('contextmenu', pos.x, pos.y, 'right', modifiers_code); } else { scroll_pos = this.page.scrollPosition(); this.trigger('contextmenu', { screenX: pos.x, screenY: pos.y, clientX: pos.x + scroll_pos['left'], clientY: pos.y + scroll_pos['top'], ctrlKey: modifier_keys.indexOf('ctrl') !== -1, altKey: modifier_keys.indexOf('alt') !== -1, metaKey: modifier_keys.indexOf('meta') !== -1, shiftKey: modifier_keys.indexOf('shift') !== -1, button: 2 }); } } else { this.page.mouseEvent(name, pos.x, pos.y, 'left', modifiers_code); } 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.18.1/lib/capybara/poltergeist/client/compiled/cmd.js0000644000004100000410000000233413347774706025263 0ustar www-datawww-dataPoltergeist.Cmd = (function() { function Cmd(owner, id, name, args) { this.owner = owner; this.id = id; this.name = name; this.args = args; this._response_sent = false; } Cmd.prototype.sendResponse = function(response) { var errors; if (!this._response_sent) { 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 { this.owner.sendResponse(this.id, response); return this._response_sent = true; } } }; Cmd.prototype.sendError = function(errors) { if (!this._response_sent) { this.owner.sendError(this.id, errors); return this._response_sent = true; } }; Cmd.prototype.run = function(browser) { var error; this.browser = browser; try { return this.browser.runCommand(this); } catch (error1) { error = error1; if (error instanceof Poltergeist.Error) { return this.sendError(error); } else { return this.sendError(new Poltergeist.BrowserError(error.toString(), error.stack)); } } }; return Cmd; })(); poltergeist-1.18.1/lib/capybara/poltergeist/client/compiled/connection.js0000644000004100000410000000144013347774706026654 0ustar www-datawww-datavar bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; Poltergeist.Connection = (function() { function Connection(owner, port, host) { this.owner = owner; this.port = port; this.host = host != null ? host : "127.0.0.1"; this.commandReceived = bind(this.commandReceived, this); this.socket = new WebSocket("ws://" + this.host + ":" + 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.18.1/lib/capybara/poltergeist/client/browser.coffee0000644000004100000410000004744613347774706025237 0ustar www-datawww-dataclass Poltergeist.Browser constructor: (width, height) -> @width = width || 1024 @height = height || 768 @pages = [] @js_errors = true @_debug = false @_counter = 0 @_page_settings = null @processed_modal_messages = [] @confirm_processes = [] @prompt_responses = [] this.resetPage() resetPage: -> [@_counter, @pages] = [0, []] if @page? unless @page.closed @page.clearLocalStorage() if @page.frameUrl() != 'about:blank' @page.close() phantom.clearCookies() @page = @currentPage = new Poltergeist.WebPage(null, @_page_settings) @page.setViewportSize(width: @width, height: @height) @page.handle = "#{@_counter++}" @pages.push(@page) @processed_modal_messages = [] @confirm_processes = [] @prompt_responses = [] @setupPageHandlers(@page) return setupPageHandlers: (page) -> 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_settings) _page.handle = "#{@_counter++}" _page.urlBlacklist = page.urlBlacklist _page.urlWhitelist = page.urlWhitelist _page.setViewportSize(page.viewportSize()) _page.setUserAgent(page.getUserAgent()) _page.setCustomHeaders(page.getPermanentCustomHeaders()) @setupPageHandlers(_page) @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) -> if @currentPage.injectExtension extension @current_command.sendResponse 'success' else @current_command.sendError(new Poltergeist.BrowserError("Unable to load extension: #{extension}")) 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? then @currentPage.currentUrl() else 'about:blank' @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(',')}" else "Timed out with no open resource requests" command.sendError(new Poltergeist.StatusFailError(url,msg)) return current_url: -> @current_command.sendResponse @currentPage.currentUrl() frame_url: -> @current_command.sendResponse @currentPage.frameUrl() 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() frame_title: -> @current_command.sendResponse @currentPage.frameTitle() 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, args...) -> for arg in args when @_isElementArgument(arg) throw new Poltergeist.ObsoleteNode if arg["ELEMENT"]["page_id"] != @currentPage.id @current_command.sendResponse @currentPage.evaluate("function() { return #{script} }", args...) evaluate_async: (script, max_wait, args...) -> for arg in args when @_isElementArgument(arg) throw new Poltergeist.ObsoleteNode if arg["ELEMENT"]["page_id"] != @currentPage.id command = @current_command cb = (result)=> command.sendResponse(result) @currentPage.evaluate_async("function() { #{script} }", cb, args...) setTimeout(=> command.sendError(new Poltergeist.ScriptTimeoutError) , max_wait*1000) execute: (script, args...) -> for arg in args when @_isElementArgument(arg) throw new Poltergeist.ObsoleteNode if arg["ELEMENT"]["page_id"] != @currentPage.id @currentPage.execute("function() { #{script} }", args...) @current_command.sendResponse(true) frameUrlFor: (frame_name) -> @currentPage.frameUrlFor(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 = @frameUrlFor(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') if frame_url && (frame_url != 'about:blank') && (@currentPage.frameUrl() == '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: (pop_all = false)-> @current_command.sendResponse(@currentPage.popFrame(pop_all)) 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.close() @current_command.sendResponse(true) else @current_command.sendResponse(false) mouse_event: (page_id, id, name, keys=[], offset={}) -> # 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, keys, offset) 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, keys, offset) -> this.mouse_event page_id, id, 'click', keys, offset right_click: (page_id, id, keys, offset) -> this.mouse_event page_id, id, 'rightclick', keys, offset double_click: (page_id, id, keys, offset) -> this.mouse_event page_id, id, 'doubleclick', keys, offset 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') @_send_keys_with_modifiers(keys) @current_command.sendResponse(true) _send_keys_with_modifiers: (keys, current_modifier_code = 0) -> for sequence in keys if sequence.key? if !(key=@currentPage.keyCode(sequence.key)) @current_command.sendError(new Poltergeist.KeyError("Unknown key: #{sequence.key}")) return else if sequence.keys? key=sequence.keys else key=sequence if sequence.modifier? modifier_keys = @currentPage.keyModifierKeys(sequence.modifier) modifier_code = @currentPage.keyModifierCode(sequence.modifier) | current_modifier_code @currentPage.sendEvent('keydown', modifier_key) for modifier_key in modifier_keys @_send_keys_with_modifiers([].concat(key), modifier_code) @currentPage.sendEvent('keyup', modifier_key) for modifier_key in modifier_keys else @currentPage.sendEvent('keypress', key, null, null, current_modifier_code) return 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: (type) -> @current_command.sendResponse(@currentPage.networkTraffic(type)) 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) -> this.add_headers(headers, false, false) add_headers: (headers, local = false, keepExisting = true) -> pages = if local then [@currentPage] else @pages pages.forEach (page) => allHeaders = if keepExisting then page.getCustomHeaders() else {} for name, value of headers allHeaders[name] = value page.setUserAgent(allHeaders['User-Agent']) if allHeaders['User-Agent'] page.setCustomHeaders(allHeaders) @current_command.sendResponse(true) add_header: (header, { permanent = true }) -> unless permanent == true @currentPage.addTempHeader(header) @currentPage.addTempHeaderToRemoveOnRedirect(header) if permanent == "no_redirect" this.add_headers(header, permanent != true) 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) set_page_settings: (settings)-> @_page_settings = settings @page.setSettings(@_page_settings) @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: -> if @currentPage.canGoBack @currentPage.state = 'wait_for_loading' @currentPage.goBack() @_waitForHistoryChange() else @current_command.sendResponse(false) go_forward: -> if @currentPage.canGoForward @currentPage.state = 'wait_for_loading' @currentPage.goForward() @_waitForHistoryChange() else @current_command.sendResponse(false) refresh: -> @currentPage.state = 'wait_for_loading' @currentPage.reload() @_waitForHistoryChange() 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) _waitForHistoryChange: -> command = @current_command @currentPage.waitState ['loading','default'], (cur_state) -> if cur_state == 'loading' # loading has started, wait for completion @waitState 'default', -> command.sendResponse(true) else # page has loaded command.sendResponse(true) , 0.5, -> # if haven't moved to loading/default in time assume history API state change @state = 'default' command.sendResponse(true) _wildcardToRegexp: (wildcard)-> wildcard = wildcard.replace(/[\-\[\]\/\{\}\(\)\+\.\\\^\$\|]/g, "\\$&") wildcard = wildcard.replace(/\*/g, ".*") wildcard = wildcard.replace(/\?/g, ".") new RegExp(wildcard, "i") _isElementArgument: (arg)-> typeof(arg) == "object" and typeof(arg['ELEMENT']) == "object" poltergeist-1.18.1/lib/capybara/poltergeist/client/agent.coffee0000644000004100000410000003106413347774706024637 0ustar www-datawww-data# This is injected into each page that is loaded class PoltergeistAgent constructor: -> @elements = [] @nodes = {} externalCall: (name, args) -> try { value: this[name].apply(this, args) } catch error { error: { message: error.toString(), stack: error.stack } } # 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. frameUrl: -> window.location.href 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: -> try localStorage?.clear() catch error wrapResults: (result, page_id)-> @_visitedObjects ||= []; switch when result in @_visitedObjects '(cyclic structure)' when Array.isArray(result) || (result instanceof NodeList) @wrapResults(res, page_id) for res in result when result && result.nodeType == 1 && result['tagName'] {'ELEMENT': { id: @register(result), page_id: page_id } }; when not result? undefined when typeof result == 'object' @_visitedObjects.push(result); obj = {} obj[key] = @wrapResults(val, page_id) for own key, val of result @_visitedObjects.pop(); obj else result 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) ? undefined 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: -> xpath = 'parent::optgroup[@disabled] | \ ancestor::select[@disabled] | \ parent::fieldset[@disabled] | \ ancestor::*[not(self::legend) or preceding-sibling::legend][parent::fieldset[@disabled]]' @element.disabled || document.evaluate(xpath, @element, null, XPathResult.BOOLEAN_TYPE, null).booleanValue 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 undefined 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, options = {}, element = @element) -> if Node.EVENTS.MOUSE.indexOf(name) != -1 event = document.createEvent('MouseEvent') event.initMouseEvent( name, true, true, window, 0, options['screenX'] || 0, options['screenY'] || 0, options['clientX'] || 0, options['clientY'] || 0, options['ctrlKey'] || false, options['altKey'] || false, options['shiftKey'] || false, options['metaKey'] || false, options['button'] || 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') ) console.log('__DOMContentLoaded') if document.readyState == 'complete' poltergeist-1.18.1/lib/capybara/poltergeist/client/web_page.coffee0000644000004100000410000004366113347774706025320 0ustar www-datawww-dataclass Poltergeist.WebPage @CALLBACKS = ['onConsoleMessage','onError', 'onLoadFinished', 'onInitialized', 'onLoadStarted', 'onResourceRequested', 'onResourceReceived', 'onResourceError', 'onResourceTimeout', 'onNavigationRequested', 'onUrlChanged', 'onPageCreated', 'onClosing', 'onCallback'] @DELEGATES = ['url', 'open', 'sendEvent', 'uploadFile', 'render', 'close', 'renderBase64', 'goBack', 'goForward', 'reload'] # @COMMANDS = ['currentUrl', 'find', 'nodeCall', 'documentSize', @COMMANDS = ['find', 'nodeCall', 'documentSize', 'beforeUpload', 'afterUpload', 'clearLocalStorage'] @EXTENSIONS = [] constructor: (@_native, settings) -> @_native or= require('webpage').create() @id = 0 @source = null @closed = false @state = 'default' @urlWhitelist = [] @urlBlacklist = [] @errors = [] @_networkTraffic = {} @_tempHeaders = {} @_blockedUrls = [] @_requestedResources = {} @_responseHeaders = [] @_tempHeadersToRemoveOnRedirect = {} @_asyncResults = {} @_asyncEvaluationId = 0 @setSettings(settings) for callback in WebPage.CALLBACKS this.bindCallback(callback) if phantom.version.major < 2 @._overrideNativeEvaluate() 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) setSettings: (settings = {})-> @_native.settings[setting] = value for setting, value of settings onInitializedNative: -> @id += 1 @source = null @injectAgent() @removeTempHeaders() @removeTempHeadersForRedirect() @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 onCallbackNative: (data) -> @_asyncResults[data['command_id']] = data['command_result'] true onResourceRequestedNative: (request, net) -> @_networkTraffic[request.id] = { request: request, responseParts: [] error: null } if @_blockRequest(request.url) @_networkTraffic[request.id].blocked = true @_blockedUrls.push request.url unless request.url in @_blockedUrls net.abort() else @lastRequestId = request.id if @normalizeURL(request.url) == @redirectURL @removeTempHeadersForRedirect() @redirectURL = null @requestId = request.id @_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 @removeTempHeadersForRedirect() @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 onResourceTimeoutNative: (request) -> console.log "Resource request timed out for #{request.url}" 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) -> name = "Control" if name == "Ctrl" this.native().event.key[name] keyModifierCode: (names) -> modifiers = this.native().event.modifier names.split(',').map((name) -> modifiers[name]).reduce((n1,n2) -> n1 | n2) keyModifierKeys: (names) -> for name in names.split(',') when name isnt 'keypad' name = name.charAt(0).toUpperCase() + name.substring(1) this.keyCode(name) _waitState_until: (states, callback, timeout, timeout_callback) -> if (@state in states) callback.call(this, @state) else if new Date().getTime() > timeout timeout_callback.call(this) else setTimeout (=> @_waitState_until(states, callback, timeout, timeout_callback)), 100 waitState: (states, callback, max_wait=0, timeout_callback) -> # callback and timeout_callback will be called with this == the current page states = [].concat(states) if @state in states callback.call(this, @state) else if max_wait != 0 timeout = new Date().getTime() + (max_wait*1000) setTimeout (=> @_waitState_until(states, callback, timeout, timeout_callback)), 100 else setTimeout (=> @waitState(states, callback)), 100 setHttpAuth: (user, password) -> this.native().settings.userName = user this.native().settings.password = password return true networkTraffic: (type) -> switch type when 'all' request for own id, request of @_networkTraffic when 'blocked' request for own id, request of @_networkTraffic when request.blocked else request for own id, request of @_networkTraffic when not request.blocked 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().title frameTitle: -> this.native().frameTitle currentUrl: -> # native url doesn't return anything when about:blank # in that case get the frame url which will be main window @native().url || @runCommand('frameUrl') frameUrl: -> if phantom.version.major > 2 || (phantom.version.major == 2 && phantom.version.minor >= 1) @native().frameUrl || @runCommand('frameUrl') else @runCommand('frameUrl') frameUrlFor: (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 ) getUserAgent: -> this.native().settings.userAgent setUserAgent: (userAgent) -> this.native().settings.userAgent = userAgent getCustomHeaders: -> this.native().customHeaders getPermanentCustomHeaders: -> allHeaders = @getCustomHeaders() for name, value of @_tempHeaders delete allHeaders[name] for name, value of @_tempHeadersToRemoveOnRedirect delete allHeaders[name] allHeaders setCustomHeaders: (headers) -> this.native().customHeaders = headers addTempHeader: (header) -> for name, value of header @_tempHeaders[name] = value @_tempHeaders addTempHeaderToRemoveOnRedirect: (header) -> for name, value of header @_tempHeadersToRemoveOnRedirect[name] = value @_tempHeadersToRemoveOnRedirect removeTempHeadersForRedirect: -> allHeaders = @getCustomHeaders() for name, value of @_tempHeadersToRemoveOnRedirect delete allHeaders[name] @setCustomHeaders(allHeaders) removeTempHeaders: -> allHeaders = @getCustomHeaders() for name, value of @_tempHeaders delete allHeaders[name] @setCustomHeaders(allHeaders) pushFrame: (name) -> return true if this.native().switchToFrame(name) # if switch by name fails - find index and try again 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) frame_no? and this.native().switchToFrame(frame_no) popFrame: (pop_all = false)-> if pop_all this.native().switchToMainFrame() else 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', modifiers = 0) -> this.sendEvent('mousemove', x, y) this.sendEvent(name, x, y, button, modifiers) evaluate: (fn, args...) -> this.injectAgent() result = this.native().evaluate("function() { var page_id = arguments[0]; var args = []; for(var i=1; i < arguments.length; i++){ if ((typeof(arguments[i]) == 'object') && (typeof(arguments[i]['ELEMENT']) == 'object')){ args.push(window.__poltergeist.get(arguments[i]['ELEMENT']['id']).element); } else { args.push(arguments[i]) } } var _result = #{this.stringifyCall(fn, "args")}; return window.__poltergeist.wrapResults(_result, page_id); }", @id, args...) result evaluate_async: (fn, callback, args...) -> command_id = ++@_asyncEvaluationId cb = callback this.injectAgent() this.native().evaluate("function(){ var page_id = arguments[0]; var args = []; for(var i=1; i < arguments.length; i++){ if ((typeof(arguments[i]) == 'object') && (typeof(arguments[i]['ELEMENT']) == 'object')){ args.push(window.__poltergeist.get(arguments[i]['ELEMENT']['id']).element); } else { args.push(arguments[i]) } } args.push(function(result){ result = window.__poltergeist.wrapResults(result, page_id); window.callPhantom( { command_id: #{command_id}, command_result: result } ); }); #{this.stringifyCall(fn, "args")}; return}", @id, args...) setTimeout( => @_checkForAsyncResult(command_id, cb) , 10) return execute: (fn, args...) -> this.native().evaluate("function() { for(var i=0; i < arguments.length; i++){ if ((typeof(arguments[i]) == 'object') && (typeof(arguments[i]['ELEMENT']) == 'object')){ arguments[i] = window.__poltergeist.get(arguments[i]['ELEMENT']['id']).element; } } #{this.stringifyCall(fn)} }", args...) stringifyCall: (fn, args_name = "arguments") -> "(#{fn.toString()}).apply(this, #{args_name})" bindCallback: (name) -> @native()[name] = => result = @[name + 'Native'].apply(@, arguments) if @[name + 'Native']? # For internal callbacks @[name].apply(@, arguments) if result != false && @[name]? # For externally set callbacks 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?.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") _checkForAsyncResult: (command_id, callback)=> if @_asyncResults.hasOwnProperty(command_id) callback(@_asyncResults[command_id]) delete @_asyncResults[command_id] else setTimeout(=> @_checkForAsyncResult(command_id, callback) , 50) return _blockRequest: (url) -> useWhitelist = @urlWhitelist.length > 0 whitelisted = @urlWhitelist.some (whitelisted_regex) -> whitelisted_regex.test url blacklisted = @urlBlacklist.some (blacklisted_regex) -> blacklisted_regex.test url if useWhitelist && !whitelisted return true if blacklisted return true false _overrideNativeEvaluate: -> # PhantomJS 1.9.x WebPage#evaluate depends on the browser context JSON, this replaces it # with the evaluate from 2.1.1 which uses the PhantomJS JSON @_native.evaluate = `function (func, args) { function quoteString(str) { var c, i, l = str.length, o = '"'; for (i = 0; i < l; i += 1) { c = str.charAt(i); if (c >= ' ') { if (c === '\\' || c === '"') { o += '\\'; } o += c; } else { switch (c) { case '\b': o += '\\b'; break; case '\f': o += '\\f'; break; case '\n': o += '\\n'; break; case '\r': o += '\\r'; break; case '\t': o += '\\t'; break; default: c = c.charCodeAt(); o += '\\u00' + Math.floor(c / 16).toString(16) + (c % 16).toString(16); } } } return o + '"'; } function detectType(value) { var s = typeof value; if (s === 'object') { if (value) { if (value instanceof Array) { s = 'array'; } else if (value instanceof RegExp) { s = 'regexp'; } else if (value instanceof Date) { s = 'date'; } } else { s = 'null'; } } return s; } var str, arg, argType, i, l; if (!(func instanceof Function || typeof func === 'string' || func instanceof String)) { throw "Wrong use of WebPage#evaluate"; } str = 'function() { return (' + func.toString() + ')('; for (i = 1, l = arguments.length; i < l; i++) { arg = arguments[i]; argType = detectType(arg); switch (argType) { case "object": //< for type "object" case "array": //< for type "array" str += JSON.stringify(arg) + "," break; case "date": //< for type "date" str += "new Date(" + JSON.stringify(arg) + ")," break; case "string": //< for type "string" str += quoteString(arg) + ','; break; default: // for types: "null", "number", "function", "regexp", "undefined" str += arg + ','; break; } } str = str.replace(/,$/, '') + '); }'; return this.evaluateJavaScript(str); };` poltergeist-1.18.1/lib/capybara/poltergeist/version.rb0000644000004100000410000000014513347774706023120 0ustar www-datawww-data# frozen_string_literal: true module Capybara module Poltergeist VERSION = "1.18.1" end end poltergeist-1.18.1/lib/capybara/poltergeist/browser.rb0000644000004100000410000002644513347774706023131 0ustar www-datawww-data# frozen_string_literal: true require "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.ScriptTimeoutError' => ScriptTimeoutError, 'Poltergeist.UnsupportedFeature' => UnsupportedFeature, 'Poltergeist.KeyError' => KeyError, } 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 frame_url command 'frame_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 frame_title command 'frame_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, *args) command 'evaluate', script, *args end def evaluate_async(script, wait_time, *args) command 'evaluate_async', script, wait_time, *args end def execute(script, *args) command 'execute', script, *args 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 switch_to_frame(handle) case handle when Capybara::Node::Base command 'push_frame', [handle.native.page_id, handle.native.id] when :parent command 'pop_frame' when :top command 'pop_frame', true end 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, keys=[], offset={}) command 'click', page_id, id, keys, offset end def right_click(page_id, id, keys=[], offset={}) command 'right_click', page_id, id, keys, offset end def double_click(page_id, id, keys=[], offset={}) command 'double_click', page_id, id, keys, offset 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(type = nil) command('network_traffic', type).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, options={}) command 'add_header', header, options 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 page_settings=(settings) command 'set_page_settings', settings 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 refresh command 'refresh' 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 KEY_ALIASES = { command: :Meta, equals: :Equal, Control: :Ctrl, control: :Ctrl, multiply: 'numpad*', add: 'numpad+', divide: 'numpad/', subtract: 'numpad-', decimal: 'numpad.' } def normalize_keys(keys) keys.map do |key_desc| case key_desc when Array # [:Shift, "s"] => { modifier: "shift", keys: "S" } # [:Shift, "string"] => { modifier: "shift", keys: "STRING" } # [:Ctrl, :Left] => { modifier: "ctrl", key: 'Left' } # [:Ctrl, :Shift, :Left] => { modifier: "ctrl,shift", key: 'Left' } # [:Ctrl, :Left, :Left] => { modifier: "ctrl", key: [:Left, :Left] } _keys = key_desc.chunk {|k| k.is_a?(Symbol) && %w(shift ctrl control alt meta command).include?(k.to_s.downcase) } modifiers = if _keys.peek[0] _keys.next[1].map do |k| k = k.to_s.downcase k = 'ctrl' if k == 'control' k = 'meta' if k == 'command' k end.join(',') else '' end letters = normalize_keys(_keys.next[1].map {|k| k.is_a?(String) ? k.upcase : k }) { modifier: modifiers, keys: letters } when Symbol # Return a known sequence for PhantomJS key = KEY_ALIASES.fetch(key_desc, key_desc) if match = key.to_s.match(/numpad(.)/) res = { keys: match[1], modifier: 'keypad' } elsif key !~ /^[A-Z]/ key = key.to_s.split('_').map{ |e| e.capitalize }.join end res || { key: key } when String key_desc # Plain string, nothing to do end end end end end poltergeist-1.18.1/lib/capybara/poltergeist/errors.rb0000644000004100000410000001344213347774706022753 0ustar www-datawww-data# frozen_string_literal: true module 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 KeyError < ::ArgumentError def initialize(response) super(response["args"].first) 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 ScriptTimeoutError < Error def message "Timed out waiting for evaluated script to resturn a value" 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.18.1/lib/capybara/poltergeist/client.rb0000644000004100000410000001147413347774706022720 0ustar www-datawww-data# frozen_string_literal: true require "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 if Capybara::Poltergeist.windows? Process.kill('KILL', pid) else Process.kill('TERM', pid) start = Time.now while Process.wait(pid, Process::WNOHANG).nil? sleep 0.05 if (Time.now - start) > KILL_TIMEOUT Process.kill('KILL', pid) Process.wait(pid) break end end end rescue Errno::ESRCH, Errno::ECHILD # Zed's dead, baby 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 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 = {in: File::NULL} process_options[:pgroup] = true unless Capybara::Poltergeist.windows? process_options[:out] = @write_io if Capybara::Poltergeist.mri? 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 << server.host 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 if Capybara::Poltergeist.mri? yield else begin prev = STDOUT.dup $stdout = @write_io STDOUT.reopen(@write_io) yield ensure STDOUT.reopen(prev) $stdout = STDOUT prev.close end end end def kill_phantomjs self.class.process_killer(pid).call @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.18.1/lib/capybara/poltergeist/cookie.rb0000644000004100000410000000113513347774706022704 0ustar www-datawww-data# frozen_string_literal: true module 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.18.1/lib/capybara/poltergeist/utility.rb0000644000004100000410000000042213347774706023134 0ustar www-datawww-data# frozen_string_literal: true module Capybara module Poltergeist class << self def windows? RbConfig::CONFIG["host_os"] =~ /mingw|mswin|cygwin/ end def mri? defined?(RUBY_ENGINE) && RUBY_ENGINE == "ruby" end end end end poltergeist-1.18.1/lib/capybara/poltergeist/node.rb0000644000004100000410000001107613347774706022365 0ustar www-datawww-data# frozen_string_literal: true module 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 if Capybara::VERSION.to_f < 3.0 filter_text command(:visible_text) else command(:visible_text).to_s .gsub(/\A[[:space:]&&[^\u00a0]]+/, "") .gsub(/[[:space:]&&[^\u00a0]]+\z/, "") .gsub(/\n+/, "\n") .tr("\u00a0", ' ') end 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, options = {}) warn "Options passed to Node#set but Poltergeist doesn't currently support any - ignoring" unless options.empty? 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[:isContentEditable] 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(keys=[], offset={}) command :click, keys, offset end def right_click(keys=[], offset={}) command :right_click, keys, offset end def double_click(keys=[], offset={}) command :double_click, keys, offset 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) (page_id == other.page_id) && 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 # @api private def to_json(*) JSON.generate as_json end # @api private def as_json(*) { ELEMENT: {page_id: @page_id, id: @id} } end private def filter_text(text, visible = true) if Capybara::VERSION.to_f < 3 Capybara::Helpers.normalize_whitespace(text.to_s) else text.gsub(/[\u200b\u200e\u200f]/, '') .gsub(/[\ \n\f\t\v\u2028\u2029]+/, ' ') .gsub(/\A[[:space:]&&[^\u00a0]]+/, "") .gsub(/[[:space:]&&[^\u00a0]]+\z/, "") .tr("\u00a0", ' ') end end end end poltergeist-1.18.1/lib/capybara/poltergeist/command.rb0000644000004100000410000000057113347774706023054 0ustar www-datawww-data# frozen_string_literal: true require '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 end poltergeist-1.18.1/lib/capybara/poltergeist/network_traffic/0000755000004100000410000000000013347774706024275 5ustar www-datawww-datapoltergeist-1.18.1/lib/capybara/poltergeist/network_traffic/request.rb0000644000004100000410000000077013347774706026316 0ustar www-datawww-data# frozen_string_literal: true module 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.18.1/lib/capybara/poltergeist/network_traffic/error.rb0000644000004100000410000000044713347774706025760 0ustar www-datawww-data# frozen_string_literal: true module 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.18.1/lib/capybara/poltergeist/network_traffic/response.rb0000644000004100000410000000110513347774706026455 0ustar www-datawww-data# frozen_string_literal: true module 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.18.1/lib/capybara/poltergeist/network_traffic.rb0000644000004100000410000000041613347774706024623 0ustar www-datawww-data# frozen_string_literal: true module 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.18.1/lib/capybara/poltergeist/server.rb0000644000004100000410000000204313347774706022740 0ustar www-datawww-data# frozen_string_literal: true module Capybara::Poltergeist class Server attr_reader :socket, :fixed_port, :timeout, :custom_host def initialize(fixed_port = nil, timeout = nil, custom_host = nil) @fixed_port = fixed_port @timeout = timeout @custom_host = custom_host start end def port @socket.port end def host @socket.host end def timeout=(sec) @timeout = @socket.timeout = sec end def start @socket = WebSocketServer.new(fixed_port, timeout, custom_host) 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.18.1/lib/capybara/poltergeist/web_socket_server.rb0000644000004100000410000000642013347774706025150 0ustar www-datawww-data# frozen_string_literal: true require '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, :host attr_accessor :timeout def initialize(port = nil, timeout = nil, custom_host = nil) @timeout = timeout @server = start_server(port, custom_host) @receive_mutex = Mutex.new end def start_server(port, custom_host) time = Time.now begin TCPServer.open(custom_host || HOST, port || 0).tap do |server| @port = server.addr[1] @host = server.addr[2] 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.18.1/lib/capybara/poltergeist/inspector.rb0000644000004100000410000000242613347774706023445 0ustar www-datawww-data# frozen_string_literal: true module 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