capybara-ng 0.0.3

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.
@@ -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