Skip to content

Commit

Permalink
Port more protractor code
Browse files Browse the repository at this point in the history
  • Loading branch information
bootstraponline committed May 21, 2015
1 parent b5581ec commit 2230374
Show file tree
Hide file tree
Showing 7 changed files with 349 additions and 87 deletions.
240 changes: 176 additions & 64 deletions lib/angular_webdriver/protractor/protractor.rb
Original file line number Diff line number Diff line change
@@ -1,32 +1,135 @@
require 'rubygems'
require 'selenium-webdriver'
require 'selenium/webdriver/common/error'
require_relative 'webdriver_patch'

require_relative 'webdriver_patch'
require_relative 'client_side_scripts'

class Protractor
# code/comments from protractor/lib/protractor.js
attr_accessor :root_element, :ignore_sync

attr_reader :client_side_scripts, :driver
attr_reader :client_side_scripts, :driver, :reset_url, :base_url

# The css selector for an element on which to find Angular. This is usually
# 'body' but if your ng-app is on a subsection of the page it may be
# a subelement.
#
# @return [String]
def self.root_element
@@root_element
end

# The css selector for an element on which to find Angular. This is usually
# 'body' but if your ng-app is on a subsection of the page it may be
# a subelement.
#
# @return [String]
def root_element
@@root_element
end

# @see webdriver.WebDriver.get
#
# Navigate to the given destination. Assumes that the page being loaded uses Angular.
# If you need to access a page which does not have Angular on load, set
# ignore_sync to true before invoking get
#
# @example
# browser.get('https://angularjs.org/');
# expect(browser.getCurrentUrl()).toBe('https://angularjs.org/');
#
# @param destination [String] The destination URL to load, can be relative if base_url is set
# @param opt_timeout [Integer] Number of seconds to wait for Angular to start. Default 30.
def get destination, opt_timeout
timeout = opt_timeout ? opt_timeout : 30

destination = base_url.start_with?('file://') ?
base_url + destination : URI.join(base_url, destination)

msg = lambda { |str| 'Protractor.get(' + destination + ') - ' + str }

return driver.get(destination) if ignore_sync

driver.get(reset_url)
executeScript_(
'window.location.replace("' + destination + '");',
msg.call('reset url'))

wait(timeout) do
url = executeScript_('return window.location.href;', msg('get url'))
raise 'still on reset url' unless url != reset_url
end

# now that the url has changed, make sure Angular has loaded
# note that the mock module logic is omitted.
waitForAngular
end

# @see webdriver.WebDriver.refresh
#
# Makes a full reload of the current page and loads mock modules before
# Angular. Assumes that the page being loaded uses Angular.
# If you need to access a page which does not have Angular on load, use
# the wrapped webdriver directly.
#
# @param opt_timeout [Integer] Number of seconds to wait for Angular to start.
def refresh opt_timeout
timeout = opt_timeout || 10

return driver.navigate.refresh if ignore_sync

executeScript_('return window.location.href;',
'Protractor.refresh() - getUrl')

get(href, timeout)
end

# Browse to another page using in-page navigation.
#
# @example
# browser.get('http://angular.github.io/protractor/#/tutorial');
# browser.setLocation('api');
# expect(browser.getCurrentUrl())
# .toBe('http://angular.github.io/protractor/#/api');
#
# @param url [String] In page URL using the same syntax as $location.url()
#
def setLocation url
waitForAngular

begin
executeScript_(client_side_scripts.set_location,
'Protractor.setLocation()', [root_element, url])
rescue Exception => e
raise e.class, "Error while navigating to '#{url}' : #{e}"
end
end

# Returns the current absolute url from AngularJS.
#
# @example
# browser.get('http://angular.github.io/protractor/#/api');
# expect(browser.getLocationAbsUrl())
# .toBe('http://angular.github.io/protractor/#/api');
#
def getLocationAbsUrl
waitForAngular
executeScript_(client_side_scripts.get_location_abs_url,
'Protractor.getLocationAbsUrl()', root_element)
end

