Class: Protractor

Inherits:
Object
  • Object
show all
Defined in:
lib/angular_webdriver/protractor/protractor.rb

Constant Summary collapse

NEW_FINDERS_KEYS =
%i(
  binding
  exactBinding
  partialButtonText
  buttonText
  model
  options
  cssContainingText
  repeater
).freeze
NEW_FINDERS_HASH =
:binding
NEW_FINDERS_KEYS.map { |e| [e, e.to_s] }.to_h.freeze
ABOUT_BLANK =

Reset URL used on IE & Safari since they don’t work well with data URLs

'about:blank'.freeze
DEFAULT_RESET_URL =

Reset URL used by non-IE/Safari browsers

'data:text/html,<html></html>'.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(opts = {}) ⇒ Protractor

Creates a new protractor instance and dynamically patches the provided driver.

Parameters:

  • opts (Hash) (defaults to: {})

    the options to initialize with

Options Hash (opts):

  • :watir (Watir::Browser)

    the watir instance used for automation

  • :root_element (String)

    the root element on which to find Angular

  • :ignore_sync (Boolean)

    if true, Protractor won’t auto sync the page



207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
# File 'lib/angular_webdriver/protractor/protractor.rb', line 207

def initialize opts={}
  @watir = opts[:watir]

  valid_watir = defined?(Watir::Browser) && @watir.is_a?(Watir::Browser)
  raise "Driver must be a Watir::Browser not #{@driver.class}" unless valid_watir
  @driver = @watir.driver

  unless Selenium::WebDriver::SearchContext::FINDERS.keys.include?(NEW_FINDERS_KEYS)
    Selenium::WebDriver::SearchContext::FINDERS.merge!(NEW_FINDERS_HASH)
  end

  unless Watir::ElementLocator::WD_FINDERS.include? NEW_FINDERS_KEYS
    old = Watir::ElementLocator::WD_FINDERS
    # avoid const redefinition warning
    Watir::ElementLocator.send :remove_const, :WD_FINDERS
    Watir::ElementLocator.send :const_set, :WD_FINDERS, old + NEW_FINDERS_KEYS
  end

  @driver.protractor = self

  # 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]
  #
  @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
  # until $timeouts and $http calls have been processed, which can cause
  # tests to become flaky. This should be used only when necessary, such as
  # when a page continuously polls an API using $timeout.
  #
  # @return [Boolean]
  #
  @ignore_sync       = !!opts.fetch(:ignore_sync, false)

  @client_side_scripts = ClientSideScripts

  browser_name = driver.capabilities[:browser_name].to_s.strip
  @reset_url   = reset_url_for_browser browser_name

  @base_url          = opts.fetch(:base_url, nil)

  # must be local var for use with define element below.
  protractor_element = AngularWebdriver::ProtractorElement.new @watir

  # Top level element method to enable protractor syntax.
  # redefine element to point to the new protractor element instance.
  #
  # toplevel self enables by/element from within pry. rspec helpers enables
  # by/element within rspec tests when used with install_rspec_helpers.
  [eval('self', TOPLEVEL_BINDING), AngularWebdriver::RSpecHelpers].each do |obj|
    obj.send :define_singleton_method, :element do |*args|
      protractor_element.element *args
    end

    obj.send :define_singleton_method, :by do
      AngularWebdriver::By
    end
  end

  self
end

Instance Attribute Details

#base_urlString

File.join(base_url, destination) when using driver.get and protractor.get (if sync is on, base_url is set, and destination is not absolute).

Returns:

  • (String)

    Default nil.



52
53
54
# File 'lib/angular_webdriver/protractor/protractor.rb', line 52

def base_url
  @base_url
end

#client_side_scriptsObject (readonly)

All scripts to be run on the client via executeAsyncScript or

executeScript should be put here.

NOTE: These scripts are transmitted over the wire as JavaScript text
constructed using their toString representation, and# cannot*
reference external variables.

Some implementations seem to have issues with // comments, so use star-style
inside scripts. that caused the switch to avoid the // comments.)


64
65
66
# File 'lib/angular_webdriver/protractor/protractor.rb', line 64

def client_side_scripts
  @client_side_scripts
end

#driverObject (readonly)

The Selenium::WebDriver driver object



67
68
69
# File 'lib/angular_webdriver/protractor/protractor.rb', line 67

def driver
  @driver
end

#ignore_syncBoolean

If true, Protractor will not attempt to synchronize with the page before performing actions. This can be harmful because Protractor will not wait until $timeouts and $http calls have been processed, which can cause tests to become flaky. This should be used only when necessary, such as when a page continuously polls an API using $timeout.

Returns:

  • (Boolean)


