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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 19ed7589902a6d0325189f481b625b7b51c76f69e1db77007908d782ec8f8284
4
- data.tar.gz: 5e3812be9fffb593695a0c29b3aa989a9d0930410883bb8040f062a308b41b64
3
+ metadata.gz: ee0028ff6473dfd7ee60d23ed3ca3f3a07e1412fa11beed4faf28a7da1e110b1
4
+ data.tar.gz: faa416fd1f5adb9cf61be4b465351c67771229f9bbb393fea143d46a39214fe2
5
5
  SHA512:
6
- metadata.gz: 8dfff66e5fd86435ff0daa5f1786d537fb581c55d588923b7d373811d0fa4426567072134d8ad82c621fc5f514ae9b3e8e61fd86f55348b05784f665a7f2c45e
7
- data.tar.gz: 9c2b0307ddb544cb7745cc9922ea7ec78a435f1cc4cc9c74bd1a34e480a9faad08ac7561a6d46cf6f608c8e2ac73801e6f04dc082b7e10d408bc100196352d2e
6
+ metadata.gz: b0a7cf1c8f639162f80e6040c8f3d20766aa80e8aec827002e53561ea87bed3ace6d3141f05b6741de842d5fdc952bd12a12b0c5af14181fd53293c8f74ac0ff
7
+ data.tar.gz: 6e810df6910e92e214a76a93242b4e62b331a26ca3dd7a35f2af002c719d04f938ac1d7d92d5b5a9ec115236eaa5aade1afd818fd30448b0930969689b154d3a
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- capybara_spa (0.3.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.3)
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
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/capybara_spa`. To experiment with that code, run `bin/console` for an interactive prompt.
3
+ ![https://travis-ci.org/mhs/capybara_spa.svg?branch=master](https://travis-ci.org/mhs/capybara_spa.svg?branch=master)
4
4
 
5
- TODO: Delete this and the text above, and describe your gem
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 your application's Gemfile:
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
- TODO: Write usage instructions here
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/zdennis/capybara_spa.
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
@@ -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
- # * NG_APP_TAG: the HTML tag where the angular app is stored. Defaults to app-root.
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
- # * NG_LOG_FILE: where to log the output of angular-http-server. Defaults to /dev/null
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('NG_APP_TAG', 'app-root')
14
- self.log_file = ENV.fetch('NG_LOG_FILE', STDOUT)
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
- module Server
17
- end
18
- end
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,5 @@
1
+ module CapybaraSpa
2
+ module Server
3
+ class Error < ::StandardError ; end
4
+ end
5
+ end
@@ -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
@@ -1,3 +1,3 @@
1
1
  module CapybaraSpa
2
- VERSION = "0.3.0"
2
+ VERSION = "0.4.0"
3
3
  end
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.3.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-09 00:00:00.000000000 Z
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