Module: AngularWebdriver

Defined in:
lib/angular_webdriver/version.rb,
lib/angular_webdriver/protractor/by.rb,
lib/angular_webdriver/protractor/watir_patch.rb,
lib/angular_webdriver/protractor/rspec_helpers.rb,
lib/angular_webdriver/protractor/by_repeater_inner.rb,
lib/angular_webdriver/protractor/protractor_element.rb

Defined Under Namespace

Modules: RSpecHelpers Classes: By, ByRepeaterInner, ProtractorElement

Constant Summary collapse

VERSION =
'1.0.8'
DATE =
'2015-08-17'

Class Method Summary collapse

Class Method Details

.define_page_methods(opts = {}) ⇒ Object


28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/angular_webdriver/protractor/rspec_helpers.rb', line 28

def define_page_methods opts={}
  method        = opts.fetch(:method, :define_singleton_method)
  page_module   = opts[:page_module] || raise('must set page_module')
  target_class  = opts[:target_class] || raise('must set target_class')
  driver_object = opts[:watir] || opts[:driver] || raise('must set driver')
  page_module.constants.each do |page_class|
    qualified_class = page_module.const_get(page_class)

    # enable use of by/element/no_wait within block passed to pageobject element
    # element(:greet_button) { element(by.binding('greet'))   }
    AngularWebdriver.install_rspec_helpers qualified_class

    # ButtonsPage => buttons_page
    # https://github.com/rails/rails/blob/daaa21bc7d20f2e4ff451637423a25ff2d5e75c7/activesupport/lib/active_support/inflector/methods.rb#L96
    page_name = page_class.to_s.gsub(/([a-z\d])([A-Z])/, '\1_\2').downcase
    target_class.send(method, page_name) do
      instance_name = "@#{page_module}#{page_class}"
      # must always set because the driver may have changed sessions
      obj = qualified_class.new driver_object
      instance_variable_set instance_name, obj
    end
  end
end

.install_rspec_helpers(context = RSpec::Core::ExampleGroup) ⇒ Object

call after invoking Protractor.new. Installs by/element/no_wait


10
11
12
13
14
15
16
17
18
# File 'lib/angular_webdriver/protractor/rspec_helpers.rb', line 10

def install_rspec_helpers context = RSpec::Core::ExampleGroup
  helpers = AngularWebdriver::RSpecHelpers
  helpers.singleton_methods(false).each do |method_symbol|
    context.send(:define_method, method_symbol) do |*args, &block|
      args.length == 0 ? helpers.send(method_symbol, &block) :
        helpers.send(method_symbol, *args, &block)
    end
  end
end

.patch_watirObject

Patch watir must be invoked before watir-webdriver is required to ensure the methods are defined before inheritance occurs. Then patch_watir must be invoked after requiring watir-webdriver to restore the methods that were overridden.


14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
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
# File 'lib/angular_webdriver/protractor/watir_patch.rb', line 14