44
45
46
# File 'lib/angular_webdriver/protractor/protractor.rb', line 44

def ignore_sync
  @ignore_sync
end

#reset_urlObject (readonly)

URL to a blank page. Differs depending on the browser.



72
73
74
# File 'lib/angular_webdriver/protractor/protractor.rb', line 72

def reset_url
  @reset_url
end

#root_elementString

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.

Returns:

  • (String)


34
35
36
# File 'lib/angular_webdriver/protractor/protractor.rb', line 34

def root_element
  @root_element
end

Instance Method Details

#_js_comment(description) ⇒ Object

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



364
365
366
367
# File 'lib/angular_webdriver/protractor/protractor.rb', line 364

def _js_comment description
  description = description ? '/* ' + description.gsub(/\s+/, ' ').strip + ' */' : ''
  description.strip + "\n"
end

#allowAnimations(web_element, value = nil) ⇒ Boolean

Determine if animation is allowed on the current underlying elements. // Turns off ng-animate animations for all elements in the <body> element(by.css(‘body’)).allowAnimations(false);

Parameters:

  • web_element (Element)
    • the web element to act upon

  • value (Boolean) (defaults to: nil)
    • turn on/off ng-animate animations.

Returns:

  • (Boolean)

    whether animation is allowed.



429
430
431
# File 'lib/angular_webdriver/protractor/protractor.rb', line 429

def allowAnimations web_element, value=nil
  executeScript_ client_side_scripts.allow_animations, 'Protractor.allow_animations()', web_element, value
end

#debuggerObject

Injects client side scripts into window.clientSideScripts for debugging.

Example:

“‘ruby # inject the scripts protractor.debugger

# now that the scripts are injected, they can be used via execute_script driver.execute_script “window.clientSideScripts.getLocationAbsUrl(‘body’)” “‘

This should be used under Pry. The window client side scripts can be invoked using chrome dev tools after calling debugger.



416
417
418
# File 'lib/angular_webdriver/protractor/protractor.rb', line 416

def debugger
  executeScript_ client_side_scripts.install_in_browser, 'Protractor.debugger()'
end

#driver_get(url) ⇒ Object

Invokes the underlying driver.get. Does not wait for angular. Does not use base_url or reset_url logic.



140
141
142
# File 'lib/angular_webdriver/protractor/protractor.rb', line 140

def driver_get url
  driver.bridge.driver_get url
end

#evaluate(element, expression) ⇒ Object

Evaluate an Angular expression as if it were on the scope of the given element.

Parameters:

  • element (Element)

    The element in whose scope to evaluate.

  • expression (String)

    The expression to evaluate.

Returns:

  • (Object)

    The result of the evaluation.



440
441
442
443
# File 'lib/angular_webdriver/protractor/protractor.rb', line 440

def evaluate element, expression
  # angular.element(element).scope().$eval(expression);
  executeScript_ client_side_scripts.evaluate, 'Protractor.evaluate()', element, expression
end

#executeAsyncScript_(script, description, *args) ⇒ Object

The same as 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.


378
379
380
381
382
383
# File 'lib/angular_webdriver/protractor/protractor.rb', line 378

def executeAsyncScript_ script, description, *args
  # add description as comment to script so it shows up in server logs
  script = _js_comment(description) + script

  driver.execute_async_script script, *args
end

#executeScript_(script, description, *args) ⇒ Object

The same as 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.


394
395
396
397
398
399
# File 'lib/angular_webdriver/protractor/protractor.rb', line 394

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

#finder?(finder_name) ⇒ boolean

Return true if given finder is a protractor finder.

Parameters:

  • finder_name (Symbol|String)

    the name of the finder

Returns:

  • (boolean)


22
23
24
# File 'lib/angular_webdriver/protractor/protractor.rb', line 22

def finder? finder_name
  NEW_FINDERS_KEYS.include? finder_name.intern
end

#get(destination, opt_timeout = driver.max_page_wait_seconds) ⇒ Object

@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,
use driver_get.

@example
browser.get('https://angularjs.org/');
expect(browser.getCurrentUrl()).toBe('https://angularjs.org/');

Parameters:

  • destination (String)

    The destination URL to load, can be relative if base_url is set

  • opt_timeout (Integer) (defaults to: driver.max_page_wait_seconds)

    Number of seconds to wait for Angular to start. Default 30



88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
# File 'lib/angular_webdriver/protractor/protractor.rb', line 88