# Creates a new protractor instance and dynamically patches the provided
# driver.
#
# @param [Hash] opts the options to initialize with
# @option opts [String] :root_element the root element on which to find Angular
# @option opts [Boolean] :ignore_sync if true, Protractor won't auto sync the page
def initialize opts={}
@driver = opts[:driver]
raise 'Must supply Selenium::WebDriver' unless @driver

watir = defined?(Watir::Browser) && @driver.is_a?(Watir::Browser)
watir = defined?(Watir::Browser) && @driver.is_a?(Watir::Browser)
@driver = watir ? @driver.driver : @driver

@driver.protractor = self
Expand All @@ -36,7 +139,7 @@ def initialize opts={}
# a subelement.
#
# @return [String]
@@root_element = opts.fetch :root_element, 'body'
@@root_element = opts.fetch :root_element, 'body'

# If true, Protractor will not attempt to synchronize with the page before
# performing actions. This can be harmful because Protractor will not wait
Expand All @@ -45,9 +148,35 @@ def initialize opts={}
# when a page continuously polls an API using $timeout.
#
# @return [Boolean]
@ignore_sync = !!opts.fetch(:ignore_sync, false)
@ignore_sync = !!opts.fetch(:ignore_sync, false)

@client_side_scripts = ClientSideScripts

browser_name = driver.capabilities[:browser_name].strip
if ['internet explorer', 'safari'].include?(browser_name)
@reset_url = 'about:blank'.freeze
else
@reset_url = 'data:text/html,<html></html>'.freeze
end

@base_url = URI.parse opts.fetch(:base_url, '')
end

# Syncs the webdriver command if it's whitelisted
#
# @param webdriver_command [Symbol] the webdriver command to check for syncing
def sync webdriver_command
return unless webdriver_command
webdriver_command = webdriver_command.intern
sync_whitelist = [
:getCurrentUrl, :get, :refresh, :getPageSource,
:getTitle, :findElement, :findElements,
:findChildElement, :findChildElements,
:setLocation
]
must_sync = sync_whitelist.include? webdriver_command

self.waitForAngular if must_sync
end

# Instruct webdriver to wait until Angular has finished rendering and has
Expand All @@ -65,75 +194,58 @@ def waitForAngular opt_description='' # Protractor.prototype.waitForAngular
# the client side script will return a string on error
# the string won't be raised as an error unless we explicitly do so here
error = executeAsyncScript_(client_side_scripts.wait_for_angular,
"Protractor.waitForAngular() #{opt_description}",
root_element)
raise Selenium::WebDriver::Error::JavascriptError, error if error
"Protractor.waitForAngular() #{opt_description}",
root_element)
raise Selenium::WebDriver::Error::JavascriptError, error if error
rescue Exception => e
# https://github.com/angular/protractor/blob/master/docs/faq.md
raise e.class, "Error while waiting for Protractor to sync with the page: #{e}"
end
end

def executeAsyncScript_ script, description, args
# ensure description is exactly one line that ends in a newline
# must use /* */ not // due to some browsers having problems
# with // comments when used with execute script
# Ensure description is exactly one line that ends in a newline
# must use /* */ not // due to some browsers having problems
# with // comments when used with execute script
def _js_comment description
description = description ? '/* ' + description.gsub(/\W+/, ' ') + ' */' : ''
description = description.strip + "\n"
description.strip + "\n"
end

# The same as {@code webdriver.WebDriver.prototype.executeAsyncScript},
# but with a customized description for debugging.
#
# @private
# @param script [String] The javascript to execute.
# @param description [String] A description of the command for debugging.
# @param args [var_args] The arguments to pass to the script.
# @return The scripts return value.
def executeAsyncScript_ script, description, *args
# add description as comment to script so it shows up in server logs
script = description + script

# puts "Evaluating:\n#{script}"
script = _js_comment(description) + script

driver.execute_async_script script, args
end

