diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b04a8c8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +/.bundle/ +/.yardoc +/_yardoc/ +/coverage/ +/doc/ +/pkg/ +/spec/reports/ +/tmp/ + +# rspec failure tracking +.rspec_status diff --git a/.rspec b/.rspec new file mode 100644 index 0000000..34c5164 --- /dev/null +++ b/.rspec @@ -0,0 +1,3 @@ +--format documentation +--color +--require spec_helper diff --git a/.ruby-version b/.ruby-version new file mode 100644 index 0000000..005119b --- /dev/null +++ b/.ruby-version @@ -0,0 +1 @@ +2.4.1 diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..d9c26bd --- /dev/null +++ b/.travis.yml @@ -0,0 +1,5 @@ +sudo: false +language: ruby +rvm: + - 2.4.1 +before_install: gem install bundler -v 1.16.1 diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..9d97ea6 --- /dev/null +++ b/Gemfile @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } + +gemspec diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..63500b7 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,88 @@ +PATH + remote: . + specs: + capybara_spa (0.1.0) + capybara (~> 3.0) + +GEM + remote: https://rubygems.org/ + specs: + addressable (2.5.2) + public_suffix (>= 2.0.2, < 4.0) + archive-zip (0.11.0) + io-like (~> 0.3.0) + byebug (10.0.2) + capybara (3.0.3) + addressable + mini_mime (>= 0.1.3) + nokogiri (~> 1.8) + rack (>= 1.6.0) + rack-test (>= 0.6.3) + xpath (~> 3.0) + childprocess (0.9.0) + ffi (~> 1.0, >= 1.0.11) + chromedriver-helper (1.2.0) + archive-zip (~> 0.10) + nokogiri (~> 1.8) + coderay (1.1.2) + diff-lcs (1.3) + docile (1.3.0) + ffi (1.9.23) + io-like (0.3.0) + json (2.1.0) + method_source (0.9.0) + mini_mime (1.0.0) + mini_portile2 (2.3.0) + nokogiri (1.8.2) + mini_portile2 (~> 2.3.0) + pry (0.11.3) + coderay (~> 1.1.0) + method_source (~> 0.9.0) + pry-byebug (3.6.0) + byebug (~> 10.0) + pry (~> 0.10) + public_suffix (3.0.2) + rack (2.0.5) + rack-test (1.0.0) + rack (>= 1.0, < 3) + rake (10.5.0) + rspec (3.7.0) + rspec-core (~> 3.7.0) + rspec-expectations (~> 3.7.0) + rspec-mocks (~> 3.7.0) + rspec-core (3.7.1) + rspec-support (~> 3.7.0) + rspec-expectations (3.7.0) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.7.0) + rspec-mocks (3.7.0) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.7.0) + rspec-support (3.7.1) + rubyzip (1.2.1) + selenium-webdriver (3.11.0) + childprocess (~> 0.5) + rubyzip (~> 1.2) + simplecov (0.16.1) + docile (~> 1.1) + json (>= 1.8, < 3) + simplecov-html (~> 0.10.0) + simplecov-html (0.10.2) + xpath (3.0.0) + nokogiri (~> 1.8) + +PLATFORMS + ruby + +DEPENDENCIES + bundler (~> 1.16) + capybara_spa! + chromedriver-helper (~> 1.2) + pry-byebug (~> 3.6) + rake (~> 10.0) + rspec (~> 3.0) + selenium-webdriver (~> 3.11) + simplecov (~> 0.16) + +BUNDLED WITH + 1.16.1 diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..6e1132e --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2018 Zach Dennis + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..47d024e --- /dev/null +++ b/README.md @@ -0,0 +1,39 @@ +# CapybaraSpa + +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. + +TODO: Delete this and the text above, and describe your gem + +## Installation + +Add this line to your application's Gemfile: + +```ruby +gem 'capybara_spa' +``` + +And then execute: + + $ bundle + +Or install it yourself as: + + $ gem install capybara_spa + +## Usage + +TODO: Write usage instructions here + +## Development + +After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. + +To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org). + +## Contributing + +Bug reports and pull requests are welcome on GitHub at https://github.com/zdennis/capybara_spa. + +## License + +The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT). diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..143c961 --- /dev/null +++ b/Rakefile @@ -0,0 +1,6 @@ +require "bundler/gem_tasks" +require "rspec/core/rake_task" + +RSpec::Core::RakeTask.new(:spec) + +task :default => :spec \ No newline at end of file diff --git a/bin/console b/bin/console new file mode 100755 index 0000000..ba50327 --- /dev/null +++ b/bin/console @@ -0,0 +1,14 @@ +#!/usr/bin/env ruby + +require "bundler/setup" +require "capybara_spa" + +# You can add fixtures and/or initialization code here to make experimenting +# with your gem easier. You can also use a different console, if you like. + +# (If you use this, don't forget to add pry to your Gemfile!) +# require "pry" +# Pry.start + +require "irb" +IRB.start(__FILE__) diff --git a/bin/setup b/bin/setup new file mode 100755 index 0000000..dce67d8 --- /dev/null +++ b/bin/setup @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +set -euo pipefail +IFS=$'\n\t' +set -vx + +bundle install + +# Do any other automated setup that you need to do here diff --git a/capybara_spa.gemspec b/capybara_spa.gemspec new file mode 100644 index 0000000..a64dd79 --- /dev/null +++ b/capybara_spa.gemspec @@ -0,0 +1,41 @@ + +lib = File.expand_path("../lib", __FILE__) +$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) +require "capybara_spa/version" + +Gem::Specification.new do |spec| + spec.name = "capybara_spa" + spec.version = CapybaraSpa::VERSION + spec.authors = ["Zach Dennis"] + spec.email = ["zach.dennis@gmail.com"] + + spec.summary = %q{A friendly library for Capybara to make running single page application servers easy} + spec.description = %q{A friendly library for Capybara to make running single page application servers easy as pie for integration, e2e, and feature level specs.} + spec.homepage = "https://github.com/mhs/capybara-spa" + spec.license = "MIT" + + # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host' + # to allow pushing to a single host or delete this section to allow pushing to any host. + if spec.respond_to?(:metadata) + spec.metadata["allowed_push_host"] = "https://rubygems.org" + else + raise "RubyGems 2.0 or newer is required to protect against " \ + "public gem pushes." + end + + spec.files = `git ls-files -z`.split("\x0").reject do |f| + f.match(%r{^(test|spec|features)/}) + end + spec.bindir = "exe" + spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } + spec.require_paths = ["lib"] + + spec.add_dependency "capybara", "~> 3.0" + spec.add_development_dependency "bundler", "~> 1.16" + spec.add_development_dependency "rake", "~> 10.0" + spec.add_development_dependency "rspec", "~> 3.0" + spec.add_development_dependency "chromedriver-helper", "~> 1.2" + spec.add_development_dependency "pry-byebug", "~> 3.6" + spec.add_development_dependency "selenium-webdriver", "~> 3.11" + spec.add_development_dependency "simplecov", "~> 0.16" +end diff --git a/lib/capybara_spa.rb b/lib/capybara_spa.rb new file mode 100644 index 0000000..d773ca2 --- /dev/null +++ b/lib/capybara_spa.rb @@ -0,0 +1,18 @@ +require File.join(File.dirname(__FILE__), 'capybara_spa/capybara_dsl_ext') +require File.join(File.dirname(__FILE__), 'capybara_spa/server/ng_static_server') + +module CapybaraSpa + class << self + # * NG_APP_TAG: the HTML tag where the angular app is stored. Defaults to app-root. + attr_accessor :app_tag + + # * NG_LOG_FILE: where to log the output of angular-http-server. Defaults to /dev/null + attr_accessor :log_file + end + + self.app_tag = ENV.fetch('NG_APP_TAG', 'app-root') + self.log_file = ENV.fetch('NG_LOG_FILE', '/dev/null') + + module Server + end +end \ No newline at end of file diff --git a/lib/capybara_spa/capybara_dsl_ext.rb b/lib/capybara_spa/capybara_dsl_ext.rb new file mode 100644 index 0000000..b0b0b2f --- /dev/null +++ b/lib/capybara_spa/capybara_dsl_ext.rb @@ -0,0 +1,33 @@ +require 'capybara/dsl' + +module Capybara + module DSL + def page + wait_until_angular_app_is_found unless @ignoring_angular + Capybara.current_session + end + + def wait_until_angular_app_is_found + return if @angular_app_found + + @angular_app_found = false + + loop do + Capybara.current_session.visit('/') + app_tag = CapybaraSpa.app_tag + @angular_app_found = Capybara.current_session.evaluate_script <<-JAVASCRIPT + document.getElementsByTagName('#{app_tag}').length === 1 + JAVASCRIPT + break if @angular_app_found + sleep 0.25 + end + end + + def ignoring_angular + @ignoring_angular = true + yield + ensure + @ignoring_angular = false + end + end +end diff --git a/lib/capybara_spa/server/ng_static_server.rb b/lib/capybara_spa/server/ng_static_server.rb new file mode 100644 index 0000000..ed6a85d --- /dev/null +++ b/lib/capybara_spa/server/ng_static_server.rb @@ -0,0 +1,164 @@ +require 'logger' + +module CapybaraSpa + module Server + # CapybaraSpa::Server::NgStaticServer is a class that wraps running + # a static Angular app using angular-http-server. It can take the + # following environment variables: + # + # * NG_BUILD_PATH: where the angular app has been built to. Defaults to nil. + # * NG_HTTP_SERVER_BIN: where angular-http-server binary/script is located. Defaults to nil \ + # which will force lookup of it in PATH, then in node_modules/. + # * NG_PID_FILE: where to write a PID file to. Defaults to /tmp/angular-process.pid + # * NG_PORT: what port to run the Angular app on. Defaults to 5001. + # + class NgStaticServer + class NgAppNotFound < ::StandardError ; end + class NgHttpServerNotFound < ::StandardError ; end + class NgHttpServerNotExecutable < ::StandardError ; end + class NodeModulesDirectoryNotFound < ::StandardError ; end + + attr_accessor :build_path, :http_server_bin_path, :log_file, :pid_file, :port + attr_accessor :pid + + def initialize(build_path:, http_server_bin_path: nil, log_file: CapybaraSpa.log_file, pid_file: nil, port: nil) + @build_path = build_path || ENV.fetch('NG_BUILD_PATH', nil) + @http_server_bin_path = http_server_bin_path || ENV.fetch('NG_HTTP_SERVER_BIN') { find_http_server_bin_path } + @log_file = log_file + @pid_file = pid_file || ENV.fetch('NG_PID_FILE', '/tmp/angular-process.pid') + @port = port || ENV.fetch('NG_PORT', 5001) + @started = false + end + + def started? + @started + end + + def stopped? + !@started + end + + def start + return false if started? + + check_requirements! + + @pid = fork do + STDOUT.reopen(@log_file) + run_server + end + File.write(pid_file, pid) + + at_exit { stop } + @started = true + end + + def stop + if File.exist?(pid_file) + pid = File.read(pid_file).to_i + puts "capybara-angular/angular-http-server:parent#at_exit sending SIGTERM to pid: #{pid}" if ENV['DEBUG'] + begin + Process.kill 'SIGTERM', pid + Process.wait pid + rescue Errno::ECHILD => ex + # no-op, the child process does not exist + end + + puts "capybara-angular/angular-http-server removing pid_file: #{pid_file}" if ENV['DEBUG'] + FileUtils.rm pid_file + @started = false + true + else + puts "capybara-angular/angular-http-server did not find pid_file, no process to SIGHUP: #{pid_file}" if ENV['DEBUG'] + false + end + end + + private + + def check_requirements! + check_executable_requirements! + check_ng_app_requirements! + end + + def check_executable_requirements! + executable_name = File.basename(http_server_bin_path) + + if File.exist?(http_server_bin_path) + if !File.executable?(http_server_bin_path) + raise NgHttpServerNotExecutable, 'File found, but not executable!' + end + else + error_message = <<-ERROR.gsub(/^\s*\|/, '') + |#{executable_name + ' not found!'} Make sure it's installed via npm: + | + |To the project: + | + | npm install --save-dev #{executable_name} + | + |Or globally: + | + | npm install -g #{executable_name} + | + ERROR + raise NgHttpServerNotFound, error_message + end + end + + def check_ng_app_requirements! + unless Dir.exist?(build_path) + error_message = <<-ERROR.gsub(/^\s*\|/, '') + |#{File.expand_path(build_path)} directory not found! Make sure the angular app is being built: + | + |E.g. ng build --aot --environment integration-tests --target=development --output-path=public/app/ + | + ERROR + raise NgAppNotFound, error_message + end + end + + def find_http_server_bin_path + http_server_bin_path = `which angular-http-server`.chomp + + # if no http-server found in default PATH then try to find it in node_modules + if http_server_bin_path.length == 0 + http_server_bin_path = File.join(node_modules_path, '.bin', 'angular-http-server') + end + + http_server_bin_path + end + + def run_server + build_dir = File.dirname(build_path) + Dir.chdir(build_dir) do + cmd = "#{http_server_bin_path} -p #{port} --path #{File.basename(build_path)}" + puts "capybara-angular/angular-http-server is executing command: #{cmd}" # if ENV['DEBUG'] + spawn_cmd(cmd) + end + end + + def spawn_cmd(cmd) + puts "capybara-angular/angular-http-server is executing command: #{cmd}" if ENV['DEBUG'] + + # use spawn(cmd, arg1, ... ) version to avoid launching a shell that launches the + # http-server or ng process. We want this pid to be the actual process to kill when + # this program is done exiting. + pid = spawn *cmd.split(/\s+/) + + puts "capybara-angular/angular-http-server:forked child with pid: #{pid}" if ENV['DEBUG'] + + at_exit do + puts "capybara-angular/angular-http-server:forked#at_exit is sending SIGTERM signal to pid: #{pid}" if ENV['DEBUG'] + begin + Process.kill 'TERM', pid + Process.wait pid + rescue Errno::ESRCH + # no-op: the process is already dead + end + end + + Process.waitall + end + end + end +end \ No newline at end of file diff --git a/lib/capybara_spa/version.rb b/lib/capybara_spa/version.rb new file mode 100644 index 0000000..ce87465 --- /dev/null +++ b/lib/capybara_spa/version.rb @@ -0,0 +1,3 @@ +module CapybaraSpa + VERSION = "0.1.0" +end diff --git a/spec/capybara_spa/server/ng_static_server_spec.rb b/spec/capybara_spa/server/ng_static_server_spec.rb new file mode 100644 index 0000000..374bdae --- /dev/null +++ b/spec/capybara_spa/server/ng_static_server_spec.rb @@ -0,0 +1,209 @@ +require 'spec_helper' +require 'timeout' + +describe CapybaraSpa::Server::NgStaticServer, js: true do + let(:spec_dir) { File.join File.dirname(__FILE__), '../../' } + let(:tmp_dir) { File.join spec_dir, '..', 'tmp' } + let(:angular_app_path) { File.join tmp_dir, 'angular-app' } + let(:angular_build_path) { File.join angular_app_path, 'dist/angular-app' } + + let(:server) do + CapybaraSpa::Server::NgStaticServer.new(**server_args) + end + + let(:default_constructor_args) do + { + build_path: angular_build_path + } + end + let(:server_args) { default_constructor_args } + + def self.it_can_start_and_stop_a_static_server + it 'can start and stop a static server' do + begin + started = server.start + expect(started).to eq true + expect(server.started?).to be true + + visit '/' + expect(page).to have_content('Welcome to app!') + + pid_file = server.pid_file + expect(File.exist?(pid_file)).to be true + + stopped = server.stop + expect(stopped).to be true + expect(server.stopped?).to be true + expect(File.exist?(pid_file)).to_not be true + ensure + # if the test fails above we want to ensure the + # server is stopped + server.stop if server.started? + end + end + end + + context 'with minimal options' do + it_can_start_and_stop_a_static_server + end + + context 'with a custom HTML tag' do + let(:custom_tag) { 'my-app' } + let(:default_tag) { 'app-root' } + + let(:index_file) { File.join(angular_build_path, 'index.html') } + let!(:index_file_contents) { File.read(index_file) } + + around do |example| + original_app_tag = CapybaraSpa.app_tag + files_to_contents = {} + begin + CapybaraSpa.app_tag = custom_tag + + Dir.chdir(angular_build_path) do + files = `grep -l app-root *`.split + files.each do |file| + files_to_contents[file] = File.read(file) + end + files_to_contents.each_pair do |file, contents| + File.write(file, contents.gsub(/#{default_tag}/, custom_tag)) + end + end + Timeout.timeout(10) do + example.run + end + ensure + CapybaraSpa.app_tag = default_tag + + Dir.chdir(angular_build_path) do + files_to_contents.each_pair do |file, contents| + File.write(file, contents.gsub(/#{custom_tag}/, default_tag)) + end + end + end + end + + it_can_start_and_stop_a_static_server + end + + context 'with a custom build path' do + let(:custom_build_path) { File.join angular_app_path, 'dist/my-custom-angular-app' } + + let(:server_args) do + default_constructor_args.merge(build_path: custom_build_path) + end + + around do |example| + Dir.chdir(angular_app_path) do + begin + `mv #{angular_build_path} #{custom_build_path}` + example.run + ensure + `mv #{custom_build_path} #{angular_build_path}` + end + end + end + + it_can_start_and_stop_a_static_server + end + + context 'when angular-http-server is not found' do + let(:http_server_bin_path) { File.join(tmp_dir, 'non-existent', 'angular-http-server') } + + let(:server_args) do + default_constructor_args.merge(http_server_bin_path: http_server_bin_path) + end + + it 'raises an AngularHttpServerNotFound error' do + expect do + server.start + end.to raise_error(CapybaraSpa::Server::NgStaticServer::NgHttpServerNotFound) + expect(server.started?).to eq false + end + end + + context 'when angular-http-server is found, but not an executable' do + let(:http_server_bin_path) { File.join(tmp_dir, 'non-executable-server') } + + let(:server_args) do + default_constructor_args.merge(http_server_bin_path: http_server_bin_path) + end + + around do |example| + FileUtils.touch(http_server_bin_path) + begin + example.run + ensure + server.stop if server && server.started? + FileUtils.rm(http_server_bin_path) if File.exist?(http_server_bin_path) + end + end + + it 'raises an NgStaticServerNotExecutable error' do + expect do + server.start + end.to raise_error(CapybaraSpa::Server::NgStaticServer::NgHttpServerNotExecutable) + expect(server.started?).to eq false + end + end + + context 'when the angular-app is not found/built' do + let(:angular_app_path) { File.join tmp_dir, '/non-existent/angular-app' } + + let(:server_args) do + default_constructor_args.merge(build_path: angular_app_path) + end + + it 'prints an error' do + expect do + server.start + end.to raise_error(CapybaraSpa::Server::NgStaticServer::NgAppNotFound) + expect(server.started?).to eq false + end + end + + context 'with a custom log file' do + let(:log_file) { File.join(tmp_dir, 'angular-http-server.log') } + let(:server_args) do + default_constructor_args.merge(log_file: log_file) + end + + around do |example| + FileUtils.touch log_file + server.start + begin + example.run + ensure + server.stop if server && server.started? + FileUtils.rm(log_file) if File.exist?(log_file) + end + end + + it 'logs out the angular-http-server output to the log file' do + expect do + visit '/' + end.to change { File.read(log_file) } + end + end + + context 'with a custom pid file' do + let(:custom_pid_file) { File.join(tmp_dir, 'angular-http-server.pid') } + let(:server_args) { default_constructor_args.merge(pid_file: custom_pid_file) } + + before { server.start } + after { server.stop } + + it 'writes out the PID of the angular-http-server process' do + expect(File.exist?(custom_pid_file)).to be true + pid = File.read(custom_pid_file).to_i + + # pid should not be zero + expect(pid).to be > 0 + + expect do + # Ensure process is running, will raise error if it's not + Process.kill 'SIGHUP', pid + end.to_not raise_error + end + end +end diff --git a/spec/capybara_spa_spec.rb b/spec/capybara_spa_spec.rb new file mode 100644 index 0000000..82eff8d --- /dev/null +++ b/spec/capybara_spa_spec.rb @@ -0,0 +1,5 @@ +RSpec.describe CapybaraSpa do + it "has a version number" do + expect(CapybaraSpa::VERSION).not_to be nil + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb new file mode 100644 index 0000000..e4f05eb --- /dev/null +++ b/spec/spec_helper.rb @@ -0,0 +1,185 @@ +require 'simplecov' +SimpleCov.start + +require 'pry' +$LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', 'lib') + +Bundler.require(:default, :test) + +require 'capybara/rspec' +require 'selenium/webdriver' + + +require 'capybara_spa' + +chrome_options = {} +logging_preferences = { browser: 'ALL' } + +Capybara.register_driver :chrome do |app| + capabilities = Selenium::WebDriver::Remote::Capabilities.chrome( + chromeOptions: chrome_options, + loggingPrefs: logging_preferences + ) + + Capybara::Selenium::Driver.new( + app, + browser: :chrome, + desired_capabilities: capabilities + ) +end + +Capybara.register_driver :headless_chrome do |app| + capabilities = Selenium::WebDriver::Remote::Capabilities.chrome( + chromeOptions: chrome_options.merge(args: %w(headless disable-gpu)), + loggingPrefs: logging_preferences + ) + + Capybara::Selenium::Driver.new( + app, + browser: :chrome, + desired_capabilities: capabilities + ) +end + +Capybara.app_host = "http://localhost:5001" +Capybara.always_include_port = true +Capybara.default_max_wait_time = 10 +Capybara.javascript_driver = :headless_chrome +Capybara.server = :puma +Capybara.server_port = 3001 + +STDOUT.sync = true + +# This file was generated by the `rspec --init` command. Conventionally, all +# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. +# The generated `.rspec` file contains `--require spec_helper` which will cause +# this file to always be loaded, without a need to explicitly require it in any +# files. +# +# Given that it is always loaded, you are encouraged to keep this file as +# light-weight as possible. Requiring heavyweight dependencies from this file +# will add to the boot time of your test suite on EVERY test run, even for an +# individual file that may not need all of that loaded. Instead, consider making +# a separate helper file that requires the additional dependencies and performs +# the additional setup, and require it from the spec files that actually need +# it. +# +# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration +RSpec.configure do |config| + # rspec-expectations config goes here. You can use an alternate + # assertion/expectation library such as wrong or the stdlib/minitest + # assertions if you prefer. + config.expect_with :rspec do |expectations| + # This option will default to `true` in RSpec 4. It makes the `description` + # and `failure_message` of custom matchers include text for helper methods + # defined using `chain`, e.g.: + # be_bigger_than(2).and_smaller_than(4).description + # # => "be bigger than 2 and smaller than 4" + # ...rather than: + # # => "be bigger than 2" + expectations.include_chain_clauses_in_custom_matcher_descriptions = true + end + + # rspec-mocks config goes here. You can use an alternate test double + # library (such as bogus or mocha) by changing the `mock_with` option here. + config.mock_with :rspec do |mocks| + # Prevents you from mocking or stubbing a method that does not exist on + # a real object. This is generally recommended, and will default to + # `true` in RSpec 4. + mocks.verify_partial_doubles = true + end + + # This option will default to `:apply_to_host_groups` in RSpec 4 (and will + # have no way to turn it off -- the option exists only for backwards + # compatibility in RSpec 3). It causes shared context metadata to be + # inherited by the metadata hash of host groups and examples, rather than + # triggering implicit auto-inclusion in groups with matching metadata. + config.shared_context_metadata_behavior = :apply_to_host_groups + +# The settings below are suggested to provide a good initial experience +# with RSpec, but feel free to customize to your heart's content. +=begin + # This allows you to limit a spec run to individual examples or groups + # you care about by tagging them with `:focus` metadata. When nothing + # is tagged with `:focus`, all examples get run. RSpec also provides + # aliases for `it`, `describe`, and `context` that include `:focus` + # metadata: `fit`, `fdescribe` and `fcontext`, respectively. + config.filter_run_when_matching :focus + + # Allows RSpec to persist some state between runs in order to support + # the `--only-failures` and `--next-failure` CLI options. We recommend + # you configure your source control system to ignore this file. + config.example_status_persistence_file_path = "spec/examples.txt" + + # Limits the available syntax to the non-monkey patched syntax that is + # recommended. For more details, see: + # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/ + # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ + # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode + config.disable_monkey_patching! + + # This setting enables warnings. It's recommended, but in some cases may + # be too noisy due to issues in dependencies. + config.warnings = true + + # Many RSpec users commonly either run the entire suite or an individual + # file, and it's useful to allow more verbose output when running an + # individual spec file. + if config.files_to_run.one? + # Use the documentation formatter for detailed output, + # unless a formatter has already been configured + # (e.g. via a command-line flag). + config.default_formatter = "doc" + end + + # Print the 10 slowest examples and example groups at the + # end of the spec run, to help surface which specs are running + # particularly slow. + config.profile_examples = 10 + + # Run specs in random order to surface order dependencies. If you find an + # order dependency and want to debug it, you can fix the order by providing + # the seed, which is printed after each run. + # --seed 1234 + config.order = :random + + # Seed global randomization in this process using the `--seed` CLI option. + # Setting this allows you to use `--seed` to deterministically reproduce + # test failures related to randomization by passing the same `--seed` value + # as the one that triggered the failure. + Kernel.srand config.seed +=end + + config.include Capybara::DSL, js: true + + config.before(:suite) do + angular_app_path = File.join File.dirname(__FILE__), '..', 'tmp', 'angular-app' + output_path = File.join angular_app_path, 'dist' + + build_static_angular_app = -> { + result = system <<-SHELL + cd #{angular_app_path} && + ng build --configuration integration-test + SHELL + puts result.inspect + } + + if Dir.exist?(output_path) + puts <<-MESSAGE.gsub(/^\s*\|/, '') + | + |Using angular app found at #{output_path} + |To rebuild the app delete this directory, then re-run the tests. + | + MESSAGE + else + puts <<-MESSAGE.gsub(/^\s*\|/, '') + | + |Angular app not found at #{output_path} + |Building. This may take a minute... + | + MESSAGE + + build_static_angular_app.call + end + end +end