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.
Files changed (71) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -0
  3. data/.gitmodules +3 -0
  4. data/.rspec +2 -0
  5. data/.travis.yml +32 -0
  6. data/Gemfile +2 -0
  7. data/Thorfile +33 -1
  8. data/angular_webdriver.gemspec +10 -3
  9. data/docs/overview.md +101 -0
  10. data/docs/sync.md +53 -0
  11. data/lib/angular_webdriver/protractor/by.rb +331 -0
  12. data/lib/angular_webdriver/protractor/by_repeater_inner.rb +106 -0
  13. data/lib/angular_webdriver/protractor/client_side_scripts.rb +1035 -0
  14. data/lib/angular_webdriver/protractor/protractor.rb +396 -77
  15. data/lib/angular_webdriver/protractor/protractor_element.rb +33 -0
  16. data/lib/angular_webdriver/protractor/rspec_helpers.rb +19 -0
  17. data/lib/angular_webdriver/protractor/watir_patch.rb +209 -0
  18. data/lib/angular_webdriver/protractor/webdriver_patch.rb +246 -0
  19. data/lib/angular_webdriver/version.rb +2 -2
  20. data/lib/angular_webdriver.rb +14 -1
  21. data/{LICENSE → license/angular_webdriver/LICENSE.txt} +0 -0
  22. data/{lib/angular_webdriver → license}/protractor/LICENSE.txt +0 -0
  23. data/{lib/angular_webdriver/protractor/get_url_trace.rb → notes/bootstrap_notes.md} +13 -0
  24. data/notes/element_by_id/element_by_id_sync_off.txt +12 -0
  25. data/notes/element_by_id/element_by_id_sync_on.txt +74 -0
  26. data/notes/element_chaining_debug.txt +94 -0
  27. data/notes/evaluate/js_evaluate_sync_on.txt +60 -0
  28. data/notes/evaluate/ruby_evaluate_sync_on.txt +35 -0
  29. data/notes/get_title/browser_get_title_sync_off.txt +11 -0
  30. data/notes/get_title/browser_get_title_sync_on.txt +54 -0
  31. data/notes/phantomjs.md +23 -0
  32. data/notes/protractor_cli_bugs.txt +39 -0
  33. data/notes/protractor_get/protractor_get.rb +102 -0
  34. data/notes/protractor_get/protractor_get_website_sync_off.txt +11 -0
  35. data/notes/protractor_get/protractor_get_website_sync_on.txt +86 -0
  36. data/notes/repeater/findAllRepeaterRows_annotated.txt +150 -0
  37. data/notes/repeater/findAllRepeaterRows_raw.txt +145 -0
  38. data/notes/repeater/findRepeaterColumn_annotated.txt +317 -0
  39. data/notes/repeater/findRepeaterColumn_raw.txt +310 -0
  40. data/notes/repeater/findRepeaterElement_annotated.txt +152 -0
  41. data/notes/repeater/findRepeaterElement_raw.txt +146 -0
  42. data/notes/repeater/findRepeaterRows_annotated.txt +156 -0
  43. data/notes/repeater/findRepeaterRows_raw.txt +152 -0
  44. data/notes/sync_after.md +46 -0
  45. data/notes/sync_notes.md +137 -0
  46. data/notes/synchronize_spec/status_gettext.txt +121 -0
  47. data/notes/synchronize_spec/status_gettext_x3.txt +451 -0
  48. data/notes/synchronize_spec/synchronize_spec.js.txt +74 -0
  49. data/notes/synchronize_spec/watir_gettext.txt +73 -0
  50. data/readme.md +52 -12
  51. data/release_notes.md +127 -0
  52. data/selenium_server/lib/logs.rb +50 -0
  53. data/selenium_server/lib/selenium_server.rb +21 -0
  54. data/selenium_server/readme.md +3 -0
  55. data/selenium_server/spec/logs_spec.rb +18 -0
  56. data/selenium_server/spec/nodejs_sync_spec_waithttp_annotated.txt +54 -0
  57. data/selenium_server/spec/nodejs_sync_spec_waithttp_raw.txt +367 -0
  58. data/selenium_server/spec/nodejs_sync_spec_waithttp_raw_processed.txt +43 -0
  59. data/selenium_server/spec/ruby_sync_spec_waithttp_annotated.txt +59 -0
  60. data/selenium_server/spec/ruby_sync_spec_waithttp_raw.txt +267 -0
  61. data/selenium_server/spec/ruby_sync_spec_waithttp_raw_processed.txt +39 -0
  62. data/selenium_server/spec/spec_helper.rb +6 -0
  63. data/selenium_server/spec/status_gettext_x3.txt +429 -0
  64. data/selenium_server/spec/status_gettext_x3_annotated.txt +86 -0
  65. metadata +91 -18
  66. data/lib/angular_webdriver/protractor/clientSideScripts.json +0 -19
  67. data/lib/angular_webdriver/protractor/clientsidescripts.js +0 -671
  68. data/lib/angular_webdriver/protractor/scripts.rb +0 -7
  69. data/lib/angular_webdriver/protractor/scripts_to_json.js +0 -11
  70. data/spec/protractor_spec.rb +0 -40
  71. data/spec/spec_helper.rb +0 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 773f24595c7f7a746b7dac72ca67b1bb3169172e