def get destination, opt_timeout=driver.max_page_wait_seconds
  # do not use driver.get because that redirects to this method
  # instead driver_get is provided.

  timeout = opt_timeout

  raise "Invalid timeout #{timeout}" unless timeout.is_a?(Numeric)

  unless destination.is_a?(String) || destination.is_a?(URI)
    raise "Invalid destination #{destination}"
  end

  # URI.join doesn't allow for http://localhost:8081/#/ as a base_url
  # so this departs from the Protractor behavior and favors File.join instead.
  #
  # In protractor: url.resolve('http://localhost:8081/#/', 'async')
  #                => http://localhost:8081/async
  # In Ruby:       File.join('http://localhost:8081/#/', 'async')
  #                => http://localhost:8081/#/async
  #
  base_url_exists = base_url && !base_url.empty?
  no_scheme = !URI.parse(destination).scheme rescue true

  if base_url_exists && no_scheme
    destination = File.join(base_url, destination.to_s)
  end

  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.call('get url'))
    not_on_reset_url     = url != reset_url
    destination_is_reset = destination == reset_url
    raise 'still on reset url' unless not_on_reset_url || destination_is_reset
  end

  # now that the url has changed, make sure Angular has loaded
  # note that the mock module logic is omitted.
  #
  waitForAngular description: 'Protractor.get', timeout: timeout
end

#getLocationAbsUrlObject

Returns the current absolute url from AngularJS.

Waits for Angular.

@example
browser.get('http://angular.github.io/protractor/#/api');
expect(browser.getLocationAbsUrl())
    .toBe('/api');


193
194
195
196
197
# File 'lib/angular_webdriver/protractor/protractor.rb', line 193

def getLocationAbsUrl
  waitForAngular
  executeScript_(client_side_scripts.get_location_abs_url,
                 'Protractor.getLocationAbsUrl()', root_element)
end

#refresh(opt_timeout) ⇒ Object

@see webdriver.WebDriver.refresh

Makes a full reload of the current page. Assumes that the page being loaded uses Angular.
If you need to access a page which does not have Angular on load, use
driver_get.

@param opt_timeout [Integer] Number of seconds to wait for Angular to start.


152
153
154
155
156
157
158
159
160
161
# File 'lib/angular_webdriver/protractor/protractor.rb', line 152

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

#reset_url_for_browser(browser_name) ⇒ Object

IE and Safari require about:blank because they don’t work well with data urls (flaky). For other browsers, data urls are stable.

browser_name [String] the browser name from driver caps. Must be ‘safari’

or 'internet explorer'


286
287
288
289
290
291
292
# File 'lib/angular_webdriver/protractor/protractor.rb', line 286

def reset_url_for_browser browser_name
  if ['internet explorer', 'safari'].include?(browser_name)
    ABOUT_BLANK
  else
    DEFAULT_RESET_URL
  end
end

#setLocation(url) ⇒ Object

Browse to another page using in-page navigation.

Assumes that the page being loaded uses Angular.

@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()


174
175
176
177
178
179
180
181
182
183
# File 'lib/angular_webdriver/protractor/protractor.rb', line 174

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

#sync(webdriver_command) ⇒ Object

Syncs the webdriver command if it’s white listed

Parameters:

  • webdriver_command (Symbol)

    the webdriver command to check for syncing



300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
# File 'lib/angular_webdriver/protractor/protractor.rb', line 300

def sync webdriver_command
  return unless webdriver_command
  webdriver_command = webdriver_command.intern
  # Note get must not sync here because the get command is redirected to
  # protractor.get which already has the sync logic built in.
  #
  # also don't sync set location (protractor custom command already waits
  # for angular). the selenium set location is for latitude/longitude/altitude
  # and that doesn't require syncing
  #
  sync_whitelist    = %i(
    getCurrentUrl
    refresh
    getPageSource
    getTitle
    findElement
    findElements
    findChildElement
    findChildElements
  )
  must_sync         = sync_whitelist.include? webdriver_command

  waitForAngular if must_sync
end

#waitForAngular(opts = {}) ⇒ WebDriver::Element, ...

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.

Will wait up to driver.max_wait_seconds (set with driver.set_max_wait)

Parameters:

  • opts (Hash) (defaults to: {})

Options Hash (opts):

  • :description (String)

    An optional description to be added to webdriver logs.

  • :timeout (Integer)

    Amount of time in seconds to wait for angular to load. Default driver.max_wait_seconds

Returns:

  • (WebDriver::Element, Integer, Float, Boolean, NilClass, String, Array)


339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
# File 'lib/angular_webdriver/protractor/protractor.rb', line 339

def waitForAngular opts={} # Protractor.prototype.waitForAngular
  return if ignore_sync

  description = opts.fetch(:description, '')
  timeout     = opts.fetch(:timeout, driver.max_wait_seconds)

  wait(timeout: timeout, bubble: true) do
    begin
      # 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() #{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
end