summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGrzegorz Bizon <grzegorz@gitlab.com>2017-03-13 15:01:15 +0000
committerGrzegorz Bizon <grzegorz@gitlab.com>2017-03-13 15:01:15 +0000
commit0240caa02a69e6e4e30b7fa473ba3e1baf76ef0c (patch)
tree8afae53851b53f5c762e038cb14e42e726d72c20
parenta9aff2f7989df871f836a9963cef9264523903af (diff)
parent0731365b9de9411a937bd786d6e3fd7e10099800 (diff)
downloadgitlab-ce-0240caa02a69e6e4e30b7fa473ba3e1baf76ef0c.tar.gz
Merge branch 'feature/gb/gitlab-qa-integration-tests' into 'master'
Add GitLab QA integrations tests to GitLab CE / EE Closes gitlab-qa#30 See merge request !9370
-rw-r--r--qa/.rspec3
-rw-r--r--qa/Dockerfile14
-rw-r--r--qa/Gemfile7
-rw-r--r--qa/README.md18
-rwxr-xr-xqa/bin/qa7
-rwxr-xr-xqa/bin/test3
-rw-r--r--qa/qa.rb81
-rw-r--r--qa/qa/ce/strategy.rb15
-rw-r--r--qa/qa/git/repository.rb71
-rw-r--r--qa/qa/page/admin/menu.rb19
-rw-r--r--qa/qa/page/base.rb12
-rw-r--r--qa/qa/page/main/entry.rb26
-rw-r--r--qa/qa/page/main/groups.rb20
-rw-r--r--qa/qa/page/main/menu.rb46
-rw-r--r--qa/qa/page/main/projects.rb16
-rw-r--r--qa/qa/page/project/new.rb24
-rw-r--r--qa/qa/page/project/show.rb23
-rw-r--r--qa/qa/runtime/namespace.rb15
-rw-r--r--qa/qa/runtime/release.rb28
-rw-r--r--qa/qa/runtime/user.rb15
-rw-r--r--qa/qa/scenario/actable.rb23
-rw-r--r--qa/qa/scenario/gitlab/project/create.rb31
-rw-r--r--qa/qa/scenario/template.rb16
-rw-r--r--qa/qa/scenario/test/instance.rb26
-rw-r--r--qa/qa/specs/config.rb78
-rw-r--r--qa/qa/specs/features/login/standard_spec.rb14
-rw-r--r--qa/qa/specs/features/project/create_spec.rb19
-rw-r--r--qa/qa/specs/features/repository/clone_spec.rb57
-rw-r--r--qa/qa/specs/features/repository/push_spec.rb39
-rw-r--r--qa/qa/specs/runner.rb15
-rw-r--r--qa/spec/runtime/release_spec.rb50
-rw-r--r--qa/spec/scenario/actable_spec.rb47
-rw-r--r--qa/spec/spec_helper.rb19
33 files changed, 897 insertions, 0 deletions
diff --git a/qa/.rspec b/qa/.rspec
new file mode 100644
index 00000000000..b83d9b7aa65
--- /dev/null
+++ b/qa/.rspec
@@ -0,0 +1,3 @@
+--color
+--format documentation
+--require spec_helper
diff --git a/qa/Dockerfile b/qa/Dockerfile
new file mode 100644
index 00000000000..2814a7bdef0
--- /dev/null
+++ b/qa/Dockerfile
@@ -0,0 +1,14 @@
+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"]
diff --git a/qa/Gemfile b/qa/Gemfile
new file mode 100644
index 00000000000..6bfe25ba437
--- /dev/null
+++ b/qa/Gemfile
@@ -0,0 +1,7 @@
+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'
diff --git a/qa/README.md b/qa/README.md
new file mode 100644
index 00000000000..b6b5a76f1d3
--- /dev/null
+++ b/qa/README.md
@@ -0,0 +1,18 @@
+## Integration tests for GitLab
+
+This directory contains integration tests for GitLab.
+
+It is part of [GitLab QA project](https://gitlab.com/gitlab-org/gitlab-qa).
+
+## What GitLab QA is?
+
+GitLab QA is an integration tests suite for GitLab.
+
+These are black-box and entirely click-driven integration tests you can run
+against any existing instance.
+
+## How does it work?
+
+1. When we release a new version of GitLab, we build a Docker images for it.
+1. Along with GitLab Docker Images we also build and publish GitLab QA images.
+1. GitLab QA project uses these images to execute integration tests.
diff --git a/qa/bin/qa b/qa/bin/qa
new file mode 100755
index 00000000000..cecdeac14db
--- /dev/null
+++ b/qa/bin/qa
@@ -0,0 +1,7 @@
+#!/usr/bin/env ruby
+
+require_relative '../qa'
+
+QA::Scenario
+ .const_get(ARGV.shift)
+ .perform(*ARGV)
diff --git a/qa/bin/test b/qa/bin/test
new file mode 100755
index 00000000000..997392ad6e4
--- /dev/null
+++ b/qa/bin/test
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+xvfb-run bundle exec bin/qa $@
diff --git a/qa/qa.rb b/qa/qa.rb
new file mode 100644
index 00000000000..58cf615cc9f
--- /dev/null
+++ b/qa/qa.rb
@@ -0,0 +1,81 @@
+$: << File.expand_path(File.dirname(__FILE__))
+
+module QA
+ ##
+ # GitLab QA runtime classes, mostly singletons.
+ #
+ module Runtime
+ autoload :Release, 'qa/runtime/release'
+ 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
+ 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'
+ 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
+
+QA::Runtime::Release.extend_autoloads!
diff --git a/qa/qa/ce/strategy.rb b/qa/qa/ce/strategy.rb
new file mode 100644
index 00000000000..6d1601dfa48
--- /dev/null
+++ b/qa/qa/ce/strategy.rb
@@ -0,0 +1,15 @@
+module QA
+ module CE
+ module Strategy
+ extend self
+
+ def extend_autoloads!
+ # noop
+ end
+
+ def perform_before_hooks
+ # noop
+ end
+ end
+ end
+end
diff --git a/qa/qa/git/repository.rb b/qa/qa/git/repository.rb
new file mode 100644
index 00000000000..b9e199000d6
--- /dev/null
+++ b/qa/qa/git/repository.rb
@@ -0,0 +1,71 @@
+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
diff --git a/qa/qa/page/admin/menu.rb b/qa/qa/page/admin/menu.rb
new file mode 100644
index 00000000000..b01a4e10f93
--- /dev/null
+++ b/qa/qa/page/admin/menu.rb
@@ -0,0 +1,19 @@
+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
diff --git a/qa/qa/page/base.rb b/qa/qa/page/base.rb
new file mode 100644
index 00000000000..d55326c5262
--- /dev/null
+++ b/qa/qa/page/base.rb
@@ -0,0 +1,12 @@
+module QA
+ module Page
+ class Base
+ include Capybara::DSL
+ include Scenario::Actable
+
+ def refresh
+ visit current_path
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/main/entry.rb b/qa/qa/page/main/entry.rb
new file mode 100644
index 00000000000..fe80deb6429
--- /dev/null
+++ b/qa/qa/page/main/entry.rb
@@ -0,0 +1,26 @@
+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
diff --git a/qa/qa/page/main/groups.rb b/qa/qa/page/main/groups.rb
new file mode 100644
index 00000000000..84597719a84
--- /dev/null
+++ b/qa/qa/page/main/groups.rb
@@ -0,0 +1,20 @@
+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
diff --git a/qa/qa/page/main/menu.rb b/qa/qa/page/main/menu.rb
new file mode 100644
index 00000000000..45db7a92fa4
--- /dev/null
+++ b/qa/qa/page/main/menu.rb
@@ -0,0 +1,46 @@
+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('.navbar-nav') do
+ yield
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/main/projects.rb b/qa/qa/page/main/projects.rb
new file mode 100644
index 00000000000..28d3a424022
--- /dev/null
+++ b/qa/qa/page/main/projects.rb
@@ -0,0 +1,16 @@
+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
diff --git a/qa/qa/page/project/new.rb b/qa/qa/page/project/new.rb
new file mode 100644
index 00000000000..b31bec27b59
--- /dev/null
+++ b/qa/qa/page/project/new.rb
@@ -0,0 +1,24 @@
+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
diff --git a/qa/qa/page/project/show.rb b/qa/qa/page/project/show.rb
new file mode 100644
index 00000000000..56a270d8fcc
--- /dev/null
+++ b/qa/qa/page/project/show.rb
@@ -0,0 +1,23 @@
+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
diff --git a/qa/qa/runtime/namespace.rb b/qa/qa/runtime/namespace.rb
new file mode 100644
index 00000000000..e4910b63a14
--- /dev/null
+++ b/qa/qa/runtime/namespace.rb
@@ -0,0 +1,15 @@
+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
diff --git a/qa/qa/runtime/release.rb b/qa/qa/runtime/release.rb
new file mode 100644
index 00000000000..4f83a773645
--- /dev/null
+++ b/qa/qa/runtime/release.rb
@@ -0,0 +1,28 @@
+module QA
+ module Runtime
+ ##
+ # Class that is responsible for plugging CE/EE extensions in, depending on
+ # existence of EE module.
+ #
+ # We need that to reduce the probability of conflicts when merging
+ # CE to EE.
+ #
+ class Release
+ def initialize
+ require "qa/#{version.downcase}/strategy"
+ end
+
+ def version
+ @version ||= File.directory?("#{__dir__}/../ee") ? :EE : :CE
+ end
+
+ def strategy
+ QA.const_get("QA::#{version}::Strategy")
+ end
+
+ def self.method_missing(name, *args)
+ self.new.strategy.public_send(name, *args)
+ end
+ end
+ end
+end
diff --git a/qa/qa/runtime/user.rb b/qa/qa/runtime/user.rb
new file mode 100644
index 00000000000..12ceda015f0
--- /dev/null
+++ b/qa/qa/runtime/user.rb
@@ -0,0 +1,15 @@
+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
diff --git a/qa/qa/scenario/actable.rb b/qa/qa/scenario/actable.rb
new file mode 100644
index 00000000000..6cdbd24780e
--- /dev/null
+++ b/qa/qa/scenario/actable.rb
@@ -0,0 +1,23 @@
+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
diff --git a/qa/qa/scenario/gitlab/project/create.rb b/qa/qa/scenario/gitlab/project/create.rb
new file mode 100644
index 00000000000..38522714e64
--- /dev/null
+++ b/qa/qa/scenario/gitlab/project/create.rb
@@ -0,0 +1,31 @@
+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
diff --git a/qa/qa/scenario/template.rb b/qa/qa/scenario/template.rb
new file mode 100644
index 00000000000..341998af160
--- /dev/null
+++ b/qa/qa/scenario/template.rb
@@ -0,0 +1,16 @@
+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
diff --git a/qa/qa/scenario/test/instance.rb b/qa/qa/scenario/test/instance.rb
new file mode 100644
index 00000000000..689292bc60b
--- /dev/null
+++ b/qa/qa/scenario/test/instance.rb
@@ -0,0 +1,26 @@
+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, *files)
+ Specs::Config.perform do |specs|
+ specs.address = address
+ end
+
+ ##
+ # Perform before hooks, which are different for CE and EE
+ #
+ Runtime::Release.perform_before_hooks
+
+ Specs::Runner.perform do |specs|
+ specs.rspec('--tty', files.any? ? files : 'qa/specs/features')
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/config.rb b/qa/qa/specs/config.rb
new file mode 100644
index 00000000000..d72187fcd34
--- /dev/null
+++ b/qa/qa/specs/config.rb
@@ -0,0 +1,78 @@
+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
diff --git a/qa/qa/specs/features/login/standard_spec.rb b/qa/qa/specs/features/login/standard_spec.rb
new file mode 100644
index 00000000000..8e1ae6efa47
--- /dev/null
+++ b/qa/qa/specs/features/login/standard_spec.rb
@@ -0,0 +1,14 @@
+module QA
+ feature 'standard root login' 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
diff --git a/qa/qa/specs/features/project/create_spec.rb b/qa/qa/specs/features/project/create_spec.rb
new file mode 100644
index 00000000000..610492b9717
--- /dev/null
+++ b/qa/qa/specs/features/project/create_spec.rb
@@ -0,0 +1,19 @@
+module QA
+ feature 'create a new project' 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
diff --git a/qa/qa/specs/features/repository/clone_spec.rb b/qa/qa/specs/features/repository/clone_spec.rb
new file mode 100644
index 00000000000..521bd955857
--- /dev/null
+++ b/qa/qa/specs/features/repository/clone_spec.rb
@@ -0,0 +1,57 @@
+module QA
+ feature 'clone code from the repository' 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
diff --git a/qa/qa/specs/features/repository/push_spec.rb b/qa/qa/specs/features/repository/push_spec.rb
new file mode 100644
index 00000000000..5fe45d63d37
--- /dev/null
+++ b/qa/qa/specs/features/repository/push_spec.rb
@@ -0,0 +1,39 @@
+module QA
+ feature 'push code to repository' 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
diff --git a/qa/qa/specs/runner.rb b/qa/qa/specs/runner.rb
new file mode 100644
index 00000000000..83ae15d0995
--- /dev/null
+++ b/qa/qa/specs/runner.rb
@@ -0,0 +1,15 @@
+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
diff --git a/qa/spec/runtime/release_spec.rb b/qa/spec/runtime/release_spec.rb
new file mode 100644
index 00000000000..e6b5a8dc315
--- /dev/null
+++ b/qa/spec/runtime/release_spec.rb
@@ -0,0 +1,50 @@
+describe QA::Runtime::Release do
+ context 'when release version has extension strategy' do
+ let(:strategy) { spy('strategy') }
+
+ before do
+ stub_const('QA::CE::Strategy', strategy)
+ stub_const('QA::EE::Strategy', strategy)
+ end
+
+ describe '#version' do
+ it 'return either CE or EE version' do
+ expect(subject.version).to eq(:CE).or eq(:EE)
+ end
+ end
+
+ describe '#strategy' do
+ it 'return the strategy constant' do
+ expect(subject.strategy).to eq strategy
+ end
+ end
+
+ describe 'delegated class methods' do
+ it 'delegates all calls to strategy class' do
+ described_class.some_method(1, 2)
+
+ expect(strategy).to have_received(:some_method)
+ .with(1, 2)
+ end
+ end
+ end
+
+ context 'when release version does not have extension strategy' do
+ before do
+ allow_any_instance_of(described_class)
+ .to receive(:version).and_return('something')
+ end
+
+ describe '#strategy' do
+ it 'raises error' do
+ expect { subject.strategy }.to raise_error(LoadError)
+ end
+ end
+
+ describe 'delegated class methods' do
+ it 'raises error' do
+ expect { described_class.some_method(2, 3) }.to raise_error(LoadError)
+ end
+ end
+ end
+end
diff --git a/qa/spec/scenario/actable_spec.rb b/qa/spec/scenario/actable_spec.rb
new file mode 100644
index 00000000000..422763910e4
--- /dev/null
+++ b/qa/spec/scenario/actable_spec.rb
@@ -0,0 +1,47 @@
+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
diff --git a/qa/spec/spec_helper.rb b/qa/spec/spec_helper.rb
new file mode 100644
index 00000000000..c07a3234673
--- /dev/null
+++ b/qa/spec/spec_helper.rb
@@ -0,0 +1,19 @@
+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