4
- data.tar.gz: 24cc4556436a5b24331643c1eac49e6dac68539a
3
+ metadata.gz: 36f37457d0ef4a33d6270ff82c314b816287f340
4
+ data.tar.gz: f322210de5d4c17b9cc69c544399f793f0e5f901
5
5
  SHA512:
6
- metadata.gz: a3452bf11123a4c95bf9fc5debabd8b1f7c8168ec1cde4ef29fd2cb039d63f9e597028c66073cff16f8392eb6b66467af646108d9bb4486cc857e2cea0f217d2
7
- data.tar.gz: e1357c868b83a4c9318d8c06e0e2d6b5b22669a88153bff792851abba9de032527d6748c1879f8647bb96adf618d1070e0a055e76cf7e6295c3d5eaa2d7e8c6c
6
+ metadata.gz: b26d552ec0240432af01e600c4936e5ecce7e014275168df066e3fb99d3b51e9db6b74b1c283730f29fe3d1ac393a29f8df42a5402349a0e6757410f1790c94f
7
+ data.tar.gz: 6322971ae12c782a76058ba8355e8ac1a526ff9dfbf4fab50180080eb5017faa4537ef4bb217bf7db883a0311616b43a1c8dcc91bd0336f98bc859e087f87225
data/.gitignore CHANGED
@@ -35,3 +35,6 @@ build/
35
35
 
36
36
  # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
37
37
  .rvmrc
38
+
39
+ chromedriver
40
+ *.zip
data/.gitmodules ADDED
@@ -0,0 +1,3 @@
1
+ [submodule "protractor"]
2
+ path = protractor
3
+ url = https://github.com/angular/protractor.git
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,32 @@
1
+ language: node_js
2
+ node_js:
3
+ - stable
4
+ sudo: false
5
+ cache:
6
+ bundler: true
7
+ directories:
8
+ - protractor/node_modules
9
+ before_install:
10
+ - rvm install 2.2.2
11
+ - rvm --default use 2.2.2
12
+ - gem update --remote bundler
13
+ install:
14
+ - bundle install --retry=3
15
+ before_script:
16
+ - export DISPLAY=:99.0
17
+ - sh -e /etc/init.d/xvfb start
18
+ - ruby -v
19
+ - cd protractor
20
+ - npm install
21
+ - cd testapp
22
+ - npm start & # must background the webserver
23
+ - cd ..; cd .. # return to project root
24
+ - sleep 5 # wait for servers to start
25
+ script:
26
+ - bundle exec thor gen # regenerate every time before testing
27
+ - bundle exec thor compare # ensure ported protractor tests stay up to date
28
+ - bundle exec thor spec
29
+ notifications:
30
+ email:
31
+ on_success: never
32
+ on_failure: never
data/Gemfile CHANGED
@@ -1,3 +1,5 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
3
  gemspec
