Commit 7d20e476 authored by Grzegorz Bizon's avatar Grzegorz Bizon

Add GitLab QA integrations tests to GitLab CE / EE

parent 72e940df
--color
--format documentation
--require spec_helper
FROM ruby:2.3
LABEL maintainer "Grzegorz Bizon <grzegorz@gitlab.com>"
RUN sed -i "s/httpredir.debian.org/ftp.us.debian.org/" /etc/apt/sources.list && \
apt-get update && apt-get install -y --force-yes \
libqt5webkit5-dev qt5-qmake qt5-default build-essential xvfb git && \
apt-get clean
WORKDIR /home/qa
COPY ./ ./
RUN bundle install
ENTRYPOINT ["bin/test"]
source 'https://rubygems.org'
gem 'capybara', '~> 2.12.1'
gem 'capybara-screenshot', '~> 1.0.14'
gem 'capybara-webkit', '~> 1.12.0'
gem 'rake', '~> 12.0.0'
gem 'rspec', '~> 3.5'
gem 'rubocop', '~> 0.47.1'
## Integration tests for GitLab
#!/bin/sh
case "$1" in
build)
docker pull $CI_REGISTRY_IMAGE:latest
docker build --cache-from $CI_REGISTRY_IMAGE:latest \
-t $CI_REGISTRY_IMAGE:ce-latest -t $CI_REGISTRY_IMAGE:ee-latest \
-t $CI_REGISTRY_IMAGE:ce-nightly -t $CI_REGISTRY_IMAGE:ee-nightly \
-t $CI_REGISTRY_IMAGE:latest .
;;
publish)
test -n "$CI_BUILD_TOKEN" || exit 1
docker login --username gitlab-ci-token --password $CI_BUILD_TOKEN registry.gitlab.com
docker push $CI_REGISTRY_IMAGE:latest
docker push $CI_REGISTRY_IMAGE:ce-latest
docker push $CI_REGISTRY_IMAGE:ee-latest
docker push $CI_REGISTRY_IMAGE:ee-nightly
docker push $CI_REGISTRY_IMAGE:ee-nightly
docker logout registry.gitlab.com
;;
*)
echo "Usage: $0 [build|publish]"
exit 1
;;
esac
#!/usr/bin/env ruby
require_relative '../qa'
QA::Scenario
.const_get(ARGV.shift)
.perform(*ARGV)
#!/bin/bash
xvfb-run bundle exec bin/qa $@
$LOAD_PATH << File.expand_path(File.dirname(__FILE__))
module QA
##
# GitLab QA runtime classes, mostly singletons.
#
module Runtime
autoload :User, 'qa/runtime/user'
autoload :Namespace, 'qa/runtime/namespace'
end
##
# GitLab QA Scenarios
#
module Scenario
##
# Support files
#
autoload :Actable, 'qa/scenario/actable'
autoload :Template, 'qa/scenario/template'
##
# Test scenario entrypoints.
#
module Test
autoload :Instance, 'qa/scenario/test/instance'
end
##
# GitLab instance scenarios.
#
module Gitlab
module Project
autoload :Create, 'qa/scenario/gitlab/project/create'
end
module License
autoload :Add, 'qa/scenario/gitlab/license/add'
end
end
end
##
# Classes describing structure of GitLab, pages, menus etc.
#
# Needed to execute click-driven-only black-box tests.
#
module Page
autoload :Base, 'qa/page/base'
module Main
autoload :Entry, 'qa/page/main/entry'
autoload :Menu, 'qa/page/main/menu'
autoload :Groups, 'qa/page/main/groups'
autoload :Projects, 'qa/page/main/projects'
end
module Project
autoload :New, 'qa/page/project/new'
autoload :Show, 'qa/page/project/show'
end
module Admin
autoload :Menu, 'qa/page/admin/menu'
autoload :License, 'qa/page/admin/license'
end
end
##
# Classes describing operations on Git repositories.
#
module Git
autoload :Repository, 'qa/git/repository'
end
##
# Classes that make it possible to execute features tests.
#
module Specs
autoload :Config, 'qa/specs/config'
autoload :Runner, 'qa/specs/runner'
end
end
require 'uri'
module QA
module Git
class Repository
include Scenario::Actable
def self.perform(*args)
Dir.mktmpdir do |dir|
Dir.chdir(dir) { super }
end
end
def location=(address)
@location = address
@uri = URI(address)
end
def username=(name)
@username = name
@uri.user = name
end
def password=(pass)
@password = pass
@uri.password = pass
end
def use_default_credentials
self.username = Runtime::User.name
self.password = Runtime::User.password
end
def clone(opts = '')
`git clone #{opts} #{@uri.to_s} ./`
end
def shallow_clone
clone('--depth 1')
end
def configure_identity(name, email)
`git config user.name #{name}`
`git config user.email #{email}`
end
def commit_file(name, contents, message)
add_file(name, contents)
commit(message)
end
def add_file(name, contents)
File.write(name, contents)
`git add #{name}`
end
def commit(message)
`git commit -m "#{message}"`
end
def push_changes(branch = 'master')
`git push #{@uri.to_s} #{branch}`
end
def commits
`git log --oneline`.split("\n")
end
end
end
end
module QA
module Page
module Admin
class License < Page::Base
def no_license?
page.has_content?('No GitLab Enterprise Edition ' \
'license has been provided yet')
end
def add_new_license(key)
raise 'License key empty!' if key.to_s.empty?
choose 'Enter license key'
fill_in 'License key', with: key
click_button 'Upload license'
end
end
end
end
end
module QA
module Page
module Admin
class Menu < Page::Base
def go_to_license
within_middle_menu { click_link 'License' }
end
private
def within_middle_menu
page.within('.nav-control') do
yield
end
end
end
end
end
end
module QA
module Page
class Base
include Capybara::DSL
include Scenario::Actable
def refresh
visit current_path
end
end
end
end
module QA
module Page
module Main
class Entry < Page::Base
def initialize
visit('/')
# This resolves cold boot problems with login page
find('.application', wait: 120)
end
def sign_in_using_credentials
if page.has_content?('Change your password')
fill_in :user_password, with: Runtime::User.password
fill_in :user_password_confirmation, with: Runtime::User.password
click_button 'Change your password'
end
fill_in :user_login, with: Runtime::User.name
fill_in :user_password, with: Runtime::User.password
click_button 'Sign in'
end
end
end
end
end
module QA
module Page
module Main
class Groups < Page::Base
def prepare_test_namespace
return if page.has_content?(Runtime::Namespace.name)
click_on 'New Group'
fill_in 'group_path', with: Runtime::Namespace.name
fill_in 'group_description',
with: "QA test run at #{Runtime::Namespace.time}"
choose 'Private'
click_button 'Create group'
end
end
end
end
end
module QA
module Page
module Main
class Menu < Page::Base
def go_to_groups
within_global_menu { click_link 'Groups' }
end
def go_to_projects
within_global_menu { click_link 'Projects' }
end
def go_to_admin_area
within_user_menu { click_link 'Admin Area' }
end
def sign_out
within_user_menu do
find('.header-user-dropdown-toggle').click
click_link('Sign out')
end
end
def has_personal_area?
page.has_selector?('.header-user-dropdown-toggle')
end
private
def within_global_menu
find('.global-dropdown-toggle').click
page.within('.global-dropdown-menu') do
yield
end
end
def within_user_menu
page.within('.dropdown-menu-nav') do
yield
end
end
end
end
end
end
module QA
module Page
module Main
class Projects < Page::Base
def go_to_new_project
##
# There are 'New Project' and 'New project' buttons on the projects
# page, so we can't use `click_on`.
#
button = find('a', text: /^new project$/i)
button.click
end
end
end
end
end
module QA
module Page
module Project
class New < Page::Base
def choose_test_namespace
find('#s2id_project_namespace_id').click
find('.select2-result-label', text: Runtime::Namespace.name).click
end
def choose_name(name)
fill_in 'project_path', with: name
end
def add_description(description)
fill_in 'project_description', with: description
end
def create_new_project
click_on 'Create project'
end
end
end
end
end
module QA
module Page
module Project
class Show < Page::Base
def choose_repository_clone_http
find('#clone-dropdown').click
page.within('#clone-dropdown') do
find('span', text: 'HTTP').click
end
end
def repository_location
find('#project_clone').value
end
def wait_for_push
sleep 5
end
end
end
end
end
module QA
module Runtime
module Namespace
extend self
def time
@time ||= Time.now
end
def name
'qa_test_' + time.strftime('%d_%m_%Y_%H-%M-%S')
end
end
end
end
module QA
module Runtime
module User
extend self
def name
ENV['GITLAB_USERNAME'] || 'root'
end
def password
ENV['GITLAB_PASSWORD'] || 'test1234'
end
end
end
end
module QA
module Scenario
module Actable
def act(*args, &block)
instance_exec(*args, &block)
end
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def perform
yield new if block_given?
end
def act(*args, &block)
new.act(*args, &block)
end
end
end
end
end
module QA
module Scenario
module Gitlab
module License
class Add < Scenario::Template
def perform
Page::Main::Entry.act { sign_in_using_credentials }
Page::Main::Menu.act { go_to_admin_area }
Page::Admin::Menu.act { go_to_license }
Page::Admin::License.act do
add_new_license(ENV['EE_LICENSE']) if no_license?
end
Page::Main::Menu.act { sign_out }
end
end
end
end
end
end
require 'securerandom'
module QA
module Scenario
module Gitlab
module Project
class Create < Scenario::Template
attr_writer :description
def name=(name)
@name = "#{name}-#{SecureRandom.hex(8)}"
end
def perform
Page::Main::Menu.act { go_to_groups }
Page::Main::Groups.act { prepare_test_namespace }
Page::Main::Menu.act { go_to_projects }
Page::Main::Projects.act { go_to_new_project }
Page::Project::New.perform do |page|
page.choose_test_namespace
page.choose_name(@name)
page.add_description(@description)
page.create_new_project
end
end
end
end
end
end
end
module QA
module Scenario
class Template
def self.perform(*args)
new.tap do |scenario|
yield scenario if block_given?
return scenario.perform(*args)
end
end
def perform(*_args)
raise NotImplementedError
end
end
end
end
module QA
module Scenario
module Test
##
# Run test suite against any GitLab instance,
# including staging and on-premises installation.
#
class Instance < Scenario::Template
def perform(address, tag, *files)
Specs::Config.perform do |specs|
specs.address = address
end
##
# Temporary CE + EE support
Scenario::Gitlab::License::Add.perform if tag.to_s == 'ee'
Specs::Runner.perform do |specs|
files = files.any? ? files : 'qa/specs/features'
specs.rspec('--tty', '--tag', tag.to_s, files)
end
end
end
end
end
end
require 'rspec/core'
require 'capybara/rspec'
require 'capybara-webkit'
require 'capybara-screenshot/rspec'
# rubocop:disable Metrics/MethodLength
# rubocop:disable Metrics/LineLength
module QA
module Specs
class Config < Scenario::Template
attr_writer :address
def initialize
@address = ENV['GITLAB_URL']
end
def perform
raise 'Please configure GitLab address!' unless @address
configure_rspec!
configure_capybara!
configure_webkit!
end
def configure_rspec!
RSpec.configure do |config|
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`.
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
end
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
# Run specs in random order to surface order dependencies.
config.order = :random
Kernel.srand config.seed
config.before(:all) do
page.current_window.resize_to(1200, 1800)
end
config.formatter = :documentation
config.color = true
end
end
def configure_capybara!
Capybara.configure do |config|
config.app_host = @address
config.default_driver = :webkit
config.javascript_driver = :webkit
config.default_max_wait_time = 4
# https://github.com/mattheworiordan/capybara-screenshot/issues/164
config.save_path = 'tmp'
end
end
def configure_webkit!
Capybara::Webkit.configure do |config|
config.allow_url(@address)
config.block_unknown_urls
end
rescue RuntimeError # rubocop:disable Lint/HandleExceptions
# TODO, Webkit is already configured, this make this
# configuration step idempotent, should be improved.
end
end
end
end
module QA
feature 'standard root login', :ce, :ee do
scenario 'user logs in using credentials' do
Page::Main::Entry.act { sign_in_using_credentials }
# TODO, since `Signed in successfully` message was removed
# this is the only way to tell if user is signed in correctly.
#
Page::Main::Menu.perform do |menu|
expect(menu).to have_personal_area
end
end
end
end
module QA
feature 'create a new project', :ce, :ee, :staging do
scenario 'user creates a new project' do
Page::Main::Entry.act { sign_in_using_credentials }
Scenario::Gitlab::Project::Create.perform do |project|
project.name = 'awesome-project'
project.description = 'create awesome project test'
end
expect(page).to have_content(
/Project \S?awesome-project\S+ was successfully created/
)
expect(page).to have_content('create awesome project test')
expect(page).to have_content('The repository for this project is empty')
end
end
end
module QA
feature 'clone code from the repository', :ce, :ee, :staging do
context 'with regular account over http' do
given(:location) do
Page::Project::Show.act do
choose_repository_clone_http
repository_location
end
end
before do
Page::Main::Entry.act { sign_in_using_credentials }
Scenario::Gitlab::Project::Create.perform do |scenario|
scenario.name = 'project-with-code'
scenario.description = 'project for git clone tests'
end
Git::Repository.perform do |repository|
repository.location = location
repository.use_default_credentials
repository.act do
clone
configure_identity('GitLab QA', 'root@gitlab.com')
commit_file('test.rb', 'class Test; end', 'Add Test class')
commit_file('README.md', '# Test', 'Add Readme')
push_changes
end
end
end
scenario 'user performs a deep clone' do
Git::Repository.perform do |repository|
repository.location = location
repository.use_default_credentials
repository.act { clone }
expect(repository.commits.size).to eq 2
end
end
scenario 'user performs a shallow clone' do
Git::Repository.perform do |repository|
repository.location = location
repository.use_default_credentials
repository.act { shallow_clone }
expect(repository.commits.size).to eq 1
expect(repository.commits.first).to include 'Add Readme'
end
end
end
end
end
module QA
feature 'push code to repository', :ce, :ee, :staging do
context 'with regular account over http' do
scenario 'user pushes code to the repository' do
Page::Main::Entry.act { sign_in_using_credentials }
Scenario::Gitlab::Project::Create.perform do |scenario|
scenario.name = 'project_with_code'
scenario.description = 'project with repository'
end
Git::Repository.perform do |repository|
repository.location = Page::Project::Show.act do
choose_repository_clone_http
repository_location
end
repository.use_default_credentials
repository.act do
clone
configure_identity('GitLab QA', 'root@gitlab.com')
add_file('README.md', '# This is test project')
commit('Add README.md')
push_changes
end
end
Page::Project::Show.act do
wait_for_push
refresh
end
expect(page).to have_content('README.md')
expect(page).to have_content('This is test project')
end
end
end
end
require 'rspec/core'
module QA
module Specs
class Runner
include Scenario::Actable
def rspec(*args)
RSpec::Core::Runner.run(args.flatten, $stderr, $stdout).tap do |status|
abort if status.nonzero?
end
end
end
end
end
describe QA::Scenario::Actable do
subject do
Class.new do
include QA::Scenario::Actable
attr_accessor :something
def do_something(arg = nil)
"some#{arg}"
end
end
end
describe '.act' do
it 'provides means to run steps' do
result = subject.act { do_something }
expect(result).to eq 'some'
end
it 'supports passing variables' do
result = subject.act('thing') do |variable|
do_something(variable)
end
expect(result).to eq 'something'
end
it 'returns value from the last method' do
result = subject.act { 'test' }
expect(result).to eq 'test'
end
end
describe '.perform' do
it 'makes it possible to pass binding' do
variable = 'something'
result = subject.perform do |object|
object.something = variable
end
expect(result).to eq 'something'
end
end
end
require_relative '../qa'
RSpec.configure do |config|
config.expect_with :rspec do |expectations|
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
end
config.mock_with :rspec do |mocks|
mocks.verify_partial_doubles = true
end
config.shared_context_metadata_behavior = :apply_to_host_groups
config.disable_monkey_patching!
config.expose_dsl_globally = true
config.warnings = true
config.profile_examples = 10
config.order = :random
Kernel.srand config.seed
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment