diff --git a/.travis.yml b/.travis.yml index 6c34e64..84ae04d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -33,6 +33,15 @@ jobs: skip_cleanup: true on: tags: true + - name: "Build & Deploy Docker for Smoke Tests" + script: + - $TRAVIS_BUILD_DIR/smoke/build.sh travis-build-image-smoke + deploy: + - provider: script + script: bash $TRAVIS_BUILD_DIR/docker/ci-push-smoke $TRAVIS_TAG + skip_cleanup: true + on: + tags: true - name: "Build & Upload Package" script: - docker run -v $TRAVIS_BUILD_DIR/deployment/helm/:/helm -w /helm --entrypoint /helm/build.sh alpine/helm:3.2.1 $TRAVIS_TAG diff --git a/deployment/README.md b/deployment/README.md index 7bd82b0..4b68663 100644 --- a/deployment/README.md +++ b/deployment/README.md @@ -7,6 +7,7 @@ * [Cloud Foundry](#cloud-foundry) * [Heroku](#heroku) * [Configuration](#configuration) + * [Testing your deployment](#testing-your-deployment) ## Prerequisites 1. Download and extract the latest package from the [releases page](https://github.com/pivotal/postfacto/releases) @@ -198,3 +199,12 @@ You can customise this window with the `SESSION_TIME` env variable to the `env` ```bash SESSION_TIME=60 ./deploy ``` + +## Testing your deployment + +1. Log in to the Postfacto admin dashboard +1. Create a new admin user for the test to use by clicking on 'Admin Users' and then 'New Admin User'. Take note of the email and password you use, as these will be used in the next step +1. Run the smoke test script from the root of the package directory: + ```bash + ./smoke-test.sh + ``` \ No newline at end of file diff --git a/deployment/smoke-test.sh b/deployment/smoke-test.sh new file mode 100755 index 0000000..796a02f --- /dev/null +++ b/deployment/smoke-test.sh @@ -0,0 +1,32 @@ +#!/bin/bash + +set -e + +BASE_DIR="$(dirname "$0")" + +if [ $# -lt 4 ]; then + echo "usage: ./smoke-test.sh " + echo "This will run the smoke tests for your deployed instance of Postfacto." + exit 1 +fi + +export BASE_WEB_URL=${1} +export BASE_ADMIN_URL=${2} +export ADMIN_EMAIL=${3} +export ADMIN_PASSWORD=${4} + +APP_VERSION="dev" +if [ -f "$BASE_DIR/VERSION" ]; then + APP_VERSION=$(cat "$BASE_DIR/VERSION") +fi + +echo 'Running smoke tests...' + +docker run -it --rm \ + -e BASE_WEB_URL=$BASE_WEB_URL \ + -e BASE_ADMIN_URL=$BASE_ADMIN_URL \ + -e ADMIN_EMAIL=$ADMIN_EMAIL \ + -e ADMIN_PASSWORD=$ADMIN_PASSWORD \ + postfacto/smoke:$APP_VERSION + +echo "Smoke tests passed" \ No newline at end of file diff --git a/docker/ci-push-smoke b/docker/ci-push-smoke new file mode 100644 index 0000000..689e4a3 --- /dev/null +++ b/docker/ci-push-smoke @@ -0,0 +1,21 @@ +#!/bin/bash +set -e + +TAG=$1 +REPOSITORY=postfacto/smoke +VERSION_TAG="$REPOSITORY:$TAG" +LATEST_TAG="$REPOSITORY:latest" + +echo "Tagging: $VERSION_TAG" +docker tag travis-build-image-smoke $VERSION_TAG + +echo "Tagging: $LATEST_TAG" +docker tag travis-build-image-smoke $LATEST_TAG + +echo "$DOCKER_PASSWORD" | docker login --username "$DOCKER_USERNAME" --password-stdin + +echo "Pushing tag: $VERSION_TAG" +docker push $VERSION_TAG + +echo "Pushing tag: $LATEST_TAG" +docker push $LATEST_TAG diff --git a/docker/smoke/Dockerfile b/docker/smoke/Dockerfile new file mode 100644 index 0000000..28c4e62 --- /dev/null +++ b/docker/smoke/Dockerfile @@ -0,0 +1,44 @@ +FROM ubuntu:18.04 +MAINTAINER pivotal + +SHELL ["/bin/bash", "-c"] + +# Install dependencies +RUN apt-get update && apt-get install -y autoconf bison build-essential curl git libfontconfig libpq-dev libssl-dev libyaml-dev libreadline6-dev zlib1g-dev libncurses5-dev libffi-dev libgdbm-dev libnss3 libxi6 libgconf-2-4 unzip wget + +# Install Rbenv and Ruby +RUN git clone https://github.com/rbenv/rbenv.git ~/.rbenv && echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrc && echo 'eval "$(rbenv init -)"' >> ~/.bashrc +RUN git clone https://github.com/rbenv/ruby-build.git ~/.rbenv/plugins/ruby-build +ENV PATH $PATH:/root/.rbenv/bin:/root/.rbenv/shims +RUN cd /root/.rbenv/plugins/ruby-build && git pull && cd - +ENV RUBY_VERSION 2.6.3 +RUN rbenv install $RUBY_VERSION && rbenv global $RUBY_VERSION && rbenv rehash +RUN echo 'gem: --no-rdoc --no-ri' >> /.gemrc +RUN gem install bundler + +# Install Chrome WebDriver +RUN apt-get update && apt-get install -y apt-transport-https ca-certificates +RUN CHROMEDRIVER_VERSION=$(curl https://chromedriver.storage.googleapis.com/LATEST_RELEASE) && \ + mkdir -p /opt/chromedriver-$CHROMEDRIVER_VERSION && \ + curl -sS -o /tmp/chromedriver_linux64.zip https://chromedriver.storage.googleapis.com/$CHROMEDRIVER_VERSION/chromedriver_linux64.zip && \ + unzip -qq /tmp/chromedriver_linux64.zip -d /opt/chromedriver-$CHROMEDRIVER_VERSION && \ + rm /tmp/chromedriver_linux64.zip && \ + chmod +x /opt/chromedriver-$CHROMEDRIVER_VERSION/chromedriver && \ + ln -fs /opt/chromedriver-$CHROMEDRIVER_VERSION/chromedriver /usr/local/bin/chromedriver + +# Install Google Chrome +RUN curl -sS -o - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - && \ + echo "deb https://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google-chrome.list +RUN apt-get -yqq update && apt-get -yqq install google-chrome-stable && rm -rf /var/lib/apt/lists/* + +COPY . /smoke + +# Install Nokogiri +RUN apt-get update && \ + apt-get install -y pkg-config libxslt-dev libxml2-dev && \ + gem install nokogiri -- --use-system-libraries + +RUN cd smoke && bundle install + +WORKDIR "/smoke" +CMD ["bundle", "exec", "rspec", "--format", "documentation"] \ No newline at end of file diff --git a/package.sh b/package.sh index 13bd14d..362a6e6 100755 --- a/package.sh +++ b/package.sh @@ -111,6 +111,17 @@ cp -r deployment/upgrade-heroku.sh package/heroku/upgrade.sh cp -r deployment/mixpanel.sh package/heroku/mixpanel.sh chmod u+x package/heroku/*.sh +# Smoke tests + +cp -r deployment/smoke-test.sh package +chmod u+x package/smoke-test.sh + +# Persist version + +if [ $# -gt 0 ]; then + echo $1 > package/VERSION +fi + # Docs cp deployment/README.md package diff --git a/smoke/.ruby-version b/smoke/.ruby-version new file mode 100644 index 0000000..ec1cf33 --- /dev/null +++ b/smoke/.ruby-version @@ -0,0 +1 @@ +2.6.3 diff --git a/smoke/Gemfile b/smoke/Gemfile new file mode 100644 index 0000000..8134dd4 --- /dev/null +++ b/smoke/Gemfile @@ -0,0 +1,40 @@ +# +# Postfacto, a free, open-source and self-hosted retro tool aimed at helping +# remote teams. +# +# Copyright (C) 2016 - Present Pivotal Software, Inc. +# +# This program is free software: you can redistribute it and/or modify +# +# it under the terms of the GNU Affero General Public License as +# +# published by the Free Software Foundation, either version 3 of the +# +# License, or (at your option) any later version. +# +# +# +# This program is distributed in the hope that it will be useful, +# +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# +# GNU Affero General Public License for more details. +# +# +# +# You should have received a copy of the GNU Affero General Public License +# +# along with this program. If not, see . +# +source 'https://rubygems.org' + +ruby '~> 2.6' + +group :development, :test do + gem 'rspec' + gem 'rspec-retry' + gem 'capybara', '>= 3.28.0' + gem 'selenium-webdriver', '>= 3.141.0' +end diff --git a/smoke/Gemfile.lock b/smoke/Gemfile.lock new file mode 100644 index 0000000..c1e7e14 --- /dev/null +++ b/smoke/Gemfile.lock @@ -0,0 +1,60 @@ +GEM + remote: https://rubygems.org/ + specs: + addressable (2.7.0) + public_suffix (>= 2.0.2, < 5.0) + capybara (3.32.2) + addressable + mini_mime (>= 0.1.3) + nokogiri (~> 1.8) + rack (>= 1.6.0) + rack-test (>= 0.6.3) + regexp_parser (~> 1.5) + xpath (~> 3.2) + childprocess (3.0.0) + diff-lcs (1.3) + mini_mime (1.0.2) + mini_portile2 (2.4.0) + nokogiri (1.10.9) + mini_portile2 (~> 2.4.0) + public_suffix (4.0.5) + rack (2.2.2) + rack-test (1.1.0) + rack (>= 1.0, < 3) + regexp_parser (1.7.0) + rspec (3.9.0) + rspec-core (~> 3.9.0) + rspec-expectations (~> 3.9.0) + rspec-mocks (~> 3.9.0) + rspec-core (3.9.2) + rspec-support (~> 3.9.3) + rspec-expectations (3.9.2) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.9.0) + rspec-mocks (3.9.1) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.9.0) + rspec-retry (0.6.2) + rspec-core (> 3.3) + rspec-support (3.9.3) + rubyzip (2.3.0) + selenium-webdriver (3.142.7) + childprocess (>= 0.5, < 4.0) + rubyzip (>= 1.2.2) + xpath (3.2.0) + nokogiri (~> 1.8) + +PLATFORMS + ruby + +DEPENDENCIES + capybara (>= 3.28.0) + rspec + rspec-retry + selenium-webdriver (>= 3.141.0) + +RUBY VERSION + ruby 2.6.3p62 + +BUNDLED WITH + 1.17.3 diff --git a/smoke/build.sh b/smoke/build.sh new file mode 100755 index 0000000..e33c7f4 --- /dev/null +++ b/smoke/build.sh @@ -0,0 +1,37 @@ +#!/bin/sh +# +# Postfacto, a free, open-source and self-hosted retro tool aimed at helping +# remote teams. +# +# Copyright (C) 2016 - Present Pivotal Software, Inc. +# +# This program is free software: you can redistribute it and/or modify +# +# it under the terms of the GNU Affero General Public License as +# +# published by the Free Software Foundation, either version 3 of the +# +# License, or (at your option) any later version. +# +# +# +# This program is distributed in the hope that it will be useful, +# +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# +# GNU Affero General Public License for more details. +# +# +# +# You should have received a copy of the GNU Affero General Public License +# +# along with this program. If not, see . +# +set -e + +TAG=${1:-"postfacto/smoke:dev"} +BASE_DIR="$(dirname "$0")" + +docker build $BASE_DIR -f $BASE_DIR/../docker/smoke/Dockerfile -t $TAG \ No newline at end of file diff --git a/smoke/spec/simple_journey_spec.rb b/smoke/spec/simple_journey_spec.rb new file mode 100644 index 0000000..f3af515 --- /dev/null +++ b/smoke/spec/simple_journey_spec.rb @@ -0,0 +1,118 @@ +# +# Postfacto, a free, open-source and self-hosted retro tool aimed at helping +# remote teams. +# +# Copyright (C) 2016 - Present Pivotal Software, Inc. +# +# This program is free software: you can redistribute it and/or modify +# +# it under the terms of the GNU Affero General Public License as +# +# published by the Free Software Foundation, either version 3 of the +# +# License, or (at your option) any later version. +# +# +# +# This program is distributed in the hope that it will be useful, +# +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# +# GNU Affero General Public License for more details. +# +# +# +# You should have received a copy of the GNU Affero General Public License +# +# along with this program. If not, see . +# +require 'spec_helper' +require 'securerandom' + +describe 'A Simple Journey', type: :feature, js: true do + + before(:all) do + test_id = SecureRandom.alphanumeric(6) + @retro_slug = 'my-awesome-new-private-retro' + test_id + @retro_url = RETRO_APP_BASE_URL + '/retros/' + @retro_slug + @retro_name = 'My awesome new private retro' + @retro_password = SecureRandom.alphanumeric(32) + end + + after(:all) do + delete_retro_as_admin(@retro_slug) + expect(page).not_to have_content(@retro_slug), "Failed to delete retro with slug #{@retro_slug}. This was created for testing purposes, please delete manually." + end + + specify ('Alex can create a retro') do + in_browser(:alex) do + create_retro_as_admin(@retro_name, @retro_slug, @retro_password) + end + end + + specify ('Peter can access the retro') do + in_browser(:peter) do + visit_retro_board(@retro_url, @retro_password) + expect(page).to have_content(@retro_name) + end + end + + specify ('Peter and Felicity can create and see each others retro items in real time') do + in_browser(:peter) do + visit_retro_board(@retro_url, @retro_password) + expect(page).to have_content(@retro_name) + end + + in_browser(:felicity) do + visit_retro_board(@retro_url, @retro_password) + expect(page).to have_content(@retro_name) + + fill_in("I'm glad that...", with: 'this is a happy item') + find('.column-happy textarea.retro-item-add-input').native.send_keys(:return) + + expect(page).to have_content 'this is a happy item' + end + + in_browser(:peter) do + expect(page).to have_content 'this is a happy item' + + fill_in("It wasn't so great that...", with: 'this is a sad item') + find('.column-sad textarea.retro-item-add-input').native.send_keys(:return) + + expect(page).to have_content 'this is a sad item' + end + + in_browser(:felicity) do + expect(page).to have_content 'this is a sad item' + + fill_in("Add an action item", with: 'this is an action') + find('.retro-action-header .retro-item-add-input').native.send_keys(:return) + + expect(page).to have_content('this is an action') + end + + in_browser(:peter) do + expect(page).to have_content('this is an action') + end + end + + + specify ('Felicity can archive the retro') do + in_browser(:felicity) do + visit_retro_board(@retro_url, @retro_password) + expect(page).to have_content(@retro_name) + + click_menu_item 'Archive this retro' + click_button 'Archive' + end + + in_browser(:peter) do + visit_retro_board(@retro_url, @retro_password) + expect(page).to have_content(@retro_name) + + expect(page).to_not have_css('.retro-item') + end + end +end diff --git a/smoke/spec/spec_helper.rb b/smoke/spec/spec_helper.rb new file mode 100644 index 0000000..2297e7e --- /dev/null +++ b/smoke/spec/spec_helper.rb @@ -0,0 +1,136 @@ +# +# Postfacto, a free, open-source and self-hosted retro tool aimed at helping +# remote teams. +# +# Copyright (C) 2016 - Present Pivotal Software, Inc. +# +# This program is free software: you can redistribute it and/or modify +# +# it under the terms of the GNU Affero General Public License as +# +# published by the Free Software Foundation, either version 3 of the +# +# License, or (at your option) any later version. +# +# +# +# This program is distributed in the hope that it will be useful, +# +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# +# GNU Affero General Public License for more details. +# +# +# +# You should have received a copy of the GNU Affero General Public License +# +# along with this program. If not, see . +# +require 'capybara/rspec' +require 'rspec/retry' +require 'selenium/webdriver' +require 'securerandom' + +Capybara.register_driver(:headless_chrome) do |app| + options = ::Selenium::WebDriver::Chrome::Options.new( + args: ['--headless','--disable-gpu', '--no-sandbox', '--disable-dev-shm-usage'] + ) + + Capybara::Selenium::Driver.new( + app, + browser: :chrome, + options: options + ) +end + +RETRO_APP_BASE_URL = ENV['BASE_WEB_URL'] || 'http://localhost:4000' +RETRO_ADMIN_BASE_URL = ENV['BASE_ADMIN_URL'] || 'http://localhost:4000/admin' +RETRO_ADMIN_EMAIL = ENV['ADMIN_EMAIL'] || 'email@example.com' +RETRO_ADMIN_PASSWORD = ENV['ADMIN_PASSWORD'] || 'password' + +Capybara.run_server = false +Capybara.default_driver = :headless_chrome +Capybara.app_host = RETRO_APP_BASE_URL +Capybara.javascript_driver = :headless_chrome +Capybara.default_max_wait_time = 10 + +module SpecHelpers + def click_menu_item(menu_item) + scroll_to_top + + find('.retro-menu button').click + expect(page).to have_content(menu_item) + find('.retro-menu-item', text: menu_item).click + expect(page).not_to have_content(menu_item) + end + + def visit_active_admin_page + visit RETRO_ADMIN_BASE_URL + end + + def login_as_admin + visit_active_admin_page + + fill_in 'admin_user_email', with: RETRO_ADMIN_EMAIL + fill_in 'admin_user_password', with: RETRO_ADMIN_PASSWORD + click_on 'Login' + end + + def create_retro_as_admin(name, slug, password) + login_as_admin + + click_on 'Retros' + click_on 'New Retro' + + fill_in 'retro_name', with: name + fill_in 'retro_slug', with: slug + fill_in 'retro_password', with: password + + click_on 'Create Retro' + end + + def delete_retro_as_admin(slug) + login_as_admin + + click_on 'Retros' + fill_in 'q_slug', with: slug + click_on 'Filter' + + click_on 'Delete' + + page.driver.browser.switch_to.alert.accept + end + + def visit_retro_board(url, password) + visit url + fill_in 'Password', with: password + click_button 'Login' + end + + def scroll_to_top + page.execute_script('window.scrollTo(0,0)') + end + + def in_browser(name, &block) + old_session = Capybara.session_name + + Capybara.session_name = name + result = block.call + Capybara.session_name = old_session + + return result + end +end + +RSpec.configure do |c| + c.include SpecHelpers + c.filter_run focus: true + c.run_all_when_everything_filtered = true + c.verbose_retry = true + c.display_try_failure_messages = true + c.around :each, :js do |ex| + ex.run_with_retry retry: 1 + end +end