=begin
/**
* Instruct webdriver to wait until Angular has finished rendering and has
* no outstanding $http or $timeout calls before continuing.
* Note that Protractor automatically applies this command before every
* WebDriver action.
*
* @param {string=} opt_description An optional description to be added
* to webdriver logs.
* @return {!webdriver.promise.Promise} A promise that will resolve to the
* scripts return value.
*/
Protractor.prototype.waitForAngular = function(opt_description) {
var description = opt_description ? ' - ' + opt_description : '';
if (this.ignoreSynchronization) {
return webdriver.promise.fulfilled();
}
return this.executeAsyncScript_(
clientSideScripts.waitForAngular,
'Protractor.waitForAngular()' + description,
this.rootEl).
then(function(browserErr) {
if (browserErr) {
throw 'Error while waiting for Protractor to ' +
'sync with the page: ' + JSON.stringify(browserErr);
}
}).then(null, function(err) {
var timeout;
if (/asynchronous script timeout/.test(err.message)) {
// Timeout on Chrome
timeout = /-?[\d\.]*\ seconds/.exec(err.message);
} else if (/Timed out waiting for async script/.test(err.message)) {
// Timeout on Firefox
timeout = /-?[\d\.]*ms/.exec(err.message);
} else if (/Timed out waiting for an asynchronous script/.test(err.message)) {
// Timeout on Safari
timeout = /-?[\d\.]*\ ms/.exec(err.message);
}
if (timeout) {
throw 'Timed out waiting for Protractor to synchronize with ' +
'the page after ' + timeout + '. Please see ' +
'https://github.com/angular/protractor/blob/master/docs/faq.md';
} else {
throw err;
}
});
};
=end
# The same as {@code webdriver.WebDriver.prototype.executeScript},
# but with a customized description for debugging.
#
# @private
# @param script [String] The javascript to execute.
# @param description [String] A description of the command for debugging.
# @param args [var_args] The arguments to pass to the script.
# @return The scripts return value.
def executeScript_ script, description, *args
# add description as comment to script so it shows up in server logs
script = _js_comment(description) + script

driver.execute_script script, args
end

# Adds a task to the control flow to pause the test and inject helper functions
# into the browser, so that debugging may be done in the browser console.
#
# This should be used under Pry
def debugger
executeScript_ client_side_scripts.install_in_browser, 'Protractor.debugger()'
end
end
15 changes: 15 additions & 0 deletions lib/angular_webdriver/protractor/webdriver_patch.rb
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,21 @@ def find_elements_by(how, what, parent = nil)

ids.map { |id| Element.new self, element_id_from(id) }
end


#
# executes a command on the remote server.
#
#
# Returns the 'value' of the returned payload
#

def execute(*args)
protractor.sync(args.first) unless protractor.ignore_sync

raw_execute(*args)['value']
end

end # class Bridge
end # module Remote
end # module WebDriver
Expand Down
19 changes: 19 additions & 0 deletions notes/protractor_cli_bugs.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
CLI randomly flakes out. press enter a bunch of times.


(node) warning: possible EventEmitter memory leak detected. 11 break listeners added. Use emitter.setMaxListeners() to increase limit.
Trace
at Client.addListener (events.js:179:15)
at Client.Readable.on (_stream_readable.js:671:33)
at CommandRepl.evaluate_ (/usr/local/lib/node_modules/protractor/lib/debugger/modes/commandRepl.js:106:15)
at CommandRepl.stepEval (/usr/local/lib/node_modules/protractor/lib/debugger/modes/commandRepl.js:34:8)
at REPLServer.stepEval (/usr/local/lib/node_modules/protractor/lib/debugger/clients/explorer.js:95:13)
at bound (domain.js:254:14)
at REPLServer.runBound [as eval] (domain.js:267:12)
at REPLServer.<anonymous> (repl.js:279:12)
at REPLServer.emit (events.js:107:17)
at REPLServer.Interface._onLine (readline.js:214:10)



doesn't sync on goForward or goBackward ?
Loading

0 comments on commit 2230374

Please sign in to comment.