diff options
Diffstat (limited to 'qa')
56 files changed, 807 insertions, 264 deletions
diff --git a/qa/README.md b/qa/README.md index ef6f202464d..124a79a36b4 100644 --- a/qa/README.md +++ b/qa/README.md @@ -34,8 +34,23 @@ using `package-and-qa` manual action, to test if everything works fine. ## How can I use it? -You can use GitLab QA to exercise tests on any live instance! For example, the -following call would login to a local [GDK] instance and run all specs in +You can use GitLab QA to exercise tests on any live instance! If you don't +have an instance available you can follow the instructions below to use +the [GitLab Development Kit (GDK)][GDK]. +This is the recommended option if you would like to contribute to the tests. + +### Run the end-to-end tests in a local development environment + +Follow the GDK instructions to [prepare](https://gitlab.com/gitlab-org/gitlab-development-kit/blob/master/doc/prepare.md) +and [install](https://gitlab.com/gitlab-org/gitlab-development-kit/blob/master/doc/set-up-gdk.md) +your local GitLab development environment. + +Once you have GDK running, switch to the `qa` directory. E.g., if you setup +GDK to develop in the main `gitlab-ce` repo, the GitLab source code will be +in a `gitlab` directory and so the end-to-end test code will be in `gitlab/qa`. + +From there you can run the tests. For example, the +following call would login to the GDK instance and run all specs in `qa/specs/features`: ``` diff --git a/qa/Rakefile b/qa/Rakefile index 7ac018f7286..7ba8a6d68ba 100644 --- a/qa/Rakefile +++ b/qa/Rakefile @@ -13,8 +13,9 @@ task :delete_subgroups do end desc "Generate Performance Testdata" -task :generate_perf_testdata do - QA::Tools::GeneratePerfTestdata.new.run +task :generate_perf_testdata, :type do |t, args| + args.with_defaults(type: :all) + QA::Tools::GeneratePerfTestdata.new.method(args[:type]).call end desc "Run artillery load tests" @@ -162,9 +162,12 @@ module QA module File autoload :Form, 'qa/page/file/form' autoload :Show, 'qa/page/file/show' + autoload :Edit, 'qa/page/file/edit' module Shared autoload :CommitMessage, 'qa/page/file/shared/commit_message' + autoload :CommitButton, 'qa/page/file/shared/commit_button' + autoload :Editor, 'qa/page/file/shared/editor' end end @@ -218,6 +221,7 @@ module QA autoload :Operations, 'qa/page/project/sub_menus/operations' autoload :Repository, 'qa/page/project/sub_menus/repository' autoload :Settings, 'qa/page/project/sub_menus/settings' + autoload :Project, 'qa/page/project/sub_menus/project' end module Issue @@ -323,6 +327,7 @@ module QA autoload :DropdownFilter, 'qa/page/component/dropdown_filter' autoload :UsersSelect, 'qa/page/component/users_select' autoload :Note, 'qa/page/component/note' + autoload :ConfirmModal, 'qa/page/component/confirm_modal' module Issuable autoload :Common, 'qa/page/component/issuable/common' diff --git a/qa/qa/page/base.rb b/qa/qa/page/base.rb index d247a273637..d0fe2987b0a 100644 --- a/qa/qa/page/base.rb +++ b/qa/qa/page/base.rb @@ -197,6 +197,10 @@ module QA views.map(&:elements).flatten end + def send_keys_to_element(name, keys) + find_element(name).send_keys(keys) + end + class DSL attr_reader :views diff --git a/qa/qa/page/component/confirm_modal.rb b/qa/qa/page/component/confirm_modal.rb new file mode 100644 index 00000000000..355e2783fb7 --- /dev/null +++ b/qa/qa/page/component/confirm_modal.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +module QA + module Page + module Component + module ConfirmModal + def self.included(base) + base.view 'app/views/shared/_confirm_modal.html.haml' do + element :confirm_modal + element :confirm_input + element :confirm_button + end + end + + def fill_confirmation_text(text) + fill_element :confirm_input, text + end + + def click_confirm_button + click_element :confirm_button + end + end + end + end +end diff --git a/qa/qa/page/component/select2.rb b/qa/qa/page/component/select2.rb index e40bc4b1d3e..85d4abcde9b 100644 --- a/qa/qa/page/component/select2.rb +++ b/qa/qa/page/component/select2.rb @@ -18,6 +18,10 @@ module QA find('.select2-input').set(item_text) select_item(item_text) end + + def expand_select_list + find('span.select2-arrow').click + end end end end diff --git a/qa/qa/page/dashboard/projects.rb b/qa/qa/page/dashboard/projects.rb index 7ab8ee39f72..0c23d7cffbb 100644 --- a/qa/qa/page/dashboard/projects.rb +++ b/qa/qa/page/dashboard/projects.rb @@ -5,7 +5,7 @@ module QA module Dashboard class Projects < Page::Base view 'app/views/shared/projects/_search_form.html.haml' do - element :form_filter_by_name, /form_tag.+id: 'project-filter-form'/ # rubocop:disable QA/ElementWithPattern + element :project_filter_form, required: true end def go_to_project(name) @@ -14,10 +14,14 @@ module QA find_link(text: name).click end + def self.path + '/' + end + private def filter_by_name(name) - page.within('form#project-filter-form') do + within_element(:project_filter_form) do fill_in :name, with: name end end diff --git a/qa/qa/page/file/edit.rb b/qa/qa/page/file/edit.rb new file mode 100644 index 00000000000..3a4a1837b1c --- /dev/null +++ b/qa/qa/page/file/edit.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module QA + module Page + module File + class Edit < Page::Base + include Shared::CommitMessage + include Shared::CommitButton + include Shared::Editor + end + end + end +end diff --git a/qa/qa/page/file/form.rb b/qa/qa/page/file/form.rb index e42de7d65c5..a6251f185f9 100644 --- a/qa/qa/page/file/form.rb +++ b/qa/qa/page/file/form.rb @@ -6,14 +6,11 @@ module QA class Form < Page::Base include Shared::CommitMessage include Page::Component::DropdownFilter + include Shared::CommitButton + include Shared::Editor view 'app/views/projects/blob/_editor.html.haml' do element :file_name, "text_field_tag 'file_name'" # rubocop:disable QA/ElementWithPattern - element :editor, '#editor' # rubocop:disable QA/ElementWithPattern - end - - view 'app/views/projects/_commit_button.html.haml' do - element :commit_changes, "button_tag 'Commit changes'" # rubocop:disable QA/ElementWithPattern end view 'app/views/projects/blob/_template_selectors.html.haml' do @@ -28,20 +25,6 @@ module QA fill_in 'file_name', with: name end - def add_content(content) - text_area.set content - end - - def remove_content - text_area.send_keys([:command, 'a'], :backspace) - end - - def commit_changes - click_on 'Commit changes' - - finished_loading? - end - def select_template(template_type, template) click_element :template_type_dropdown click_link template_type @@ -60,12 +43,6 @@ module QA end filter_and_select template end - - private - - def text_area - find('#editor>textarea', visible: false) - end end end end diff --git a/qa/qa/page/file/shared/commit_button.rb b/qa/qa/page/file/shared/commit_button.rb new file mode 100644 index 00000000000..d8e751dd7b6 --- /dev/null +++ b/qa/qa/page/file/shared/commit_button.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module QA + module Page + module File + module Shared + module CommitButton + def self.included(base) + base.view 'app/views/projects/_commit_button.html.haml' do + element :commit_button + end + end + + def commit_changes + click_element(:commit_button) + end + end + end + end + end +end diff --git a/qa/qa/page/file/shared/editor.rb b/qa/qa/page/file/shared/editor.rb new file mode 100644 index 00000000000..448c09cfbca --- /dev/null +++ b/qa/qa/page/file/shared/editor.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +module QA + module Page + module File + module Shared + module Editor + def self.included(base) + base.view 'app/views/projects/blob/_editor.html.haml' do + element :editor + end + end + + def add_content(content) + text_area.set content + end + + def remove_content + text_area.send_keys([:command, 'a'], :backspace) + end + + private + + def text_area + within_element :editor do + find('textarea', visible: false) + end + end + end + end + end + end +end diff --git a/qa/qa/page/file/show.rb b/qa/qa/page/file/show.rb index eaf88c6e69e..92f9181f99d 100644 --- a/qa/qa/page/file/show.rb +++ b/qa/qa/page/file/show.rb @@ -5,6 +5,8 @@ module QA module File class Show < Page::Base include Shared::CommitMessage + include Project::SubMenus::Settings + include Project::SubMenus::Common view 'app/helpers/blob_helper.rb' do element :edit_button, "_('Edit')" # rubocop:disable QA/ElementWithPattern diff --git a/qa/qa/page/merge_request/show.rb b/qa/qa/page/merge_request/show.rb index 5aef868a805..6a415b56e50 100644 --- a/qa/qa/page/merge_request/show.rb +++ b/qa/qa/page/merge_request/show.rb @@ -111,6 +111,14 @@ module QA end end + def has_title?(title) + has_element?(:title, text: title) + end + + def has_description?(description) + has_element?(:description, text: description) + end + def merge! # The merge button is disabled on load wait do diff --git a/qa/qa/page/project/issue/show.rb b/qa/qa/page/project/issue/show.rb index 9df3db1bba0..b59540d0377 100644 --- a/qa/qa/page/project/issue/show.rb +++ b/qa/qa/page/project/issue/show.rb @@ -8,11 +8,6 @@ module QA include Page::Component::Issuable::Common include Page::Component::Note - view 'app/views/shared/notes/_form.html.haml' do - element :new_note_form, 'new-note' # rubocop:disable QA/ElementWithPattern - element :new_note_form, 'attr: :note' # rubocop:disable QA/ElementWithPattern - end - view 'app/assets/javascripts/notes/components/comment_form.vue' do element :comment_button element :comment_input @@ -27,9 +22,25 @@ module QA element :noteable_note_item end + view 'app/helpers/dropdowns_helper.rb' do + element :dropdown_input_field + end + + view 'app/views/shared/notes/_form.html.haml' do + element :new_note_form, 'new-note' # rubocop:disable QA/ElementWithPattern + element :new_note_form, 'attr: :note' # rubocop:disable QA/ElementWithPattern + end + + view 'app/views/shared/issuable/_sidebar.html.haml' do + element :labels_block + element :edit_link_labels + element :dropdown_menu_labels + end + # Adds a comment to an issue # attachment option should be an absolute path - def comment(text, attachment: nil) + def comment(text, attachment: nil, filter: :all_activities) + method("select_#{filter}_filter").call fill_element :comment_input, text unless attachment.nil? @@ -46,6 +57,10 @@ module QA end end + def select_all_activities_filter + select_filter_with_text('Show all activity') + end + def select_comments_only_filter select_filter_with_text('Show comments only') end @@ -54,8 +69,26 @@ module QA select_filter_with_text('Show history only') end - def select_all_activities_filter - select_filter_with_text('Show all activity') + def select_labels_and_refresh(labels) + click_element(:edit_link_labels) + + labels.each do |label| + within_element(:dropdown_menu_labels, text: label) do + send_keys_to_element(:dropdown_input_field, [label, :enter]) + end + end + + click_body + + labels.each do |label| + has_element?(:labels_block, text: label) + end + + refresh + end + + def text_of_labels_block + find_element(:labels_block) end private diff --git a/qa/qa/page/project/pipeline/index.rb b/qa/qa/page/project/pipeline/index.rb index 68850d989b1..fae7818f871 100644 --- a/qa/qa/page/project/pipeline/index.rb +++ b/qa/qa/page/project/pipeline/index.rb @@ -4,7 +4,7 @@ module QA::Page module Project::Pipeline class Index < QA::Page::Base view 'app/assets/javascripts/pipelines/components/pipeline_url.vue' do - element :pipeline_link, 'class="js-pipeline-url-link"' # rubocop:disable QA/ElementWithPattern + element :pipeline_link, 'class="js-pipeline-url-link' # rubocop:disable QA/ElementWithPattern end def click_on_latest_pipeline diff --git a/qa/qa/page/project/settings/advanced.rb b/qa/qa/page/project/settings/advanced.rb index 75530832860..ab4e3d757b6 100644 --- a/qa/qa/page/project/settings/advanced.rb +++ b/qa/qa/page/project/settings/advanced.rb @@ -5,9 +5,13 @@ module QA module Project module Settings class Advanced < Page::Base + include Component::Select2 + include Component::ConfirmModal + view 'app/views/projects/edit.html.haml' do element :project_path_field element :change_path_button + element :transfer_button end def update_project_path_to(path) @@ -22,6 +26,18 @@ module QA def click_change_path_button click_element :change_path_button end + + def select_transfer_option(namespace) + search_and_select(namespace) + end + + def transfer_project!(project_name, namespace) + expand_select_list + select_transfer_option(namespace) + click_element(:transfer_button) + fill_confirmation_text(project_name) + click_confirm_button + end end end end diff --git a/qa/qa/page/project/settings/main.rb b/qa/qa/page/project/settings/main.rb index d1f3b15f950..dbbe62e3b1d 100644 --- a/qa/qa/page/project/settings/main.rb +++ b/qa/qa/page/project/settings/main.rb @@ -6,6 +6,8 @@ module QA module Settings class Main < Page::Base include Common + include Component::Select2 + include SubMenus::Project view 'app/views/projects/edit.html.haml' do element :advanced_settings diff --git a/qa/qa/page/project/sub_menus/project.rb b/qa/qa/page/project/sub_menus/project.rb new file mode 100644 index 00000000000..5e0ee3c274a --- /dev/null +++ b/qa/qa/page/project/sub_menus/project.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +module QA + module Page + module Project + module SubMenus + module Project + include Common + + def self.included(base) + base.class_eval do + view 'app/views/layouts/nav/sidebar/_project.html.haml' do + element :link_project + end + end + end + + def click_project + retry_on_exception do + within_sidebar do + click_element(:link_project) + end + end + end + end + end + end + end +end diff --git a/qa/qa/page/project/sub_menus/settings.rb b/qa/qa/page/project/sub_menus/settings.rb index 22743ebd0a1..88b45ec55ae 100644 --- a/qa/qa/page/project/sub_menus/settings.rb +++ b/qa/qa/page/project/sub_menus/settings.rb @@ -10,6 +10,7 @@ module QA view 'app/views/layouts/nav/sidebar/_project.html.haml' do element :settings_item element :link_members_settings + element :general_settings_link end end end @@ -38,6 +39,14 @@ module QA end end + def go_to_general_settings + hover_settings do + within_submenu do + click_element :general_settings_link + end + end + end + def click_settings within_sidebar do click_on 'Settings' diff --git a/qa/qa/page/settings/common.rb b/qa/qa/page/settings/common.rb index 8cd0b6bb49c..bede3fde105 100644 --- a/qa/qa/page/settings/common.rb +++ b/qa/qa/page/settings/common.rb @@ -11,7 +11,7 @@ module QA within_element(element_name) do # Because it is possible to click the button before the JS toggle code is bound wait(reload: false) do - click_button 'Expand' unless first('button', text: 'Collapse') + click_button 'Expand' unless has_css?('button', text: 'Collapse') has_content?('Collapse') end diff --git a/qa/qa/resource/api_fabricator.rb b/qa/qa/resource/api_fabricator.rb index de04467ff5b..d1d75b6179e 100644 --- a/qa/qa/resource/api_fabricator.rb +++ b/qa/qa/resource/api_fabricator.rb @@ -13,6 +13,8 @@ module QA ResourceURLMissingError = Class.new(RuntimeError) attr_reader :api_resource, :api_response + attr_writer :api_client + attr_accessor :user def api_support? respond_to?(:api_get_path) && @@ -29,9 +31,12 @@ module QA end def eager_load_api_client! + return unless api_client.nil? + api_client.tap do |client| # Eager-load the API client so that the personal token creation isn't # taken in account in the actual resource creation timing. + client.user = user client.personal_access_token end end @@ -76,7 +81,7 @@ module QA def api_client @api_client ||= begin - Runtime::API::Client.new(:gitlab, is_new_session: !current_url.start_with?('http')) + Runtime::API::Client.new(:gitlab, is_new_session: !current_url.start_with?('http'), user: user) end end diff --git a/qa/qa/resource/group.rb b/qa/qa/resource/group.rb index 0b567a474c8..44d9dc8f296 100644 --- a/qa/qa/resource/group.rb +++ b/qa/qa/resource/group.rb @@ -67,6 +67,10 @@ module QA visibility: 'public' } end + + def full_path + sandbox.path + ' / ' + path + end end end end diff --git a/qa/qa/resource/issue.rb b/qa/qa/resource/issue.rb index 2c2f27fe231..9c57a0f5afb 100644 --- a/qa/qa/resource/issue.rb +++ b/qa/qa/resource/issue.rb @@ -12,6 +12,8 @@ module QA end end + attribute :id + attribute :labels attribute :title def fabricate! @@ -25,6 +27,21 @@ module QA page.create_new_issue end end + + def api_get_path + "/projects/#{project.id}/issues/#{id}" + end + + def api_post_path + "/projects/#{project.id}/issues" + end + + def api_post_body + { + labels: [labels], + title: title + } + end end end end diff --git a/qa/qa/resource/label.rb b/qa/qa/resource/label.rb index 7c899db31f3..3750725c440 100644 --- a/qa/qa/resource/label.rb +++ b/qa/qa/resource/label.rb @@ -34,6 +34,27 @@ module QA page.click_label_create_button end end + + def resource_web_url(resource) + super + rescue ResourceURLMissingError + # this particular resource does not expose a web_url property + end + + def api_get_path + raise NotImplementedError, "The Labels API doesn't expose a single-resource endpoint so this method cannot be properly implemented." + end + + def api_post_path + "/projects/#{project.id}/labels" + end + + def api_post_body + { + color: @color, + name: @title + } + end end end end diff --git a/qa/qa/resource/merge_request_from_fork.rb b/qa/qa/resource/merge_request_from_fork.rb index 5d20a6e9c75..6c9a096289b 100644 --- a/qa/qa/resource/merge_request_from_fork.rb +++ b/qa/qa/resource/merge_request_from_fork.rb @@ -21,7 +21,7 @@ module QA def fabricate! populate(:push) - fork.visit! + fork.project.visit! Page::Project::Show.perform(&:new_merge_request) Page::MergeRequest::New.perform(&:create_merge_request) diff --git a/qa/qa/resource/project.rb b/qa/qa/resource/project.rb index d706439a891..c0a6004fe27 100644 --- a/qa/qa/resource/project.rb +++ b/qa/qa/resource/project.rb @@ -11,14 +11,20 @@ module QA attribute :id attribute :name + attribute :add_name_uuid attribute :description + attribute :standalone attribute :group do Group.fabricate! end attribute :path_with_namespace do - "#{group.sandbox.path}/#{group.path}/#{name}" if group + "#{sandbox_path}#{group.path}/#{name}" if group + end + + def sandbox_path + group.respond_to?('sandbox') ? "#{group.sandbox.path}/" : '' end attribute :repository_ssh_location do @@ -34,18 +40,21 @@ module QA end def initialize + @add_name_uuid = true + @standalone = false @description = 'My awesome project' @initialize_with_readme = false end def name=(raw_name) - @name = "#{raw_name}-#{SecureRandom.hex(8)}" + @name = @add_name_uuid ? "#{raw_name}-#{SecureRandom.hex(8)}" : raw_name end def fabricate! - group.visit! - - Page::Group::Show.perform(&:go_to_new_project) + unless @standalone + group.visit! + Page::Group::Show.perform(&:go_to_new_project) + end Page::Project::New.perform do |page| page.choose_test_namespace @@ -67,19 +76,28 @@ module QA "/projects/#{CGI.escape(path_with_namespace)}" end + def api_get_archive_path(type = 'tar.gz') + "#{api_get_path}/repository/archive.#{type}" + end + def api_post_path '/projects' end def api_post_body - { - namespace_id: group.id, - path: name, + post_body = { name: name, description: description, visibility: 'public', initialize_with_readme: @initialize_with_readme } + + unless @standalone + post_body[:namespace_id] = group.id + post_body[:path] = name + end + + post_body end private diff --git a/qa/qa/resource/project_milestone.rb b/qa/qa/resource/project_milestone.rb index 8ace75f695a..70640eac095 100644 --- a/qa/qa/resource/project_milestone.rb +++ b/qa/qa/resource/project_milestone.rb @@ -31,6 +31,21 @@ module QA milestone_new.click_milestone_create_button end end + + def api_get_path + "/projects/#{project.id}/milestones/#{id}" + end + + def api_post_path + "/projects/#{project.id}/milestones" + end + + def api_post_body + { + description: @description, + title: @title + } + end end end end diff --git a/qa/qa/resource/sandbox.rb b/qa/qa/resource/sandbox.rb index 942eea5cc40..e2b1c4c0831 100644 --- a/qa/qa/resource/sandbox.rb +++ b/qa/qa/resource/sandbox.rb @@ -44,6 +44,10 @@ module QA "/groups/#{path}" end + def api_members_path + "#{api_get_path}/members" + end + def api_post_path '/groups' end diff --git a/qa/qa/resource/user.rb b/qa/qa/resource/user.rb index 6c5e91b6488..eec46f46d99 100644 --- a/qa/qa/resource/user.rb +++ b/qa/qa/resource/user.rb @@ -88,7 +88,7 @@ module QA }.merge(ldap_post_body) end - def self.fabricate_or_use(username, password) + def self.fabricate_or_use(username = nil, password = nil) if Runtime::Env.signup_disabled? self.new.tap do |user| user.username = username diff --git a/qa/qa/runtime/address.rb b/qa/qa/runtime/address.rb index 98d042fb43a..c622051bb6d 100644 --- a/qa/qa/runtime/address.rb +++ b/qa/qa/runtime/address.rb @@ -5,7 +5,7 @@ module QA class Address attr_reader :address - def initialize(instance, page = nil) + def initialize(instance, page) @instance = instance @address = host + (page.is_a?(String) ? page : page&.path) end diff --git a/qa/qa/runtime/api/client.rb b/qa/qa/runtime/api/client.rb index 40a3bc85195..663be27a849 100644 --- a/qa/qa/runtime/api/client.rb +++ b/qa/qa/runtime/api/client.rb @@ -6,31 +6,34 @@ module QA module Runtime module API class Client - attr_reader :address + attr_reader :address, :user - def initialize(address = :gitlab, personal_access_token: nil, is_new_session: true) + def initialize(address = :gitlab, personal_access_token: nil, is_new_session: true, user: nil) @address = address @personal_access_token = personal_access_token @is_new_session = is_new_session + @user = user end def personal_access_token @personal_access_token ||= begin # you can set the environment variable GITLAB_QA_ACCESS_TOKEN # to use a specific access token rather than create one from the UI - Runtime::Env.personal_access_token ||= create_personal_access_token + # unless a specific user has been passed + @user.nil? ? Runtime::Env.personal_access_token ||= create_personal_access_token : create_personal_access_token end end private def create_personal_access_token - Runtime::Browser.visit(@address, Page::Main::Login) if @is_new_session - do_create_personal_access_token - end + Page::Main::Menu.perform(&:sign_out) if @is_new_session && Page::Main::Menu.perform { |p| p.has_personal_area?(wait: 0) } + + unless Page::Main::Menu.perform { |p| p.has_personal_area?(wait: 0) } + Runtime::Browser.visit(@address, Page::Main::Login) + Page::Main::Login.perform { |login| login.sign_in_using_credentials(@user) } + end - def do_create_personal_access_token - Page::Main::Login.perform(&:sign_in_using_credentials) Resource::PersonalAccessToken.fabricate!.access_token end end diff --git a/qa/qa/runtime/browser.rb b/qa/qa/runtime/browser.rb index 3bf4b3bbbfb..ed0779b93cc 100644 --- a/qa/qa/runtime/browser.rb +++ b/qa/qa/runtime/browser.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require 'rspec/core' +require 'rspec/expectations' require 'capybara/rspec' require 'capybara-screenshot/rspec' require 'selenium-webdriver' @@ -27,13 +28,12 @@ module QA # In case of an address that is a symbol we will try to guess address # based on `Runtime::Scenario#something_address`. # - def visit(address, page = nil, &block) - Browser::Session.new(address, page).perform(&block) + def visit(address, page_class, &block) + Browser::Session.new(address, page_class).perform(&block) end - def self.visit(address, page = nil, &block) - new.visit(address, page, &block) - page.validate_elements_present! + def self.visit(address, page_class, &block) + new.visit(address, page_class, &block) end def self.configure! @@ -128,8 +128,11 @@ module QA class Session include Capybara::DSL - def initialize(instance, page = nil) - @session_address = Runtime::Address.new(instance, page) + attr_reader :page_class + + def initialize(instance, page_class) + @session_address = Runtime::Address.new(instance, page_class) + @page_class = page_class end def url @@ -139,6 +142,8 @@ module QA def perform(&block) visit(url) + page_class.validate_elements_present! + if QA::Runtime::Env.qa_cookies browser = Capybara.current_session.driver.browser QA::Runtime::Env.qa_cookies.each do |cookie| diff --git a/qa/qa/runtime/env.rb b/qa/qa/runtime/env.rb index 82510dfa03c..96f337dc081 100644 --- a/qa/qa/runtime/env.rb +++ b/qa/qa/runtime/env.rb @@ -10,13 +10,26 @@ module QA # The environment variables used to indicate if the environment under test # supports the given feature SUPPORTED_FEATURES = { - git_protocol_v2: 'QA_CAN_TEST_GIT_PROTOCOL_V2' + git_protocol_v2: 'QA_CAN_TEST_GIT_PROTOCOL_V2', + admin: 'QA_CAN_TEST_ADMIN_FEATURES' }.freeze def supported_features SUPPORTED_FEATURES end + def admin_password + ENV['GITLAB_ADMIN_PASSWORD'] + end + + def admin_username + ENV['GITLAB_ADMIN_USERNAME'] + end + + def admin_personal_access_token + ENV['GITLAB_QA_ADMIN_ACCESS_TOKEN'] + end + def debug? enabled?(ENV['QA_DEBUG'], default: false) end @@ -92,14 +105,6 @@ module QA ENV['GITLAB_PASSWORD'] end - def admin_username - ENV['GITLAB_ADMIN_USERNAME'] - end - - def admin_password - ENV['GITLAB_ADMIN_PASSWORD'] - end - def github_username ENV['GITHUB_USERNAME'] end diff --git a/qa/qa/service/shellout.rb b/qa/qa/service/shellout.rb index 7065ab0e7f3..217df669db3 100644 --- a/qa/qa/service/shellout.rb +++ b/qa/qa/service/shellout.rb @@ -19,7 +19,7 @@ module QA Open3.popen2e(*command) do |stdin, out, wait| stdin.puts(stdin_data) if stdin_data stdin.close if stdin_data - out.each { |line| puts line } + out.each_char { |char| print char } if wait.value.exited? && wait.value.exitstatus.nonzero? raise CommandError, "Command `#{command}` failed!" diff --git a/qa/qa/specs/features/api/3_create/repository/project_archive_compare_spec.rb b/qa/qa/specs/features/api/3_create/repository/project_archive_compare_spec.rb new file mode 100644 index 00000000000..3fe04e8b835 --- /dev/null +++ b/qa/qa/specs/features/api/3_create/repository/project_archive_compare_spec.rb @@ -0,0 +1,75 @@ +# frozen_string_literal: true + +require 'securerandom' +require 'digest' + +module QA + context 'Create' do + describe 'Compare archives of different user projects with the same name and check they\'re different' do + include Support::Api + + before(:all) do + @project_name = "project-archive-download-#{SecureRandom.hex(8)}" + @archive_types = %w(tar.gz tar.bz2 tar zip) + @users = { + user1: { username: Runtime::Env.gitlab_qa_username_1, password: Runtime::Env.gitlab_qa_password_1 }, + user2: { username: Runtime::Env.gitlab_qa_username_2, password: Runtime::Env.gitlab_qa_password_2 } + } + + @users.each do |_, user_info| + user_info[:user] = Resource::User.fabricate_or_use(user_info[:username], user_info[:password]) + user_info[:api_client] = Runtime::API::Client.new(:gitlab, user: user_info[:user]) + user_info[:api_client].personal_access_token + user_info[:project] = create_project(user_info[:user], user_info[:api_client], @project_name) + Page::Main::Menu.perform(&:sign_out) + end + end + + it 'download archives of each user project then check they are different' do + archive_checksums = {} + + @users.each do |user_key, user_info| + archive_checksums[user_key] = {} + + @archive_types.each do |type| + archive_path = download_project_archive_via_api(user_info[:api_client], user_info[:project], type).path + archive_checksums[user_key][type] = Digest::MD5.hexdigest(File.read(archive_path)) + end + end + + QA::Runtime::Logger.debug("Archive checksums are #{archive_checksums}") + + expect(archive_checksums[:user1]).not_to include(archive_checksums[:user2]) + end + + def create_project(user, api_client, project_name) + project = Resource::Project.fabricate! do |project| + project.standalone = true + project.add_name_uuid = false + project.name = project_name + project.path_with_namespace = "#{user.name}/#{project_name}" + project.user = user + project.api_client = api_client + end + + Resource::Repository::ProjectPush.fabricate! do |push| + push.project = project + push.file_name = 'README.md' + push.file_content = '# This is a test project' + push.commit_message = 'Add README.md' + push.user = user + end + + project + end + + def download_project_archive_via_api(api_client, project, type = 'tar.gz') + get_project_archive_zip = Runtime::API::Request.new(api_client, project.api_get_archive_path(type)) + project_archive_download = get(get_project_archive_zip.url, raw_response: true) + expect(project_archive_download.code).to eq(200) + + project_archive_download.file + end + end + end +end diff --git a/qa/qa/specs/features/browser_ui/1_manage/group/transfer_project_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/group/transfer_project_spec.rb new file mode 100644 index 00000000000..a9de64e357a --- /dev/null +++ b/qa/qa/specs/features/browser_ui/1_manage/group/transfer_project_spec.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +module QA + context 'Manage' do + describe 'Project transfer between groups' do + it 'user transfers a project between groups' do + Runtime::Browser.visit(:gitlab, Page::Main::Login) + Page::Main::Login.act { sign_in_using_credentials } + + source_group = Resource::Group.fabricate! do |group| + group.path = 'source-group' + end + + target_group = Resource::Group.fabricate! do |group| + group.path = 'target-group' + end + + project = Resource::Project.fabricate! do |project| + project.group = source_group + project.name = 'transfer-project' + project.initialize_with_readme = true + end + + project.visit! + + Page::Project::Show.perform do |project| + project.click_file('README.md') + end + + Page::File::Show.perform(&:click_edit) + + edited_readme_content = 'Here is the edited content.' + + Page::File::Edit.perform do |file| + file.remove_content + file.add_content(edited_readme_content) + file.commit_changes + end + + Page::File::Show.perform(&:go_to_general_settings) + + Page::Project::Settings::Main.perform(&:expand_advanced_settings) + + Page::Project::Settings::Advanced.perform do |advanced| + advanced.transfer_project!(project.name, target_group.full_path) + end + + Page::Project::Settings::Main.perform(&:click_project) + + Page::Project::Show.perform do |project| + expect(project).to have_text(target_group.path) + expect(project).to have_text(edited_readme_content) + end + end + end + end +end diff --git a/qa/qa/specs/features/browser_ui/1_manage/login/login_via_oauth_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/login/login_via_oauth_spec.rb index 15cd59f041b..a118176eb8a 100644 --- a/qa/qa/specs/features/browser_ui/1_manage/login/login_via_oauth_spec.rb +++ b/qa/qa/specs/features/browser_ui/1_manage/login/login_via_oauth_spec.rb @@ -1,8 +1,7 @@ # frozen_string_literal: true module QA - # https://gitlab.com/gitlab-org/quality/nightly/issues/100 - context 'Manage', :orchestrated, :oauth, :quarantine do + context 'Manage', :orchestrated, :oauth do describe 'OAuth login' do it 'User logs in to GitLab with GitHub OAuth' do Runtime::Browser.visit(:gitlab, Page::Main::Login) diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/collapse_comments_in_discussions_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/collapse_comments_in_discussions_spec.rb index fa779bd1f4e..4478ea41662 100644 --- a/qa/qa/specs/features/browser_ui/2_plan/issue/collapse_comments_in_discussions_spec.rb +++ b/qa/qa/specs/features/browser_ui/2_plan/issue/collapse_comments_in_discussions_spec.rb @@ -9,7 +9,7 @@ module QA Runtime::Browser.visit(:gitlab, Page::Main::Login) Page::Main::Login.perform(&:sign_in_using_credentials) - Resource::Issue.fabricate! do |issue| + Resource::Issue.fabricate_via_browser_ui! do |issue| issue.title = issue_title end diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb index 9b083d59a5e..1eea3efec7f 100644 --- a/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb +++ b/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb @@ -23,7 +23,6 @@ module QA create_issue Page::Project::Issue::Show.perform do |show| - show.select_all_activities_filter show.comment('See attached banana for scale', attachment: file_to_attach) show.refresh @@ -43,7 +42,7 @@ module QA Runtime::Browser.visit(:gitlab, Page::Main::Login) Page::Main::Login.act { sign_in_using_credentials } - Resource::Issue.fabricate! do |issue| + Resource::Issue.fabricate_via_browser_ui! do |issue| issue.title = issue_title end end diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/filter_issue_comments_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/filter_issue_comments_spec.rb index c0d597af076..ad2773b41ac 100644 --- a/qa/qa/specs/features/browser_ui/2_plan/issue/filter_issue_comments_spec.rb +++ b/qa/qa/specs/features/browser_ui/2_plan/issue/filter_issue_comments_spec.rb @@ -9,16 +9,15 @@ module QA Runtime::Browser.visit(:gitlab, Page::Main::Login) Page::Main::Login.act { sign_in_using_credentials } - Resource::Issue.fabricate! do |issue| + Resource::Issue.fabricate_via_browser_ui! do |issue| issue.title = issue_title end expect(page).to have_content(issue_title) Page::Project::Issue::Show.perform do |show_page| - show_page.select_comments_only_filter - show_page.comment('/confidential') - show_page.comment('My own comment') + show_page.comment('/confidential', filter: :comments_only) + show_page.comment('My own comment', filter: :comments_only) expect(show_page).not_to have_content("made the issue confidential") expect(show_page).to have_content("My own comment") diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb index 0ff71baed90..6969f123f95 100644 --- a/qa/qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb @@ -1,74 +1,64 @@ # frozen_string_literal: true module QA - # Failure issue: https://gitlab.com/gitlab-org/quality/staging/issues/50 - context 'Create', :quarantine do - describe 'Merge request creation' do - it 'user creates a new merge request', :smoke do + context 'Create' do + describe 'Create a new merge request' do + before do Runtime::Browser.visit(:gitlab, Page::Main::Login) - Page::Main::Login.act { sign_in_using_credentials } + Page::Main::Login.perform(&:sign_in_using_credentials) - current_project = Resource::Project.fabricate! do |project| - project.name = 'project-with-merge-request' + @project = Resource::Project.fabricate_via_api! do |project| + project.name = 'project' end - merge_request_title = 'This is a merge request' - merge_request_description = 'Great feature' + @merge_request_title = 'One merge request to rule them all' + @merge_request_description = '... to find them, to bring them all, and in the darkness bind them' + end - Resource::MergeRequest.fabricate! do |merge_request| - merge_request.title = merge_request_title - merge_request.description = merge_request_description - merge_request.project = current_project + it 'creates a basic merge request', :smoke do + Resource::MergeRequest.fabricate_via_browser_ui! do |merge_request| + merge_request.project = @project + merge_request.title = @merge_request_title + merge_request.description = @merge_request_description end - expect(page).to have_content(merge_request_title) - expect(page).to have_content(merge_request_description) - expect(page).to have_content(/Opened [\w\s]+ ago/) + Page::MergeRequest::Show.perform do |merge_request| + expect(merge_request).to have_title(@merge_request_title) + expect(merge_request).to have_description(@merge_request_description) + end end - it 'user creates a new merge request with a milestone and label' do + it 'creates a merge request with a milestone and label' do gitlab_account_username = "@#{Runtime::User.username}" - Runtime::Browser.visit(:gitlab, Page::Main::Login) - Page::Main::Login.act { sign_in_using_credentials } - - current_project = Resource::Project.fabricate! do |project| - project.name = 'project-with-merge-request-and-milestone' - end - - current_milestone = Resource::ProjectMilestone.fabricate! do |milestone| - milestone.title = 'unique-milestone' - milestone.project = current_project + milestone = Resource::ProjectMilestone.fabricate_via_api! do |milestone| + milestone.project = @project + milestone.title = 'milestone' end - new_label = Resource::Label.fabricate! do |label| - label.project = current_project - label.title = 'qa-mr-test-label' - label.description = 'Merge Request label' + label = Resource::Label.fabricate_via_api! do |label| + label.project = @project + label.title = 'label' end - merge_request_title = 'This is a merge request with a milestone and a label' - merge_request_description = 'Great feature with milestone' - - Resource::MergeRequest.fabricate! do |merge_request| - merge_request.title = merge_request_title - merge_request.description = merge_request_description - merge_request.project = current_project - merge_request.milestone = current_milestone + Resource::MergeRequest.fabricate_via_browser_ui! do |merge_request| + merge_request.title = @merge_request_title + merge_request.description = @merge_request_description + merge_request.project = @project + merge_request.milestone = milestone merge_request.assignee = 'me' - merge_request.labels.push(new_label) + merge_request.labels.push(label) end Page::MergeRequest::Show.perform do |merge_request| - expect(merge_request).to have_content(merge_request_title) - expect(merge_request).to have_content(merge_request_description) - expect(merge_request).to have_content(/Opened [\w\s]+ ago/) + expect(merge_request).to have_title(@merge_request_title) + expect(merge_request).to have_description(@merge_request_description) expect(merge_request).to have_assignee(gitlab_account_username) - expect(merge_request).to have_label(new_label.title) + expect(merge_request).to have_label(label.title) end Page::Issuable::Sidebar.perform do |sidebar| - expect(sidebar).to have_milestone(current_milestone.title) + expect(sidebar).to have_milestone(milestone.title) end end end diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb index c7db595284e..6ca7af8a3af 100644 --- a/qa/qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb @@ -1,8 +1,7 @@ # frozen_string_literal: true module QA - # Failure issue: https://gitlab.com/gitlab-org/quality/nightly/issues/94 - context 'Create', :quarantine do + context 'Create' do describe 'Merge request creation from fork' do it 'user forks a project, submits a merge request and maintainer merges it' do Runtime::Browser.visit(:gitlab, Page::Main::Login) diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/squash_merge_request_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/squash_merge_request_spec.rb index 86692623790..a93f2695ec2 100644 --- a/qa/qa/specs/features/browser_ui/3_create/merge_request/squash_merge_request_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/squash_merge_request_spec.rb @@ -1,8 +1,7 @@ # frozen_string_literal: true module QA - # Failure issue: https://gitlab.com/gitlab-org/quality/nightly/issues/93 - context 'Create', :quarantine do + context 'Create' do describe 'Merge request squashing' do it 'user squashes commits while merging' do Runtime::Browser.visit(:gitlab, Page::Main::Login) diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/add_file_template_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/add_file_template_spec.rb index 2750b171a85..567c6a83ddf 100644 --- a/qa/qa/specs/features/browser_ui/3_create/repository/add_file_template_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/repository/add_file_template_spec.rb @@ -2,8 +2,7 @@ module QA context 'Create' do - # Issue: https://gitlab.com/gitlab-org/quality/nightly/issues/97 - describe 'File templates', :quarantine do + describe 'File templates' do include Runtime::Fixtures def login diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/add_list_delete_branches_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/add_list_delete_branches_spec.rb index 37a784248d4..2b3d9b1711d 100644 --- a/qa/qa/specs/features/browser_ui/3_create/repository/add_list_delete_branches_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/repository/add_list_delete_branches_spec.rb @@ -1,8 +1,7 @@ # frozen_string_literal: true module QA - # Failure issue: https://gitlab.com/gitlab-org/quality/nightly/issues/62 - context 'Create', :quarantine do + context 'Create' do describe 'Create, list, and delete branches via web' do master_branch = 'master' second_branch = 'second-branch' diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_file_size_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_file_size_spec.rb index 5bfafdfa041..247cde38e52 100644 --- a/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_file_size_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_file_size_spec.rb @@ -1,74 +1,57 @@ # frozen_string_literal: true module QA - # Failure issue: https://gitlab.com/gitlab-org/quality/staging/issues/37 - context 'Create', :quarantine do + context 'Create', :requires_admin do describe 'push after setting the file size limit via admin/application_settings' do - before(:all) do - push = Resource::Repository::ProjectPush.fabricate! do |p| - p.file_name = 'README.md' - p.file_content = '# This is a test project' - p.commit_message = 'Add README.md' + before(:context) do + @project = Resource::Project.fabricate_via_api! do |p| + p.name = 'project-test-push-limit' + p.initialize_with_readme = true end - @project = push.project + @api_client = Runtime::API::Client.new(:gitlab, personal_access_token: Runtime::Env.admin_personal_access_token) end - before do - Runtime::Browser.visit(:gitlab, Page::Main::Login) - Page::Main::Login.perform(&:sign_in_using_credentials) - end - - after(:all) do + after(:context) do # need to set the default value after test # default value for file size limit is empty - Runtime::Browser.visit(:gitlab, Page::Main::Login) - Page::Main::Login.perform(&:sign_in_using_credentials) - - set_file_size_limit('') - - Page::Main::Menu.perform(&:sign_out) + set_file_size_limit(nil) end it 'push successful when the file size is under the limit' do set_file_size_limit(5) - expect(page).to have_content("Application settings saved successfully") - push = push_new_file('oversize_file_1.bin', wait_for_push: true) expect(push.output).not_to have_content 'remote: fatal: pack exceeds maximum allowed size' end it 'push fails when the file size is above the limit' do set_file_size_limit(1) - expect(page).to have_content("Application settings saved successfully") - expect { push_new_file('oversize_file_2.bin', wait_for_push: false) } .to raise_error(QA::Git::Repository::RepositoryCommandError, /remote: fatal: pack exceeds maximum allowed size/) end def set_file_size_limit(limit) - Page::Main::Menu.perform(&:click_admin_area) - Page::Admin::Menu.perform(&:go_to_general_settings) + request = Runtime::API::Request.new(@api_client, '/application/settings') + put request.url, receive_max_input_size: limit - Page::Admin::Settings::General.perform do |setting| - setting.expand_account_and_limit do |page| - page.set_max_file_size(limit) - page.save_settings - end - end + expect_status(200) + expect(json_body).to match( + a_hash_including(receive_max_input_size: limit) + ) end def push_new_file(file_name, wait_for_push: true) - @project.visit! - - Resource::Repository::ProjectPush.fabricate! do |p| - p.project = @project + commit_message = 'Adding a new file' + output = Resource::Repository::Push.fabricate! do |p| + p.repository_http_uri = @project.repository_http_location.uri p.file_name = file_name p.file_content = SecureRandom.random_bytes(2000000) - p.commit_message = 'Adding a new file' - p.wait_for_push = wait_for_push + p.commit_message = commit_message p.new_branch = false end + @project.wait_for_push commit_message + + output end end end diff --git a/qa/qa/specs/features/browser_ui/3_create/snippet/create_snippet_spec.rb b/qa/qa/specs/features/browser_ui/3_create/snippet/create_snippet_spec.rb index f6f0468e76e..796de44a012 100644 --- a/qa/qa/specs/features/browser_ui/3_create/snippet/create_snippet_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/snippet/create_snippet_spec.rb @@ -1,8 +1,7 @@ # frozen_string_literal: true module QA - # Failure issue: https://gitlab.com/gitlab-org/quality/staging/issues/49 - context 'Create', :smoke, :quarantine do + context 'Create', :smoke do describe 'Snippet creation' do it 'User creates a snippet' do Runtime::Browser.visit(:gitlab, Page::Main::Login) @@ -13,7 +12,7 @@ module QA Resource::Snippet.fabricate_via_browser_ui! do |snippet| snippet.title = 'Snippet title' snippet.description = 'Snippet description' - snippet.visibility = 'Public' + snippet.visibility = 'Private' snippet.file_name = 'New snippet file name' snippet.file_content = 'Snippet file text' end @@ -21,8 +20,7 @@ module QA Page::Dashboard::Snippet::Show.perform do |snippet| expect(snippet).to have_snippet_title('Snippet title') expect(snippet).to have_snippet_description('Snippet description') - expect(snippet).to have_embed_type('Embed') - expect(snippet).to have_visibility_type('Public') + expect(snippet).to have_visibility_type('Private') expect(snippet).to have_file_name('New snippet file name') expect(snippet).to have_file_content('Snippet file text') end diff --git a/qa/qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb b/qa/qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb index 078d3b2b5b1..c09c65a57a5 100644 --- a/qa/qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb @@ -1,8 +1,7 @@ # frozen_string_literal: true module QA - # Failure issue: https://gitlab.com/gitlab-org/quality/staging/issues/46 - context 'Create', :quarantine do + context 'Create' do describe 'Web IDE file templates' do include Runtime::Fixtures diff --git a/qa/qa/specs/features/browser_ui/non_devops/performance_bar_spec.rb b/qa/qa/specs/features/browser_ui/non_devops/performance_bar_spec.rb index fc4ff364fd4..a04efb94def 100644 --- a/qa/qa/specs/features/browser_ui/non_devops/performance_bar_spec.rb +++ b/qa/qa/specs/features/browser_ui/non_devops/performance_bar_spec.rb @@ -19,7 +19,7 @@ module QA it 'shows results for the original request and AJAX requests' do # Issue pages always make AJAX requests - Resource::Issue.fabricate! do |issue| + Resource::Issue.fabricate_via_browser_ui! do |issue| issue.title = 'Performance bar test' end diff --git a/qa/qa/support/api.rb b/qa/qa/support/api.rb index a5c86425465..203064b2665 100644 --- a/qa/qa/support/api.rb +++ b/qa/qa/support/api.rb @@ -16,11 +16,12 @@ module QA e.response end - def get(url) + def get(url, raw_response: false) RestClient::Request.execute( method: :get, url: url, - verify_ssl: false) + verify_ssl: false, + raw_response: raw_response) rescue RestClient::ExceptionWithResponse => e e.response end diff --git a/qa/qa/support/page/logging.rb b/qa/qa/support/page/logging.rb index 02ebd96ad49..93d8fa99c0a 100644 --- a/qa/qa/support/page/logging.rb +++ b/qa/qa/support/page/logging.rb @@ -125,7 +125,7 @@ module QA super end - def within_element(name) + def within_element(name, text: nil) log("within element :#{name}") element = super diff --git a/qa/qa/tools/generate_perf_testdata.rb b/qa/qa/tools/generate_perf_testdata.rb index de8cfa1aed9..b0477951967 100644 --- a/qa/qa/tools/generate_perf_testdata.rb +++ b/qa/qa/tools/generate_perf_testdata.rb @@ -19,26 +19,30 @@ module QA raise ArgumentError, "Please provide GITLAB_QA_ACCESS_TOKEN" unless ENV['GITLAB_QA_ACCESS_TOKEN'] @api_client = Runtime::API::Client.new(ENV['GITLAB_ADDRESS'], personal_access_token: ENV['GITLAB_QA_ACCESS_TOKEN']) - @group_name = "gitlab-qa-perf-sandbox-#{SecureRandom.hex(8)}" - @project_name = "my-test-project-#{SecureRandom.hex(8)}" + @group_name = ENV['GROUP_NAME'] || "gitlab-qa-perf-sandbox-#{SecureRandom.hex(8)}" + @project_name = ENV['PROJECT_NAME'] || "my-test-project-#{SecureRandom.hex(8)}" @visibility = "public" @urls = { host: ENV['GITLAB_ADDRESS'] } end - def run + def all STDOUT.puts 'Running...' group_id = create_group create_project(group_id) - create_branch - add_new_file + + create_many_branches + create_many_new_files + create_mr_with_many_commits + methods_arr = [ - method(:create_issues), - method(:create_labels), - method(:create_todos), - method(:create_merge_requests), - method(:create_issue_with_500_discussions), - method(:create_mr_with_large_files) + method(:create_many_issues), + method(:create_many_labels), + method(:create_many_todos), + method(:create_many_merge_requests), + method(:create_an_issue_with_many_discussions), + method(:create_an_mr_with_large_files_and_many_mr_discussions) ] + threads_arr = [] methods_arr.each do |m| @@ -51,103 +55,102 @@ module QA STDOUT.puts "\nDone" end - private - def create_group - group_search_response = post Runtime::API::Request.new(@api_client, "/groups").url, "name=#{@group_name}&path=#{@group_name}&visibility=#{@visibility}" + group_search_response = create_a_group_api_req(@group_name, @visibility) group = JSON.parse(group_search_response.body) @urls[:group_page] = group["web_url"] group["id"] + STDOUT.puts "Created a group: #{@urls[:group_page]}" end def create_project(group_id) - create_project_response = post Runtime::API::Request.new(@api_client, "/projects").url, "name=#{@project_name}&namespace_id=#{group_id}&visibility=#{@visibility}" + create_project_response = create_a_project_api_req(@project_name, group_id, @visibility) @urls[:project_page] = JSON.parse(create_project_response.body)["web_url"] + STDOUT.puts "Created a project: #{@urls[:project_page]}" end - def create_issues + def create_many_issues 30.times do |i| - post Runtime::API::Request.new(@api_client, "/projects/#{@group_name}%2F#{@project_name}/issues").url, "title=issue#{i}&description=desc#{i}" + create_an_issue_api_req("#{@group_name}%2F#{@project_name}", "issue#{i}", "desc#{i}") end @urls[:issues_list_page] = @urls[:project_page] + "/issues" - STDOUT.puts "Created Issues" + STDOUT.puts "Created many issues: #{@urls[:issues_list_page]}" end - def create_todos + def create_many_todos 30.times do |i| - post Runtime::API::Request.new(@api_client, "/projects/#{@group_name}%2F#{@project_name}/issues/#{i + 1}/todo").url, nil + create_a_todo_api_req("#{@group_name}%2F#{@project_name}", "#{i + 1}") end @urls[:todos_page] = ENV['GITLAB_ADDRESS'] + "/dashboard/todos" - STDOUT.puts "Created todos" + STDOUT.puts "Created many todos: #{@urls[:todos_page]}" end - def create_labels + def create_many_labels 30.times do |i| - post Runtime::API::Request.new(@api_client, "/projects/#{@group_name}%2F#{@project_name}/labels").url, - "name=label#{i}&color=#{Faker::Color.hex_color}" + create_a_label_api_req("#{@group_name}%2F#{@project_name}", "label#{i}", "#{Faker::Color.hex_color}") end @urls[:labels_page] = @urls[:project_page] + "/labels" - STDOUT.puts "Created labels" + STDOUT.puts "Created many labels: #{@urls[:labels_page]}" end - def create_merge_requests + def create_many_merge_requests 30.times do |i| - post Runtime::API::Request.new(@api_client, "/projects/#{@group_name}%2F#{@project_name}/merge_requests").url, "source_branch=branch#{i}&target_branch=master&title=MR#{i}" + create_a_merge_request_api_req("#{@group_name}%2F#{@project_name}", "branch#{i}", "master", "MR#{i}") end @urls[:mr_list_page] = @urls[:project_page] + "/merge_requests" - STDOUT.puts "Created MRs" + STDOUT.puts "Created many MRs: #{@urls[:mr_list_page]}" end - def add_new_file - post Runtime::API::Request.new(@api_client, "/projects/#{@group_name}%2F#{@project_name}/repository/files/hello.txt").url, "branch=master&commit_message=\"hello\"&content=\"my new content\"" + def create_many_new_files + create_a_new_file_api_req("hello.txt", "master", "#{@group_name}%2F#{@project_name}", "hello", "my new content") 30.times do |i| - post Runtime::API::Request.new(@api_client, "/projects/#{@group_name}%2F#{@project_name}/repository/files/hello#{i}.txt").url, "branch=branch#{i}&commit_message=\"hello\"&content=\"my new content\"" + create_a_new_file_api_req("hello#{i}.txt", "branch#{i}", "#{@group_name}%2F#{@project_name}", "hello", "my new content") end - STDOUT.puts "Added Files" + + @urls[:files_page] = @urls[:project_page] + "/tree/master" + STDOUT.puts "Added many new files: #{@urls[:files_page]}" end - def create_branch + def create_many_branches 30.times do |i| - post Runtime::API::Request.new(@api_client, "/projects/#{@group_name}%2F#{@project_name}/repository/branches").url, "branch=branch#{i}&ref=master" + create_a_branch_api_req("branch#{i}", "#{@group_name}%2F#{@project_name}") end - STDOUT.puts "Created branches" + @urls[:branches_page] = @urls[:project_page] + "/-/branches" + STDOUT.puts "Created many branches: #{@urls[:branches_page]}" end - def create_issue_with_500_discussions + def create_an_issue_with_many_discussions issue_id = 1 500.times do - post Runtime::API::Request.new(@api_client, "/projects/#{@group_name}%2F#{@project_name}/issues/#{issue_id}/discussions").url, "body=\"Let us discuss\"" + create_a_discussion_on_issue_api_req("#{@group_name}%2F#{@project_name}", issue_id, "Let us discuss") end - labels_list = (0..15).map {|i| "label#{i}"}.join(',') + labels_list = (0..15).map { |i| "label#{i}" }.join(',') # Add description and labels - put Runtime::API::Request.new(@api_client, "/projects/#{@group_name}%2F#{@project_name}/issues/#{issue_id}").url, "description=#{Faker::Lorem.sentences(500).join(" ")}&labels=#{labels_list}" + update_an_issue_api_req("#{@group_name}%2F#{@project_name}", issue_id, "#{Faker::Lorem.sentences(500).join(" ")}", labels_list) @urls[:large_issue] = @urls[:project_page] + "/issues/#{issue_id}" - STDOUT.puts "Created Issue with 500 Discussions" + STDOUT.puts "Created an issue with many discussions: #{@urls[:large_issue]}" end - def create_mr_with_large_files + def create_an_mr_with_large_files_and_many_mr_discussions content_arr = [] 16.times do |i| faker_line_arr = Faker::Lorem.sentences(1500) content = faker_line_arr.join("\n\r") - post Runtime::API::Request.new(@api_client, "/projects/#{@group_name}%2F#{@project_name}/repository/files/hello#{i}.txt").url, - "branch=master&commit_message=\"Add hello#{i}.txt\"&content=#{content}" + create_a_new_file_api_req("hello#{i}.txt", "master", "#{@group_name}%2F#{@project_name}", "Add hello#{i}.txt", content) content_arr[i] = faker_line_arr end - post Runtime::API::Request.new(@api_client, "/projects/#{@group_name}%2F#{@project_name}/repository/branches").url, - "branch=performance&ref=master" + create_a_branch_api_req("performance", "#{@group_name}%2F#{@project_name}") 16.times do |i| missed_line_array = content_arr[i].each_slice(2).map(&:first) content = missed_line_array.join("\n\rIm new!:D \n\r ") - put Runtime::API::Request.new(@api_client, "/projects/#{@group_name}%2F#{@project_name}/repository/files/hello#{i}.txt").url, - "branch=performance&commit_message=\"Update hello#{i}.txt\"&content=#{content}" + + update_file_api_req("hello#{i}.txt", "performance", "#{@group_name}%2F#{@project_name}", "Update hello#{i}.txt", content) end - create_mr_response = post Runtime::API::Request.new(@api_client, """/projects/#{@group_name}%2F#{@project_name}/merge_requests""").url, - "source_branch=performance&target_branch=master&title=Large_MR" + create_mr_response = create_a_merge_request_api_req("#{@group_name}%2F#{@project_name}", "performance", "master", "Large_MR") iid = JSON.parse(create_mr_response.body)["iid"] diff_refs = JSON.parse(create_mr_response.body)["diff_refs"] @@ -161,8 +164,8 @@ module QA if should_resolve discussion_id = JSON.parse(create_diff_note_response.body)["id"] - put Runtime::API::Request.new(@api_client, """/projects/#{@group_name}%2F#{@project_name}/merge_requests/#{iid}/discussions/#{discussion_id}""").url, - "resolved=true" + + update_a_discussion_on_issue_api_req("#{@group_name}%2F#{@project_name}", iid, discussion_id, "true") end should_resolve ^= true @@ -171,23 +174,93 @@ module QA # Add discussions to main tab 100.times do - post Runtime::API::Request.new(@api_client, "/projects/#{@group_name}%2F#{@project_name}/merge_requests/#{iid}/discussions").url, - "body=\"Let us discuss\"" + create_a_discussion_on_mr_api_req("#{@group_name}%2F#{@project_name}", iid, "Let us discuss") end @urls[:large_mr] = JSON.parse(create_mr_response.body)["web_url"] - STDOUT.puts "Created MR with 500 Discussions and 20 Very Large Files" + STDOUT.puts "Created an MR with many discussions and many very large Files: #{@urls[:large_mr]}" end def create_diff_note(iid, file_count, line_count, head_sha, start_sha, base_sha, line_type) post Runtime::API::Request.new(@api_client, "/projects/#{@group_name}%2F#{@project_name}/merge_requests/#{iid}/discussions").url, - """body=\"Let us discuss\"& + "" "body=\"Let us discuss\"& position[position_type]=text& position[new_path]=hello#{file_count}.txt& position[old_path]=hello#{file_count}.txt& position[#{line_type}]=#{line_count * 100}& position[head_sha]=#{head_sha}& position[start_sha]=#{start_sha}& - position[base_sha]=#{base_sha}""" + position[base_sha]=#{base_sha}" "" + end + + def create_mr_with_many_commits + project_path = "#{@group_name}%2F#{@project_name}" + branch_name = "branch_with_many_commits-#{SecureRandom.hex(8)}" + file_name = "file_for_many_commits.txt" + create_a_branch_api_req(branch_name, project_path) + create_a_new_file_api_req(file_name, branch_name, project_path, "Initial commit for new file", "Initial file content") + create_mr_response = create_a_merge_request_api_req(project_path, branch_name, "master", "MR with many commits-#{SecureRandom.hex(8)}") + @urls[:mr_with_many_commits] = JSON.parse(create_mr_response.body)["web_url"] + 100.times do |i| + update_file_api_req(file_name, branch_name, project_path, Faker::Lorem.sentences(5).join(" "), Faker::Lorem.sentences(500).join("\n")) + end + STDOUT.puts "Created an MR with many commits: #{@urls[:mr_with_many_commits]}" + end + + private + + # API Requests + + def create_a_discussion_on_issue_api_req(project_path_or_id, issue_id, body) + post Runtime::API::Request.new(@api_client, "/projects/#{project_path_or_id}/issues/#{issue_id}/discussions").url, "body=\"#{body}\"" + end + + def update_a_discussion_on_issue_api_req(project_path_or_id, mr_iid, discussion_id, resolved_status) + put Runtime::API::Request.new(@api_client, "/projects/#{project_path_or_id}/merge_requests/#{mr_iid}/discussions/#{discussion_id}").url, "resolved=#{resolved_status}" + end + + def create_a_discussion_on_mr_api_req(project_path_or_id, mr_iid, body) + post Runtime::API::Request.new(@api_client, "/projects/#{project_path_or_id}/merge_requests/#{mr_iid}/discussions").url, + "body=\"#{body}\"" + end + + def create_a_label_api_req(project_path_or_id, name, color) + post Runtime::API::Request.new(@api_client, "/projects/#{project_path_or_id}/labels").url, "name=#{name}&color=#{color}" + end + + def create_a_todo_api_req(project_path_or_id, issue_id) + post Runtime::API::Request.new(@api_client, "/projects/#{project_path_or_id}/issues/#{issue_id}/todo").url, nil + end + + def create_an_issue_api_req(project_path_or_id, title, description) + post Runtime::API::Request.new(@api_client, "/projects/#{project_path_or_id}/issues").url, "title=#{title}&description=#{description}" + end + + def update_an_issue_api_req(project_path_or_id, issue_id, description, labels_list) + put Runtime::API::Request.new(@api_client, "/projects/#{project_path_or_id}/issues/#{issue_id}").url, "description=#{description}&labels=#{labels_list}" + end + + def create_a_project_api_req(project_name, group_id, visibility) + post Runtime::API::Request.new(@api_client, "/projects").url, "name=#{project_name}&namespace_id=#{group_id}&visibility=#{visibility}" + end + + def create_a_group_api_req(group_name, visibility) + post Runtime::API::Request.new(@api_client, "/groups").url, "name=#{group_name}&path=#{group_name}&visibility=#{visibility}" + end + + def create_a_branch_api_req(branch_name, project_path_or_id) + post Runtime::API::Request.new(@api_client, "/projects/#{project_path_or_id}/repository/branches").url, "branch=#{branch_name}&ref=master" + end + + def create_a_new_file_api_req(file_path, branch_name, project_path_or_id, commit_message, content) + post Runtime::API::Request.new(@api_client, "/projects/#{project_path_or_id}/repository/files/#{file_path}").url, "branch=#{branch_name}&commit_message=\"#{commit_message}\"&content=\"#{content}\"" + end + + def create_a_merge_request_api_req(project_path_or_id, source_branch, target_branch, mr_title) + post Runtime::API::Request.new(@api_client, "/projects/#{project_path_or_id}/merge_requests").url, "source_branch=#{source_branch}&target_branch=#{target_branch}&title=#{mr_title}" + end + + def update_file_api_req(file_path, branch_name, project_path_or_id, commit_message, content) + put Runtime::API::Request.new(@api_client, "/projects/#{project_path_or_id}/repository/files/#{file_path}").url, "branch=#{branch_name}&commit_message=\"#{commit_message}\"&content=\"#{content}\"" end end end diff --git a/qa/spec/page/logging_spec.rb b/qa/spec/page/logging_spec.rb index 092c6a17c9c..0f1ed039149 100644 --- a/qa/spec/page/logging_spec.rb +++ b/qa/spec/page/logging_spec.rb @@ -135,9 +135,9 @@ describe QA::Support::Page::Logging do end it 'logs within_element' do - expect { subject.within_element(:element) } + expect { subject.within_element(:element, text: nil) } .to output(/within element :element/).to_stdout_from_any_process - expect { subject.within_element(:element) } + expect { subject.within_element(:element, text: nil) } .to output(/end within element :element/).to_stdout_from_any_process end diff --git a/qa/spec/runtime/api/client_spec.rb b/qa/spec/runtime/api/client_spec.rb index cf19b52700b..6f7020d6595 100644 --- a/qa/spec/runtime/api/client_spec.rb +++ b/qa/spec/runtime/api/client_spec.rb @@ -16,26 +16,56 @@ describe QA::Runtime::API::Client do end describe '#personal_access_token' do - context 'when QA::Runtime::Env.personal_access_token is present' do + context 'when user is nil and QA::Runtime::Env.personal_access_token is present' do before do allow(QA::Runtime::Env).to receive(:personal_access_token).and_return('a_token') end it 'returns specified token from env' do - expect(described_class.new.personal_access_token).to eq 'a_token' + expect(subject.personal_access_token).to eq 'a_token' end end - context 'when QA::Runtime::Env.personal_access_token is nil' do + context 'when user is present and QA::Runtime::Env.personal_access_token is nil' do before do allow(QA::Runtime::Env).to receive(:personal_access_token).and_return(nil) end it 'returns a created token' do + subject { described_class.new(user: { username: 'foo' }) } + expect(subject).to receive(:create_personal_access_token).and_return('created_token') expect(subject.personal_access_token).to eq 'created_token' end end + + context 'when user is nil and QA::Runtime::Env.personal_access_token is nil' do + before do + allow(QA::Runtime::Env).to receive(:personal_access_token).and_return(nil) + end + + it 'returns a created token' do + client = described_class.new + + expect(client).to receive(:create_personal_access_token).and_return('created_token') + + expect(client.personal_access_token).to eq 'created_token' + end + end + + context 'when user is present and QA::Runtime::Env.personal_access_token is present' do + before do + allow(QA::Runtime::Env).to receive(:personal_access_token).and_return('a_token') + end + + it 'returns a created token' do + client = described_class.new(user: { username: 'foo' }) + + expect(client).to receive(:create_personal_access_token).and_return('created_token') + + expect(client.personal_access_token).to eq 'created_token' + end + end end end diff --git a/qa/spec/runtime/env_spec.rb b/qa/spec/runtime/env_spec.rb index 2560695ef2e..caf96a213e1 100644 --- a/qa/spec/runtime/env_spec.rb +++ b/qa/spec/runtime/env_spec.rb @@ -227,6 +227,12 @@ describe QA::Runtime::Env do env_key: 'QA_CAN_TEST_GIT_PROTOCOL_V2', default: true + it_behaves_like 'boolean method with parameter', + method: :can_test?, + param: :admin, + env_key: 'QA_CAN_TEST_ADMIN_FEATURES', + default: true + it 'raises ArgumentError if feature is unknown' do expect { described_class.can_test? :foo }.to raise_error(ArgumentError, 'Unknown feature "foo"') end diff --git a/qa/spec/specs/runner_spec.rb b/qa/spec/specs/runner_spec.rb index 5c86c102105..f94145d148e 100644 --- a/qa/spec/specs/runner_spec.rb +++ b/qa/spec/specs/runner_spec.rb @@ -1,16 +1,22 @@ # frozen_string_literal: true +require 'active_support/core_ext/hash' + describe QA::Specs::Runner do + shared_examples 'excludes orchestrated' do + it 'excludes the orchestrated tag and includes default args' do + expect_rspec_runner_arguments(['--tag', '~orchestrated', *described_class::DEFAULT_TEST_PATH_ARGS]) + + subject.perform + end + end + context '#perform' do before do allow(QA::Runtime::Browser).to receive(:configure!) end - it 'excludes the orchestrated tag by default' do - expect_rspec_runner_arguments(['--tag', '~orchestrated', *described_class::DEFAULT_TEST_PATH_ARGS]) - - subject.perform - end + it_behaves_like 'excludes orchestrated' context 'when tty is set' do subject { described_class.new.tap { |runner| runner.tty = true } } @@ -67,8 +73,6 @@ describe QA::Specs::Runner do allow(QA::Runtime::Env).to receive(:signup_disabled?).and_return(true) end - subject { described_class.new } - it 'includes default args and excludes the skip_signup_disabled tag' do expect_rspec_runner_arguments(['--tag', '~orchestrated', '--tag', '~skip_signup_disabled', *described_class::DEFAULT_TEST_PATH_ARGS]) @@ -76,18 +80,54 @@ describe QA::Specs::Runner do end end - context 'when git protocol v2 is not supported' do - before do - allow(QA::Runtime::Env).to receive(:can_test?).with(:git_protocol_v2).and_return(false) + context 'testable features' do + shared_examples 'one supported feature' do |feature| + before do + QA::Runtime::Env.supported_features.each do |tag, _| + allow(QA::Runtime::Env).to receive(:can_test?).with(tag).and_return(false) + end + + allow(QA::Runtime::Env).to receive(:can_test?).with(feature).and_return(true) unless feature.nil? + end + + it 'includes default args and excludes all unsupported tags' do + expect_rspec_runner_arguments(['--tag', '~orchestrated', *excluded_feature_tags_except(feature), *described_class::DEFAULT_TEST_PATH_ARGS]) + + subject.perform + end end - subject { described_class.new } + context 'when only git protocol 2 is supported' do + it_behaves_like 'one supported feature', :git_protocol_v2 + end - it 'includes default args and excludes the requires_git_protocol_v2 tag' do - expect_rspec_runner_arguments(['--tag', '~orchestrated', '--tag', '~requires_git_protocol_v2', *described_class::DEFAULT_TEST_PATH_ARGS]) + context 'when only admin features are supported' do + it_behaves_like 'one supported feature', :admin + end - subject.perform + context 'when no features are supported' do + it_behaves_like 'one supported feature', nil end + + context 'when all features are supported' do + before do + QA::Runtime::Env.supported_features.each do |tag, _| + allow(QA::Runtime::Env).to receive(:can_test?).with(tag).and_return(true) + end + end + + it_behaves_like 'excludes orchestrated' + end + + context 'when features are not specified' do + it_behaves_like 'excludes orchestrated' + end + end + + def excluded_feature_tags_except(tag) + QA::Runtime::Env.supported_features.except(tag).map do |tag, _| + ['--tag', "~requires_#{tag}"] + end.flatten end def expect_rspec_runner_arguments(arguments) |