Class: Element

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

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(name, by, locator, opts = {}) ⇒ Element

Returns a new instance of Element.



10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# File 'lib/element.rb', line 10

def initialize(name, by, locator, opts = {})
  @name = name
  @by = by
  @locator = locator
  @timeout = opts[:timeout] || Gridium.config.element_timeout #don't set this to zero
  @element_screenshot = nil #used to store the path of element screenshots for comparison

  # wrapped driver
  @driver = Driver.driver

  # selenium web element
  @element = nil

  # should always be driver unless getting an element's child
  @parent ||= (opts[:parent] || @driver)

  #how long to wait between clearing an input and sending keys to it
  @text_padding_time = 0.15
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(method_sym, *arguments, &block) ⇒ Object



409
410
411
412
413
414
415
416
# File 'lib/element.rb', line 409

def method_missing(method_sym, *arguments, &block)
  Log.debug("[GRIDIUM::Element] called #{method_sym} on element #{@locator} by #{@by_type}")
  if @element.respond_to?(method_sym)
    @element.method(method_sym).call(*arguments, &block)
  else
    super
  end
end

Instance Attribute Details

#byObject (readonly)

Returns the value of attribute by.



6
7
8
# File 'lib/element.rb', line 6

def by
  @by
end

#locatorObject (readonly)

Returns the value of attribute locator.



6
7
8
# File 'lib/element.rb', line 6

def locator
  @locator
end

#nameObject (readonly)

Returns the value of attribute name.



6
7
8
# File 'lib/element.rb', line 6

def name
  @name
end

Instance Method Details

#append_keys(*args) ⇒ Object

add to what’s already in the text field for cases when you don’t want to stomp what’s already in the text field



184
185
186
187
188
189
190
191
192
193
# File 'lib/element.rb', line 184

def append_keys(*args)
  ElementExtensions.highlight(self) if Gridium.config.highlight_verifications
  $verification_passes += 1
  unless element.enabled?
    raise "Browser Error: tried to enter #{args} but the input is disabled"
  end
  element.send_keys(*args)
  sleep @text_padding_time
  # when it's possible to validate for more than non-empty outcomes, do that here
end

#attribute(name) ⇒ Object



93
94
95
# File 'lib/element.rb', line 93

def attribute(name)
  element.attribute(name)
end

#clearObject



142
143
144
145
# File 'lib/element.rb', line 142

def clear
  element.clear
  sleep @text_padding_time
end

#clickObject



155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
# File 'lib/element.rb', line 155

def click
  Log.debug("[GRIDIUM::Element] Clicking on #{self}")
  click_retry = 2
  if element.enabled?
    ElementExtensions.highlight(self) if Gridium.config.highlight_verifications
    $verification_passes += 1
    begin
      element.click
    rescue StandardError => e
      Log.warn("[GRIDIUM::Element] Click Exception retrying...")
      sleep 1
      click_retry -= 1
      if click_retry >= 0
        retry
      else
        Log.error("[GRIDIUM::Element] Click Exception #{e}")
        fail
      end
    end
  else
    Log.error('[GRIDIUM::Element] Cannot click on element.  Element is not present.')
  end
end

#compare_element_screenshot(base_image_path) ⇒ Object



370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
# File 'lib/element.rb', line 370