def self.patch_watir
  #
  # This patch serves a few purposes. The first is matching Protractor semantics
  # of lazy finding elements and always relocating elements (ex: element.text)
  #
  # The second is removing unnecessary bloatware from Watir which has a number
  # of checks that don't make sense for angular.js testing. The specifics
  # of this patch will change in the next Watir release. Currently version
  # 0.7.0 is targeted.
  #
  # The third is teaching Watir about angular specific locators
  #
  # Design goal: element.all(by.binding('slowHttpStatus'))
  #              should not make any server requests
  #

  ::Watir::Browser.class_eval do
    # avoid prepending 'http://' to non-urls because protractor handles
    # the prepending at the driver level.
    def goto(uri)
      @driver.navigate.to uri
      run_checkers

      url
    end
  end

  ::Watir::HTMLElementCollection.class_eval do
    # Return original selector.
    # Method added for protractor compatibility
    def locator
      @selector
    end
  end

  ::Watir::Container.module_eval do
    #
    # Alias of elements for Protractor
    #

    def all(*args)
      elements(*args)
    end

    # Redefine extract_selector to wrap find by repeater
    upstream_extract_selector = instance_method(:extract_selector)
    define_method(:extract_selector) do |selectors|
      selectors = AngularWebdriver::ByRepeaterInner.wrap_repeater selectors

      upstream_extract_selector.bind(self).call selectors
    end

  end # ::Watir::Container.module_eval

  #
  # Base class for HTML elements.
  #

  # Note the element class is different on master.

  ::Watir::Element.class_eval do
    # Always raise on stale element ref error. Prevents infinite retry loop.
    def element_call
      yield
    rescue Selenium::WebDriver::Error::StaleElementReferenceError
      raise
    end

    # Rescue all exceptions. Guarantee that we'll return true or false.
    #
    # Returns true if element exists and false otherwise.
    def exists?
      assert_exists
      true
    rescue Exception
      false
    end

    def selected?
      assert_exists
      element_call { @element.selected? }
    end

    # required for watir otherwise execute_script will fail
    #
    # e = browser.element(tag_name: 'div')
    # driver.execute_script 'return arguments[0].tagName', e
    # {"script":"return arguments[0].tagName","args":[{"ELEMENT":"0"}]}
    #
    # Convert to a WebElement JSON Object for transmission over the wire.
    # @see https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol#basic-terms-and-concepts
    #
    # @api private
    #
    def to_json(*args)
      assert_exists
      { ELEMENT: @element.ref }.to_json
    end

    # Ensure that the element exists by always relocating it
    # Required to trigger waitForAngular. Caching the element here will
    # break the Protractor sync feature so this must be @element = locate.
    def assert_exists
      @element = locate
    end

    def assert_not_stale
      nil
    end

    def assert_enabled
      nil
    end

    # Return original selector.
    # Method added for protractor compatibility
    def locator
      @selector
    end

    # avoid context lookup
    def locate
      locator_class.new(@parent.wd, @selector, self.class.attribute_list).locate
    end

    # Invoke protractor.allowAnimations with freshly located element and
    # optional value.
    def allowAnimations value=nil
      assert_exists
      driver.protractor.allowAnimations @element, value
    end

    # Watir doesn't define a clear method on element so we have to provide one.
    def clear
      assert_exists
      element_call { @element.clear }
    end

    # Evaluate an Angular expression as if it were on the scope
    # of the current element.
    #
    # @param expression <String> The expression to evaluate.
    #
    # @return <Object> The result of the evaluation.
    def evaluate expression
      assert_exists
      driver.protractor.evaluate @element, expression
    end

    #
    # Returns true if the element exists and is visible on the page.
    # Returns false if the element doesn't exist or isn't visible.
    #
    # @return [Boolean]
    # @see Watir::Wait
    #
    #
    # rescue element not found
    def present?
      exists? && visible?
    rescue Exception
      # if the element disappears between the exists? and visible? calls,
      # consider it not present.
      false
    end
  end # ::Watir::Element.class_eval

  #
  # The main class through which you control the browser.
  #

  ::Watir::Browser.class_eval do
    def assert_exists
      # remove expensive window check
      raise Exception::Error, 'browser was closed' if @closed
    end

    def inspect
      nil # avoid expensive browser url and title lookup
    end
  end # ::Watir::Browser.class_eval

  ::Watir::ElementLocator.class_eval do
    def validate_element(element)
      tn = @selector[:tag_name]
      return element unless tn # don't validate nil tag names
      element_tag_name = element.tag_name.downcase

      return if tn && !tag_name_matches?(element_tag_name, tn)

      if element_tag_name == 'input'
        return if @selector[:type] && @selector[:type] != element.attribute(:type)
      end

      element
    end

    # always raise element not found / stale reference error
    def locate
      # element.all(by.partialButtonText('text')).to_a[0].value creates the
      # selector {:element=>#<Selenium::WebDriver::Element ...>}
      # in that case we've already located the element.
      #
      # see 'should find multiple buttons containing "text"' in locators_spec.rb
      return @selector[:element] if @selector.is_a?(Hash) && @selector[:element].is_a?(Selenium::WebDriver::Element)

      e = by_id and return e # short-circuit if :id is given

      if @selector.size == 1
        element = find_first_by_one
      else
        element = find_first_by_multiple
      end

      # This actually only applies when finding by xpath/css - browser.text_field(:xpath, "//input[@type='radio']")
      # We don't need to validate the element if we built the xpath ourselves.
      # It is also used to alter behavior of methods locating more than one type of element
      # (e.g. text_field locates both input and textarea)
      validate_element(element) if element
    end
  end # ::Watir::ElementLocator.class_eval
end

.require_all_pages(glob_path = nil) ⇒ Object


20
21
22
23
24
25
26
# File 'lib/angular_webdriver/protractor/rspec_helpers.rb', line 20

def require_all_pages glob_path=nil
  base_dir = Rake.application.original_dir if defined?(Rake)
  base_dir ||= @original_dir ||= Dir.pwd

  glob_path ||= File.join(base_dir, 'page', '**', '*.rb')
  Dir.glob(glob_path) { |file| require_relative file }
end