angular_webdriver 0.0.7 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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