def compare_element_screenshot(base_image_path)
  #Returns TRUE if there are no differences, FALSE if there are
  begin
    Log.debug("[GRIDIUM::Element] Loading Images for Comparison...")
    images = [
        ChunkyPNG::Image.from_file(base_image_path),
        ChunkyPNG::Image.from_file(@element_screenshot)
    ]
    #used to store image x,y diff
    diff = []
    Log.debug("[GRIDIUM::Element] Comparing Images...")
    images.first.height.times do |y|
      images.first.row(y).each_with_index do |pixel, x|
        diff << [x,y] unless pixel == images.last[x,y]
      end
    end

    Log.debug("[GRIDIUM::Element] Pixels total:    #{images.first.pixels.length}")
    Log.debug("[GRIDIUM::Element] Pixels changed:  #{diff.length}")
    Log.debug("[GRIDIUM::Element] Pixels changed:  #{(diff.length.to_f / images.first.pixels.length) * 100}%")

    x, y = diff.map{|xy| xy[0]}, diff.map{|xy| xy[1]}

    if x.any? && y.any?
      Log.debug("[GRIDIUM::Element] Differences Detected! Writing Diff Image...")
      name = self.name.gsub(' ', '_')
      #timestamp = Time.now.strftime("%Y_%m_%d__%H_%M_%S")
      element_screenshot_path = File.join($current_run_dir, "#{name}__diff_.png")
      images.last.rect(x.min, y.min, x.max, y.max, ChunkyPNG::Color(0,255,0))
      images.last.save(element_screenshot_path)
      return false
    else
      return true
    end
  rescue StandardError => e
    Log.error("There was a problem comparing element images. #{e.message}")
  end
end

#css_value(name) ⇒ Object



116
117
118
# File 'lib/element.rb', line 116

def css_value(name)
  element.css_value(name)
end

#disabled?Boolean

Returns:

  • (Boolean)


138
139
140
# File 'lib/element.rb', line 138

def disabled?
  !enabled?
end

#displayed?(opts = {}) ⇒ Boolean

Returns:

  • (Boolean)


127
128
129
130
131
132
# File 'lib/element.rb', line 127

def displayed?(opts = {})
  return element(opts).displayed?
rescue StandardError => error
  Log.debug("[GRIDIUM::Element] element.displayed? is false because this error was rescued: #{error}")
  return false
end

#displayed_elementObject



52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/element.rb', line 52

def displayed_element
  found_element = nil
  #Found an issue where the element would go stale after it's found
  begin
    elements = @parent.find_elements(@by, @locator)
    elements.each do |element|
      if element.displayed? #removed check for element.enabled
        found_element = element; #this will always return the last displayed element
      end
    end
    if found_element.nil?
      Log.debug "[GRIDIUM::Element] found #{elements.length} element(s) via #{@by} and #{@locator} and 0 are displayed"
    end
  rescue StandardError => error
    Log.debug("[GRIDIUM::Element] element.displayed_element rescued: #{error}")
    if found_element
      Log.warn("[GRIDIUM::Element] An element was found, but it was not displayed on the page. Gridium.config.visible_elements_only set to: #{Gridium.config.visible_elements_only} Element: #{self}")
    else
      Log.warn("[GRIDIUM::Element] Could not find Element: #{self}")
    end
  end

  found_element
end

#drag_to(target) ⇒ Object

HTML5 Drag n drop

Parameters:

  • target (Element)

    element to drag to

Raises:



262
263
264
265
266
267
268
# File 'lib/element.rb', line 262

def drag_to(target)
  raise Gridium::InvalidTypeError, "source element selector must be ':css'" unless self.by == :css
  raise Gridium::InvalidTypeError, "target element selector must be ':css'" unless target.by == :css

  Log.debug("[GRIDIUM::Element] Dragging (#{self}) to (#{target})")
  ElementExtensions.drag_to(self, target)
end

#element(opts = {}) ⇒ Object



34
35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/element.rb', line 34

def element(opts = {})
  timeout = opts[:timeout] || @timeout
  if stale?
    wait = Selenium::WebDriver::Wait.new :timeout => timeout, :interval => 1
    if Gridium.config.visible_elements_only
      human_readable_error { wait.until { @element = displayed_element } }
    else
      human_readable_error { wait.until { @element = @parent.find_element(@by, @locator); Log.debug("[GRIDIUM::Element] Finding element #{self}..."); @element.enabled? } }
    end

  end
  @element
end

#element=(e) ⇒ Object



48
49
50
# File 'lib/element.rb', line 48

def element=(e)
  @element = e
end

#enabled?Boolean

Returns:

  • (Boolean)


134
135
136
# File 'lib/element.rb', line 134

def enabled?
  element.enabled?
end

#find_element(by, locator) ⇒ Element

Search for an element within this element

Parameters:

  • by (Symbol)

    (:css or :xpath)

  • locator (String)

