capybara-ng 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,354 @@
1
+ module Angular
2
+ class Driver
3
+ include Capybara::DSL
4
+
5
+ DEFER_LABEL = 'NG_DEFER_BOOTSTRAP!';
6
+
7
+ WEB_ELEMENT_FUNCTIONS = [
8
+ 'click', 'sendKeys', 'getTagName', 'getCssValue', 'getAttribute', 'getText',
9
+ 'getSize', 'getLocation', 'isEnabled', 'isSelected', 'submit', 'clear',
10
+ 'isDisplayed', 'getOuterHtml', 'getInnerHtml', 'getId'
11
+ ]
12
+
13
+ DEFAULT_RESET_URL = 'data:text/html,<html></html>'
14
+ DEFAULT_GET_PAGE_TIMEOUT = 10000
15
+
16
+
17
+ def initialize(opt)
18
+ @element = ElementHelper.new(self)
19
+
20
+ # All get methods will be resolved against this base URL. Relative URLs are =
21
+ # resolved the way anchor tags resolve.
22
+ #
23
+ # @type {string}
24
+ @baseUrl = opt[:base_url] || '';
25
+
26
+ #
27
+ # The css selector for an element on which to find Angular. This is usually
28
+ # 'body' but if your ng-app is on a subsection of the page it may be
29
+ # a subelement.
30
+ #
31
+ # @type {string}
32
+ @rootEl = opt[:root_element] || 'body';
33
+
34
+ #
35
+ # If true, Protractor will not attempt to synchronize with the page before
36
+ # performing actions. This can be harmful because Protractor will not wait
37
+ # until $timeouts and $http calls have been processed, which can cause
38
+ # tests to become flaky. This should be used only when necessary, such as
39
+ # when a page continuously polls an API using $timeout.
40
+ #
41
+ # @type {boolean}
42
+ @ignoreSynchronization = false;
43
+
44
+ #
45
+ # Timeout in milliseconds to wait for pages to load when calling `get`.
46
+ #
47
+ # @type {number}
48
+ @getPageTimeout = DEFAULT_GET_PAGE_TIMEOUT;
49
+
50
+ #
51
+ # An object that holds custom test parameters.
52
+ #
53
+ # @type {Object}
54
+ @params = {};
55
+
56
+ #
57
+ # The reset URL to use between page loads.
58
+ #
59
+ # @type {string}
60
+ @resetUrl = DEFAULT_RESET_URL;
61
+
62
+ #
63
+ # Information about mock modules that will be installed during every
64
+ # get().
65
+ #
66
+ # @type {Array<{name: string, script: function|string, args: Array.<string>}>}
67
+ @mockModules = [];
68
+
69
+ @addBaseMockModules_();
70
+
71
+ @functions = ClientScript.functions
72
+ end
73
+
74
+ # @see http://artsy.github.io/blog/2012/02/03/reliably-testing-asynchronous-ui-w-slash-rspec-and-capybara/
75
+ def wait_for_dom(timeout = Capybara.default_wait_time)
76
+ uuid = SecureRandom.uuid
77
+ page.find("body")
78
+ page.evaluate_script <<-EOS
79
+ _.defer(function() {
80
+ $('body').append("<div id='#{uuid}'></div>");
81
+ });
82
+ EOS
83
+ page.find("##{uuid}")
84
+ end
85
+
86
+ def setup
87
+ <<-FN
88
+ // These functions should delegate to the webdriver instance, but should
89
+ // wait for Angular to sync up before performing the action. This does not
90
+ // include functions which are overridden by protractor below.
91
+ var methodsToSync = ['getCurrentUrl', 'getPageSource', 'getTitle'];
92
+
93
+ // Mix all other driver functionality into Protractor.
94
+ for (var method in webdriverInstance) {
95
+ if(!this[method] && typeof webdriverInstance[method] == 'function') {
96
+ if (methodsToSync.indexOf(method) !== -1) {
97
+ mixin(this, webdriverInstance, method, this.waitForAngular.bind(this));
98
+ } else {
99
+ mixin(this, webdriverInstance, method);
100
+ }
101
+ }
102
+ }
103
+ FN
104
+ end
105
+
106
+ def page
107
+ Capybara.current_session
108
+ end
109
+
110
+ def wait_for_angular
111
+ return if @ignoreSynchronization
112
+ result = page.evaluate_script(functions[:waitForAngular])
113
+
114
+ <<-FN
115
+ Protractor.prototype.waitForAngular = function() {
116
+ if (this.ignoreSynchronization) {
117
+ return webdriver.promise.fulfilled();
118
+ }
119
+ return this.driver.executeAsyncScript(
120
+ clientSideScripts.waitForAngular, this.rootEl).then(function(browserErr) {
121
+ if (browserErr) {
122
+ throw 'Error while waiting for Protractor to ' +
123
+ 'sync with the page: ' + JSON.stringify(browserErr);
124
+ }
125
+ }).then(null, function(err) {
126
+ var timeout;
127
+ if (/asynchronous script timeout/.test(err.message)) {
128
+ // Timeout on Chrome
129
+ timeout = /-?[\d\.]*\ seconds/.exec(err.message);
130
+ } else if (/Timed out waiting for async script/.test(err.message)) {
131
+ // Timeout on Firefox
132
+ timeout = /-?[\d\.]*ms/.exec(err.message);
133
+ } else if (/Timed out waiting for an asynchronous script/.test(err.message)) {
134
+ // Timeout on Safari
135
+ timeout = /-?[\d\.]*\ ms/.exec(err.message);
136
+ }
137
+ if (timeout) {
138
+ throw 'Timed out waiting for Protractor to synchronize with ' +
139
+ 'the page after ' + timeout + '. Please see ' +
140
+ 'https://github.com/angular/protractor/blob/master/docs/faq.md';
141
+ } else {
142
+ throw err;
143
+ }
144
+ });
145
+ };
146
+ FN
147
+ end
148
+
149
+ def findElement
150
+ <<-FN
151
+ /**
152
+ * Waits for Angular to finish rendering before searching for elements.
153
+ * @see webdriver.WebDriver.findElement
154
+ * @return {!webdriver.WebElement}
155
+ */
156
+ function(locator) {
157
+ return this.element(locator).getWebElement();
158
+ };
159
+ FN
160
+ end
161
+
162
+ def findElements
163
+ <<-FN
164
+ /**
165
+ * Waits for Angular to finish rendering before searching for elements.
166
+ * @see webdriver.WebDriver.findElements
167
+ * @return {!webdriver.promise.Promise} A promise that will be resolved to an
168
+ * array of the located {@link webdriver.WebElement}s.
169
+ */
170
+ function(locator) {
171
+ return this.element.all(locator).getWebElements();
172
+ };
173
+ FN
174
+ end
175
+
176
+ def isElementPresent
177
+ <<-FN
178
+ /**
179
+ * Tests if an element is present on the page.
180
+ * @see webdriver.WebDriver.isElementPresent
181
+ * @return {!webdriver.promise.Promise} A promise that will resolve to whether
182
+ * the element is present on the page.
183
+ */
184
+ function(locatorOrElement) {
185
+ var element = (locatorOrElement instanceof webdriver.promise.Promise) ?
186
+ locatorOrElement : this.element(locatorOrElement);
187
+ return element.isPresent();
188
+ };
189
+ FN
190
+ end
191
+
192
+ def get
193
+ <<-FN
194
+ /**
195
+ * See webdriver.WebDriver.get
196
+ *
197
+ * Navigate to the given destination and loads mock modules before
198
+ * Angular. Assumes that the page being loaded uses Angular.
199
+ * If you need to access a page which does not have Angular on load, use
200
+ * the wrapped webdriver directly.
201
+ *
202
+ * @param {string} destination Destination URL.
203
+ * @param {number=} opt_timeout Number of milliseconds to wait for Angular to
204
+ * start.
205
+ */
206
+ function(destination, opt_timeout) {
207
+ var timeout = opt_timeout ? opt_timeout : this.getPageTimeout;
208
+ var self = this;
209
+
210
+ destination = this.baseUrl.indexOf('file://') === 0 ?
211
+ this.baseUrl + destination : url.resolve(this.baseUrl, destination);
212
+
213
+ if (this.ignoreSynchronization) {
214
+ return this.driver.get(destination);
215
+ }
216
+
217
+ this.driver.get(this.resetUrl);
218
+ this.driver.executeScript(
219
+ 'window.name = "' + DEFER_LABEL + '" + window.name;' +
220
+ 'window.location.replace("' + destination + '");');
221
+
222
+ // We need to make sure the new url has loaded before
223
+ // we try to execute any asynchronous scripts.
224
+ this.driver.wait(function() {
225
+ return self.driver.executeScript('return window.location.href;').
226
+ then(function(url) {
227
+ return url !== self.resetUrl;
228
+ }, function(err) {
229
+ if (err.code == 13) {
230
+ // Ignore the error, and continue trying. This is because IE
231
+ // driver sometimes (~1%) will throw an unknown error from this
232
+ // execution. See https://github.com/angular/protractor/issues/841
233
+ // This shouldn't mask errors because it will fail with the timeout
234
+ // anyway.
235
+ return false;
236
+ } else {
237
+ throw err;
238
+ }
239
+ });
240
+ }, timeout,
241
+ 'Timed out waiting for page to load after ' + timeout + 'ms');
242
+
243
+ // Make sure the page is an Angular page.
244
+ self.driver.executeAsyncScript(clientSideScripts.testForAngular,
245
+ Math.floor(timeout / 1000)).
246
+ then(function(angularTestResult) {
247
+ var hasAngular = angularTestResult[0];
248
+ if (!hasAngular) {
249
+ var message = angularTestResult[1];
250
+ throw new Error('Angular could not be found on the page ' +
251
+ destination + ' : ' + message);
252
+ }
253
+ }, function(err) {
254
+ throw 'Error while running testForAngular: ' + err.message;
255
+ });
256
+
257
+ // At this point, Angular will pause for us until angular.resumeBootstrap
258
+ // is called.
259
+ var moduleNames = [];
260
+ for (var i = 0; i < this.mockModules_.length; ++i) {
261
+ var mockModule = this.mockModules_[i];
262
+ var name = mockModule.name;
263
+ moduleNames.push(name);
264
+ var executeScriptArgs = [mockModule.script].concat(mockModule.args);
265
+ this.driver.executeScript.apply(this, executeScriptArgs).
266
+ then(null, function(err) {
267
+ throw 'Error while running module script ' + name +
268
+ ': ' + err.message;
269
+ });
270
+ }
271
+
272
+ return this.driver.executeScript(
273
+ 'angular.resumeBootstrap(arguments[0]);',
274
+ moduleNames);
275
+ };
276
+ FN
277
+ end
278
+
279
+ def refresh
280
+ <<-FN
281
+ /**
282
+ * See webdriver.WebDriver.refresh
283
+ *
284
+ * Makes a full reload of the current page and loads mock modules before
285
+ * Angular. Assumes that the page being loaded uses Angular.
286
+ * If you need to access a page which does not have Angular on load, use
287
+ * the wrapped webdriver directly.
288
+ *
289
+ * @param {number=} opt_timeout Number of seconds to wait for Angular to start.
290
+ */
291
+ = function(opt_timeout) {
292
+ var timeout = opt_timeout || 10;
293
+ var self = this;
294
+
295
+ if (self.ignoreSynchronization) {
296
+ return self.driver.navigate().refresh();
297
+ }
298
+
299
+ return self.driver.executeScript('return window.location.href').then(function(href) {
300
+ return self.get(href, timeout);
301
+ });
302
+ };
303
+ FN
304
+ end
305
+
306
+ def navigate
307
+ <<-FN
308
+ /**
309
+ * Mixin navigation methods back into the navigation object so that
310
+ * they are invoked as before, i.e. driver.navigate().refresh()
311
+ */
312
+ function() {
313
+ var nav = this.driver.navigate();
314
+ mixin(nav, this, 'refresh');
315
+ return nav;
316
+ };
317
+ FN
318
+ end
319
+
320
+ def setLocation
321
+ <<-FN
322
+ /**
323
+ * Browse to another page using in-page navigation.
324
+ *
325
+ * @param {string} url In page URL using the same syntax as $location.url()
326
+ * @returns {!webdriver.promise.Promise} A promise that will resolve once
327
+ * page has been changed.
328
+ */
329
+ function(url) {
330
+ this.waitForAngular();
331
+ return this.driver.executeScript(clientSideScripts.setLocation, this.rootEl, url)
332
+ .then(function(browserErr) {
333
+ if (browserErr) {
334
+ throw 'Error while navigating to \'' + url + '\' : ' +
335
+ JSON.stringify(browserErr);
336
+ }
337
+ });
338
+ };
339
+ FN
340
+ end
341
+
342
+ def getLocationAbsUrl
343
+ <<-FN
344
+ /**
345
+ * Returns the current absolute url from AngularJS.
346
+ */
347
+ function() {
348
+ this.waitForAngular();
349
+ return this.driver.executeScript(clientSideScripts.getLocationAbsUrl, this.rootEl);
350
+ };
351
+ FN
352
+ end
353
+ end
354
+ end
@@ -0,0 +1,143 @@
1
+ module Angular
2
+ module DSL
3
+ def ng
4
+ @ng ||= Angular::Setup.new(Capybara.current_session)
5
+ end
6
+
7
+ def ng_install
8
+ ng.install
9
+ end
10
+
11
+ def ng_wait
12
+ ng.ng_wait
13
+ end
14
+
15
+ #
16
+ # @return current location absolute url
17
+ #
18
+ def ng_location_abs(using = 'body')
19
+ ng.make_call :getLocationAbsUrl, [using], false
20
+ end
21
+
22
+ # @return current location absolute url
23
+ #
24
+ def ng_location(using = 'body')
25
+ ng.make_call :getLocation, [using], false
26
+ end
27
+
28
+ #
29
+ # @return current location
30
+ #
31
+ def ng_set_location(url, using = 'body')
32
+ ng.make_call :setLocation, [using, url], false
33
+ end
34
+
35
+ #
36
+ # @return eval result
37
+ #
38
+ def ng_eval(selector, expr)
39
+ ng.make_call :evaluate, [selector, expr], false
40
+ end
41
+
42
+ #
43
+ # Node for nth binding match
44
+ # @return nth node
45
+ #
46
+ def ng_binding(binding, exact = false, row = 0, using = nil , rootSelector = 'body')
47
+ ng_bindings(binding, exact, using , rootSelector)[row]
48
+ end
49
+
50
+ #
51
+ # All nodes matching binding
52
+ #
53
+ # @return [node, ...]
54
+ #
55
+ def ng_bindings(binding, exact = false, using = nil , rootSelector = 'body')
56
+ ng.make_call :findBindings, [binding, exact, using, rootSelector], true
57
+ end
58
+
59
+ #
60
+ # Node for nth model match
61
+ #
62
+ # @return nth node
63
+ #
64
+ def ng_model(model, row = 0, using = nil , rootSelector = 'body')
65
+ ng_models(model, using, rootSelector)[row]
66
+ end
67
+
68
+ #
69
+ # All nodes matching model
70
+ #
71
+ # @return [node, ...]
72
+ #
73
+ def ng_models(model, using = nil , rootSelector = 'body')
74
+ ng.make_call :findByModel, [model, using, rootSelector], true
75
+ end
76
+
77
+ #
78
+ # Node for nth option
79
+ #
80
+ # @return nth node
81
+ #
82
+ def ng_option(options, row = 0, using = nil , rootSelector = 'body')
83
+ ng_options(options, using, rootSelector)[row]
84
+ end
85
+
86
+ #
87
+ # All option values matching option
88
+ # @return [node, ...]
89
+ #
90
+ def ng_options(options, using = nil , rootSelector = 'body')
91
+ ng.make_call :findByOptions, [options, using, rootSelector], true
92
+ end
93
+
94
+ #
95
+ # Node for nth repeater row
96
+ # @return nth node
97
+ #
98
+ def ng_repeater_row(repeater, row = 0, using = nil , rootSelector = 'body')
99
+ ng.make_call(:findRepeaterRows, [repeater, row, using, rootSelector], true).first
100
+ end
101
+
102
+ #
103
+ # All nodes matching repeater
104
+ #
105
+ # @return [node, ...]
106
+ #
107
+ def ng_repeater_rows(repeater, using = nil , rootSelector = 'body')
108
+ ng.make_call :findAllRepeaterRows, [repeater, using, rootSelector], true
109
+ end
110
+
111
+ #
112
+ # Node for column binding value in row
113
+ #
114
+ # @return nth node
115
+ #
116
+ def ng_repeater_column(repeater, binding, row = 0, using = nil , rootSelector = 'body')
117
+ ng_repeater_columns(repeater, binding, using, rootSelector)[row]
118
+ end
119
+
120
+ #
121
+ # Node for column binding value in all rows
122
+ #
123
+ # @return [node, ...]
124
+ #
125
+ def ng_repeater_columns(repeater, binding, using = nil , rootSelector = 'body')
126
+ ng.make_call :findRepeaterColumn, [repeater, binding, using, rootSelector], true
127
+ end
128
+
129
+ #
130
+ # @return nth node
131
+ #
132
+ def ng_repeater_element(repeater, index, binding, row = 0, using = nil, rootSelector = 'body')
133
+ ng_repeater_elements(repeater, index, binding, using, rootSelector)[row]
134
+ end
135
+
136
+ #
137
+ # @return [node, ...]
138
+ #
139
+ def ng_repeater_elements(repeater, index, binding, using = nil, rootSelector = 'body')
140
+ ng.make_call :findRepeaterElement, [repeater, index, binding, using, rootSelector], true
141
+ end
142
+ end
143
+ end