4
+
5
+ gem 'thor' # enable: bundle exec thor spec
data/Thorfile CHANGED
@@ -1,6 +1,38 @@
1
+ require 'rubygems'
1
2
  require 'appium_thor'
2
3
 
3
4
  Appium::Thor::Config.set do
4
5
  gem_name 'angular_webdriver'
5
6
  github_owner 'bootstraponline'
6
- end
7
+ end
8
+
9
+ # Must use '::' otherwise Default will point to Thor::Sandbox::Default
10
+ # Debug by calling Thor::Base.subclass_files via Pry
11
+ #
12
+ # https://github.com/erikhuda/thor/issues/484
13
+ #
14
+ class ::Default < Thor
15
+ desc 'spec', 'Run RSpec tests'
16
+ def spec
17
+ exec 'rspec spec'
18
+ end
19
+
20
+ desc 'gen', 'Generate client_side_scripts.rb'
21
+ def gen
22
+ commands = [
23
+ 'node ./gen/scripts_to_json.js',
24
+ 'ruby ./gen/json_to_rb.rb'
25
+ ].join ';'
26
+ exec commands
27
+
28
+ # only the first exec will work so we can't use two of them.
29
+ end
30
+
31
+ desc 'compare', 'Compare protractor JS specs to ruby specs'
32
+ def compare
33
+ commands = [
34
+ 'rspec ./gen/compare_specs.rb'
35
+ ].join ';'
36
+ exec commands
37
+ end
38
+ end
@@ -14,12 +14,19 @@ Gem::Specification.new do |s|
14
14
  s.homepage = 'https://github.com/bootstraponline/angular_webdriver'
15
15
  s.require_paths = ['lib']
16
16
 
17
- s.add_runtime_dependency 'selenium-webdriver', '>= 2.45.0'
17
+ # The selenium-webdriver and watir-webdriver patches may require updating
18
+ # as the gems change. To ensure angular_webdriver continues to work
19
+ # exact versions of both are used.
20
+ s.add_runtime_dependency 'selenium-webdriver', '= 2.46.2'
21
+ s.add_runtime_dependency 'watir-webdriver', '= 0.7.0'
22
+ s.add_runtime_dependency 'webdriver_utils', '>= 0.0.5'
18
23
 
19
24
  s.add_development_dependency 'rspec', '>= 3.2.0'
20
25
  s.add_development_dependency 'appium_thor', '>= 0.0.7'
21
26
  s.add_development_dependency 'pry', '>= 0.10.1'
22
- s.add_development_dependency 'webdriver_utils', '>= 0.0.3'
27
+ s.add_development_dependency 'trace_files', '~> 0.0.2'
23
28
 
24
- s.files = `git ls-files`.split "\n"
29
+ s.files = `git ls-files -z`.split("\x0").reject do |f|
30
+ f.match(%r{^(gen|test|spec|features|protractor)/})
31
+ end
25
32
  end
