Skip to content

Commit

Permalink
Add by.repeater and by.exactRepeater
Browse files Browse the repository at this point in the history
  • Loading branch information
bootstraponline committed Jun 3, 2015
1 parent 428aab9 commit 72643a2
Show file tree
Hide file tree
Showing 12 changed files with 430 additions and 30 deletions.
2 changes: 1 addition & 1 deletion gen/json_to_rb.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def #{prefix}#{method_name}
source += <<'S'
# class methods
def self.client_side_scripts
def self.scripts
@@client_side_scripts
end
Expand Down
10 changes: 9 additions & 1 deletion lib/angular_webdriver.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
require_relative 'angular_webdriver/version'

require 'rubygems'
require 'selenium-webdriver'
require 'watir-webdriver'

require_relative 'angular_webdriver/protractor/by'
require_relative 'angular_webdriver/protractor/by_repeater_inner'
require_relative 'angular_webdriver/protractor/client_side_scripts'
require_relative 'angular_webdriver/protractor/protractor'
require_relative 'angular_webdriver/protractor/protractor_element'
require_relative 'angular_webdriver/protractor/by'
require_relative 'angular_webdriver/protractor/webdriver_patch'
require_relative 'angular_webdriver/protractor/watir_patch'
83 changes: 80 additions & 3 deletions lib/angular_webdriver/protractor/by.rb
Original file line number Diff line number Diff line change
Expand Up @@ -166,9 +166,86 @@ def cssContainingText css_selector, search_text
# to a string by the time it's seen by selenium webdriver.
{ cssContainingText: { cssSelector: css_selector, searchText: search_text }.to_json }
end
end
end
end

# Find elements inside an ng-repeat.
#
# @view
# <div ng-repeat="cat in pets">
# <span>{{cat.name}}</span>
# <span>{{cat.age}}</span>
# </div>
#
# <div class="book-img" ng-repeat-start="book in library">
# <span>{{$index}}</span>
# </div>
# <div class="book-info" ng-repeat-end>
# <h4>{{book.name}}</h4>
# <p>{{book.blurb}}</p>
# </div>
#
# @example
# // Returns the DIV for the second cat.
# secondCat = element(by.repeater('cat in pets').row(1));
#
# // Returns the SPAN for the first cat's name.
# firstCatName = element(by.repeater('cat in pets').
# row(0).column('cat.name'));
#
# // Returns a promise that resolves to an array of WebElements from a column
# ages = element.all(
# by.repeater('cat in pets').column('cat.age'));
#
# // Returns a promise that resolves to an array of WebElements containing
# // all top level elements repeated by the repeater. For 2 pets rows resolves
# // to an array of 2 elements.
# rows = element.all(by.repeater('cat in pets'));
#
# // Returns a promise that resolves to an array of WebElements containing all
# // the elements with a binding to the book's name.
# divs = element.all(by.repeater('book in library').column('book.name'));
#
# // Returns a promise that resolves to an array of WebElements containing
# // the DIVs for the second book.
# bookInfo = element.all(by.repeater('book in library').row(1));
#
# // Returns the H4 for the first book's name.
# firstBookName = element(by.repeater('book in library').
# row(0).column('book.name'));
#
# // Returns a promise that resolves to an array of WebElements containing
# // all top level elements repeated by the repeater. For 2 books divs
# // resolves to an array of 4 elements.
# divs = element.all(by.repeater('book in library'));
#
# @param {string} repeatDescriptor
# @return {{findElementsOverride: findElementsOverride, toString: Function|string}}
#
def repeater repeat_descriptor
ByRepeaterInner.new exact: false, repeat_descriptor: repeat_descriptor
end

# Find an element by exact repeater.
#
# @view
# <li ng-repeat="person in peopleWithRedHair"></li>
# <li ng-repeat="car in cars | orderBy:year"></li>
#
# @example
# expect(element(by.exactRepeater('person in peopleWithRedHair')).present?)
# .to eq(true);
# expect(element(by.exactRepeater('person in people')).present?).to eq(false);
# expect(element(by.exactRepeater('car in cars')).present?).to eq(true);
#
# @param {string} repeatDescriptor
# @return {{findElementsOverride: findElementsOverride, toString: Function|string}}
#
def exactRepeater repeat_descriptor
ByRepeaterInner.new exact: true, repeat_descriptor: repeat_descriptor
end

end # class << self
end # class By
end # module AngularWebdriver

def by
AngularWebdriver::By
Expand Down
106 changes: 106 additions & 0 deletions lib/angular_webdriver/protractor/by_repeater_inner.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
module AngularWebdriver
class ByRepeaterInner
attr_reader :exact, :repeat_descriptor, :row_index, :column_binding

# Takes args and returns wrapped repeater if the first arg is a repeater
#
# @param args [Array] args to wrap
def self.wrap_repeater args
if args && args.first.is_a?(self)
[repeater: args.first.process] # watir requires an array containing a hash
else
args
end
end

