poltergeist 0.5.0 → 0.6.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.
- data/README.md +63 -54
- data/lib/capybara/poltergeist/client.rb +1 -1
- data/lib/capybara/poltergeist/client/agent.coffee +1 -4
- data/lib/capybara/poltergeist/client/browser.coffee +67 -41
- data/lib/capybara/poltergeist/client/compiled/agent.js +3 -9
- data/lib/capybara/poltergeist/client/compiled/browser.js +62 -38
- data/lib/capybara/poltergeist/client/compiled/main.js +61 -12
- data/lib/capybara/poltergeist/client/compiled/node.js +2 -2
- data/lib/capybara/poltergeist/client/compiled/web_page.js +10 -16
- data/lib/capybara/poltergeist/client/main.coffee +54 -13
- data/lib/capybara/poltergeist/client/node.coffee +2 -2
- data/lib/capybara/poltergeist/client/web_page.coffee +15 -16
- data/lib/capybara/poltergeist/errors.rb +31 -9
- data/lib/capybara/poltergeist/version.rb +1 -1
- metadata +21 -21
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# Poltergeist - A PhantomJS driver for Capybara #
|
2
2
|
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.6.0
|
4
4
|
|
5
5
|
[](http://travis-ci.org/jonleighton/poltergeist)
|
6
6
|
[](https://gemnasium.com/jonleighton/poltergeist)
|
@@ -42,59 +42,59 @@ this doesn't affect you.
|
|
42
42
|
|
43
43
|
## Installing PhantomJS ##
|
44
44
|
|
45
|
-
You need PhantomJS 1.
|
45
|
+
You need PhantomJS 1.5.0. There are no other dependencies (you don't
|
46
|
+
need Qt, or Xvfb, etc.)
|
46
47
|
|
47
|
-
###
|
48
|
+
### Mac ###
|
48
49
|
|
49
|
-
|
50
|
-
|
51
|
-
PhantomJS for Linux, Mac and Windows. This is the easiest and best way
|
52
|
-
to install it. The binaries including a patched version of Qt 4.8 so you
|
53
|
-
don't need to install that separately.
|
50
|
+
* *With homebrew*: `brew install phantomjs`
|
51
|
+
* *Without homebrew*: [Download this](http://code.google.com/p/phantomjs/downloads/detail?name=phantomjs-1.5.0-macosx-static.zip&can=2&q=)
|
54
52
|
|
55
|
-
|
56
|
-
the relationship between `bin/phantomjs` and `lib/`. This is because the
|
57
|
-
`bin/phantomjs` binary looks in `../lib/` for its library files. So the
|
58
|
-
best thing to do is to link (rather than copy) it into your `PATH`:
|
53
|
+
### Linux ###
|
59
54
|
|
60
|
-
|
55
|
+
* Download the [32
|
56
|
+
bit](http://code.google.com/p/phantomjs/downloads/detail?name=phantomjs-1.5.0-linux-x86-dynamic.tar.gz&can=2&q=)
|
57
|
+
or [64
|
58
|
+
bit](http://code.google.com/p/phantomjs/downloads/detail?name=phantomjs-1.5.0-linux-x86_64-dynamic.tar.gz&can=2&q=)
|
59
|
+
binary.
|
60
|
+
* Extract it: `sudo tar xvzf phantomjs-1.5.0-linux-*-dynamic.tar.gz -C /usr/local`
|
61
|
+
* Link it: `sudo ln -s /usr/local/phantomjs/bin/phantomjs /usr/local/bin/phantomjs`
|
61
62
|
|
62
|
-
|
63
|
+
(Note that you cannot copy the `/usr/local/phantomjs/bin/phantomjs`
|
64
|
+
binary elsewhere on its own as it dynamically links with other files in
|
65
|
+
`/usr/local/phantomjs/lib`.)
|
63
66
|
|
64
|
-
|
65
|
-
compile PhantomJS yourself. PhantomJS must be built against Qt 4.8, and
|
66
|
-
some patches must be applied, so note that you cannot build it against
|
67
|
-
your system install of Qt.
|
67
|
+
### Manual compilation ###
|
68
68
|
|
69
|
-
|
70
|
-
|
71
|
-
The script will
|
72
|
-
download Qt, apply some patches, build it, and then build PhantomJS
|
73
|
-
against the patched build of Qt. It takes quite a while, around 30
|
74
|
-
minutes on a modern computer with two hyperthreaded cores. Afterwards,
|
75
|
-
you should copy (or link) the `bin/phantomjs` binary into your `PATH`.
|
69
|
+
Do this as a last resort if the binaries don't work for you. It will
|
70
|
+
take quite a long time as it has to build WebKit.
|
76
71
|
|
77
|
-
|
72
|
+
* Download [the source tarball](http://code.google.com/p/phantomjs/downloads/detail?name=phantomjs-1.5.0-source.tar.gz&can=2&q=)
|
73
|
+
* Extract and cd in
|
74
|
+
* `./build.sh`
|
78
75
|
|
79
|
-
|
80
|
-
so to run it on a continuous integration
|
81
|
-
server you will need to install [Xvfb](http://en.wikipedia.org/wiki/Xvfb).
|
76
|
+
## Compatibility ##
|
82
77
|
|
83
|
-
|
78
|
+
Supported: MRI 1.8.7, MRI 1.9.2, MRI 1.9.3, JRuby 1.8, JRuby 1.9.
|
84
79
|
|
85
|
-
|
86
|
-
rake`).
|
80
|
+
Not supported:
|
87
81
|
|
88
|
-
|
82
|
+
* Rubinius (due to some unknown socket related issues)
|
83
|
+
* Windows
|
89
84
|
|
90
|
-
|
91
|
-
|
85
|
+
Contributions are welcome in order to move 'unsupported'
|
86
|
+
items into the 'supported' list.
|
92
87
|
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
88
|
+
## Running on a CI ##
|
89
|
+
|
90
|
+
There are no special steps to take. You don't need Xvfb or any running X
|
91
|
+
server at all.
|
92
|
+
|
93
|
+
[Travis CI](http://travis-ci.org/) has PhantomJS 1.5.0 installed.
|
94
|
+
|
95
|
+
You may like to use their [chef
|
96
|
+
cookbook](https://github.com/travis-ci/travis-cookbooks/tree/master/ci_environment/phantomjs)
|
97
|
+
on your own servers.
|
98
98
|
|
99
99
|
## What's supported? ##
|
100
100
|
|
@@ -117,6 +117,19 @@ the entire page, use `page.driver.render('/path/to/file.png', :full => true)`.
|
|
117
117
|
Sometimes the window size is important to how things are rendered. Poltergeist sets the window
|
118
118
|
size to 1024x768 by default, but you can set this yourself with `page.driver.resize(width, height)`.
|
119
119
|
|
120
|
+
### Remote debugging (experimental) ###
|
121
|
+
|
122
|
+
If you use the `:inspector => true` option (see below), remote debugging
|
123
|
+
will be enabled.
|
124
|
+
|
125
|
+
When this option is enabled, you can insert `page.driver.debug` into
|
126
|
+
your tests to pause the test and launch a browser which gives you the
|
127
|
+
WebKit inspector to view your test run with.
|
128
|
+
|
129
|
+
(This feature is considered experimental - it needs more polish
|
130
|
+
and [apparently will only work on
|
131
|
+
Linux](http://code.google.com/p/phantomjs/issues/detail?id=430).)
|
132
|
+
|
120
133
|
## Customization ##
|
121
134
|
|
122
135
|
You can customize the way that Capybara sets up Poltegeist via the following code in your
|
@@ -136,6 +149,7 @@ end
|
|
136
149
|
* `:timeout` (Numeric) - The number of seconds we'll wait for a response
|
137
150
|
when communicating with PhantomJS. `nil` means wait forever. Default
|
138
151
|
is 30.
|
152
|
+
* `:inspector` (Boolean, String) - See 'Remote Debugging', above.
|
139
153
|
|
140
154
|
## Bugs ##
|
141
155
|
|
@@ -143,20 +157,6 @@ Please file bug reports on Github and include example code to reproduce the prob
|
|
143
157
|
possible. (Tests are even better.) Please also provide the output with
|
144
158
|
`:debug` turned on, and screenshots if you think it's relevant.
|
145
159
|
|
146
|
-
## Differences from [capybara-webkit](https://github.com/thoughtbot/capybara-webkit) ##
|
147
|
-
|
148
|
-
Poltergeist is similar to capybara-webkit, but here are the key
|
149
|
-
differences:
|
150
|
-
|
151
|
-
* It's more hackable. Poltergeist is written in Ruby + CoffeeScript.
|
152
|
-
We only have to worry about C++ when dealing with issues in
|
153
|
-
PhantomJS itself. In contrast, the majority of capybara-webkit is
|
154
|
-
written in C++.
|
155
|
-
|
156
|
-
* We're able to tap into the PhantomJS community. When PhantomJS
|
157
|
-
improves, Poltergeist improves. User's don't have to install Qt
|
158
|
-
because self-contained PhantomJS binary packages are available.
|
159
|
-
|
160
160
|
## Hacking ##
|
161
161
|
|
162
162
|
Contributions are very welcome and I will happily give commit access to
|
@@ -173,6 +173,13 @@ makes debugging easier). Running `rake autocompile` will watch the
|
|
173
173
|
|
174
174
|
## Changes ##
|
175
175
|
|
176
|
+
### 0.6.0 ###
|
177
|
+
|
178
|
+
#### Features ####
|
179
|
+
|
180
|
+
* Updated to PhantomJS 1.5.0, giving us proper support for reporting
|
181
|
+
Javascript exception backtraces.
|
182
|
+
|
176
183
|
### 0.5.0 ###
|
177
184
|
|
178
185
|
#### Features ####
|
@@ -199,6 +206,8 @@ makes debugging easier). Running `rake autocompile` will watch the
|
|
199
206
|
* Errors produced by Javascript on the page will now generate an
|
200
207
|
exception within Ruby. [Issue #27]
|
201
208
|
|
209
|
+
* JRuby support. [Issue #20]
|
210
|
+
|
202
211
|
#### Bug fixes ####
|
203
212
|
|
204
213
|
* Fix bug where we could end up interacting with an obsolete element. [Issue #30]
|
@@ -8,10 +8,7 @@ class PoltergeistAgent
|
|
8
8
|
this.pushWindow(window)
|
9
9
|
|
10
10
|
externalCall: (name, arguments) ->
|
11
|
-
|
12
|
-
{ value: this[name].apply(this, arguments) }
|
13
|
-
catch error
|
14
|
-
{ error: error.toString() }
|
11
|
+
{ value: this[name].apply(this, arguments) }
|
15
12
|
|
16
13
|
pushWindow: (new_window) ->
|
17
14
|
@windows.push(new_window)
|
@@ -29,11 +29,25 @@ class Poltergeist.Browser
|
|
29
29
|
else
|
30
30
|
@owner.sendResponse(response)
|
31
31
|
|
32
|
-
|
32
|
+
getNode: (page_id, id, callback) ->
|
33
33
|
if page_id == @page_id
|
34
|
-
@page.get(id)
|
34
|
+
callback.call this, @page.get(id)
|
35
35
|
else
|
36
|
-
|
36
|
+
@owner.sendError(new Poltergeist.ObsoleteNode)
|
37
|
+
|
38
|
+
nodeCall: (page_id, id, fn, args...) ->
|
39
|
+
callback = args.pop()
|
40
|
+
|
41
|
+
this.getNode(
|
42
|
+
page_id, id,
|
43
|
+
(node) ->
|
44
|
+
result = node[fn](args...)
|
45
|
+
|
46
|
+
if result instanceof Poltergeist.ObsoleteNode
|
47
|
+
@owner.sendError(result)
|
48
|
+
else
|
49
|
+
callback.call(this, result, node)
|
50
|
+
)
|
37
51
|
|
38
52
|
visit: (url) ->
|
39
53
|
@state = 'loading'
|
@@ -52,20 +66,19 @@ class Poltergeist.Browser
|
|
52
66
|
this.sendResponse(page_id: @page_id, ids: @page.find(selector))
|
53
67
|
|
54
68
|
find_within: (page_id, id, selector) ->
|
55
|
-
this.
|
69
|
+
this.nodeCall(page_id, id, 'find', selector, this.sendResponse)
|
56
70
|
|
57
71
|
text: (page_id, id) ->
|
58
|
-
this.
|
72
|
+
this.nodeCall(page_id, id, 'text', this.sendResponse)
|
59
73
|
|
60
74
|
attribute: (page_id, id, name) ->
|
61
|
-
this.
|
75
|
+
this.nodeCall(page_id, id, 'getAttribute', name, this.sendResponse)
|
62
76
|
|
63
77
|
value: (page_id, id) ->
|
64
|
-
this.
|
78
|
+
this.nodeCall(page_id, id, 'value', this.sendResponse)
|
65
79
|
|
66
80
|
set: (page_id, id, value) ->
|
67
|
-
this.
|
68
|
-
this.sendResponse(true)
|
81
|
+
this.nodeCall(page_id, id, 'set', value, -> this.sendResponse(true))
|
69
82
|
|
70
83
|
# PhantomJS only allows us to reference the element by CSS selector, not XPath,
|
71
84
|
# so we have to add an attribute to the element to identify it, then remove it
|
@@ -75,28 +88,28 @@ class Poltergeist.Browser
|
|
75
88
|
# by temporarily changing it to a single-file input. This obviously could break
|
76
89
|
# things in various ways, which is not ideal, but it works in the simplest case.
|
77
90
|
select_file: (page_id, id, value) ->
|
78
|
-
|
79
|
-
|
80
|
-
|
91
|
+
this.nodeCall(
|
92
|
+
page_id, id, 'isMultiple',
|
93
|
+
(multiple, node) ->
|
94
|
+
node.removeAttribute('multiple') if multiple
|
95
|
+
node.setAttribute('_poltergeist_selected', '')
|
81
96
|
|
82
|
-
|
83
|
-
element.setAttribute('_poltergeist_selected', '')
|
97
|
+
@page.uploadFile('[_poltergeist_selected]', value)
|
84
98
|
|
85
|
-
|
99
|
+
node.removeAttribute('_poltergeist_selected')
|
100
|
+
node.setAttribute('multiple', 'multiple') if multiple
|
86
101
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
this.sendResponse(true)
|
102
|
+
this.sendResponse(true)
|
103
|
+
)
|
91
104
|
|
92
105
|
select: (page_id, id, value) ->
|
93
|
-
this.
|
106
|
+
this.nodeCall(page_id, id, 'select', value, this.sendResponse)
|
94
107
|
|
95
108
|
tag_name: (page_id, id) ->
|
96
|
-
this.
|
109
|
+
this.nodeCall(page_id, id, 'tagName', this.sendResponse)
|
97
110
|
|
98
111
|
visible: (page_id, id) ->
|
99
|
-
this.
|
112
|
+
this.nodeCall(page_id, id, 'isVisible', this.sendResponse)
|
100
113
|
|
101
114
|
evaluate: (script) ->
|
102
115
|
this.sendResponse JSON.parse(@page.evaluate("function() { return JSON.stringify(#{script}) }"))
|
@@ -114,30 +127,43 @@ class Poltergeist.Browser
|
|
114
127
|
this.sendResponse(true)
|
115
128
|
|
116
129
|
click: (page_id, id) ->
|
117
|
-
#
|
118
|
-
#
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
130
|
+
# We just check the node is not obsolete before proceeding. If it is,
|
131
|
+
# the callback will not fire.
|
132
|
+
this.nodeCall(
|
133
|
+
page_id, id, 'isObsolete',
|
134
|
+
(obsolete, node) ->
|
135
|
+
# If the click event triggers onLoadStarted, we will transition to the 'loading'
|
136
|
+
# state and wait for onLoadFinished before sending a response.
|
137
|
+
@state = 'clicked'
|
138
|
+
|
139
|
+
click = node.click()
|
140
|
+
|
141
|
+
# Use a timeout in order to let the stack clear, so that the @page.onLoadStarted
|
142
|
+
# callback can (possibly) fire, before we decide whether to send a response.
|
143
|
+
setTimeout(
|
144
|
+
=>
|
145
|
+
if @state == 'clicked'
|
146
|
+
@state = 'default'
|
147
|
+
|
148
|
+
if click instanceof Poltergeist.ClickFailed
|
149
|
+
@owner.sendError(click)
|
150
|
+
else
|
151
|
+
this.sendResponse(true)
|
152
|
+
,
|
153
|
+
10
|
154
|
+
)
|
132
155
|
)
|
133
156
|
|
134
157
|
drag: (page_id, id, other_id) ->
|
135
|
-
this.
|
136
|
-
|
158
|
+
this.nodeCall(
|
159
|
+
page_id, id, 'isObsolete'
|
160
|
+
(obsolete, node) ->
|
161
|
+
node.dragTo(@page.get(other_id))
|
162
|
+
this.sendResponse(true)
|
163
|
+
)
|
137
164
|
|
138
165
|
trigger: (page_id, id, event) ->
|
139
|
-
this.
|
140
|
-
this.sendResponse(event)
|
166
|
+
this.nodeCall(page_id, id, 'trigger', event, -> this.sendResponse(event))
|
141
167
|
|
142
168
|
reset: ->
|
143
169
|
this.resetPage()
|
@@ -8,15 +8,9 @@ PoltergeistAgent = (function() {
|
|
8
8
|
this.pushWindow(window);
|
9
9
|
}
|
10
10
|
PoltergeistAgent.prototype.externalCall = function(name, arguments) {
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
};
|
15
|
-
} catch (error) {
|
16
|
-
return {
|
17
|
-
error: error.toString()
|
18
|
-
};
|
19
|
-
}
|
11
|
+
return {
|
12
|
+
value: this[name].apply(this, arguments)
|
13
|
+
};
|
20
14
|
};
|
21
15
|
PoltergeistAgent.prototype.pushWindow = function(new_window) {
|
22
16
|
this.windows.push(new_window);
|
@@ -1,4 +1,4 @@
|
|
1
|
-
var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
|
1
|
+
var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, __slice = Array.prototype.slice;
|
2
2
|
Poltergeist.Browser = (function() {
|
3
3
|
function Browser(owner) {
|
4
4
|
this.owner = owner;
|
@@ -36,13 +36,27 @@ Poltergeist.Browser = (function() {
|
|
36
36
|
return this.owner.sendResponse(response);
|
37
37
|
}
|
38
38
|
};
|
39
|
-
Browser.prototype.
|
39
|
+
Browser.prototype.getNode = function(page_id, id, callback) {
|
40
40
|
if (page_id === this.page_id) {
|
41
|
-
return this.page.get(id);
|
41
|
+
return callback.call(this, this.page.get(id));
|
42
42
|
} else {
|
43
|
-
|
43
|
+
return this.owner.sendError(new Poltergeist.ObsoleteNode);
|
44
44
|
}
|
45
45
|
};
|
46
|
+
Browser.prototype.nodeCall = function() {
|
47
|
+
var args, callback, fn, id, page_id;
|
48
|
+
page_id = arguments[0], id = arguments[1], fn = arguments[2], args = 4 <= arguments.length ? __slice.call(arguments, 3) : [];
|
49
|
+
callback = args.pop();
|
50
|
+
return this.getNode(page_id, id, function(node) {
|
51
|
+
var result;
|
52
|
+
result = node[fn].apply(node, args);
|
53
|
+
if (result instanceof Poltergeist.ObsoleteNode) {
|
54
|
+
return this.owner.sendError(result);
|
55
|
+
} else {
|
56
|
+
return callback.call(this, result, node);
|
57
|
+
}
|
58
|
+
});
|
59
|
+
};
|
46
60
|
Browser.prototype.visit = function(url) {
|
47
61
|
this.state = 'loading';
|
48
62
|
return this.page.open(url);
|
@@ -63,44 +77,44 @@ Poltergeist.Browser = (function() {
|
|
63
77
|
});
|
64
78
|
};
|
65
79
|
Browser.prototype.find_within = function(page_id, id, selector) {
|
66
|
-
return this.
|
80
|
+
return this.nodeCall(page_id, id, 'find', selector, this.sendResponse);
|
67
81
|
};
|
68
82
|
Browser.prototype.text = function(page_id, id) {
|
69
|
-
return this.
|
83
|
+
return this.nodeCall(page_id, id, 'text', this.sendResponse);
|
70
84
|
};
|
71
85
|
Browser.prototype.attribute = function(page_id, id, name) {
|
72
|
-
return this.
|
86
|
+
return this.nodeCall(page_id, id, 'getAttribute', name, this.sendResponse);
|
73
87
|
};
|
74
88
|
Browser.prototype.value = function(page_id, id) {
|
75
|
-
return this.
|
89
|
+
return this.nodeCall(page_id, id, 'value', this.sendResponse);
|
76
90
|
};
|
77
91
|
Browser.prototype.set = function(page_id, id, value) {
|
78
|
-
this.
|
79
|
-
|
92
|
+
return this.nodeCall(page_id, id, 'set', value, function() {
|
93
|
+
return this.sendResponse(true);
|
94
|
+
});
|
80
95
|
};
|
81
96
|
Browser.prototype.select_file = function(page_id, id, value) {
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
}
|
94
|
-
return this.sendResponse(true);
|
97
|
+
return this.nodeCall(page_id, id, 'isMultiple', function(multiple, node) {
|
98
|
+
if (multiple) {
|
99
|
+
node.removeAttribute('multiple');
|
100
|
+
}
|
101
|
+
node.setAttribute('_poltergeist_selected', '');
|
102
|
+
this.page.uploadFile('[_poltergeist_selected]', value);
|
103
|
+
node.removeAttribute('_poltergeist_selected');
|
104
|
+
if (multiple) {
|
105
|
+
node.setAttribute('multiple', 'multiple');
|
106
|
+
}
|
107
|
+
return this.sendResponse(true);
|
108
|
+
});
|
95
109
|
};
|
96
110
|
Browser.prototype.select = function(page_id, id, value) {
|
97
|
-
return this.
|
111
|
+
return this.nodeCall(page_id, id, 'select', value, this.sendResponse);
|
98
112
|
};
|
99
113
|
Browser.prototype.tag_name = function(page_id, id) {
|
100
|
-
return this.
|
114
|
+
return this.nodeCall(page_id, id, 'tagName', this.sendResponse);
|
101
115
|
};
|
102
116
|
Browser.prototype.visible = function(page_id, id) {
|
103
|
-
return this.
|
117
|
+
return this.nodeCall(page_id, id, 'isVisible', this.sendResponse);
|
104
118
|
};
|
105
119
|
Browser.prototype.evaluate = function(script) {
|
106
120
|
return this.sendResponse(JSON.parse(this.page.evaluate("function() { return JSON.stringify(" + script + ") }")));
|
@@ -118,22 +132,32 @@ Poltergeist.Browser = (function() {
|
|
118
132
|
return this.sendResponse(true);
|
119
133
|
};
|
120
134
|
Browser.prototype.click = function(page_id, id) {
|
121
|
-
this.
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
135
|
+
return this.nodeCall(page_id, id, 'isObsolete', function(obsolete, node) {
|
136
|
+
var click;
|
137
|
+
this.state = 'clicked';
|
138
|
+
click = node.click();
|
139
|
+
return setTimeout(__bind(function() {
|
140
|
+
if (this.state === 'clicked') {
|
141
|
+
this.state = 'default';
|
142
|
+
if (click instanceof Poltergeist.ClickFailed) {
|
143
|
+
return this.owner.sendError(click);
|
144
|
+
} else {
|
145
|
+
return this.sendResponse(true);
|
146
|
+
}
|
147
|
+
}
|
148
|
+
}, this), 10);
|
149
|
+
});
|
129
150
|
};
|
130
151
|
Browser.prototype.drag = function(page_id, id, other_id) {
|
131
|
-
this.
|
132
|
-
|
152
|
+
return this.nodeCall(page_id, id, 'isObsolete', function(obsolete, node) {
|
153
|
+
node.dragTo(this.page.get(other_id));
|
154
|
+
return this.sendResponse(true);
|
155
|
+
});
|
133
156
|
};
|
134
157
|
Browser.prototype.trigger = function(page_id, id, event) {
|
135
|
-
this.
|
136
|
-
|
158
|
+
return this.nodeCall(page_id, id, 'trigger', event, function() {
|
159
|
+
return this.sendResponse(event);
|
160
|
+
});
|
137
161
|
};
|
138
162
|
Browser.prototype.reset = function() {
|
139
163
|
this.resetPage();
|
@@ -1,41 +1,77 @@
|
|
1
1
|
var Poltergeist;
|
2
|
+
var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, __hasProp = Object.prototype.hasOwnProperty, __extends = function(child, parent) {
|
3
|
+
for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; }
|
4
|
+
function ctor() { this.constructor = child; }
|
5
|
+
ctor.prototype = parent.prototype;
|
6
|
+
child.prototype = new ctor;
|
7
|
+
child.__super__ = parent.prototype;
|
8
|
+
return child;
|
9
|
+
};
|
2
10
|
Poltergeist = (function() {
|
3
11
|
function Poltergeist(port) {
|
12
|
+
this.onError = __bind(this.onError, this);
|
13
|
+
var that;
|
4
14
|
this.browser = new Poltergeist.Browser(this);
|
5
15
|
this.connection = new Poltergeist.Connection(this, port);
|
16
|
+
that = this;
|
17
|
+
phantom.onError = function(message, stack) {
|
18
|
+
return that.onError(message, stack);
|
19
|
+
};
|
20
|
+
this.running = false;
|
6
21
|
}
|
7
22
|
Poltergeist.prototype.runCommand = function(command) {
|
8
|
-
|
9
|
-
|
10
|
-
} catch (error) {
|
11
|
-
return this.sendError(error);
|
12
|
-
}
|
23
|
+
this.running = true;
|
24
|
+
return this.browser[command.name].apply(this.browser, command.args);
|
13
25
|
};
|
14
26
|
Poltergeist.prototype.sendResponse = function(response) {
|
15
|
-
return this.
|
27
|
+
return this.send({
|
16
28
|
response: response
|
17
29
|
});
|
18
30
|
};
|
19
31
|
Poltergeist.prototype.sendError = function(error) {
|
20
|
-
return this.
|
32
|
+
return this.send({
|
21
33
|
error: {
|
22
34
|
name: error.name || 'Generic',
|
23
35
|
args: error.args && error.args() || [error.toString()]
|
24
36
|
}
|
25
37
|
});
|
26
38
|
};
|
39
|
+
Poltergeist.prototype.onError = function(message, stack) {
|
40
|
+
if (message === 'PoltergeistAgent.ObsoleteNode') {
|
41
|
+
return this.sendError(new Poltergeist.ObsoleteNode);
|
42
|
+
} else {
|
43
|
+
return this.sendError(new Poltergeist.BrowserError(message, stack));
|
44
|
+
}
|
45
|
+
};
|
46
|
+
Poltergeist.prototype.send = function(data) {
|
47
|
+
if (this.running) {
|
48
|
+
this.connection.send(data);
|
49
|
+
return this.running = false;
|
50
|
+
}
|
51
|
+
};
|
27
52
|
return Poltergeist;
|
28
53
|
})();
|
29
54
|
window.Poltergeist = Poltergeist;
|
55
|
+
Poltergeist.Error = (function() {
|
56
|
+
function Error() {}
|
57
|
+
return Error;
|
58
|
+
})();
|
30
59
|
Poltergeist.ObsoleteNode = (function() {
|
31
|
-
|
60
|
+
__extends(ObsoleteNode, Poltergeist.Error);
|
61
|
+
function ObsoleteNode() {
|
62
|
+
ObsoleteNode.__super__.constructor.apply(this, arguments);
|
63
|
+
}
|
32
64
|
ObsoleteNode.prototype.name = "Poltergeist.ObsoleteNode";
|
33
65
|
ObsoleteNode.prototype.args = function() {
|
34
66
|
return [];
|
35
67
|
};
|
68
|
+
ObsoleteNode.prototype.toString = function() {
|
69
|
+
return this.name;
|
70
|
+
};
|
36
71
|
return ObsoleteNode;
|
37
72
|
})();
|
38
73
|
Poltergeist.ClickFailed = (function() {
|
74
|
+
__extends(ClickFailed, Poltergeist.Error);
|
39
75
|
function ClickFailed(selector, position) {
|
40
76
|
this.selector = selector;
|
41
77
|
this.position = position;
|
@@ -47,6 +83,7 @@ Poltergeist.ClickFailed = (function() {
|
|
47
83
|
return ClickFailed;
|
48
84
|
})();
|
49
85
|
Poltergeist.JavascriptError = (function() {
|
86
|
+
__extends(JavascriptError, Poltergeist.Error);
|
50
87
|
function JavascriptError(errors) {
|
51
88
|
this.errors = errors;
|
52
89
|
}
|
@@ -56,8 +93,20 @@ Poltergeist.JavascriptError = (function() {
|
|
56
93
|
};
|
57
94
|
return JavascriptError;
|
58
95
|
})();
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
96
|
+
Poltergeist.BrowserError = (function() {
|
97
|
+
__extends(BrowserError, Poltergeist.Error);
|
98
|
+
function BrowserError(message, stack) {
|
99
|
+
this.message = message;
|
100
|
+
this.stack = stack;
|
101
|
+
}
|
102
|
+
BrowserError.prototype.name = "Poltergeist.BrowserError";
|
103
|
+
BrowserError.prototype.args = function() {
|
104
|
+
return [this.message, this.stack];
|
105
|
+
};
|
106
|
+
return BrowserError;
|
107
|
+
})();
|
108
|
+
phantom.injectJs("" + phantom.libraryPath + "/web_page.js");
|
109
|
+
phantom.injectJs("" + phantom.libraryPath + "/node.js");
|
110
|
+
phantom.injectJs("" + phantom.libraryPath + "/connection.js");
|
111
|
+
phantom.injectJs("" + phantom.libraryPath + "/browser.js");
|
63
112
|
new Poltergeist(phantom.args[0]);
|
@@ -1,7 +1,7 @@
|
|
1
1
|
var __slice = Array.prototype.slice, __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
|
2
2
|
Poltergeist.Node = (function() {
|
3
3
|
var name, _fn, _i, _len, _ref;
|
4
|
-
Node.DELEGATES = ['text', 'getAttribute', 'value', 'set', 'setAttribute', 'removeAttribute', 'isMultiple', 'select', 'tagName', 'find', 'isVisible', 'position', 'trigger', 'parentId', 'clickTest'];
|
4
|
+
Node.DELEGATES = ['text', 'getAttribute', 'value', 'set', 'setAttribute', 'isObsolete', 'removeAttribute', 'isMultiple', 'select', 'tagName', 'find', 'isVisible', 'position', 'trigger', 'parentId', 'clickTest'];
|
5
5
|
function Node(page, id) {
|
6
6
|
this.page = page;
|
7
7
|
this.id = id;
|
@@ -64,7 +64,7 @@ Poltergeist.Node = (function() {
|
|
64
64
|
if (test.status === 'success') {
|
65
65
|
return this.page.sendEvent('click', pos.x, pos.y);
|
66
66
|
} else {
|
67
|
-
|
67
|
+
return new Poltergeist.ClickFailed(test.selector, pos);
|
68
68
|
}
|
69
69
|
};
|
70
70
|
Node.prototype.dragTo = function(other) {
|
@@ -1,7 +1,7 @@
|
|
1
1
|
var __slice = Array.prototype.slice, __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
|
2
2
|
Poltergeist.WebPage = (function() {
|
3
3
|
var command, delegate, _fn, _fn2, _i, _j, _len, _len2, _ref, _ref2;
|
4
|
-
WebPage.CALLBACKS = ['onAlert', 'onConsoleMessage', 'onLoadFinished', 'onInitialized', 'onLoadStarted', 'onResourceRequested', 'onResourceReceived'];
|
4
|
+
WebPage.CALLBACKS = ['onAlert', 'onConsoleMessage', 'onLoadFinished', 'onInitialized', 'onLoadStarted', 'onResourceRequested', 'onResourceReceived', 'onError'];
|
5
5
|
WebPage.DELEGATES = ['open', 'sendEvent', 'uploadFile', 'release', 'render'];
|
6
6
|
WebPage.COMMANDS = ['currentUrl', 'find', 'nodeCall', 'pushFrame', 'popFrame', 'documentSize'];
|
7
7
|
function WebPage() {
|
@@ -54,7 +54,7 @@ Poltergeist.WebPage = (function() {
|
|
54
54
|
if (this.evaluate(function() {
|
55
55
|
return typeof __poltergeist;
|
56
56
|
}) === "undefined") {
|
57
|
-
this["native"].injectJs(
|
57
|
+
this["native"].injectJs("" + phantom.libraryPath + "/agent.js");
|
58
58
|
return this.nodes = {};
|
59
59
|
}
|
60
60
|
};
|
@@ -68,12 +68,16 @@ Poltergeist.WebPage = (function() {
|
|
68
68
|
return this._source || (this._source = this["native"].content);
|
69
69
|
};
|
70
70
|
WebPage.prototype.onConsoleMessage = function(message, line, file) {
|
71
|
-
if (
|
72
|
-
return this._errors.push(message);
|
73
|
-
} else {
|
71
|
+
if (!(this._errors.length && this._errors[this._errors.length - 1].message === message)) {
|
74
72
|
return console.log(message);
|
75
73
|
}
|
76
74
|
};
|
75
|
+
WebPage.prototype.onErrorNative = function(message, stack) {
|
76
|
+
return this._errors.push({
|
77
|
+
message: message,
|
78
|
+
stack: stack
|
79
|
+
});
|
80
|
+
};
|
77
81
|
WebPage.prototype.content = function() {
|
78
82
|
return this["native"].content;
|
79
83
|
};
|
@@ -178,17 +182,7 @@ Poltergeist.WebPage = (function() {
|
|
178
182
|
result = this.evaluate(function(name, arguments) {
|
179
183
|
return __poltergeist.externalCall(name, arguments);
|
180
184
|
}, name, arguments);
|
181
|
-
|
182
|
-
switch (result.error) {
|
183
|
-
case "PoltergeistAgent.ObsoleteNode":
|
184
|
-
throw new Poltergeist.ObsoleteNode;
|
185
|
-
break;
|
186
|
-
default:
|
187
|
-
throw result.error;
|
188
|
-
}
|
189
|
-
} else {
|
190
|
-
return result.value;
|
191
|
-
}
|
185
|
+
return result && result.value;
|
192
186
|
};
|
193
187
|
return WebPage;
|
194
188
|
}).call(this);
|
@@ -3,44 +3,85 @@ class Poltergeist
|
|
3
3
|
@browser = new Poltergeist.Browser(this)
|
4
4
|
@connection = new Poltergeist.Connection(this, port)
|
5
5
|
|
6
|
+
# The QtWebKit bridge doesn't seem to like Function.prototype.bind
|
7
|
+
that = this
|
8
|
+
phantom.onError = (message, stack) -> that.onError(message, stack)
|
9
|
+
|
10
|
+
@running = false
|
11
|
+
|
6
12
|
runCommand: (command) ->
|
7
|
-
|
8
|
-
|
9
|
-
catch error
|
10
|
-
this.sendError(error)
|
13
|
+
@running = true
|
14
|
+
@browser[command.name].apply(@browser, command.args)
|
11
15
|
|
12
16
|
sendResponse: (response) ->
|
13
|
-
|
17
|
+
this.send(response: response)
|
14
18
|
|
15
19
|
sendError: (error) ->
|
16
|
-
|
20
|
+
this.send(
|
17
21
|
error:
|
18
22
|
name: error.name || 'Generic',
|
19
23
|
args: error.args && error.args() || [error.toString()]
|
20
24
|
)
|
21
25
|
|
26
|
+
# This is a bit of a mess. We can't wrap the runCommand code in
|
27
|
+
# a try ... catch, because that will prevent stack traces being
|
28
|
+
# reported, and there is no error.stack property.
|
29
|
+
#
|
30
|
+
# And thrown errors get toString() called, which becomes the
|
31
|
+
# value of the message variable here. So we can basically only
|
32
|
+
# use strings as exceptions at the moment, which means we throw
|
33
|
+
# exceptions with extra data and then retrieve it here.
|
34
|
+
#
|
35
|
+
# The solution will be for PhantomJS to support an e.stack
|
36
|
+
# property on exceptions.
|
37
|
+
#
|
38
|
+
# See http://code.google.com/p/phantomjs/issues/detail?id=166.
|
39
|
+
onError: (message, stack) =>
|
40
|
+
if message == 'PoltergeistAgent.ObsoleteNode'
|
41
|
+
this.sendError(new Poltergeist.ObsoleteNode)
|
42
|
+
else
|
43
|
+
this.sendError(new Poltergeist.BrowserError(message, stack))
|
44
|
+
|
45
|
+
send: (data) ->
|
46
|
+
# Prevents more than one response being sent for a single
|
47
|
+
# command. This can happen in some scenarios where an error
|
48
|
+
# is raised but the script can still continue.
|
49
|
+
if @running
|
50
|
+
@connection.send(data)
|
51
|
+
@running = false
|
52
|
+
|
22
53
|
# This is necessary because the remote debugger will wrap the
|
23
54
|
# script in a function, causing the Poltergeist variable to
|
24
55
|
# become local.
|
25
56
|
window.Poltergeist = Poltergeist
|
26
57
|
|
27
|
-
class Poltergeist.
|
58
|
+
class Poltergeist.Error
|
59
|
+
|
60
|
+
class Poltergeist.ObsoleteNode extends Poltergeist.Error
|
28
61
|
name: "Poltergeist.ObsoleteNode"
|
29
62
|
args: -> []
|
63
|
+
toString: -> this.name
|
30
64
|
|
31
|
-
class Poltergeist.ClickFailed
|
65
|
+
class Poltergeist.ClickFailed extends Poltergeist.Error
|
32
66
|
constructor: (@selector, @position) ->
|
33
67
|
name: "Poltergeist.ClickFailed"
|
34
68
|
args: -> [@selector, @position]
|
35
69
|
|
36
|
-
class Poltergeist.JavascriptError
|
70
|
+
class Poltergeist.JavascriptError extends Poltergeist.Error
|
37
71
|
constructor: (@errors) ->
|
38
72
|
name: "Poltergeist.JavascriptError"
|
39
73
|
args: -> [@errors]
|
40
74
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
75
|
+
class Poltergeist.BrowserError extends Poltergeist.Error
|
76
|
+
constructor: (@message, @stack) ->
|
77
|
+
name: "Poltergeist.BrowserError"
|
78
|
+
args: -> [@message, @stack]
|
79
|
+
|
80
|
+
# We're using phantom.libraryPath so that any stack traces
|
81
|
+
# report the full path.
|
82
|
+
phantom.injectJs("#{phantom.libraryPath}/web_page.js")
|
83
|
+
phantom.injectJs("#{phantom.libraryPath}/node.js")
|
84
|
+
phantom.injectJs("#{phantom.libraryPath}/connection.js")
|
85
|
+
phantom.injectJs("#{phantom.libraryPath}/browser.js")
|
45
86
|
|
46
87
|
new Poltergeist(phantom.args[0])
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# Proxy object for forwarding method calls to the node object inside the page.
|
2
2
|
|
3
3
|
class Poltergeist.Node
|
4
|
-
@DELEGATES = ['text', 'getAttribute', 'value', 'set', 'setAttribute',
|
4
|
+
@DELEGATES = ['text', 'getAttribute', 'value', 'set', 'setAttribute', 'isObsolete',
|
5
5
|
'removeAttribute', 'isMultiple', 'select', 'tagName', 'find',
|
6
6
|
'isVisible', 'position', 'trigger', 'parentId', 'clickTest']
|
7
7
|
|
@@ -59,7 +59,7 @@ class Poltergeist.Node
|
|
59
59
|
if test.status == 'success'
|
60
60
|
@page.sendEvent('click', pos.x, pos.y)
|
61
61
|
else
|
62
|
-
|
62
|
+
new Poltergeist.ClickFailed(test.selector, pos)
|
63
63
|
|
64
64
|
dragTo: (other) ->
|
65
65
|
position = this.clickPosition()
|
@@ -1,6 +1,7 @@
|
|
1
1
|
class Poltergeist.WebPage
|
2
2
|
@CALLBACKS = ['onAlert', 'onConsoleMessage', 'onLoadFinished', 'onInitialized',
|
3
|
-
'onLoadStarted', 'onResourceRequested', 'onResourceReceived'
|
3
|
+
'onLoadStarted', 'onResourceRequested', 'onResourceReceived',
|
4
|
+
'onError']
|
4
5
|
|
5
6
|
@DELEGATES = ['open', 'sendEvent', 'uploadFile', 'release', 'render']
|
6
7
|
|
@@ -35,7 +36,7 @@ class Poltergeist.WebPage
|
|
35
36
|
|
36
37
|
injectAgent: ->
|
37
38
|
if this.evaluate(-> typeof __poltergeist) == "undefined"
|
38
|
-
@native.injectJs(
|
39
|
+
@native.injectJs("#{phantom.libraryPath}/agent.js")
|
39
40
|
@nodes = {}
|
40
41
|
|
41
42
|
onConsoleMessageNative: (message) ->
|
@@ -47,14 +48,16 @@ class Poltergeist.WebPage
|
|
47
48
|
@_source or= @native.content
|
48
49
|
|
49
50
|
onConsoleMessage: (message, line, file) ->
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
# here line == 1 && file == "". don't ask me why!
|
51
|
+
# The conditional works around a PhantomJS bug where an error can
|
52
|
+
# get wrongly reported to be onError and onConsoleMessage:
|
53
|
+
#
|
54
|
+
# http://code.google.com/p/phantomjs/issues/detail?id=166#c18
|
55
|
+
unless @_errors.length && @_errors[@_errors.length - 1].message == message
|
56
56
|
console.log(message)
|
57
57
|
|
58
|
+
onErrorNative: (message, stack) ->
|
59
|
+
@_errors.push(message: message, stack: stack)
|
60
|
+
|
58
61
|
content: ->
|
59
62
|
@native.content
|
60
63
|
|
@@ -143,17 +146,13 @@ class Poltergeist.WebPage
|
|
143
146
|
if result != false && that[name]? # For externally set callbacks
|
144
147
|
that[name].apply(that, arguments)
|
145
148
|
|
149
|
+
# Any error raised here or inside the evaluate will get reported to
|
150
|
+
# phantom.onError. If result is null, that means there was an error
|
151
|
+
# inside the agent.
|
146
152
|
runCommand: (name, arguments) ->
|
147
153
|
result = this.evaluate(
|
148
154
|
(name, arguments) -> __poltergeist.externalCall(name, arguments),
|
149
155
|
name, arguments
|
150
156
|
)
|
151
157
|
|
152
|
-
|
153
|
-
switch result.error
|
154
|
-
when "PoltergeistAgent.ObsoleteNode"
|
155
|
-
throw new Poltergeist.ObsoleteNode
|
156
|
-
else
|
157
|
-
throw result.error
|
158
|
-
else
|
159
|
-
result.value
|
158
|
+
result && result.value
|
@@ -11,30 +11,52 @@ module Capybara
|
|
11
11
|
end
|
12
12
|
end
|
13
13
|
|
14
|
+
class JSErrorItem
|
15
|
+
attr_reader :message, :stack
|
16
|
+
|
17
|
+
def initialize(message, stack)
|
18
|
+
@message = message
|
19
|
+
@stack = stack
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_s
|
23
|
+
message + "\n\n" + formatted_stack
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def formatted_stack
|
29
|
+
stack = self.stack.map do |item|
|
30
|
+
s = " #{item['file']}:#{item['line']}"
|
31
|
+
s << " in #{item['function']}" if item['function'] && !item['function'].empty?
|
32
|
+
s
|
33
|
+
end
|
34
|
+
stack.join("\n")
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
14
38
|
class BrowserError < ClientError
|
15
39
|
def name
|
16
40
|
response['name']
|
17
41
|
end
|
18
42
|
|
19
|
-
def
|
20
|
-
response['args']
|
43
|
+
def javascript_error
|
44
|
+
JSErrorItem.new(*response['args'])
|
21
45
|
end
|
22
46
|
|
23
47
|
def message
|
24
|
-
"
|
48
|
+
"There was an error inside the PhantomJS portion of Poltergeist:\n\n#{javascript_error}"
|
25
49
|
end
|
26
50
|
end
|
27
51
|
|
28
52
|
class JavascriptError < ClientError
|
29
|
-
def
|
30
|
-
response['args'].first
|
53
|
+
def javascript_errors
|
54
|
+
response['args'].first.map { |data| JSErrorItem.new(data['message'], data['stack']) }
|
31
55
|
end
|
32
56
|
|
33
57
|
def message
|
34
|
-
"One or more errors were raised in the Javascript code on the page
|
35
|
-
"
|
36
|
-
"the error occurred. (This is due to lack of support within QtWebKit.) Fixing this is a high " \
|
37
|
-
"priority, but we're not there yet."
|
58
|
+
"One or more errors were raised in the Javascript code on the page:\n\n" +
|
59
|
+
javascript_errors.map(&:to_s).join("\n")
|
38
60
|
end
|
39
61
|
end
|
40
62
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: poltergeist
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-
|
12
|
+
date: 2012-04-09 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: capybara
|
16
|
-
requirement: &
|
16
|
+
requirement: &13258700 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ~>
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: '1.0'
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *13258700
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: multi_json
|
27
|
-
requirement: &
|
27
|
+
requirement: &13258160 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ~>
|
@@ -32,10 +32,10 @@ dependencies:
|
|
32
32
|
version: '1.0'
|
33
33
|
type: :runtime
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *13258160
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: childprocess
|
38
|
-
requirement: &
|
38
|
+
requirement: &13257680 !ruby/object:Gem::Requirement
|
39
39
|
none: false
|
40
40
|
requirements:
|
41
41
|
- - ~>
|
@@ -43,10 +43,10 @@ dependencies:
|
|
43
43
|
version: '0.3'
|
44
44
|
type: :runtime
|
45
45
|
prerelease: false
|
46
|
-
version_requirements: *
|
46
|
+
version_requirements: *13257680
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: http_parser.rb
|
49
|
-
requirement: &
|
49
|
+
requirement: &13257220 !ruby/object:Gem::Requirement
|
50
50
|
none: false
|
51
51
|
requirements:
|
52
52
|
- - ~>
|
@@ -54,10 +54,10 @@ dependencies:
|
|
54
54
|
version: 0.5.3
|
55
55
|
type: :runtime
|
56
56
|
prerelease: false
|
57
|
-
version_requirements: *
|
57
|
+
version_requirements: *13257220
|
58
58
|
- !ruby/object:Gem::Dependency
|
59
59
|
name: faye-websocket
|
60
|
-
requirement: &
|
60
|
+
requirement: &13256720 !ruby/object:Gem::Requirement
|
61
61
|
none: false
|
62
62
|
requirements:
|
63
63
|
- - ~>
|
@@ -68,21 +68,21 @@ dependencies:
|
|
68
68
|
version: 0.4.4
|
69
69
|
type: :runtime
|
70
70
|
prerelease: false
|
71
|
-
version_requirements: *
|
71
|
+
version_requirements: *13256720
|
72
72
|
- !ruby/object:Gem::Dependency
|
73
73
|
name: rspec
|
74
|
-
requirement: &
|
74
|
+
requirement: &13255860 !ruby/object:Gem::Requirement
|
75
75
|
none: false
|
76
76
|
requirements:
|
77
77
|
- - ~>
|
78
78
|
- !ruby/object:Gem::Version
|
79
|
-
version: 2.8
|
79
|
+
version: '2.8'
|
80
80
|
type: :development
|
81
81
|
prerelease: false
|
82
|
-
version_requirements: *
|
82
|
+
version_requirements: *13255860
|
83
83
|
- !ruby/object:Gem::Dependency
|
84
84
|
name: sinatra
|
85
|
-
requirement: &
|
85
|
+
requirement: &13255300 !ruby/object:Gem::Requirement
|
86
86
|
none: false
|
87
87
|
requirements:
|
88
88
|
- - ~>
|
@@ -90,10 +90,10 @@ dependencies:
|
|
90
90
|
version: '1.0'
|
91
91
|
type: :development
|
92
92
|
prerelease: false
|
93
|
-
version_requirements: *
|
93
|
+
version_requirements: *13255300
|
94
94
|
- !ruby/object:Gem::Dependency
|
95
95
|
name: rake
|
96
|
-
requirement: &
|
96
|
+
requirement: &13254740 !ruby/object:Gem::Requirement
|
97
97
|
none: false
|
98
98
|
requirements:
|
99
99
|
- - ~>
|
@@ -101,10 +101,10 @@ dependencies:
|
|
101
101
|
version: 0.9.2
|
102
102
|
type: :development
|
103
103
|
prerelease: false
|
104
|
-
version_requirements: *
|
104
|
+
version_requirements: *13254740
|
105
105
|
- !ruby/object:Gem::Dependency
|
106
106
|
name: image_size
|
107
|
-
requirement: &
|
107
|
+
requirement: &13253860 !ruby/object:Gem::Requirement
|
108
108
|
none: false
|
109
109
|
requirements:
|
110
110
|
- - ~>
|
@@ -112,7 +112,7 @@ dependencies:
|
|
112
112
|
version: '1.0'
|
113
113
|
type: :development
|
114
114
|
prerelease: false
|
115
|
-
version_requirements: *
|
115
|
+
version_requirements: *13253860
|
116
116
|
description: PhantomJS driver for Capybara
|
117
117
|
email:
|
118
118
|
- j@jonathanleighton.com
|