Returns:



328
329
330
331
# File 'lib/element.rb', line 328

def find_element(by, locator)
  Log.debug('[GRIDIUM::Element] Finding element...')
  Element.new("Child of #{@name}", by, locator, parent: @element)
end

#find_elements(by, locator) ⇒ Array

Search for an elements within this element

Parameters:

  • by (Symbol)

    (:css or :xpath)

  • locator (String)

Returns:

  • (Array)

    elements



341
342
343
344
# File 'lib/element.rb', line 341

def find_elements(by, locator)
  elements = element.find_elements(by, locator)
  elements.map {|_| Element.new("Child of #{@name}", by, locator, parent: @element)}
end

#hover_awayObject



239
240
241
242
243
244
245
246
247
# File 'lib/element.rb', line 239

def hover_away
  Log.debug("[GRIDIUM::Element] Hovering away from element (#{self})...")
  if element.enabled?
    $verification_passes += 1
    ElementExtensions.hover_away(self) # Javascript workaround to above issue
  else
    Log.error('[GRIDIUM::Element] Cannot hover away from element.  Element is not present.')
  end
end

#hover_overObject



226
227
228
229
230
231
232
233
234
235
236
237
# File 'lib/element.rb', line 226

def hover_over
  Log.debug("[GRIDIUM::Element] Hovering over element (#{self})...")
  # @driver.mouse.move_to(element)            # Note: Doesn't work with Selenium 2.42 bindings for Firefox v31
  # @driver.action.move_to(element).perform
  # @driver.mouse_over(@locator)
  if element.enabled?
    $verification_passes += 1
    ElementExtensions.hover_over(self) # Javascript workaround to above issue
  else
    Log.error('[GRIDIUM::Element] Cannot hover over element.  Element is not present.')
  end
end

#innerHTMLObject



151
152
153
# File 'lib/element.rb', line 151

def innerHTML
  attribute "innerHTML"
end

#jquery_clickObject



289
290
291
292
293
294
295
296
297
# File 'lib/element.rb', line 289

def jquery_click
  Log.debug("[GRIDIUM::Element] JQuery clickin (#{self})...")
  if element.enabled?
    $verification_passes += 1
    ElementExtensions.jquery_click(self)
  else
    Log.error('[GRIDIUM::Element] Cannot jquery_click.  Element is not present.')
  end
end

#locationObject



218
219
220
# File 'lib/element.rb', line 218

def location
  element.location
end

#location_once_scrolled_into_viewObject



222
223
224
# File 'lib/element.rb', line 222

def location_once_scrolled_into_view
  element.location_once_scrolled_into_view
end

#mouse_over(x: 1, y: 1) ⇒ Object

Raw webdriver mouse over



250
251
252
253
254
255
256
257
258
# File 'lib/element.rb', line 250

def mouse_over(x: 1, y: 1)
  Log.debug("[GRIDIUM::Element] Triggering mouse over for (#{self})...")
  if element.enabled?
    $verification_passes += 1
    ElementExtensions.mouse_over(self, x: x, y: y)
  else
    Log.error('[GRIDIUM::Element] Cannot mouse over.  Element is not present.')
  end
end

#present?Boolean

Returns:

  • (Boolean)


120
121
122
123
124
125
# File 'lib/element.rb', line 120

def present?
  return element.enabled?
rescue StandardError => error
  Log.debug("[GRIDIUM::Element] element.present? is false because this error was rescued: #{error}")
  return false
end

#save_element_screenshotObject



346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
# File 'lib/element.rb', line 346

def save_element_screenshot
  Log.debug ("[GRIDIUM::Element] Capturing screenshot of element...")
  self.scroll_into_view

  timestamp = Time.now.strftime("%Y_%m_%d__%H_%M_%S")
  name = self.name.gsub(' ', '_')
  screenshot_path = File.join($current_run_dir, "#{name}__#{timestamp}.png")
  @driver.save_screenshot(screenshot_path)

  location_x = self.location.x
  location_y = self.location.y
  element_width = self.size.width
  element_height = self.size.height

  # ChunkyPNG commands tap into oily_png (performance-enhanced version of chunky_png)
  image = ChunkyPNG::Image.from_file(screenshot_path)
  image1 = image.crop(location_x, location_y, element_width, element_height)
  image2 = image1.to_image
  element_screenshot_path = File.join($current_run_dir, "#{name}__#{timestamp}.png")
  image2.save(element_screenshot_path)
  @element_screenshot = element_screenshot_path
  SpecData.screenshots_captured.push("#{name}__#{timestamp}.png")
