capybara_spa 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +2 -2
- data/README.md +146 -6
- data/Rakefile +27 -1
- data/lib/capybara_spa.rb +11 -10
- data/lib/capybara_spa/server.rb +5 -0
- data/lib/capybara_spa/server/external_server.rb +102 -0
- data/lib/capybara_spa/version.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ee0028ff6473dfd7ee60d23ed3ca3f3a07e1412fa11beed4faf28a7da1e110b1
|
4
|
+
data.tar.gz: faa416fd1f5adb9cf61be4b465351c67771229f9bbb393fea143d46a39214fe2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b0a7cf1c8f639162f80e6040c8f3d20766aa80e8aec827002e53561ea87bed3ace6d3141f05b6741de842d5fdc952bd12a12b0c5af14181fd53293c8f74ac0ff
|
7
|
+
data.tar.gz: 6e810df6910e92e214a76a93242b4e62b331a26ca3dd7a35f2af002c719d04f938ac1d7d92d5b5a9ec115236eaa5aade1afd818fd30448b0930969689b154d3a
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
capybara_spa (0.
|
4
|
+
capybara_spa (0.4.0)
|
5
5
|
capybara (~> 3.0)
|
6
6
|
|
7
7
|
GEM
|
@@ -13,7 +13,7 @@ GEM
|
|
13
13
|
io-like (~> 0.3.0)
|
14
14
|
bump (0.6.0)
|
15
15
|
byebug (10.0.2)
|
16
|
-
capybara (3.0
|
16
|
+
capybara (3.1.0)
|
17
17
|
addressable
|
18
18
|
mini_mime (>= 0.1.3)
|
19
19
|
nokogiri (~> 1.8)
|
data/README.md
CHANGED
@@ -1,15 +1,32 @@
|
|
1
1
|
# CapybaraSpa
|
2
2
|
|
3
|
-
|
3
|
+
![https://travis-ci.org/mhs/capybara_spa.svg?branch=master](https://travis-ci.org/mhs/capybara_spa.svg?branch=master)
|
4
4
|
|
5
|
-
|
5
|
+
CapybaraSpa is a library to ease testing single page applications with Capybara.
|
6
|
+
|
7
|
+
Often, when developing an API server and a front-end client separately it can be a real pain to set up an integration testing environment.
|
8
|
+
|
9
|
+
## How does it work?
|
10
|
+
|
11
|
+
CapybaraSpa either runs (and terminates) an external processes for the front-end application, or, it will connect to an externally running process on a specified port. When the test suite boots up it will spawn a child process that server your front-end application.
|
12
|
+
|
13
|
+
It also updates Capybara's `visit` method to wait for the application to boot up before running the tests.. Then, when the test suite is done it will terminate any child processes.
|
14
|
+
|
15
|
+
Currently, you can:
|
16
|
+
|
17
|
+
* Run an inline static Angular 2, 4, 5, or 6 application using the `CapybaraSpa::Server::NgStaticServer`
|
18
|
+
* Connect to an external process running on a specified port using the `CapybaraSpa::Server::ExternalServer`
|
19
|
+
|
20
|
+
Coming soon you'll be able to:
|
21
|
+
|
22
|
+
* Run an inline external process by providing the command to start
|
6
23
|
|
7
24
|
## Installation
|
8
25
|
|
9
|
-
Add this line to
|
26
|
+
Add this line to the `test` group of application's Gemfile:
|
10
27
|
|
11
28
|
```ruby
|
12
|
-
gem 'capybara_spa'
|
29
|
+
gem 'capybara_spa', group :test
|
13
30
|
```
|
14
31
|
|
15
32
|
And then execute:
|
@@ -22,7 +39,130 @@ Or install it yourself as:
|
|
22
39
|
|
23
40
|
## Usage
|
24
41
|
|
25
|
-
|
42
|
+
You can use this with any Ruby testing library that Capybara works with.
|
43
|
+
|
44
|
+
### Require it in your test_helper, spec_helper, rails_helper, etc.
|
45
|
+
|
46
|
+
Here's a sample of using this library on a Rails project. Just update the `rails_helper.rb` after you Capybara is loaded. Then, create the server:
|
47
|
+
|
48
|
+
```ruby
|
49
|
+
require 'capybara/rails'
|
50
|
+
require 'capybara_spa'
|
51
|
+
|
52
|
+
FrontendServer = CapybaraSpa::Server::NgStaticServer.new(
|
53
|
+
build_path: File.dirname(__FILE__) + '/../../public/app',
|
54
|
+
http_server_bin_path: File.dirname(__FILE__) + '/../../node_modules/.bin/angular-http-server',
|
55
|
+
log_file: File.dirname(__FILE__) + '/../../log/angular-process.log',
|
56
|
+
pid_file: File.dirname(__FILE__) + '/../../tmp/angular-process.pid'
|
57
|
+
)
|
58
|
+
```
|
59
|
+
|
60
|
+
### Configure your test library to start and stop the server
|
61
|
+
|
62
|
+
Since we're using RSpec in this README, the next thing to do is configure RSpec to start and stop the server:
|
63
|
+
|
64
|
+
```ruby
|
65
|
+
RSpec.configure do |config|
|
66
|
+
config.before(:each) do |example|
|
67
|
+
if self.class.metadata[:js]
|
68
|
+
begin
|
69
|
+
FrontendServer.start unless FrontendServer.started?
|
70
|
+
rescue CapybaraSpa::Server::Error => ex
|
71
|
+
# Server raised an exception when starting. Force exit
|
72
|
+
# so developer see the error immediately. Otherwise, RSpec/Capybara
|
73
|
+
# are hanging around waiting for Puma to boot up.
|
74
|
+
STDERR.puts ex.message, ex.backtrace.join("\n")
|
75
|
+
exit!
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
config.after(:suite) do
|
81
|
+
FrontendServer.stop if FrontendServer.started?
|
82
|
+
end
|
83
|
+
end
|
84
|
+
```
|
85
|
+
|
86
|
+
### Starting and Stopping
|
87
|
+
|
88
|
+
Above, we added a `before(:each)` block to conditionally start the server. Since starting the server can be an expensive operation we only want to do it when we are running javascript specs (e.g. `:js`) and we only want to incur the cost once. So we essentially start it on the very first test that uses javascript and then we leave it running throughout of the test suite.
|
89
|
+
|
90
|
+
After that, an `after(:suite)` block is added to ensure that we stop the server. Technically, this is not necessary as `CapybaraSpa` will install `at_exit` handlers to ensure that any child processes are terminated (to ensure no zombie processes creep up).
|
91
|
+
|
92
|
+
### Connecting to an external server
|
93
|
+
|
94
|
+
Let's say that you don't want to incur the startup cost of booting up the frontend server on every single test run. You can keep a frontend server running in a separate terminal tab and tell the test suite what port to connect to. For example, the above `CapybaraSpa::Server::NgStaticServer` could be replaced with the below lines:
|
95
|
+
|
96
|
+
```ruby
|
97
|
+
FrontendServer = CapybaraSpa::Server::ExternalServer.new(
|
98
|
+
port: 5001 # port is the port that your front-end application server is running on
|
99
|
+
)
|
100
|
+
```
|
101
|
+
|
102
|
+
If you use the `CapybaraSpa::Server::ExternalServer` you will want to leave the `start` and `stop` configuration for your test library in place. This is so that you are notified of a failure if the external process/server is not running, not listening on the correct port, or does not stop.
|
103
|
+
|
104
|
+
By default, `CapybaraSpa::Server::ExternalServer` will wait up to 1 minute to attempt to connect to the port and it will wait up to 1 second for it to be stopped. You can change these values by passing in the `start_timeout` and `stop_timeout` options to the constructor.
|
105
|
+
|
106
|
+
### Why use a global constant?
|
107
|
+
|
108
|
+
The above example uses a global constant because it makes it accessible anywhere (e.g. say we're debugging a test and want to inspect the front-end server process) and because it essentially makes our server instance a singleton.
|
109
|
+
|
110
|
+
You can use a dollar-sign global (e.g. `$frontend_server`) or even a local variable (`frontend_server`). You can also name it whatever you like. Your call. Have fun.
|
111
|
+
|
112
|
+
### Configure Capybara
|
113
|
+
|
114
|
+
Here's a sample configuration of Capybara. It sets the `app_host` to be the front-end application that the browser will hit, and then it sets the back-end server to run on port 3001.
|
115
|
+
|
116
|
+
```ruby
|
117
|
+
Capybara.app_host = "http://localhost:#{FrontendServer.port}"
|
118
|
+
```
|
119
|
+
|
120
|
+
Next, be sure to tell Capybara to hit the backend server process on the right port, e.g.
|
121
|
+
|
122
|
+
```ruby
|
123
|
+
Capybara.server_port = 3001
|
124
|
+
```
|
125
|
+
|
126
|
+
This will require that your front-end application is also configured to connect to the backend on the same port.
|
127
|
+
|
128
|
+
|
129
|
+
### Configure the front-end app
|
130
|
+
|
131
|
+
For Angular 5 and 6 make a new environment (e.g. `ANGULAR_APP/src/environments/environment.integration.ts`) based off from an existing environment (either dev or production) but with the necessary API url configured to look for the API server on port 3001.
|
132
|
+
|
133
|
+
```javascript
|
134
|
+
export const environment = {
|
135
|
+
production: false,
|
136
|
+
apiUrl: 'http://localhost:3001'
|
137
|
+
};
|
138
|
+
```
|
139
|
+
|
140
|
+
Angular 6 requires one more change to. This time, a change to the `angular.json` configuration file. Be sure to add an integration `configuration`, e.g.:
|
141
|
+
|
142
|
+
```javascript
|
143
|
+
"configurations": {
|
144
|
+
"integration": {
|
145
|
+
"fileReplacements": [
|
146
|
+
{
|
147
|
+
"replace": "src/environments/environment.ts",
|
148
|
+
"with": "src/environments/environment.integration.ts"
|
149
|
+
}
|
150
|
+
],
|
151
|
+
"optimization": true,
|
152
|
+
"outputHashing": "all",
|
153
|
+
"sourceMap": false,
|
154
|
+
"extractCss": true,
|
155
|
+
"namedChunks": false,
|
156
|
+
"aot": true,
|
157
|
+
"extractLicenses": true,
|
158
|
+
"vendorChunk": false,
|
159
|
+
"buildOptimizer": true
|
160
|
+
},
|
161
|
+
// ....
|
162
|
+
}
|
163
|
+
```
|
164
|
+
|
165
|
+
Now, you should be ready to go.
|
26
166
|
|
27
167
|
## Development
|
28
168
|
|
@@ -32,7 +172,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
|
|
32
172
|
|
33
173
|
## Contributing
|
34
174
|
|
35
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/
|
175
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/mhs/capybara_spa.
|
36
176
|
|
37
177
|
## License
|
38
178
|
|
data/Rakefile
CHANGED
@@ -3,4 +3,30 @@ require "rspec/core/rake_task"
|
|
3
3
|
|
4
4
|
RSpec::Core::RakeTask.new(:spec)
|
5
5
|
|
6
|
-
task :default => :spec
|
6
|
+
task :default => :spec
|
7
|
+
|
8
|
+
namespace :version do
|
9
|
+
desc "Show the current gem version"
|
10
|
+
task :show do
|
11
|
+
system <<-SHELL
|
12
|
+
bump current
|
13
|
+
SHELL
|
14
|
+
end
|
15
|
+
|
16
|
+
namespace :bump do
|
17
|
+
desc "Bump the gem one major version"
|
18
|
+
task :major do
|
19
|
+
system "bump major"
|
20
|
+
end
|
21
|
+
|
22
|
+
desc "Bump the gem one minor version"
|
23
|
+
task :minor do
|
24
|
+
system "bump minor"
|
25
|
+
end
|
26
|
+
|
27
|
+
desc "Bump the gem one patch version"
|
28
|
+
task :patch do
|
29
|
+
system "bump patch"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
data/lib/capybara_spa.rb
CHANGED
@@ -1,18 +1,19 @@
|
|
1
|
-
require File.join(File.dirname(__FILE__), 'capybara_spa/capybara_dsl_ext')
|
2
|
-
require File.join(File.dirname(__FILE__), 'capybara_spa/server/ng_static_server')
|
3
|
-
|
4
1
|
module CapybaraSpa
|
5
2
|
class << self
|
6
|
-
#
|
3
|
+
# +app_tag+ is the HTML tag where the single page application is stored. Defaults to app-root. \
|
4
|
+
# This can be set thru the SPA_APP_TAG environment variable.
|
7
5
|
attr_accessor :app_tag
|
8
6
|
|
9
|
-
#
|
7
|
+
# +log_file+ where to log the output of angular-http-server. Defaults to /dev/null \
|
8
|
+
# This can be set thru the SPA_LOG_FILE environment variable.
|
10
9
|
attr_accessor :log_file
|
11
10
|
end
|
12
11
|
|
13
|
-
self.app_tag = ENV.fetch('
|
14
|
-
self.log_file = ENV.fetch('
|
12
|
+
self.app_tag = ENV.fetch('SPA_APP_TAG', 'app-root')
|
13
|
+
self.log_file = ENV.fetch('SPA_LOG_FILE', STDOUT)
|
14
|
+
end
|
15
15
|
|
16
|
-
|
17
|
-
|
18
|
-
|
16
|
+
require File.join(File.dirname(__FILE__), 'capybara_spa/capybara_dsl_ext')
|
17
|
+
require File.join(File.dirname(__FILE__), 'capybara_spa/server')
|
18
|
+
require File.join(File.dirname(__FILE__), 'capybara_spa/server/external_server')
|
19
|
+
require File.join(File.dirname(__FILE__), 'capybara_spa/server/ng_static_server')
|
@@ -0,0 +1,102 @@
|
|
1
|
+
require 'socket'
|
2
|
+
require 'timeout'
|
3
|
+
|
4
|
+
module CapybaraSpa
|
5
|
+
module Server
|
6
|
+
class ExternalServerNotFoundOnPort < Error ; end
|
7
|
+
class ExternalServerStillRunning < Error ; end
|
8
|
+
|
9
|
+
# CapybaraSpa::Server::ExternalServer is a class that wraps a server running as
|
10
|
+
# as an external process. For example, let's say you wanted to always have an
|
11
|
+
# a version of your single-page-application up and running for integration tests.
|
12
|
+
# You would start this in a different terminal or shell like this:
|
13
|
+
#
|
14
|
+
# ng serve --port 5001
|
15
|
+
#
|
16
|
+
# Then you would configure your CapybaraSpa server in your test helper (e.g. \
|
17
|
+
# spec_helper.rb, rails_helper.rb, etc):
|
18
|
+
#
|
19
|
+
# server = CapybaraSpa::Server::ExternalServer.new(
|
20
|
+
# port: 5001
|
21
|
+
# )
|
22
|
+
#
|
23
|
+
class ExternalServer
|
24
|
+
# +host+ is a string of the host or ip address to connect to
|
25
|
+
attr_accessor :host
|
26
|
+
|
27
|
+
# +port+ is port number that the external process is running on
|
28
|
+
attr_accessor :port
|
29
|
+
|
30
|
+
# +start_timeout+ is the number of seconds to wait for the external process
|
31
|
+
# to begin listening on +port+. Applies to #start and #started?
|
32
|
+
attr_accessor :start_timeout
|
33
|
+
|
34
|
+
# +stop_timeout+ is the number of seconds to wait when determining the external
|
35
|
+
# process is no longer running. Applies to #stop and #stopped?
|
36
|
+
attr_accessor :stop_timeout
|
37
|
+
|
38
|
+
def initialize(host: 'localhost', port: 5001, start_timeout: 60, stop_timeout: 1)
|
39
|
+
@host = host
|
40
|
+
@port = port
|
41
|
+
@start_timeout = start_timeout
|
42
|
+
@stop_timeout = stop_timeout
|
43
|
+
end
|
44
|
+
|
45
|
+
# +start+ is a no-op, but it will wait up to the +start_timeout+ for the external
|
46
|
+
# process to start listening on the specified +port+ before giving up and raising
|
47
|
+
# an ExternalServerNotFoundOnPort error.
|
48
|
+
def start
|
49
|
+
unless is_port_open?(timeout: start_timeout)
|
50
|
+
raise ExternalServerNotFoundOnPort, <<-ERROR.gsub(/^\s*\|/, '')
|
51
|
+
|Tried for #{start_timeout} seconds but nothing was listening
|
52
|
+
|on port #{port}. Please make sure the external process is running
|
53
|
+
|successfully and that the port is correct.
|
54
|
+
ERROR
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Returns true if the an external process is running on the +host+ and +port+.
|
59
|
+
# Otherwise, returns false.
|
60
|
+
def started?
|
61
|
+
is_port_open?(timeout: start_timeout)
|
62
|
+
end
|
63
|
+
|
64
|
+
# +stop+ is a no-op, but it will wait up to the +stop_timeout+ for the external
|
65
|
+
# process to stop listening on the specified +port+ before giving up
|
66
|
+
# and raising an ExternalServerStillRunning error.
|
67
|
+
def stop
|
68
|
+
unless !is_port_open?(timeout: stop_timeout)
|
69
|
+
raise ExternalServerStillRunning, <<-ERROR.gsub(/^\s*\|/, '')
|
70
|
+
|I tried for #{stop_timeout} seconds to verify that the
|
71
|
+
|external process listening on #{port} had stopped listening,
|
72
|
+
|but it hasn't. You may have a zombie process
|
73
|
+
|or may need to increase the stop_timeout.
|
74
|
+
ERROR
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# Returns true if the an external process is not running on the +host+ and +port+.
|
79
|
+
# Otherwise, returns true.
|
80
|
+
def stopped?
|
81
|
+
!is_port_open?(timeout: stop_timeout)
|
82
|
+
end
|
83
|
+
|
84
|
+
private
|
85
|
+
|
86
|
+
def is_port_open?(timeout: 10)
|
87
|
+
Timeout.timeout(timeout) do
|
88
|
+
begin
|
89
|
+
s = TCPSocket.new(host, port)
|
90
|
+
s.close
|
91
|
+
return true
|
92
|
+
rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH
|
93
|
+
sleep 1
|
94
|
+
retry
|
95
|
+
end
|
96
|
+
end
|
97
|
+
rescue Timeout::Error
|
98
|
+
return false
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
data/lib/capybara_spa/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: capybara_spa
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Zach Dennis
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-05-
|
11
|
+
date: 2018-05-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: capybara
|
@@ -158,6 +158,8 @@ files:
|
|
158
158
|
- capybara_spa.gemspec
|
159
159
|
- lib/capybara_spa.rb
|
160
160
|
- lib/capybara_spa/capybara_dsl_ext.rb
|
161
|
+
- lib/capybara_spa/server.rb
|
162
|
+
- lib/capybara_spa/server/external_server.rb
|
161
163
|
- lib/capybara_spa/server/ng_static_server.rb
|
162
164
|
- lib/capybara_spa/version.rb
|
163
165
|
homepage: https://github.com/mhs/capybara-spa
|