angular_webdriver 0.0.7 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +3 -0
- data/.gitmodules +3 -0
- data/.rspec +2 -0
- data/.travis.yml +32 -0
- data/Gemfile +2 -0
- data/Thorfile +33 -1
- data/angular_webdriver.gemspec +10 -3
- data/docs/overview.md +101 -0
- data/docs/sync.md +53 -0
- data/lib/angular_webdriver/protractor/by.rb +331 -0
- data/lib/angular_webdriver/protractor/by_repeater_inner.rb +106 -0
- data/lib/angular_webdriver/protractor/client_side_scripts.rb +1035 -0
- data/lib/angular_webdriver/protractor/protractor.rb +396 -77
- data/lib/angular_webdriver/protractor/protractor_element.rb +33 -0
- data/lib/angular_webdriver/protractor/rspec_helpers.rb +19 -0
- data/lib/angular_webdriver/protractor/watir_patch.rb +209 -0
- data/lib/angular_webdriver/protractor/webdriver_patch.rb +246 -0
- data/lib/angular_webdriver/version.rb +2 -2
- data/lib/angular_webdriver.rb +14 -1
- data/{LICENSE → license/angular_webdriver/LICENSE.txt} +0 -0
- data/{lib/angular_webdriver → license}/protractor/LICENSE.txt +0 -0
- data/{lib/angular_webdriver/protractor/get_url_trace.rb → notes/bootstrap_notes.md} +13 -0
- data/notes/element_by_id/element_by_id_sync_off.txt +12 -0
- data/notes/element_by_id/element_by_id_sync_on.txt +74 -0
- data/notes/element_chaining_debug.txt +94 -0
- data/notes/evaluate/js_evaluate_sync_on.txt +60 -0
- data/notes/evaluate/ruby_evaluate_sync_on.txt +35 -0
- data/notes/get_title/browser_get_title_sync_off.txt +11 -0
- data/notes/get_title/browser_get_title_sync_on.txt +54 -0
- data/notes/phantomjs.md +23 -0
- data/notes/protractor_cli_bugs.txt +39 -0
- data/notes/protractor_get/protractor_get.rb +102 -0
- data/notes/protractor_get/protractor_get_website_sync_off.txt +11 -0
- data/notes/protractor_get/protractor_get_website_sync_on.txt +86 -0
- data/notes/repeater/findAllRepeaterRows_annotated.txt +150 -0
- data/notes/repeater/findAllRepeaterRows_raw.txt +145 -0
- data/notes/repeater/findRepeaterColumn_annotated.txt +317 -0
- data/notes/repeater/findRepeaterColumn_raw.txt +310 -0
- data/notes/repeater/findRepeaterElement_annotated.txt +152 -0
- data/notes/repeater/findRepeaterElement_raw.txt +146 -0
- data/notes/repeater/findRepeaterRows_annotated.txt +156 -0
- data/notes/repeater/findRepeaterRows_raw.txt +152 -0
- data/notes/sync_after.md +46 -0
- data/notes/sync_notes.md +137 -0
- data/notes/synchronize_spec/status_gettext.txt +121 -0
- data/notes/synchronize_spec/status_gettext_x3.txt +451 -0
- data/notes/synchronize_spec/synchronize_spec.js.txt +74 -0
- data/notes/synchronize_spec/watir_gettext.txt +73 -0
- data/readme.md +52 -12
- data/release_notes.md +127 -0
- data/selenium_server/lib/logs.rb +50 -0
- data/selenium_server/lib/selenium_server.rb +21 -0
- data/selenium_server/readme.md +3 -0
- data/selenium_server/spec/logs_spec.rb +18 -0
- data/selenium_server/spec/nodejs_sync_spec_waithttp_annotated.txt +54 -0
- data/selenium_server/spec/nodejs_sync_spec_waithttp_raw.txt +367 -0
- data/selenium_server/spec/nodejs_sync_spec_waithttp_raw_processed.txt +43 -0
- data/selenium_server/spec/ruby_sync_spec_waithttp_annotated.txt +59 -0
- data/selenium_server/spec/ruby_sync_spec_waithttp_raw.txt +267 -0
- data/selenium_server/spec/ruby_sync_spec_waithttp_raw_processed.txt +39 -0
- data/selenium_server/spec/spec_helper.rb +6 -0
- data/selenium_server/spec/status_gettext_x3.txt +429 -0
- data/selenium_server/spec/status_gettext_x3_annotated.txt +86 -0
- metadata +91 -18
- data/lib/angular_webdriver/protractor/clientSideScripts.json +0 -19
- data/lib/angular_webdriver/protractor/clientsidescripts.js +0 -671
- data/lib/angular_webdriver/protractor/scripts.rb +0 -7
- data/lib/angular_webdriver/protractor/scripts_to_json.js +0 -11
- data/spec/protractor_spec.rb +0 -40
- data/spec/spec_helper.rb +0 -5
@@ -0,0 +1,209 @@
|
|
1
|
+
require 'watir-webdriver/elements/element'
|
2
|
+
|
3
|
+
# match protractor semantics
|
4
|
+
# unfortunately setting always locate doesn't always locate.
|
5
|
+
Watir.always_locate = true
|
6
|
+
|
7
|
+
#
|
8
|
+
# This patch serves a few purposes. The first is matching Protractor semantics
|
9
|
+
# of lazy finding elements and always relocating elements (ex: element.text)
|
10
|
+
#
|
11
|
+
# The second is removing unnecessary bloatware from Watir which has a number
|
12
|
+
# of checks that don't make sense for angular.js testing. The specifics
|
13
|
+
# of this patch will change in the next Watir release. Currently version
|
14
|
+
# 0.7.0 is targeted.
|
15
|
+
#
|
16
|
+
# The third is teaching Watir about angular specific locators
|
17
|
+
#
|
18
|
+
# Design goal: element.all(by.binding('slowHttpStatus'))
|
19
|
+
# should not make any server requests
|
20
|
+
#
|
21
|
+
|
22
|
+
module Watir
|
23
|
+
|
24
|
+
class HTMLElementCollection
|
25
|
+
# Return original selector.
|
26
|
+
# Method added for protractor compatibility
|
27
|
+
def locator
|
28
|
+
@selector
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
module Container
|
33
|
+
#
|
34
|
+
# Alias of elements for Protractor
|
35
|
+
#
|
36
|
+
|
37
|
+
def all(*args)
|
38
|
+
elements(*args)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Redefine extract_selector to wrap find by repeater
|
42
|
+
upstream_extract_selector = instance_method(:extract_selector)
|
43
|
+
define_method(:extract_selector) do |selectors|
|
44
|
+
selectors = AngularWebdriver::ByRepeaterInner.wrap_repeater selectors
|
45
|
+
|
46
|
+
upstream_extract_selector.bind(self).call selectors
|
47
|
+
end
|
48
|
+
|
49
|
+
end # module Container
|
50
|
+
|
51
|
+
#
|
52
|
+
# Base class for HTML elements.
|
53
|
+
#
|
54
|
+
|
55
|
+
# Note the element class is different on master.
|
56
|
+
|
57
|
+
class Element
|
58
|
+
|
59
|
+
# Always raise on stale element ref error. Prevents infinite retry loop.
|
60
|
+
def element_call
|
61
|
+
yield
|
62
|
+
rescue Selenium::WebDriver::Error::StaleElementReferenceError
|
63
|
+
raise
|
64
|
+
end
|
65
|
+
|
66
|
+
def selected?
|
67
|
+
assert_exists
|
68
|
+
element_call { @element.selected? }
|
69
|
+
end
|
70
|
+
|
71
|
+
# required for watir otherwise execute_script will fail
|
72
|
+
#
|
73
|
+
# e = browser.element(tag_name: 'div')
|
74
|
+
# driver.execute_script 'return arguments[0].tagName', e
|
75
|
+
# {"script":"return arguments[0].tagName","args":[{"ELEMENT":"0"}]}
|
76
|
+
#
|
77
|
+
# Convert to a WebElement JSON Object for transmission over the wire.
|
78
|
+
# @see https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol#basic-terms-and-concepts
|
79
|
+
#
|
80
|
+
# @api private
|
81
|
+
#
|
82
|
+
def to_json(*args)
|
83
|
+
assert_exists
|
84
|
+
{ ELEMENT: @element.ref }.to_json
|
85
|
+
end
|
86
|
+
|
87
|
+
# Ensure that the element exists by always relocating it
|
88
|
+
# Required to trigger waitForAngular. Caching the element here will
|
89
|
+
# break the Protractor sync feature so this must be @element = locate.
|
90
|
+
def assert_exists
|
91
|
+
@element = locate
|
92
|
+
end
|
93
|
+
|
94
|
+
def assert_not_stale
|
95
|
+
nil
|
96
|
+
end
|
97
|
+
|
98
|
+
def assert_enabled
|
99
|
+
nil
|
100
|
+
end
|
101
|
+
|
102
|
+
# Return original selector.
|
103
|
+
# Method added for protractor compatibility
|
104
|
+
def locator
|
105
|
+
@selector
|
106
|
+
end
|
107
|
+
|
108
|
+
# avoid context lookup
|
109
|
+
def locate
|
110
|
+
locator_class.new(@parent.wd, @selector, self.class.attribute_list).locate
|
111
|
+
end
|
112
|
+
|
113
|
+
# Invoke protractor.allowAnimations with freshly located element and
|
114
|
+
# optional value.
|
115
|
+
def allowAnimations value=nil
|
116
|
+
assert_exists
|
117
|
+
driver.protractor.allowAnimations @element, value
|
118
|
+
end
|
119
|
+
|
120
|
+
# Watir doesn't define a clear method on element so we have to provide one.
|
121
|
+
def clear
|
122
|
+
assert_exists
|
123
|
+
element_call { @element.clear }
|
124
|
+
end
|
125
|
+
|
126
|
+
# Evaluate an Angular expression as if it were on the scope
|
127
|
+
# of the current element.
|
128
|
+
#
|
129
|
+
# @param expression <String> The expression to evaluate.
|
130
|
+
#
|
131
|
+
# @return <Object> The result of the evaluation.
|
132
|
+
def evaluate expression
|
133
|
+
assert_exists
|
134
|
+
driver.protractor.evaluate @element, expression
|
135
|
+
end
|
136
|
+
|
137
|
+
#
|
138
|
+
# Returns true if the element exists and is visible on the page.
|
139
|
+
#
|
140
|
+
# @return [Boolean]
|
141
|
+
# @see Watir::Wait
|
142
|
+
#
|
143
|
+
#
|
144
|
+
# rescue element not found
|
145
|
+
def present?
|
146
|
+
exists? && visible?
|
147
|
+
rescue Selenium::WebDriver::Error::NoSuchElementError, Selenium::WebDriver::Error::StaleElementReferenceError, UnknownObjectException
|
148
|
+
# if the element disappears between the exists? and visible? calls,
|
149
|
+
# consider it not present.
|
150
|
+
false
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
|
155
|
+
#
|
156
|
+
# The main class through which you control the browser.
|
157
|
+
#
|
158
|
+
|
159
|
+
class Browser
|
160
|
+
def assert_exists
|
161
|
+
# remove expensive window check
|
162
|
+
raise Exception::Error, 'browser was closed' if @closed
|
163
|
+
end
|
164
|
+
|
165
|
+
def inspect
|
166
|
+
nil # avoid expensive browser url and title lookup
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
class ElementLocator
|
171
|
+
def validate_element(element)
|
172
|
+
tn = @selector[:tag_name]
|
173
|
+
return element unless tn # don't validate nil tag names
|
174
|
+
element_tag_name = element.tag_name.downcase
|
175
|
+
|
176
|
+
return if tn && !tag_name_matches?(element_tag_name, tn)
|
177
|
+
|
178
|
+
if element_tag_name == 'input'
|
179
|
+
return if @selector[:type] && @selector[:type] != element.attribute(:type)
|
180
|
+
end
|
181
|
+
|
182
|
+
element
|
183
|
+
end
|
184
|
+
|
185
|
+
# always raise element not found / stale reference error
|
186
|
+
def locate
|
187
|
+
# element.all(by.partialButtonText('text')).to_a[0].value creates the
|
188
|
+
# selector {:element=>#<Selenium::WebDriver::Element ...>}
|
189
|
+
# in that case we've already located the element.
|
190
|
+
#
|
191
|
+
# see 'should find multiple buttons containing "text"' in locators_spec.rb
|
192
|
+
return @selector[:element] if @selector.is_a?(Hash) && @selector[:element].is_a?(Selenium::WebDriver::Element)
|
193
|
+
|
194
|
+
e = by_id and return e # short-circuit if :id is given
|
195
|
+
|
196
|
+
if @selector.size == 1
|
197
|
+
element = find_first_by_one
|
198
|
+
else
|
199
|
+
element = find_first_by_multiple
|
200
|
+
end
|
201
|
+
|
202
|
+
# This actually only applies when finding by xpath/css - browser.text_field(:xpath, "//input[@type='radio']")
|
203
|
+
# We don't need to validate the element if we built the xpath ourselves.
|
204
|
+
# It is also used to alter behavior of methods locating more than one type of element
|
205
|
+
# (e.g. text_field locates both input and textarea)
|
206
|
+
validate_element(element) if element
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
@@ -0,0 +1,246 @@
|
|
1
|
+
# https://github.com/SeleniumHQ/selenium/blob/1221ea539d92a46f392b00abccb9f48886415d26/rb/lib/selenium/webdriver/remote/bridge.rb#L576
|
2
|
+
|
3
|
+
require 'selenium/webdriver/common/search_context'
|
4
|
+
require 'selenium/webdriver/remote'
|
5
|
+
require 'selenium/webdriver/remote/bridge'
|
6
|
+
|
7
|
+
module Selenium
|
8
|
+
module WebDriver
|
9
|
+
|
10
|
+
class Driver
|
11
|
+
def protractor
|
12
|
+
@bridge.protractor
|
13
|
+
end
|
14
|
+
|
15
|
+
def protractor= protractor_object
|
16
|
+
@bridge.protractor = protractor_object
|
17
|
+
end
|
18
|
+
|
19
|
+
def bridge
|
20
|
+
@bridge
|
21
|
+
end
|
22
|
+
|
23
|
+
#
|
24
|
+
# Sets the wait time in seconds used when locating elements and
|
25
|
+
# waiting for angular tohttps://www.youtube.com/watch?v=o9c3U5_8tGY load.
|
26
|
+
#
|
27
|
+
# @param value [Numeric] the amount of time to wait in seconds
|
28
|
+
#
|
29
|
+
# @return [Numeric] the wait time in seconds
|
30
|
+
#
|
31
|
+
def set_max_wait value
|
32
|
+
@bridge.set_max_wait value
|
33
|
+
end
|
34
|
+
|
35
|
+
#
|
36
|
+
# Returns the wait time in seconds used when locating elements and
|
37
|
+
# waiting for angular to load.
|
38
|
+
#
|
39
|
+
def max_wait_seconds
|
40
|
+
@bridge.max_wait_seconds
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
module Remote
|
45
|
+
class Bridge
|
46
|
+
attr_accessor :protractor
|
47
|
+
|
48
|
+
def set_max_wait value
|
49
|
+
fail 'set_max_wait value must be a number' unless value.is_a?(Numeric)
|
50
|
+
# ensure no negative values
|
51
|
+
@max_wait_seconds = value >= 0 ? value : 0
|
52
|
+
end
|
53
|
+
|
54
|
+
def max_wait_seconds
|
55
|
+
# default to 0
|
56
|
+
@max_wait_seconds ||= 0
|
57
|
+
end
|
58
|
+
|
59
|
+
# execute_script requires a JSON representation of the element id
|
60
|
+
# otherwise the element will not be sent correctly to the browser
|
61
|
+
class WrappedParent
|
62
|
+
def initialize id
|
63
|
+
@id = id
|
64
|
+
end
|
65
|
+
|
66
|
+
def to_json *args
|
67
|
+
{ ELEMENT: @id }.to_json
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def protractor_find(many, how, what, parent = nil)
|
72
|
+
timeout = max_wait_seconds
|
73
|
+
|
74
|
+
# we have to waitForAngular here. unlike selenium locators,
|
75
|
+
# protractor locators don't go through execute. execute uses
|
76
|
+
# protractor.sync to run waitForAngular when finding elements.
|
77
|
+
wait(timeout: timeout, bubble: true) { protractor.waitForAngular }
|
78
|
+
|
79
|
+
# execute_script will invoke to_json on the parent, WrappedParent
|
80
|
+
# ensures that the JSON produces the expected result.
|
81
|
+
using = parent ? WrappedParent.new(parent) : false
|
82
|
+
root_selector = protractor.root_element
|
83
|
+
comment = "Protractor find by.#{how}"
|
84
|
+
|
85
|
+
# args order from locators.js
|
86
|
+
case how
|
87
|
+
when 'binding', 'exactBinding'
|
88
|
+
exact = how == 'exactBinding'
|
89
|
+
binding_descriptor = what
|
90
|
+
args = [binding_descriptor, exact, using, root_selector]
|
91
|
+
protractor_js = protractor.client_side_scripts.find_bindings
|
92
|
+
when 'partialButtonText'
|
93
|
+
search_text = what
|
94
|
+
args = [search_text, using, root_selector]
|
95
|
+
protractor_js = protractor.client_side_scripts.find_by_partial_button_text
|
96
|
+
when 'buttonText'
|
97
|
+
search_text = what
|
98
|
+
args = [search_text, using, root_selector]
|
99
|
+
protractor_js = protractor.client_side_scripts.find_by_button_text
|
100
|
+
when 'model'
|
101
|
+
model = what
|
102
|
+
args = [model, using, root_selector]
|
103
|
+
protractor_js = protractor.client_side_scripts.find_by_model
|
104
|
+
when 'options'
|
105
|
+
options_descriptor = what
|
106
|
+
args = [options_descriptor, using, root_selector]
|
107
|
+
protractor_js = protractor.client_side_scripts.find_by_options
|
108
|
+
when 'cssContainingText'
|
109
|
+
json = JSON.parse what
|
110
|
+
css_selector = json['cssSelector']
|
111
|
+
search_text = json['searchText']
|
112
|
+
args = [css_selector, search_text, using, root_selector]
|
113
|
+
protractor_js = protractor.client_side_scripts.find_by_css_containing_text
|
114
|
+
when 'repeater' # includes 'exactRepeater'
|
115
|
+
json = JSON.parse what
|
116
|
+
repeater_args = json['args'].values # json args is a hash
|
117
|
+
# using and root_selector are always passed to repeater even
|
118
|
+
# if the script doesn't use them.
|
119
|
+
args = repeater_args + [using, root_selector]
|
120
|
+
|
121
|
+
# findRepeaterElement, findRepeaterRows, findRepeaterColumn, findAllRepeaterRows
|
122
|
+
script_name = json['script'].intern
|
123
|
+
|
124
|
+
protractor_js = protractor.client_side_scripts.scripts[script_name]
|
125
|
+
end
|
126
|
+
|
127
|
+
finder = lambda { protractor.executeScript_(protractor_js, comment, *args) }
|
128
|
+
|
129
|
+
result = []
|
130
|
+
|
131
|
+
# Ignore any exceptions here because find_elements returns
|
132
|
+
# an empty array when there are no values found, not an error.
|
133
|
+
ignore do
|
134
|
+
wait_true(timeout) do
|
135
|
+
result = finder.call
|
136
|
+
result.length > 0
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
result ||= []
|
141
|
+
|
142
|
+
if many
|
143
|
+
return result
|
144
|
+
else
|
145
|
+
result = result.first
|
146
|
+
return result if result
|
147
|
+
fail ::Selenium::WebDriver::Error::NoSuchElementError
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def find_element_by(how, what, parent = nil)
|
152
|
+
if protractor.finder? how
|
153
|
+
return protractor_find(false, how, what, parent)
|
154
|
+
end
|
155
|
+
|
156
|
+
if parent
|
157
|
+
id = execute :findChildElement, { :id => parent }, { :using => how, :value => what }
|
158
|
+
else
|
159
|
+
id = execute :findElement, {}, { :using => how, :value => what }
|
160
|
+
end
|
161
|
+
|
162
|
+
Element.new self, element_id_from(id)
|
163
|
+
end
|
164
|
+
|
165
|
+
def find_elements_by(how, what, parent = nil)
|
166
|
+
if protractor.finder? how
|
167
|
+
return protractor_find(true, how, what, parent)
|
168
|
+
end
|
169
|
+
|
170
|
+
if parent
|
171
|
+
ids = execute :findChildElements, { :id => parent }, { :using => how, :value => what }
|
172
|
+
else
|
173
|
+
ids = execute :findElements, {}, { :using => how, :value => what }
|
174
|
+
end
|
175
|
+
|
176
|
+
ids.map { |id| Element.new self, element_id_from(id) }
|
177
|
+
end
|
178
|
+
|
179
|
+
#
|
180
|
+
# driver.get uses execute which invokes protractor.get
|
181
|
+
# protractor.get needs to call driver.get without invoking
|
182
|
+
# protractor.get.
|
183
|
+
#
|
184
|
+
# this is accomplished by using driver_get and raw_execute
|
185
|
+
#
|
186
|
+
|
187
|
+
def driver_get(url)
|
188
|
+
raw_execute(:get, {}, :url => url)['value']
|
189
|
+
end
|
190
|
+
|
191
|
+
FIND_ELEMENT_METHODS = [:findElement, :findChildElement].freeze
|
192
|
+
FIND_ELEMENTS_METHODS = [:findElements, :findChildElements].freeze
|
193
|
+
|
194
|
+
#
|
195
|
+
# executes a command on the remote server.
|
196
|
+
#
|
197
|
+
#
|
198
|
+
# Returns the 'value' of the returned payload
|
199
|
+
#
|
200
|
+
|
201
|
+
def execute(*args)
|
202
|
+
raise 'Must initialize protractor' unless protractor
|
203
|
+
method_symbol = args.first
|
204
|
+
unless protractor.ignore_sync
|
205
|
+
# override get method which has special sync logic
|
206
|
+
# (not handled via sync method)
|
207
|
+
if method_symbol == :get
|
208
|
+
url = args.last[:url]
|
209
|
+
return protractor.get url
|
210
|
+
end
|
211
|
+
|
212
|
+
protractor.sync method_symbol
|
213
|
+
end
|
214
|
+
|
215
|
+
timeout = max_wait_seconds
|
216
|
+
finder = lambda { raw_execute(*args)['value'] }
|
217
|
+
find_one_element = FIND_ELEMENT_METHODS.include?(method_symbol)
|
218
|
+
find_many_elements = FIND_ELEMENTS_METHODS.include?(method_symbol)
|
219
|
+
|
220
|
+
if find_one_element
|
221
|
+
wait(timeout: timeout, bubble: true) do
|
222
|
+
finder.call
|
223
|
+
end
|
224
|
+
elsif find_many_elements
|
225
|
+
result = []
|
226
|
+
|
227
|
+
# Ignore any exceptions here because find_elements returns
|
228
|
+
# an empty array when there are no values found, not an error.
|
229
|
+
ignore do
|
230
|
+
wait_true(timeout) do
|
231
|
+
result = finder.call
|
232
|
+
result.length > 0
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
result ||= []
|
237
|
+
result
|
238
|
+
else # all other commands
|
239
|
+
finder.call
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
end # class Bridge
|
244
|
+
end # module Remote
|
245
|
+
end # module WebDriver
|
246
|
+
end # module Selenium
|
@@ -1,4 +1,4 @@
|
|
1
1
|
module AngularWebdriver
|
2
|
-
VERSION = '0.0
|
3
|
-
DATE = '2015-
|
2
|
+
VERSION = '1.0.0' unless defined? ::AngularWebdriver::VERSION
|
3
|
+
DATE = '2015-06-06' unless defined? ::AngularWebdriver::DATE
|
4
4
|
end
|
data/lib/angular_webdriver.rb
CHANGED
@@ -1 +1,14 @@
|
|
1
|
-
require_relative 'angular_webdriver/
|
1
|
+
require_relative 'angular_webdriver/version'
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'selenium-webdriver'
|
5
|
+
require 'watir-webdriver'
|
6
|
+
|
7
|
+
require_relative 'angular_webdriver/protractor/by'
|
8
|
+
require_relative 'angular_webdriver/protractor/by_repeater_inner'
|
9
|
+
require_relative 'angular_webdriver/protractor/client_side_scripts'
|
10
|
+
require_relative 'angular_webdriver/protractor/protractor'
|
11
|
+
require_relative 'angular_webdriver/protractor/protractor_element'
|
12
|
+
require_relative 'angular_webdriver/protractor/rspec_helpers'
|
13
|
+
require_relative 'angular_webdriver/protractor/webdriver_patch'
|
14
|
+
require_relative 'angular_webdriver/protractor/watir_patch'
|
File without changes
|
File without changes
|
@@ -1,3 +1,15 @@
|
|
1
|
+
# https://github.com/SeleniumHQ/selenium/blob/51fd82ec9cb1ebe7596bf7bb3fb8290113466a9a/rb/lib/selenium/webdriver/common/search_context.rb#L23
|
2
|
+
# protractor/lib/locators.js
|
3
|
+
#
|
4
|
+
# ProtractorBy.prototype.binding = function(bindingDescriptor) {
|
5
|
+
# return {
|
6
|
+
# findElementsOverride: function(driver, using, rootSelector) {
|
7
|
+
# return driver.findElements(
|
8
|
+
# webdriver.By.js(clientSideScripts.findBindings,
|
9
|
+
# bindingDescriptor, false, using, rootSelector));
|
10
|
+
# },
|
11
|
+
|
12
|
+
```
|
1
13
|
=begin
|
2
14
|
|
3
15
|
executeAsyncScript_ & executeScript_ wrappers not required. The description
|
@@ -213,3 +225,4 @@ catch(e) { throw (e instanceof Error) ? e : new Error(e); }, [, false, null, bod
|
|
213
225
|
14:20:15.086 INFO - Executing: [get text: 1 [org.openqa.selenium.remote.RemoteWebElement@dee620f8 -> unknown locator]])
|
214
226
|
...
|
215
227
|
=end
|
228
|
+
```
|
@@ -0,0 +1,12 @@
|
|
1
|
+
browser.get('https://angularjs.org/')
|
2
|
+
element(by.id('the-basics')).getText()
|
3
|
+
The Basics
|
4
|
+
|
5
|
+
13:06:18.990 INFO - Executing: [execute script: , []])
|
6
|
+
13:06:18.990 INFO - Executing: [execute script: , []])
|
7
|
+
13:06:18.990 INFO - Executing: [find elements: By.id: the-basics])
|
8
|
+
13:06:19.704 INFO - Done: [execute script: , []]
|
9
|
+
13:06:19.799 INFO - Done: [find elements: By.id: the-basics]
|
10
|
+
13:06:19.815 INFO - Executing: [get text: 0 [[FirefoxDriver: firefox on MAC (6e728ccc-09cf-0b47-a80e-44bdfdbb8a1b)] -> id: the-basics]])
|
11
|
+
13:06:19.865 INFO - Done: [execute script: , []]
|
12
|
+
13:06:19.930 INFO - Done: [get text: 0 [[FirefoxDriver: firefox on MAC (6e728ccc-09cf-0b47-a80e-44bdfdbb8a1b)] -> id: the-basics]]
|
@@ -0,0 +1,74 @@
|
|
1
|
+
browser.get('https://angularjs.org/')
|
2
|
+
element(by.id('the-basics')).getText()
|
3
|
+
The Basics
|
4
|
+
|
5
|
+
|
6
|
+
the waitForAngular method is called before the get by id method.
|
7
|
+
|
8
|
+
|
9
|
+
13:04:57.684 INFO - Executing: [execute script: , []])
|
10
|
+
13:04:57.684 INFO - Executing: [execute script: , []])
|
11
|
+
|
12
|
+
waitForAngular - clientsidescript
|
13
|
+
|
14
|
+
13:04:57.684 INFO - Executing: [execute async script: try { return (function (rootSelector, callback) {
|
15
|
+
var el = document.querySelector(rootSelector);
|
16
|
+
|
17
|
+
try {
|
18
|
+
if (!window.angular) {
|
19
|
+
throw new Error('angular could not be found on the window');
|
20
|
+
}
|
21
|
+
if (angular.getTestability) {
|
22
|
+
angular.getTestability(el).whenStable(callback);
|
23
|
+
} else {
|
24
|
+
if (!angular.element(el).injector()) {
|
25
|
+
throw new Error('root element (' + rootSelector + ') has no injector.' +
|
26
|
+
' this may mean it is not inside ng-app.');
|
27
|
+
}
|
28
|
+
angular.element(el).injector().get('$browser').
|
29
|
+
notifyWhenNoOutstandingRequests(callback);
|
30
|
+
}
|
31
|
+
} catch (err) {
|
32
|
+
callback(err.message);
|
33
|
+
}
|
34
|
+
}).apply(this, arguments); }
|
35
|
+
catch(e) { throw (e instanceof Error) ? e : new Error(e); }, [body]])
|
36
|
+
13:04:57.684 INFO - Executing: [execute script: , []])
|
37
|
+
13:04:57.685 INFO - Executing: [execute script: , []])
|
38
|
+
13:04:58.617 INFO - Done: [execute script: , []]
|
39
|
+
13:04:58.694 INFO - Done: [execute script: , []]
|
40
|
+
|
41
|
+
waitForAngular - clientsidescript
|
42
|
+
|
43
|
+
13:04:58.771 INFO - Done: [execute async script: try { return (function (rootSelector, callback) {
|
44
|
+
var el = document.querySelector(rootSelector);
|
45
|
+
|
46
|
+
try {
|
47
|
+
if (!window.angular) {
|
48
|
+
throw new Error('angular could not be found on the window');
|
49
|
+
}
|
50
|
+
if (angular.getTestability) {
|
51
|
+
angular.getTestability(el).whenStable(callback);
|
52
|
+
} else {
|
53
|
+
if (!angular.element(el).injector()) {
|
54
|
+
throw new Error('root element (' + rootSelector + ') has no injector.' +
|
55
|
+
' this may mean it is not inside ng-app.');
|
56
|
+
}
|
57
|
+
angular.element(el).injector().get('$browser').
|
58
|
+
notifyWhenNoOutstandingRequests(callback);
|
59
|
+
}
|
60
|
+
} catch (err) {
|
61
|
+
callback(err.message);
|
62
|
+
}
|
63
|
+
}).apply(this, arguments); }
|
64
|
+
catch(e) { throw (e instanceof Error) ? e : new Error(e); }, [body]]
|
65
|
+
13:04:58.792 INFO - Executing: [find elements: By.id: the-basics])
|
66
|
+
|
67
|
+
13:04:59.019 INFO - Done: [execute script: , []]
|
68
|
+
13:04:59.085 INFO - Done: [execute script: , []]
|
69
|
+
13:04:59.218 INFO - Done: [find elements: By.id: the-basics]
|
70
|
+
13:04:59.240 INFO - Executing: [get text: 0 [[FirefoxDriver: firefox on MAC (6e728ccc-09cf-0b47-a80e-44bdfdbb8a1b)] -> id: the-basics]])
|
71
|
+
13:04:59.286 INFO - Done: [get text: 0 [[FirefoxDriver: firefox on MAC (6e728ccc-09cf-0b47-a80e-44bdfdbb8a1b)] -> id: the-basics]]
|
72
|
+
|
73
|
+
|
74
|
+
|
@@ -0,0 +1,94 @@
|
|
1
|
+
debugging 'should allow chaining while returning a single column'
|
2
|
+
|
3
|
+
["allinfo in days", false, 2, "name", "{ebfdf19b-a4a8-be46-8118-6f313da550fd}", nil]
|
4
|
+
|
5
|
+
|
6
|
+
|
7
|
+
The element reference isn't being sent properly.
|
8
|
+
|
9
|
+
|
10
|
+
Ruby server output:
|
11
|
+
|
12
|
+
catch(e) { throw (e instanceof Error) ? e : new Error(e); }, [allinfo in days, false, 2, name, 0, body]])
|
13
|
+
|
14
|
+
|
15
|
+
element.ref is 0
|
16
|
+
|
17
|
+
|
18
|
+
Protractor server output:
|
19
|
+
|
20
|
+
[allinfo in days, false, 2, name, [[FirefoxDriver: firefox on MAC (7f28005d-8e97-024e-86aa-879ee42d9e76)] -> css selector: .allinfo], body]]
|
21
|
+
|
22
|
+
catch(e) { throw (e instanceof Error) ? e : new Error(e); }, [allinfo in days, false, 2, name, [[FirefoxDriver: firefox on MAC (7f28005d-8e97-024e-86aa-879ee42d9e76)] -> css selector: .allinfo], body]]
|
23
|
+
|
24
|
+
---
|
25
|
+
|
26
|
+
browser.get('http://localhost:8081/#/repeater')
|
27
|
+
|
28
|
+
|
29
|
+
var secondName = element(by.css('.allinfo')).element(by.repeater('allinfo in days').column('name').row(2))
|
30
|
+
|
31
|
+
---
|
32
|
+
|
33
|
+
> e.ref
|
34
|
+
=> "2"
|
35
|
+
|
36
|
+
|
37
|
+
driver.execute_script 'return arguments[0].tagName', e
|
38
|
+
> DIV
|
39
|
+
driver.execute_script 'return arguments[0].tagName', e.ref
|
40
|
+
> nil
|
41
|
+
|
42
|
+
driver.execute_script 'return arguments[0].tagName', 2
|
43
|
+
=> nil
|
44
|
+
|
45
|
+
driver.execute_script 'return arguments[0].tagName', '2'
|
46
|
+
=> nil
|
47
|
+
|
48
|
+
|
49
|
+
driver.execute_script 'return arguments[0].tagName', '{7c79f087-b7a2-dd43-be77-2a4076cb5959}'
|
50
|
+
=> nil
|
51
|
+
|
52
|
+
|
53
|
+
e = browser.element(tag_name: 'div')
|
54
|
+
|
55
|
+
|
56
|
+
---
|
57
|
+
|
58
|
+
> command_hash
|
59
|
+
=> {:script=>"return arguments[0].tagName", :args=>[#<Selenium::WebDriver::Element:0x..fea95b8c7217851b0 id="0">]}
|
60
|
+
|
61
|
+
---
|
62
|
+
|
63
|
+
e = browser.element(tag_name: 'div')
|
64
|
+
driver.execute_script 'return arguments[0].tagName', e
|
65
|
+
|
66
|
+
Requesting: {"script":"return arguments[0].tagName","args":["#<Watir::HTMLElement:0x007f8a6d098ae0>"]}
|
67
|
+
|
68
|
+
patched webdriver http default to output request body
|
69
|
+
|
70
|
+
def new_request_for(verb, url, headers, payload)
|
71
|
+
puts "Requesting: #{req.body}"
|
72
|
+
|
73
|
+
---
|
74
|
+
|
75
|
+
e = driver.find_element(tag_name: 'div')
|
76
|
+
driver.execute_script 'return arguments[0].tagName', e
|
77
|
+
Requesting: {"script":"return arguments[0].tagName","args":[{"ELEMENT":"0"}]}
|
78
|
+
|
79
|
+
----
|
80
|
+
|
81
|
+
> puts e.to_json
|
82
|
+
{"ELEMENT":"0"}
|
83
|
+
|
84
|
+
---
|
85
|
+
|
86
|
+
|
87
|
+
element id changes depending on if a remote bridge or a firefox bridge is used.
|
88
|
+
remote bridges start at 0 and go up by one.
|
89
|
+
|
90
|
+
firefox bridge uses hashes.
|
91
|
+
|
92
|
+
---
|
93
|
+
|
94
|
+
driver.execute_script 'return arguments[0].tagName', WrappedParent.new('0')
|