end

#scroll_into_viewObject



270
271
272
273
274
275
276
277
# File 'lib/element.rb', line 270

def scroll_into_view
  if element.enabled?
    $verification_passes += 1
    ElementExtensions.scroll_to(self)
  else
    Log.error('[GRIDIUM::Element] Cannot scroll element into view.  Element is not present.')
  end
end

#selected?Boolean

Returns:

  • (Boolean)


303
304
305
# File 'lib/element.rb', line 303

def selected?
  element.selected?
end

#send_keys(*args) ⇒ Object Also known as: text=

overwrite to what’s already in the text field and validate afterward



200
201
202
203
204
205
206
207
208
209
210
211
212
# File 'lib/element.rb', line 200

def send_keys(*args)
  ElementExtensions.highlight(self) if Gridium.config.highlight_verifications
  $verification_passes += 1
  unless element.enabled?
    raise "Browser Error: tried to enter #{args} but the input is disabled"
  end
  if only_symbols?(*args)
    append_keys(*args)
  else
    _stomp_input_text(*args)
    field_empty_afterward?(*args)
  end
end

#set_attribute(name, value) ⇒ Object

Requires element to have an unique css

Parameters:

  • name (String)
    • attribute name

  • value (String)
    • attribute value to set



100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/element.rb', line 100

def set_attribute(name, value)
  Log.debug("[GRIDIUM::Element] setting element attribute '#{name}' to '#{value}'")

  if self.by == :css
    ElementExtensions.set_attribute(self.locator, name, value)
  else
    # see if the element has an id to work with
    id = self.attribute('id')
    if id.nil? || id.empty?
      Log.warn("[GRIDIUM::Element] #{self} does not have an 'id'. Consider adding one.")
    else
      ElementExtensions.set_attribute("[id=#{id}", name, value)
    end
  end
end

#sizeObject



299
300
301
# File 'lib/element.rb', line 299

def size
  element.size
end

#submitObject



311
312
313
# File 'lib/element.rb', line 311

def submit
  element.submit
end

#tag_nameObject



307
308
309
# File 'lib/element.rb', line 307

def tag_name
  element.tag_name
end

#textObject



315
316
317
318
# File 'lib/element.rb', line 315

def text
  #this is used for text based elements
  element.text
end

#to_sObject



30
31
32
# File 'lib/element.rb', line 30

def to_s
  "'#{@name}' (By:#{@by} => '#{@locator}')"
end

#trigger_onblurObject



279
280
281
282
283
284
285
286
287
# File 'lib/element.rb', line 279

def trigger_onblur
  Log.debug("[GRIDIUM::Element] Triggering onblur for (#{self})...")
  if element.enabled?
    $verification_passes += 1
    ElementExtensions.trigger_onblur(self)
  else
    Log.error('[GRIDIUM::Element] Cannot trigger onblur.  Element is not present.')
  end
end

#valueObject



147
148
149
# File 'lib/element.rb', line 147

def value
  attribute "value"
end

#verify(timeout: @timeout) ⇒ Object

soft failure, will not kill test immediately



82
83
84
85
# File 'lib/element.rb', line 82

def verify(timeout: @timeout)
  Log.debug('[GRIDIUM::Element] Verifying new element...')
  ElementVerification.new(self, timeout)
end

#wait_until(timeout: @timeout) ⇒ Object

hard failure, will kill test immediately



88
89
90
91
# File 'lib/element.rb', line 88

def wait_until(timeout: @timeout)
  Log.debug('[GRIDIUM::Element] Waiting for new element...')
  ElementVerification.new(self, timeout, fail_test: true)
end