capybara-ng 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +9 -1
- data/lib/angular/capybara_setup.rb +5 -0
- data/lib/angular/client_script.rb +126 -2
- data/lib/angular/dsl.rb +48 -11
- data/lib/angular/setup.rb +31 -1
- data/lib/angular/version.rb +1 -1
- data/lib/angular/waiter.rb +35 -1
- data/travis/run.sh +13 -3
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4751f5d1b3319c7ffdc1524119db8ae2af4ad77b
|
4
|
+
data.tar.gz: 8eb230d62a078b284fa5f04108f2fd26c229d754
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7693856f13f3b8488998226acb849b265ae0e03b905ea8cde880a5fc3aa3424a7f430e6942304624b4c1c19e6ae200ba6152b066831e0c4c0652cb2d1f68912c
|
7
|
+
data.tar.gz: 7c2f25c8bb781d417e3b4011fa5356be4171253526394931609b203c1508cba3e4be18d933fb6e7d799a7cc54d3a1bb7d969761a1b134777474c34bee3b5c98e
|
data/README.md
CHANGED
@@ -3,9 +3,17 @@
|
|
3
3
|
Basic bindings for AngularJS to be used with Capybara. Implementation is based into
|
4
4
|
logic copied from Protractor.
|
5
5
|
|
6
|
-
NOTE
|
6
|
+
*NOTE* At this point of development, I would classify DSL API "unstable". This means that
|
7
7
|
DSL language may change in incompatible ways.
|
8
8
|
|
9
|
+
## Supported drivers
|
10
|
+
|
11
|
+
Based into testing following drivers should work.
|
12
|
+
|
13
|
+
- chrome
|
14
|
+
- poltergeist
|
15
|
+
- webkit
|
16
|
+
|
9
17
|
## Related Projects
|
10
18
|
|
11
19
|
- https://github.com/jnicklas/capybara
|
@@ -40,10 +40,12 @@ FN
|
|
40
40
|
function(binding, exactMatch, using, rootSelector) {
|
41
41
|
rootSelector = rootSelector || 'body';
|
42
42
|
using = using || document.querySelector(rootSelector);
|
43
|
+
|
43
44
|
if (angular.getTestability) {
|
44
45
|
return angular.getTestability(using).
|
45
46
|
findBindings(using, binding, exactMatch);
|
46
47
|
}
|
48
|
+
|
47
49
|
var bindings = using.getElementsByClassName('ng-binding');
|
48
50
|
var matches = [];
|
49
51
|
for (var i = 0; i < bindings.length; ++i) {
|
@@ -67,6 +69,23 @@ function(binding, exactMatch, using, rootSelector) {
|
|
67
69
|
};
|
68
70
|
FN
|
69
71
|
|
72
|
+
# /**
|
73
|
+
# * Find a list of element Ids in the page by their angular binding.
|
74
|
+
# *
|
75
|
+
# * @param {string} binding The binding, e.g. {{cat.name}}.
|
76
|
+
# * @param {boolean} exactMatch Whether the binding needs to be matched exactly
|
77
|
+
# * @param {Element} using The scope of the search.
|
78
|
+
# * @param {string} rootSelector The selector to use for the root app element.
|
79
|
+
# *
|
80
|
+
# * @return {Array.<Element>} The elements containing the binding.
|
81
|
+
# */
|
82
|
+
FN_findBindingsIds = <<-FN
|
83
|
+
function(binding, exactMatch, using, rootSelector) {
|
84
|
+
var elements = findBindings(binding, exactMatch, using, rootSelector);
|
85
|
+
return createCapybaraNgMatches(elements);
|
86
|
+
};
|
87
|
+
FN
|
88
|
+
|
70
89
|
# /**
|
71
90
|
# * Find an array of elements matching a row within an ng-repeat.
|
72
91
|
# * Always returns an array of only one element for plain old ng-repeat.
|
@@ -120,7 +139,27 @@ function(repeater, index, using, rootSelector) {
|
|
120
139
|
}
|
121
140
|
}
|
122
141
|
return [rows[index]].concat(multiRows[index]);
|
123
|
-
|
142
|
+
};
|
143
|
+
FN
|
144
|
+
|
145
|
+
# /**
|
146
|
+
# * Find an array of element ids matching a row within an ng-repeat.
|
147
|
+
# * Always returns an array of only one element for plain old ng-repeat.
|
148
|
+
# * Returns an array of all the elements in one segment for ng-repeat-start.
|
149
|
+
# *
|
150
|
+
# * @param {string} repeater The text of the repeater, e.g. 'cat in cats'.
|
151
|
+
# * @param {number} index The row index.
|
152
|
+
# * @param {Element} using The scope of the search.
|
153
|
+
# * @param {string} rootSelector The selector to use for the root app element.
|
154
|
+
# *
|
155
|
+
# * @return {Array.<Element>} The row of the repeater, or an array of elements
|
156
|
+
# * in the first row in the case of ng-repeat-start.
|
157
|
+
# */
|
158
|
+
FN_findRepeaterRowsIds = <<-FN
|
159
|
+
function(repeater, index, using, rootSelector) {
|
160
|
+
var elements = findRepeaterRows(repeater, index, using, rootSelector);
|
161
|
+
return createCapybaraNgMatches(elements);
|
162
|
+
};
|
124
163
|
FN
|
125
164
|
|
126
165
|
# /**
|
@@ -167,7 +206,23 @@ function(repeater, using, rootSelector) {
|
|
167
206
|
}
|
168
207
|
}
|
169
208
|
return rows;
|
170
|
-
|
209
|
+
};
|
210
|
+
FN
|
211
|
+
|
212
|
+
# /**
|
213
|
+
# * Find all rows ids of an ng-repeat.
|
214
|
+
# *
|
215
|
+
# * @param {string} repeater The text of the repeater, e.g. 'cat in cats'.
|
216
|
+
# * @param {Element} using The scope of the search.
|
217
|
+
# * @param {string} rootSelector The selector to use for the root app element.
|
218
|
+
# *
|
219
|
+
# * @return {Array.<Element>} All rows of the repeater.
|
220
|
+
# */
|
221
|
+
FN_findAllRepeaterRowsIds = <<-FN
|
222
|
+
function(repeater, using, rootSelector) {
|
223
|
+
var elements = findAllRepeaterRows(repeater, using, rootSelector);
|
224
|
+
return createCapybaraNgMatches(elements);
|
225
|
+
};
|
171
226
|
FN
|
172
227
|
|
173
228
|
# /**
|
@@ -270,6 +325,24 @@ function(repeater, index, binding, using, rootSelector) {
|
|
270
325
|
};
|
271
326
|
FN
|
272
327
|
|
328
|
+
# /**
|
329
|
+
# * Find an element ids within an ng-repeat by its row and column.
|
330
|
+
# *
|
331
|
+
# * @param {string} repeater The text of the repeater, e.g. 'cat in cats'.
|
332
|
+
# * @param {number} index The row index.
|
333
|
+
# * @param {string} binding The column binding, e.g. '{{cat.name}}'.
|
334
|
+
# * @param {Element} using The scope of the search.
|
335
|
+
# * @param {string} rootSelector The selector to use for the root app element.
|
336
|
+
# *
|
337
|
+
# * @return {Array.<Element>} The element in an array.
|
338
|
+
# */
|
339
|
+
FN_findRepeaterElementIds = <<-FN
|
340
|
+
function(repeater, index, binding, using, rootSelector) {
|
341
|
+
var elements = findRepeaterElement(repeater, index, binding, using, rootSelector);
|
342
|
+
return createCapybaraNgMatches(elements);
|
343
|
+
};
|
344
|
+
FN
|
345
|
+
|
273
346
|
# /**
|
274
347
|
# * Find the elements in a column of an ng-repeat.
|
275
348
|
# *
|
@@ -367,6 +440,23 @@ function(repeater, binding, using, rootSelector) {
|
|
367
440
|
};
|
368
441
|
FN
|
369
442
|
|
443
|
+
# /**
|
444
|
+
# * Find the elements in a column of an ng-repeat.
|
445
|
+
# *
|
446
|
+
# * @param {string} repeater The text of the repeater, e.g. 'cat in cats'.
|
447
|
+
# * @param {string} binding The column binding, e.g. '{{cat.name}}'.
|
448
|
+
# * @param {Element} using The scope of the search.
|
449
|
+
# * @param {string} rootSelector The selector to use for the root app element.
|
450
|
+
# *
|
451
|
+
# * @return {Array.<Element>} The elements in the column.
|
452
|
+
# */
|
453
|
+
FN_findRepeaterColumnIds = <<-FN
|
454
|
+
function(repeater, binding, using, rootSelector) {
|
455
|
+
var elements = findRepeaterColumn(repeater, binding, using, rootSelector);
|
456
|
+
return createCapybaraNgMatches(elements);
|
457
|
+
};
|
458
|
+
FN
|
459
|
+
|
370
460
|
# /**
|
371
461
|
# * Find elements by model name.
|
372
462
|
# *
|
@@ -385,6 +475,7 @@ function(model, using, rootSelector) {
|
|
385
475
|
return angular.getTestability(using).
|
386
476
|
findModels(using, model, true);
|
387
477
|
}
|
478
|
+
|
388
479
|
var prefixes = ['ng-', 'ng_', 'data-ng-', 'x-ng-', 'ng\\\\:'];
|
389
480
|
for (var p = 0; p < prefixes.length; ++p) {
|
390
481
|
var selector = '[' + prefixes[p] + 'model="' + model + '"]';
|
@@ -396,6 +487,22 @@ function(model, using, rootSelector) {
|
|
396
487
|
};
|
397
488
|
FN
|
398
489
|
|
490
|
+
# /**
|
491
|
+
# * Find element ids by model name.
|
492
|
+
# *
|
493
|
+
# * @param {string} model The model name.
|
494
|
+
# * @param {Element} using The scope of the search.
|
495
|
+
# * @param {string} rootSelector The selector to use for the root app element.
|
496
|
+
# *
|
497
|
+
# * @return {Array.<Element>} The matching elements.
|
498
|
+
# */
|
499
|
+
FN_findByModelIds = <<-FN
|
500
|
+
function(model, using, rootSelector) {
|
501
|
+
var elements = findByModel(model, using, rootSelector);
|
502
|
+
return createCapybaraNgMatches(elements);
|
503
|
+
};
|
504
|
+
FN
|
505
|
+
|
399
506
|
# /**
|
400
507
|
# * Find elements by options.
|
401
508
|
# *
|
@@ -422,6 +529,23 @@ function(optionsDescriptor, using, rootSelector) {
|
|
422
529
|
};
|
423
530
|
FN
|
424
531
|
|
532
|
+
# /**
|
533
|
+
# * Find elements by options.
|
534
|
+
# *
|
535
|
+
# * @param {string} optionsDescriptor The descriptor for the option
|
536
|
+
# * (i.e. fruit for fruit in fruits).
|
537
|
+
# * @param {Element} using The scope of the search.
|
538
|
+
# * @param {string} rootSelector The selector to use for the root app element.
|
539
|
+
# *
|
540
|
+
# * @return {Array.<Element>} The matching elements.
|
541
|
+
# */
|
542
|
+
FN_findByOptionsIds = <<-FN
|
543
|
+
function(optionsDescriptor, using, rootSelector) {
|
544
|
+
var elements = findByOptions(optionsDescriptor, using, rootSelector);
|
545
|
+
return createCapybaraNgMatches(elements);
|
546
|
+
};
|
547
|
+
FN
|
548
|
+
|
425
549
|
# /**
|
426
550
|
# * Find buttons by textual content.
|
427
551
|
# *
|
data/lib/angular/dsl.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
module Angular
|
2
2
|
module DSL
|
3
3
|
def ng
|
4
|
-
|
4
|
+
Capybara.current_session.ng
|
5
5
|
end
|
6
6
|
|
7
7
|
def ng_root_selector(root_selector = nil)
|
@@ -100,7 +100,7 @@ module DSL
|
|
100
100
|
#
|
101
101
|
def ng_bindings(binding, opt = {})
|
102
102
|
opt[:root_selector] ||= ng_root_selector
|
103
|
-
ng.
|
103
|
+
ng.get_nodes_2 :findBindingsIds, [binding, opt[:exact] == true], opt
|
104
104
|
end
|
105
105
|
|
106
106
|
#
|
@@ -118,6 +118,18 @@ module DSL
|
|
118
118
|
rescue NotFound
|
119
119
|
false
|
120
120
|
end
|
121
|
+
#
|
122
|
+
# Does model not exist
|
123
|
+
#
|
124
|
+
# @param opt
|
125
|
+
# - :using
|
126
|
+
# - :root_selector
|
127
|
+
# - :wait
|
128
|
+
# @return true | false
|
129
|
+
#
|
130
|
+
def has_no_ng_model?(model, opt = {})
|
131
|
+
!has_ng_model?(model, opt)
|
132
|
+
end
|
121
133
|
|
122
134
|
#
|
123
135
|
# Node for nth model match
|
@@ -146,7 +158,7 @@ module DSL
|
|
146
158
|
#
|
147
159
|
def ng_models(model, opt = {})
|
148
160
|
opt[:root_selector] ||= ng_root_selector
|
149
|
-
ng.
|
161
|
+
ng.get_nodes_2 :findByModelIds, [model], opt
|
150
162
|
end
|
151
163
|
|
152
164
|
#
|
@@ -158,7 +170,7 @@ module DSL
|
|
158
170
|
# - :wait
|
159
171
|
# @return true | false
|
160
172
|
#
|
161
|
-
def
|
173
|
+
def has_ng_options?(options, opt = {})
|
162
174
|
opt[:root_selector] ||= ng_root_selector
|
163
175
|
ng_options(options, opt)
|
164
176
|
true
|
@@ -166,6 +178,19 @@ module DSL
|
|
166
178
|
false
|
167
179
|
end
|
168
180
|
|
181
|
+
#
|
182
|
+
# Does option not exist
|
183
|
+
#
|
184
|
+
# @param opt
|
185
|
+
# - :using
|
186
|
+
# - :root_selector
|
187
|
+
# - :wait
|
188
|
+
# @return true | false
|
189
|
+
#
|
190
|
+
def has_no_ng_options?(options, opt = {})
|
191
|
+
!has_ng_options?(options, opt)
|
192
|
+
end
|
193
|
+
|
169
194
|
#
|
170
195
|
# Node for nth option
|
171
196
|
#
|
@@ -193,7 +218,7 @@ module DSL
|
|
193
218
|
#
|
194
219
|
def ng_options(options, opt = {})
|
195
220
|
opt[:root_selector] ||= ng_root_selector
|
196
|
-
ng.
|
221
|
+
ng.get_nodes_2(:findByOptionsIds, [options], opt)
|
197
222
|
end
|
198
223
|
|
199
224
|
#
|
@@ -206,13 +231,25 @@ module DSL
|
|
206
231
|
# @return true | false
|
207
232
|
#
|
208
233
|
def has_ng_repeater_row?(repeater, opt = {})
|
209
|
-
opt
|
210
|
-
ng.get_nodes(:findRepeaterRows, [repeater, 0], opt)
|
234
|
+
ng_repeater_row(repeater, opt)
|
211
235
|
true
|
212
236
|
rescue NotFound
|
213
237
|
false
|
214
238
|
end
|
215
239
|
|
240
|
+
#
|
241
|
+
# Does row not exist
|
242
|
+
#
|
243
|
+
# @param opt
|
244
|
+
# - :using
|
245
|
+
# - :root_selector
|
246
|
+
# - :wait
|
247
|
+
# @return true | false
|
248
|
+
#
|
249
|
+
def has_no_ng_repeater_row?(repeater, opt = {})
|
250
|
+
!has_ng_repeater_rows?(repeater, opt)
|
251
|
+
end
|
252
|
+
|
216
253
|
#
|
217
254
|
# Node for nth repeater row
|
218
255
|
#
|
@@ -226,7 +263,7 @@ module DSL
|
|
226
263
|
def ng_repeater_row(repeater, opt = {})
|
227
264
|
opt[:root_selector] ||= ng_root_selector
|
228
265
|
row = ng.row(opt)
|
229
|
-
data = ng.
|
266
|
+
data = ng.get_nodes_2(:findRepeaterRowsIds, [repeater, row], opt)
|
230
267
|
data.first
|
231
268
|
end
|
232
269
|
|
@@ -241,7 +278,7 @@ module DSL
|
|
241
278
|
#
|
242
279
|
def ng_repeater_rows(repeater, opt = {})
|
243
280
|
opt[:root_selector] ||= ng_root_selector
|
244
|
-
ng.
|
281
|
+
ng.get_nodes_2 :findAllRepeaterRowsIds, [repeater], opt
|
245
282
|
end
|
246
283
|
|
247
284
|
#
|
@@ -271,7 +308,7 @@ module DSL
|
|
271
308
|
#
|
272
309
|
def ng_repeater_columns(repeater, binding, opt = {})
|
273
310
|
opt[:root_selector] ||= ng_root_selector
|
274
|
-
ng.
|
311
|
+
ng.get_nodes_2 :findRepeaterColumnIds, [repeater, binding], opt
|
275
312
|
end
|
276
313
|
|
277
314
|
#
|
@@ -297,7 +334,7 @@ module DSL
|
|
297
334
|
#
|
298
335
|
def ng_repeater_elements(repeater, index, binding, opt = {})
|
299
336
|
opt[:root_selector] ||= ng_root_selector
|
300
|
-
ng.
|
337
|
+
ng.get_nodes_2 :findRepeaterElementIds, [repeater, index, binding], opt
|
301
338
|
end
|
302
339
|
end
|
303
340
|
end
|
data/lib/angular/setup.rb
CHANGED
@@ -32,6 +32,37 @@ module Angular
|
|
32
32
|
make_call(method, params, opt)
|
33
33
|
end
|
34
34
|
|
35
|
+
#
|
36
|
+
# @param opt
|
37
|
+
# - :using
|
38
|
+
# - :root_selector
|
39
|
+
# - :wait
|
40
|
+
#
|
41
|
+
def get_nodes_2(method, params, opt = {})
|
42
|
+
opt = {
|
43
|
+
nodes: false,
|
44
|
+
using: nil,
|
45
|
+
root_selector: ::Angular.root_selector,
|
46
|
+
}.merge(opt)
|
47
|
+
ids = make_call(method, params, opt)
|
48
|
+
raise NotFound.new("#{method}: #{params} - #{opt.inspect}") if ids.nil? || ids.empty?
|
49
|
+
make_nodes(ids, opt)
|
50
|
+
end
|
51
|
+
|
52
|
+
def make_nodes(ids, opt)
|
53
|
+
result = []
|
54
|
+
ids.each do |id|
|
55
|
+
id = id.tr('"', '')
|
56
|
+
selector = "//*[@capybara-ng-match='#{id}']"
|
57
|
+
nodes = page.driver.find_xpath(selector)
|
58
|
+
|
59
|
+
raise NotFound.new("Failed to match found id to node") if nodes.empty?
|
60
|
+
result.concat(nodes)
|
61
|
+
end
|
62
|
+
page.evaluate_script("clearCapybaraNgMatches('#{opt[:root_selector]}')");
|
63
|
+
result
|
64
|
+
end
|
65
|
+
|
35
66
|
#
|
36
67
|
# @param opt
|
37
68
|
# - :nodes
|
@@ -60,7 +91,6 @@ module Angular
|
|
60
91
|
|
61
92
|
js = "#{method}(#{js_params.join(', ')});"
|
62
93
|
logger.debug js
|
63
|
-
|
64
94
|
js_result = page.evaluate_script(js);
|
65
95
|
# logger.debug js_result
|
66
96
|
|
data/lib/angular/version.rb
CHANGED
data/lib/angular/waiter.rb
CHANGED
@@ -33,8 +33,42 @@ module Angular
|
|
33
33
|
|
34
34
|
def setup_waiter
|
35
35
|
script = <<-JS
|
36
|
-
window.ngReady = false;
|
37
36
|
(function() {
|
37
|
+
"use strict";
|
38
|
+
|
39
|
+
window.ngReady = false;
|
40
|
+
|
41
|
+
window.nextCapybaraId = function() {
|
42
|
+
window.capybaraId = window.capybaraId || 1;
|
43
|
+
return window.capybaraId++;
|
44
|
+
};
|
45
|
+
|
46
|
+
window.createCapybaraNgMatches = function(nodes) {
|
47
|
+
if (!nodes) {
|
48
|
+
return nodes;
|
49
|
+
}
|
50
|
+
|
51
|
+
var matches = [];
|
52
|
+
angular.forEach(nodes, function(node) {
|
53
|
+
if (node) {
|
54
|
+
var match = "cb_" + window.nextCapybaraId();
|
55
|
+
node.setAttribute('capybara-ng-match', match);
|
56
|
+
matches.push(match);
|
57
|
+
}
|
58
|
+
});
|
59
|
+
return matches;
|
60
|
+
};
|
61
|
+
|
62
|
+
window.clearCapybaraNgMatches = function(rootSelector) {
|
63
|
+
rootSelector = rootSelector || 'body';
|
64
|
+
var root = document.querySelector(rootSelector);
|
65
|
+
|
66
|
+
var nodes = root.querySelectorAll('[capybara-ng-match]');
|
67
|
+
angular.forEach(nodes, function(node) {
|
68
|
+
node.removeAttribute('capybara-ng-match');
|
69
|
+
});
|
70
|
+
};
|
71
|
+
|
38
72
|
var app = angular.element(document.querySelector('[ng-app], [data-ng-app]'));
|
39
73
|
var injector = app.injector();
|
40
74
|
var callback = function() {
|
data/travis/run.sh
CHANGED
@@ -1,13 +1,23 @@
|
|
1
1
|
cd spec/dummy
|
2
2
|
export BUNDLE_GEMFILE=$PWD/Gemfile
|
3
3
|
|
4
|
-
|
4
|
+
echo "cucumber - poltergeist"
|
5
|
+
xvfb-run -a export CAPYBARA_DRIVER=poltergeist && bundle exec cucumber
|
5
6
|
EXIT_1=$?
|
6
7
|
|
7
|
-
|
8
|
+
echo "cucumber - webkit"
|
9
|
+
xvfb-run -a export CAPYBARA_DRIVER=webkit && bundle exec cucumber
|
8
10
|
EXIT_2=$?
|
9
11
|
|
10
|
-
|
12
|
+
echo "rspec - poltergeist"
|
13
|
+
xvfb-run -a export CAPYBARA_DRIVER=poltergeist && bundle exec rspec
|
14
|
+
EXIT_3=$?
|
15
|
+
|
16
|
+
echo "rspec - webkit"
|
17
|
+
xvfb-run -a export CAPYBARA_DRIVER=webkit && bundle exec rspec
|
18
|
+
EXIT_4=$?
|
19
|
+
|
20
|
+
if [[ $EXIT_1 != 0 || $EXIT_2 != 0 || $EXIT_3 != 0 || $EXIT_4 != 0 ]]; then
|
11
21
|
echo "Failed"
|
12
22
|
exit 1
|
13
23
|
fi
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: capybara-ng
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- kari
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-04-
|
11
|
+
date: 2015-04-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|