# Generate either by.repeater or by.exactRepeater
# @option opts [Boolean] :exact exact matching
# @option opts [String] :repeat_descriptor repeat description
def initialize opts={}
exact = opts.fetch(:exact)
raise "#{exact} is not a valid value" unless [true, false].include?(exact)
repeat_descriptor = opts.fetch(:repeat_descriptor)
raise "#{repeat_descriptor} is not a valid repeat_descriptor" unless repeat_descriptor.is_a?(String)

@exact = exact
@repeat_descriptor = repeat_descriptor
self
end

def row row_index
raise "#{row_index} is not a valid row index" unless row_index.is_a?(Integer)
@row_index = row_index
self
end

def column column_binding
raise "#{column_binding} is not a valid column binding" unless column_binding.is_a?(String)
@column_binding = column_binding
self
end

# Return JSON representation of the correct repeater method and args
def process
# findRepeaterElement - (repeater, exact, index, binding, using, rootSelector) - by.repeater('baz in days').row(0).column('b') - [baz in days, false, 0, b, null, body]
# findRepeaterRows - (repeater, exact, index, using) - by.repeater('baz in days').row(0) - [baz in days, false, 0, null, body]
# findRepeaterColumn - (repeater, exact, binding, using, rootSelector) - by.repeater('baz in days').column('b') - [baz in days, false, b, null, body]
# findAllRepeaterRows - (repeater, exact, using) - by.repeater('baz in days') - [baz in days, false, null, body]
#
#
# using - parent element
# rootSelector - selector for finding angular js
# exact - true if by.exactRepeater false when by.repeater
#
#
# repeater (repeat_descriptor), binding (column_binding), index (row_index)
#
# findRepeaterElement - (repeat_descriptor, row_index, column_binding)
# findRepeaterRows - (repeat_descriptor, row_index)
# findRepeaterColumn - (repeat_descriptor, column_binding)
# findAllRepeaterRows - (repeat_descriptor)

result = if repeat_descriptor && row_index && column_binding
{
script: :findRepeaterElement,
args: {
repeat_descriptor: repeat_descriptor,
exact: exact,
row_index: row_index,
column_binding: column_binding,

}
}
elsif repeat_descriptor && row_index
{
script: :findRepeaterRows,
args: {
repeat_descriptor: repeat_descriptor,
exact: exact,
row_index: row_index
}
}
elsif repeat_descriptor && column_binding
{
script: :findRepeaterColumn,
args: {
repeat_descriptor: repeat_descriptor,
exact: exact,
column_binding: column_binding
}
}
elsif repeat_descriptor
{
script: :findAllRepeaterRows,
args: {
repeat_descriptor: repeat_descriptor,
exact: exact
}
}
else
raise 'Invalid repeater'
end

result.to_json
end # def process
end # class ByRepeaterInner
end # module AngularWebdriver
2 changes: 1 addition & 1 deletion lib/angular_webdriver/protractor/client_side_scripts.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ module ClientSideScripts

# class methods

def self.client_side_scripts
def self.scripts
@@client_side_scripts
end

Expand Down
6 changes: 1 addition & 5 deletions lib/angular_webdriver/protractor/protractor.rb
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
require 'rubygems'
require 'selenium-webdriver'
require 'selenium/webdriver/common/error'

require_relative 'webdriver_patch'
require_relative 'client_side_scripts'

class Protractor

NEW_FINDERS_KEYS = %i(
Expand All @@ -14,6 +9,7 @@ class Protractor
model
options
cssContainingText
repeater
).freeze # [:binding]
NEW_FINDERS_HASH = NEW_FINDERS_KEYS.map { |e| [e, e.to_s] }.to_h.freeze # {binding: 'binding'}

Expand Down
21 changes: 11 additions & 10 deletions lib/angular_webdriver/protractor/protractor_element.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,24 @@
# designed for use with watir-webdriver

# todo: avoid global browser / @browser
# todo: use module namespace for classes

class ProtractorElement
attr_accessor :watir
module AngularWebdriver
class ProtractorElement
attr_accessor :watir

def initialize watir
@watir = watir
end
def initialize watir
@watir = watir
end

# @return Watir::HTMLElementCollection
def all *args
watir.elements *args
# @return Watir::HTMLElementCollection
def all *args
watir.elements *args
end
end
end

def protractor_element
@pelement ||= ProtractorElement.new @browser
@pelement ||= AngularWebdriver::ProtractorElement.new @browser
end

# @return Watir::HTMLElement
Expand Down
32 changes: 29 additions & 3 deletions lib/angular_webdriver/protractor/watir_patch.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
require 'watir-webdriver'
require 'watir-webdriver/elements/element'

require_relative 'protractor'

# match protractor semantics
# unfortunately setting always locate doesn't always locate.
Watir.always_locate = true
Expand Down Expand Up @@ -31,6 +28,15 @@ module Container
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 # module Container

#
Expand All @@ -40,7 +46,27 @@ def all(*args)
# Note the element class is different on master.

class Element

# 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
Expand Down
Loading

0 comments on commit 72643a2

Please sign in to comment.