data/docs/overview.md ADDED
@@ -0,0 +1,101 @@
1
+ # Overview
2
+
3
+ angular_webdriver is a Ruby wrapper for Protractor's client side JavaScript.
4
+
5
+ Features supported:
6
+
7
+ - **Auto synchronization feature** - Protractor automatically
8
+ runs the waitForAngular command before selected WebDriver actions.
9
+ See [sync](sync.md) for details.
10
+ - **Protractor.root_element** - The css selector for an element on which to find Angular.
11
+ - **Protractor.ignore_sync** - If true, Protractor will not attempt to synchronize with
12
+ the page before performing actions.
13
+ - **Protractor.base_url** - When set driver.get will resolve relative urls
14
+ against the base_url
15
+ - **Protractor.client_side_scripts** - All of protractor's client side scripts
16
+ are available as strings
17
+ - **Protractor.reset_url** - driver.get will use an appropriate reset url when
18
+ synchronizing with the page
19
+ - **Protractor.get** - Navigate to the given destination. Assumes that the page
20
+ being loaded uses Angular. driver.get uses protractor.get. Uses base_url,
21
+ reset_url, and waits for angular to load.
22
+ - **Protractor.refresh** - Makes a full reload of the current page. Assumes
23
+ that the page being loaded uses Angular.
24
+ - **Protractor.setLocation** - Browse to another page using in-page navigation.
25
+ Assumes that the page being loaded uses Angular.
26
+ - **Protractor.getLocationAbsUrl** - Returns the current absolute url from
27
+ AngularJS. Waits for angular.
28
+ - **Protractor.waitForAngular** - Waits for angular to finish loading.
29
+ - **Protractor.executeAsyncScript_** - Same as driver.execute_async_script
30
+ but with comment for debugging.
31
+ - **Protractor.executeScript_** - Same as driver.execute_script but with
32
+ comment for debugging.
33
+ - **Protractor.debugger** - Injects client side scripts into
34
+ window.clientSideScripts for debugging.
35
+ - **Protractor.allowAnimations** - Control if animation is allowed on
36
+ the current underlying elements.
37
+ - **element.evaluate** - Evaluate an Angular expression as if it were on the scope
38
+ of the given element.
39
+
40
+ ## Protractor semantics
41
+
42
+ The `by` syntax, such as `by.binding`, lazily finds elements.
43
+
44
+ - `element(by.binding('slowHttpStatus'))` - This will not locate the element until
45
+ the element is used (such as calling .value or explicitly invoking .locate)
46
+ - `element.all(by.partialButtonText('text'))` - This will not locate the elements
47
+ until `.to_a` is invoked.
48
+
49
+ In addition to lazy locating, elements are always rediscovered. element.value
50
+ will always first find the element and then get the value. The reason elements
51
+ are rediscovered each time instead of cached is that Protrator relies on running
52
+ waitForAngular before certain webdriver commands. For elements, the sync behavior
53
+ is triggered when we find an element. If we didn't always rediscover elements then
54
+ the element.value method wouldn't trigger a waitForAngular call and the page
55
+ could still be processing angular logic.
56
+
57
+ ## Supported Protractor Locators
58
+
59
+ Note these work the same as standard locators.
60
+ You can find a single element (find_element), multiple elements (find_elements),
61
+ and the finders are chainable (finding elements from a parent element). The protractor syntax
62
+ (element/element.all) is also available as an alternative to find_element/find_elements.
63
+
64
+ Client side script | Protractor | WebDriver
65
+ --- | --- | ---
66
+ **binding** | `element(by.binding('slowHttpStatus')).locate` | `driver.find_element(:binding, 'slowHttpStatus')`
67
+ **findByPartialButtonText** | `element.all(by.partialButtonText('text')).to_a` | `driver.find_elements(:findByPartialButtonText, 'slowHttpStatus')`
68
+ **findByButtonText** | `element.all(by.buttonText('Exact text')).to_a` | `driver.find_elements(:buttonText, 'Exact text')`
69
+ **findByModel** | `element(by.model('username'))` | `driver.find_element(:model, 'username')`
70
+ **findByOptions** | `element.all(by.options('fruit')).to_a` | `driver.find_elements(:options, 'fruit')`
71
+ **findByCssContainingText** | `element.all(by.cssContainingText('#animals ul .pet', 'dog')).to_a` | `driver.find_elements(:cssContainingText, { cssSelector: '#animals ul .pet', searchText: 'dog' }.to_json)`
72
+ **findRepeaterRows** | `element(by.repeater('baz in days').row(0))` | -
73
+ **findAllRepeaterRows** | `element(by.repeater('baz in days'));` | -
74
+ **findRepeaterElement** | `element(by.repeater('baz in days').row(0).column('b'))` | -
75
+ **findRepeaterColumn** | `element(by.repeater('baz in days').column('b'))` | -
76
+
77
+ ## By locators
78
+
79
+ All the [Protractor by locators](http://angular.github.io/protractor/#/api?view=ProtractorBy) are supported.
80
+ Camel case (by.deepCss) and snake case (by.deep_css) are both supported.
81
+
82
+ - by.binding
83
+ - by.exactBinding
84
+ - by.partialButtonText
85
+ - by.buttonText
86
+ - by.model
87
+ - by.options
88
+ - by.cssContainingText
89
+ - by.repeater
90
+ - by.exactRepeater
91
+ - by.deepCss
92
+
93
+ ## Waiting
94
+
95
+ Implicit waits are [unreliable](http://stackoverflow.com/questions/15164742/combining-implicit-wait-and-explicit-wait-together-results-in-unexpected-wait-ti#answer-15174978)
96
+ due to being baked into the remote driver. Waiting in angular_webdriver has been
97
+ reimplemented client side to avoid flakiness.
98
+
99
+ **driver.set_max_wait(5)** - wait up to 5 seconds for an exception to not be raised
100
+ when finding an element.
101
+ **driver.max_wait_seconds** - The max wait amount (default 0) in seconds.
data/docs/sync.md ADDED
@@ -0,0 +1,53 @@
1
+ # Sync
2
+
3
+ Protractor provides two methods to synchronize with AngularJS. The first is
4
+ `testForAngular`. `testForAngular` is exclusively used for protractor's custom
5
+ get method for pausing the angular bootstrap before injecting mocks then resuming
6
+ the bootstrap.
7
+
8
+ There are better ways to mock angular apps than injecting them via selenium tests.
9
+ The mock feature of Protractor is not planned to be implemented in angular_webdriver
10
+ unless a compelling use case is found. The `testForAngular` client side script
11
+ remains available however it's not recommended for use.
12
+
13
+ ## waitForAngular
14
+
15
+ `waitForAngular` is protractor's second method for syncing with Angular.
16
+ `waitForAngular` is automatically invoked before selected webdriver commands
17
+ to eliminate the need for waits. Any command that interacts with an element,
18
+ such as element.value, will cause that element to be located again. When an
19
+ element is requested to be located, the `waitForAngular` command is
20
+ run before locating the element.
21
+
22
+ The following webdriver commands execute `waitForAngular` before running.
23
+ The single exception is `get`, in that case `waitForAngular` executes after
24
+ the `get` command completes.
25
+
26
+ Internal Command | Driver command
27
+ --- | ---
28
+ `:getCurrentUrl` | driver.current_url
29
+ `:get` | driver.get 'http://www.angularjs.org'
30
+ `:refresh` | driver.navigate.refresh
31
+ `:getPageSource` | driver.page_source
32
+ `:getTitle` | driver.title
33
+ `:findElement` | driver.find_element(:tag_name, 'html')
34
+ `:findElements` | driver.find_elements(:tag_name, 'html')
35
+ `:findChildElement` | driver.find_element(:tag_name, 'html').find_element(:xpath, '//html')
36
+ `:findChildElements` | driver.find_element(:tag_name, 'html').find_elements(:xpath, '//html')
37
+
38
+ The following custom Protractor commands also automatically execute `waitForAngular`
39
+
40
+ Protractor Command | Note
41
+ --- | ---
42
+ `Protractor.get 'url'` | driver.get redirects to protractor.get
43
+ `Protractor.setLocation 'url'` | Note this is unrelated to the selenium setLocation for geographic position
44
+ `Protractor.getLocationAbsUrl` |
45
+
46
+ Sync can be toggled by running:
47
+
48
+ - `protractor.ignore_sync = true` Don't run waitForAngular
49
+ - `protractor.ignore_sync = false` Run waitForAngular
50
+
51
+ To get always get a url without syncing use:
52
+
53
+ `protractor.driver_get 'url'`
@@ -0,0 +1,331 @@
1
+ module AngularWebdriver
2
+ class By
3
+ class << self
4
+
5
+ #
6
+ # Selenium locators
7
+ #
8
+
9
+ def class what
10
+ { class: what }
11
+ end
12
+
13
+ def class_name what
14
+ { class_name: what }
15
+ end
16
+
17
+ def css what
18
+ { css: what }
19
+ end
20
+
21
+ def id what
22
+ { id: what }
23
+ end
24
+
25
+ def link what
26
+ { link: what }
27
+ end
28
+
29
+ def link_text what
30
+ { link_text: what }
31
+ end
32
+
33
+ def name what
34
+ { name: what }
35
+ end
36
+
37
+ def partial_link_text what
38
+ { partial_link_text: what }
39
+ end
40
+
41
+ def tag_name what
42
+ { tag_name: what }
43
+ end
44
+
45
+ def xpath what
46
+ { xpath: what }
47
+ end
48
+
49
+ #
50
+ # Protractor locators
51
+ # See protractor/lib/locators.js
52
+ #
53
+
54
+ # Find an element by binding. Does a partial match, so any elements bound to
55
+ # variables containing the input string will be returned.
56
+ #
57
+ # Note: For AngularJS version 1.2, the interpolation brackets, usually {{}},
58
+ # are allowed in the binding description string. For Angular version 1.3, they
59
+ # are not allowed, and no elements will be found if they are used.
60
+ #
61
+ # @view
62
+ # <span>{{person.name}}</span>
63
+ # <span ng-bind="person.email"></span>
64
+ #
65
+ # @example
66
+ # span1 = element(by.binding('person.name'))
67
+ # expect(span1.text).to eq('Foo')
68
+ #
69
+ # span2 = element(by.binding('person.email'))
70
+ # expect(span2.text.to eq('foo@bar.com')
71
+ #
72
+ # # You can also use a substring for a partial match
73
+ # span1alt = element(by.binding('name'))
74
+ # expect(span1alt.text).to eq('Foo')
75
+ #
76
+ # # This works for sites using Angular 1.2 but NOT 1.3
77
+ # deprecatedSyntax = element(by.binding('{{person.name}}'))
78
+ #
79
+ # @param binding_descriptor <String>
80
+ # @return { binding: binding_descriptor }
81
+ def binding binding_descriptor
82
+ { binding: binding_descriptor }
83
+ end
84
+
85
+ # Find an element by exact binding.
86
+ #
87
+ # @view
88
+ # <span>{{ person.name }}</span>
89
+ # <span ng-bind="person-email"></span>
90
+ # <span>{{person_phone|uppercase}}</span>
91
+ #
92
+ # @example
93
+ # expect(element(by.exactBinding('person.name')).present?).to eq(true);
94
+ # expect(element(by.exactBinding('person-email')).present?).to eq(true);
95
+ # expect(element(by.exactBinding('person')).present?).to eq(false);
96
+ # expect(element(by.exactBinding('person_phone')).present?).to eq(true);
97
+ # expect(element(by.exactBinding('person_phone|uppercase')).present?).to eq(true);
98
+ # expect(element(by.exactBinding('phone')).present?).to eq(false);
99
+ #
100
+ # @param binding_descriptor <String>
101
+ # @return { exactBinding: binding_descriptor }
102
+ def exactBinding binding_descriptor
103
+ { exactBinding: binding_descriptor }
104
+ end
105
+
106
+ # Find a button by partial text.
107
+ #
108
+ # @view
109
+ # <button>Save my file</button>
110
+ #
111
+ # @example
112
+ # element(by.partialButtonText('Save'))
113
+ #
114
+ # @param search_text <String>
115
+ # @return { partialButtonText: search_text }
116
+ def partialButtonText search_text
117
+ { partialButtonText: search_text }
118
+ end
119
+
120
+ # Find a button by text.
121
+ #
122
+ # @view
123
+ # <button>Save</button>
124
+ #
125
+ # @example
126
+ # element(by.buttonText('Save'))
127
+ #
128
+ # @param search_text <String>
129
+ # @return {buttonText: search_text }
130
+ def buttonText search_text
131
+ { buttonText: search_text }
132
+ end
133
+
134
+ # Find an element by ng-model expression.
135
+ #
136
+ # @alias by.model(modelName)
137
+ # @view
138
+ # <input type="text" ng-model="person.name">
139
+ #
140
+ # @example
141
+ # input = element(by.model('person.name'))
142
+ # input.send_keys('123')
143
+ # expect(input.value).to eq('Foo123')
144
+ #
145
+ # @param model_expression <String> ng-model expression.
146
+ # @return { model: model_expression }
147
+ def model model_expression
148
+ { model: model_expression }
149
+ end
150
+
151
+ # Find an element by ng-options expression.
152
+ #
153
+ # @alias by.options(optionsDescriptor)
154
+ # @view
155
+ # <select ng-model="color" ng-options="c for c in colors">
156
+ # <option value="0" selected="selected">red</option>
157
+ # <option value="1">green</option>
158
+ # </select>
159
+ #
160
+ # @example
161
+ # allOptions = element.all(by.options('c for c in colors')).to_a
162
+ # expect(allOptions.length).to eq(2);
163
+ # firstOption = allOptions.first
164
+ # expect(firstOption.text).to eq('red')
165
+ #
166
+ # @param options_descriptor <String> ng-options expression.
167
+ # @return { options: options_descriptor }
168
+ def options options_descriptor
169
+ { options: options_descriptor }
170
+ end
171
+
172
+ # Find elements by CSS which contain a certain string.
173
+ #
174
+ # @view
175
+ # <ul>
176
+ # <li class="pet">Dog</li>
177
+ # <li class="pet">Cat</li>
178
+ # </ul>
179
+ #
180
+ # @example
181
+ # # Returns the li for the dog, but not cat.
182
+ # dog = element(by.cssContainingText('.pet', 'Dog'))
183
+ # @return { cssContainingText: { cssSelector: css_selector, searchText: search_text } }
184
+ def cssContainingText css_selector, search_text
185
+ # the "what" must be a string or watir will complain it's not a valid what.
186
+ # even if watir is patched to accept hashes, the what will be converted
187
+ # to a string by the time it's seen by selenium webdriver.
188
+ { cssContainingText: { cssSelector: css_selector, searchText: search_text }.to_json }
189
+ end
190
+
191
+ # Find elements inside an ng-repeat.
192
+ #
193
+ # @view
194
+ # <div ng-repeat="cat in pets">
195
+ # <span>{{cat.name}}</span>
196
+ # <span>{{cat.age}}</span>
197
+ # </div>
198
+ #
199
+ # <div class="book-img" ng-repeat-start="book in library">
200
+ # <span>{{$index}}</span>
201
+ # </div>
202
+ # <div class="book-info" ng-repeat-end>
203
+ # <h4>{{book.name}}</h4>
204
+ # <p>{{book.blurb}}</p>
205
+ # </div>
206
+ #
207
+ # @example
208
+ # // Returns the DIV for the second cat.
209
+ # secondCat = element(by.repeater('cat in pets').row(1));
210
+ #
211
+ # // Returns the SPAN for the first cat's name.
212
+ # firstCatName = element(by.repeater('cat in pets').
213
+ # row(0).column('cat.name'));
214
+ #
215
+ # // Returns a promise that resolves to an array of WebElements from a column
216
+ # ages = element.all(
217
+ # by.repeater('cat in pets').column('cat.age'));
218
+ #
219
+ # // Returns a promise that resolves to an array of WebElements containing
220
+ # // all top level elements repeated by the repeater. For 2 pets rows resolves
221
+ # // to an array of 2 elements.
222
+ # rows = element.all(by.repeater('cat in pets'));
223
+ #
224
+ # // Returns a promise that resolves to an array of WebElements containing all
225
+ # // the elements with a binding to the book's name.
226
+ # divs = element.all(by.repeater('book in library').column('book.name'));
227
+ #
228
+ # // Returns a promise that resolves to an array of WebElements containing
229
+ # // the DIVs for the second book.
230
+ # bookInfo = element.all(by.repeater('book in library').row(1));
231
+ #
232
+ # // Returns the H4 for the first book's name.
233
+ # firstBookName = element(by.repeater('book in library').
234
+ # row(0).column('book.name'));
235
+ #
236
+ # // Returns a promise that resolves to an array of WebElements containing
237
+ # // all top level elements repeated by the repeater. For 2 books divs
238
+ # // resolves to an array of 4 elements.
239
+ # divs = element.all(by.repeater('book in library'));
240
+ #
241
+ # @param {string} repeatDescriptor
242
+ # @return {{findElementsOverride: findElementsOverride, toString: Function|string}}
243
+ #
244
+ def repeater repeat_descriptor
245
+ ByRepeaterInner.new exact: false, repeat_descriptor: repeat_descriptor
246
+ end
247
+
248
+ # Find an element by exact repeater.
249
+ #
250
+ # @view
251
+ # <li ng-repeat="person in peopleWithRedHair"></li>
252
+ # <li ng-repeat="car in cars | orderBy:year"></li>
253
+ #
254
+ # @example
255
+ # expect(element(by.exactRepeater('person in peopleWithRedHair')).present?)
256
+ # .to eq(true);
257
+ # expect(element(by.exactRepeater('person in people')).present?).to eq(false);
258
+ # expect(element(by.exactRepeater('car in cars')).present?).to eq(true);
259
+ #
260
+ # @param {string} repeatDescriptor
261
+ # @return {{findElementsOverride: findElementsOverride, toString: Function|string}}
262
+ #
263
+ def exactRepeater repeat_descriptor
264
+ ByRepeaterInner.new exact: true, repeat_descriptor: repeat_descriptor
265
+ end
266
+
267
+ # Find an element by css selector within the Shadow DOM.
268
+ #
269
+ # @alias by.deepCss(selector)
270
+ # @view
271
+ # <div>
272
+ # <span id="outerspan">
273
+ # <"shadow tree">
274
+ # <span id="span1"></span>
275
+ # <"shadow tree">
276
+ # <span id="span2"></span>
277
+ # </>
278
+ # </>
279
+ # </div>
280
+ # @example
281
+ # spans = element.all(by.deepCss('span'));
282
+ # expect(spans.count()).to eq(3);
283
+ def deepCss selector
284
+ # TODO: syntax will change from /deep/ to >>> at some point.
285
+ { css: '* /deep/ ' + selector }
286
+ end
287
+ end # class << self
288
+ end # class By
289
+ end # module AngularWebdriver
290
+
291
+ # Support camel and snake case for protractor compatibility
292
+ # deepCss & deep_css
293
+ AngularWebdriver::By.singleton_methods.each do |method_symbol|
294
+ by = AngularWebdriver::By
295
+ next if method_symbol == :yaml_tag # skip ruby core psych
296
+
297
+ method_name = method_symbol.to_s
298
+ is_snake_case = method_name.include? '_'
299
+ is_camel_case = !!method_name.match(/[a-z][A-Z]/)
300
+
301
+ if is_snake_case # deep_css -> deepCss
302
+ camel_case = method_name.gsub(/([a-z\d])_([a-z])/) do
303
+ full, one, two = Regexp.last_match.to_a
304
+ "#{one}#{two.upcase}"
305
+ end
306
+ next if by.respond_to? camel_case
307
+ by.define_singleton_method camel_case do |*args|
308
+ by.method(method_symbol).call *args
309
+ end
310
+ elsif is_camel_case # deepCss -> deep_css
311
+ snake_case = method_name.gsub(/([a-z\d])([A-Z])/, '\1_\2').downcase
312
+ next if by.respond_to? snake_case
313
+ by.define_singleton_method snake_case do |*args|
314
+ by.method(method_symbol).call *args
315
+ end
316
+ end
317
+ end
318
+
319
+ =begin
320
+ > Selenium::WebDriver::SearchContext::FINDERS
321
+ => {:class=>"class name",
322
+ :class_name=>"class name",
323
+ :css=>"css selector",
324
+ :id=>"id",
325
+ :link=>"link text",
326
+ :link_text=>"link text",
327
+ :name=>"name",
328
+ :partial_link_text=>"partial link text",
329
+ :tag_name=>"tag name",
330
+ :xpath=>"xpath"}
331
+ =end