diff options
Diffstat (limited to 'qa')
43 files changed, 547 insertions, 173 deletions
diff --git a/qa/Gemfile.lock b/qa/Gemfile.lock index 8d28fcacc05..d61ecf8fbb5 100644 --- a/qa/Gemfile.lock +++ b/qa/Gemfile.lock @@ -56,7 +56,7 @@ GEM byebug (~> 9.1) pry (~> 0.10) public_suffix (3.0.1) - rack (2.0.3) + rack (2.0.6) rack-test (0.8.2) rack (>= 1.0, < 3) rake (12.3.0) @@ -103,4 +103,4 @@ DEPENDENCIES selenium-webdriver (~> 3.8.0) BUNDLED WITH - 1.16.4 + 1.17.1 diff --git a/qa/README.md b/qa/README.md index 746bd5cf94b..08ba59e117d 100644 --- a/qa/README.md +++ b/qa/README.md @@ -80,6 +80,15 @@ GITLAB_USERNAME=jsmith GITLAB_PASSWORD=password GITLAB_SANDBOX_NAME=jsmith-qa-sa All [supported environment variables are here](https://gitlab.com/gitlab-org/gitlab-qa/blob/master/docs/what_tests_can_be_run.md#supported-environment-variables). +### Sending additional cookies + +The environment variable `QA_COOKIES` can be set to send additional cookies +on every request. This is necessary on gitlab.com to direct traffic to the +canary fleet. To do this set `QA_COOKIES="gitlab_canary=true"`. + +To set multiple cookies, separate them with the `;` character, for example: `QA_COOKIES="cookie1=value;cookie2=value2"` + + ### Building a Docker image to test Once you have made changes to the CE/EE repositories, you may want to build a @@ -184,6 +184,7 @@ module QA autoload :Runners, 'qa/page/project/settings/runners' autoload :MergeRequest, 'qa/page/project/settings/merge_request' autoload :Members, 'qa/page/project/settings/members' + autoload :MirroringRepositories, 'qa/page/project/settings/mirroring_repositories' end module Issue @@ -272,6 +273,7 @@ module QA # module Component autoload :ClonePanel, 'qa/page/component/clone_panel' + autoload :LegacyClonePanel, 'qa/page/component/legacy_clone_panel' autoload :Dropzone, 'qa/page/component/dropzone' autoload :GroupsFilter, 'qa/page/component/groups_filter' autoload :Select2, 'qa/page/component/select2' @@ -329,6 +331,7 @@ module QA module Page autoload :Logging, 'qa/support/page/logging' end + autoload :Api, 'qa/support/api' end end diff --git a/qa/qa/fixtures/auto_devops_rack/Gemfile.lock b/qa/qa/fixtures/auto_devops_rack/Gemfile.lock index 09cf72c48ac..d44ccbb5e69 100644 --- a/qa/qa/fixtures/auto_devops_rack/Gemfile.lock +++ b/qa/qa/fixtures/auto_devops_rack/Gemfile.lock @@ -1,7 +1,7 @@ GEM remote: https://rubygems.org/ specs: - rack (2.0.4) + rack (2.0.6) rake (12.3.0) PLATFORMS @@ -12,4 +12,4 @@ DEPENDENCIES rake BUNDLED WITH - 1.16.1 + 1.17.1 diff --git a/qa/qa/page/base.rb b/qa/qa/page/base.rb index 91e229c4c8c..88ade66f47d 100644 --- a/qa/qa/page/base.rb +++ b/qa/qa/page/base.rb @@ -15,7 +15,7 @@ module QA def_delegators :evaluator, :view, :views def refresh - visit current_url + page.refresh end def wait(max: 60, time: 0.1, reload: true) @@ -80,8 +80,8 @@ module QA page.evaluate_script('xhr.status') == 200 end - def find_element(name) - find(element_selector_css(name)) + def find_element(name, wait: Capybara.default_max_wait_time) + find(element_selector_css(name), wait: wait) end def all_elements(name) @@ -100,6 +100,14 @@ module QA find_element(name).set(content) end + def select_element(name, value) + element = find_element(name) + + return if element.text.downcase.to_s == value.to_s + + element.select value.to_s.capitalize + end + def has_element?(name) has_css?(element_selector_css(name)) end @@ -110,6 +118,12 @@ module QA end end + def within_element_by_index(name, index) + page.within all_elements(name)[index] do + yield + end + end + def scroll_to_element(name, *args) scroll_to(element_selector_css(name), *args) end @@ -118,6 +132,10 @@ module QA Page::Element.new(name).selector_css end + def click_link_with_text(text) + click_link text + end + def self.path raise NotImplementedError end diff --git a/qa/qa/page/component/clone_panel.rb b/qa/qa/page/component/clone_panel.rb index 94e761b0e0c..d37b63c716a 100644 --- a/qa/qa/page/component/clone_panel.rb +++ b/qa/qa/page/component/clone_panel.rb @@ -5,26 +5,20 @@ module QA module Component module ClonePanel def self.included(base) - base.view 'app/views/shared/_clone_panel.html.haml' do + base.view 'app/views/projects/buttons/_clone.html.haml' do element :clone_dropdown - element :clone_options_dropdown, '.clone-options-dropdown' # rubocop:disable QA/ElementWithPattern - element :project_repository_location, 'text_field_tag :project_clone' # rubocop:disable QA/ElementWithPattern + element :clone_options + element :ssh_clone_url + element :http_clone_url end end - def choose_repository_clone_http - choose_repository_clone('HTTP', 'http') + def repository_clone_http_location + repository_clone_location(:http_clone_url) end - def choose_repository_clone_ssh - # It's not always beginning with ssh:// so detecting with @ - # would be more reliable because ssh would always contain it. - # We can't use .git because HTTP also contain that part. - choose_repository_clone('SSH', '@') - end - - def repository_location - Git::Location.new(find('#project_clone').value) + def repository_clone_ssh_location + repository_clone_location(:ssh_clone_url) end def wait_for_push @@ -34,16 +28,13 @@ module QA private - def choose_repository_clone(kind, detect_text) + def repository_clone_location(kind) wait(reload: false) do click_element :clone_dropdown - page.within('.clone-options-dropdown') do - click_link(kind) + within_element :clone_options do + Git::Location.new(find_element(kind).value) end - - # Ensure git clone textbox was updated - repository_location.git_uri.include?(detect_text) end end end diff --git a/qa/qa/page/component/legacy_clone_panel.rb b/qa/qa/page/component/legacy_clone_panel.rb new file mode 100644 index 00000000000..99132190f3f --- /dev/null +++ b/qa/qa/page/component/legacy_clone_panel.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +module QA + module Page + module Component + module LegacyClonePanel + def self.included(base) + base.view 'app/views/shared/_clone_panel.html.haml' do + element :clone_dropdown + element :clone_options_dropdown, '.clone-options-dropdown' # rubocop:disable QA/ElementWithPattern + element :project_repository_location, 'text_field_tag :project_clone' # rubocop:disable QA/ElementWithPattern + end + end + + def choose_repository_clone_http + choose_repository_clone('HTTP', 'http') + end + + def choose_repository_clone_ssh + # It's not always beginning with ssh:// so detecting with @ + # would be more reliable because ssh would always contain it. + # We can't use .git because HTTP also contain that part. + choose_repository_clone('SSH', '@') + end + + def repository_location + Git::Location.new(find('#project_clone').value) + end + + def wait_for_push + sleep 5 + refresh + end + + private + + def choose_repository_clone(kind, detect_text) + wait(reload: false) do + click_element :clone_dropdown + + page.within('.clone-options-dropdown') do + click_link(kind) + end + + # Ensure git clone textbox was updated + repository_location.git_uri.include?(detect_text) + end + end + end + end + end +end diff --git a/qa/qa/page/component/select2.rb b/qa/qa/page/component/select2.rb index 30829eb0221..6d07d5a10e6 100644 --- a/qa/qa/page/component/select2.rb +++ b/qa/qa/page/component/select2.rb @@ -3,7 +3,12 @@ module QA module Component module Select2 def select_item(item_text) - find('ul.select2-result-sub > li', text: item_text).click + find('.select2-result-label', text: item_text).click + end + + def search_and_select(item_text) + find('.select2-input').set(item_text) + select_item(item_text) end end end diff --git a/qa/qa/page/main/menu.rb b/qa/qa/page/main/menu.rb index fb45ebef1b6..cc2724618e9 100644 --- a/qa/qa/page/main/menu.rb +++ b/qa/qa/page/main/menu.rb @@ -68,6 +68,12 @@ module QA end end + def has_admin_area_link?(wait: Capybara.default_max_wait_time) + using_wait_time(wait) do + page.has_selector?(element_selector_css(:admin_area_link)) + end + end + private def within_top_menu diff --git a/qa/qa/page/merge_request/new.rb b/qa/qa/page/merge_request/new.rb index 1f8f1fbca8e..20d9c336367 100644 --- a/qa/qa/page/merge_request/new.rb +++ b/qa/qa/page/merge_request/new.rb @@ -26,6 +26,10 @@ module QA element :issuable_label end + view 'app/views/shared/issuable/form/_metadata_merge_request_assignee.html.haml' do + element :assign_to_me_link + end + def create_merge_request click_element :issuable_create_button end @@ -50,6 +54,10 @@ module QA click_link label.title end + + def assign_to_me + click_element :assign_to_me_link + end end end end diff --git a/qa/qa/page/merge_request/show.rb b/qa/qa/page/merge_request/show.rb index 2fd30e15ffb..869dc0b9d21 100644 --- a/qa/qa/page/merge_request/show.rb +++ b/qa/qa/page/merge_request/show.rb @@ -52,6 +52,7 @@ module QA end view 'app/views/shared/issuable/_sidebar.html.haml' do + element :assignee_block element :labels_block end @@ -100,6 +101,12 @@ module QA end end + def has_assignee?(username) + page.within(element_selector_css(:assignee_block)) do + has_text?(username) + end + end + def has_label?(label) page.within(element_selector_css(:labels_block)) do element = find('span', text: label) diff --git a/qa/qa/page/profile/personal_access_tokens.rb b/qa/qa/page/profile/personal_access_tokens.rb index 2f0202951bb..9191dbe9cf3 100644 --- a/qa/qa/page/profile/personal_access_tokens.rb +++ b/qa/qa/page/profile/personal_access_tokens.rb @@ -8,7 +8,7 @@ module QA element :scopes_api_radios, "label :scopes" # rubocop:disable QA/ElementWithPattern end - view 'app/views/profiles/personal_access_tokens/index.html.haml' do + view 'app/views/shared/_personal_access_tokens_created_container.html.haml' do element :create_token_field, "text_field_tag 'created-personal-access-token'" # rubocop:disable QA/ElementWithPattern end diff --git a/qa/qa/page/project/menu.rb b/qa/qa/page/project/menu.rb index cb4a10e1b6a..835e1ed00b5 100644 --- a/qa/qa/page/project/menu.rb +++ b/qa/qa/page/project/menu.rb @@ -29,11 +29,9 @@ module QA element :fly_out, "classList.add('fly-out-list')" # rubocop:disable QA/ElementWithPattern end - def click_repository_settings - hover_settings do - within_submenu do - click_link('Repository') - end + def click_ci_cd_pipelines + within_sidebar do + click_element :link_pipelines end end @@ -45,11 +43,9 @@ module QA end end - def click_operations_environments - hover_operations do - within_submenu do - click_element(:operations_environments_link) - end + def click_issues + within_sidebar do + click_link('Issues') end end @@ -61,61 +57,71 @@ module QA end end - def click_operations_kubernetes + def click_merge_requests + within_sidebar do + click_link('Merge Requests') + end + end + + def click_operations_environments hover_operations do within_submenu do - click_link('Kubernetes') + click_element(:operations_environments_link) end end end - def click_ci_cd_pipelines - within_sidebar do - click_element :link_pipelines + def click_operations_kubernetes + hover_operations do + within_submenu do + click_link('Kubernetes') + end end end - def go_to_settings + def click_milestones within_sidebar do - click_on 'Settings' + click_element :milestones_link end end - def click_issues + def click_repository within_sidebar do - click_link('Issues') + click_link('Repository') end end - def go_to_labels - hover_issues do + def click_repository_settings + hover_settings do within_submenu do - click_element(:labels_link) + click_link('Repository') end end end - def click_merge_requests + def click_wiki within_sidebar do - click_link('Merge Requests') + click_link('Wiki') end end - def click_milestones + def go_to_activity within_sidebar do - click_element :milestones_link + click_on 'Activity' end end - def click_wiki - within_sidebar do - click_link('Wiki') + def go_to_labels + hover_issues do + within_submenu do + click_element(:labels_link) + end end end - def click_repository + def go_to_settings within_sidebar do - click_link('Repository') + click_on 'Settings' end end @@ -129,17 +135,17 @@ module QA end end - def hover_settings + def hover_operations within_sidebar do - find('.qa-settings-item').hover + find('.shortcuts-operations').hover yield end end - def hover_operations + def hover_settings within_sidebar do - find('.shortcuts-operations').hover + find('.qa-settings-item').hover yield end @@ -151,12 +157,6 @@ module QA end end - def go_to_activity - within_sidebar do - click_on 'Activity' - end - end - def within_submenu page.within('.fly-out-list') do yield diff --git a/qa/qa/page/project/settings/mirroring_repositories.rb b/qa/qa/page/project/settings/mirroring_repositories.rb new file mode 100644 index 00000000000..a73be7dfeda --- /dev/null +++ b/qa/qa/page/project/settings/mirroring_repositories.rb @@ -0,0 +1,91 @@ +# frozen_string_literal: true + +module QA + module Page + module Project + module Settings + class MirroringRepositories < Page::Base + view 'app/views/projects/mirrors/_authentication_method.html.haml' do + element :authentication_method + element :password + end + + view 'app/views/projects/mirrors/_mirror_repos.html.haml' do + element :mirror_repository_url_input + element :mirror_repository_button + element :mirror_repository_url + element :mirror_last_update_at + element :mirrored_repository_row + end + + view 'app/views/projects/mirrors/_mirror_repos_form.html.haml' do + element :mirror_direction + end + + view 'app/views/shared/_remote_mirror_update_button.html.haml' do + element :update_now_button + end + + def repository_url=(value) + fill_element :mirror_repository_url_input, value + end + + def password=(value) + fill_element :password, value + end + + def mirror_direction=(value) + raise ArgumentError, "Mirror direction must be :push or :pull" unless [:push, :pull].include? value + + select_element(:mirror_direction, value) + end + + def authentication_method=(value) + raise ArgumentError, "Authentication method must be :password or :none" unless [:password, :none].include? value + + select_element(:authentication_method, value) + end + + def mirror_repository + click_element :mirror_repository_button + end + + def update(url) + row_index = find_repository_row_index url + + within_element_by_index(:mirrored_repository_row, row_index) do + click_element :update_now_button + end + + # Wait a few seconds for the sync to occur and then refresh the page + # so that 'last update' shows 'just now' or a period in seconds + sleep 5 + refresh + + wait(time: 1) do + within_element_by_index(:mirrored_repository_row, row_index) do + last_update = find_element(:mirror_last_update_at, wait: 0) + last_update.has_text?('just now') || last_update.has_text?('seconds') + end + end + + # Fail early if the page still shows that there has been no update + within_element_by_index(:mirrored_repository_row, row_index) do + find_element(:mirror_last_update_at, wait: 0).assert_no_text('Never') + end + end + + private + + def find_repository_row_index(target_url) + all_elements(:mirror_repository_url).index do |url| + # The url might be a sanitized url but the target_url won't be so + # we compare just the paths instead of the full url + URI.parse(url.text).path == target_url.path + end + end + end + end + end + end +end diff --git a/qa/qa/page/project/settings/repository.rb b/qa/qa/page/project/settings/repository.rb index 53ebe28970b..ac0b87aca5e 100644 --- a/qa/qa/page/project/settings/repository.rb +++ b/qa/qa/page/project/settings/repository.rb @@ -13,6 +13,10 @@ module QA element :protected_branches_settings end + view 'app/views/projects/mirrors/_mirror_repos.html.haml' do + element :mirroring_repositories_settings + end + def expand_deploy_keys(&block) expand_section(:deploy_keys_settings) do DeployKeys.perform(&block) @@ -30,6 +34,12 @@ module QA DeployTokens.perform(&block) end end + + def expand_mirroring_repositories(&block) + expand_section(:mirroring_repositories_settings) do + MirroringRepositories.perform(&block) + end + end end end end diff --git a/qa/qa/page/project/show.rb b/qa/qa/page/project/show.rb index d6dddf03ffb..99d849db439 100644 --- a/qa/qa/page/project/show.rb +++ b/qa/qa/page/project/show.rb @@ -6,6 +6,11 @@ module QA class Show < Page::Base include Page::Component::ClonePanel + view 'app/views/layouts/header/_new_dropdown.haml' do + element :new_menu_toggle + element :new_issue_link, "link_to _('New issue'), new_project_issue_path(@project)" # rubocop:disable QA/ElementWithPattern + end + view 'app/views/projects/_last_push.html.haml' do element :create_merge_request end @@ -14,14 +19,12 @@ module QA element :project_name end - view 'app/views/layouts/header/_new_dropdown.haml' do - element :new_menu_toggle - element :new_issue_link, "link_to _('New issue'), new_project_issue_path(@project)" # rubocop:disable QA/ElementWithPattern + view 'app/views/projects/_files.html.haml' do + element :tree_holder, '.tree-holder' # rubocop:disable QA/ElementWithPattern end - view 'app/views/shared/_ref_switcher.html.haml' do - element :branches_select - element :branches_dropdown + view 'app/views/projects/buttons/_dropdown.html.haml' do + element :create_new_dropdown end view 'app/views/projects/buttons/_fork.html.haml' do @@ -29,44 +32,50 @@ module QA element :fork_link, "link_to new_project_fork_path(@project)" # rubocop:disable QA/ElementWithPattern end - view 'app/views/projects/_files.html.haml' do - element :tree_holder, '.tree-holder' # rubocop:disable QA/ElementWithPattern + view 'app/views/projects/empty.html.haml' do + element :quick_actions end - view 'app/views/projects/buttons/_dropdown.html.haml' do - element :create_new_dropdown - element :new_file_option + view 'app/views/projects/tree/_tree_content.html.haml' do + element :file_tree end view 'app/views/projects/tree/_tree_header.html.haml' do + element :add_to_tree + element :new_file_option element :web_ide_button end - view 'app/views/projects/tree/_tree_content.html.haml' do - element :file_tree + view 'app/views/shared/_ref_switcher.html.haml' do + element :branches_select + element :branches_dropdown end - def project_name - find('.qa-project-name').text + def create_first_new_file! + within_element(:quick_actions) do + click_link_with_text 'New file' + end end def create_new_file! - click_element :create_new_dropdown + click_element :add_to_tree click_element :new_file_option end + def fork_project + click_on 'Fork' + end + def go_to_file(filename) within_element(:file_tree) do click_on filename end end - def switch_to_branch(branch_name) - find_element(:branches_select).click + def go_to_new_issue + click_element :new_menu_toggle - within_element(:branches_dropdown) do - click_on branch_name - end + click_link 'New issue' end def last_commit_content @@ -81,24 +90,26 @@ module QA click_element :create_merge_request end - def wait_for_import - wait(reload: true) do - has_css?('.tree-holder') - end + def open_web_ide! + click_element :web_ide_button end - def go_to_new_issue - click_element :new_menu_toggle - - click_link 'New issue' + def project_name + find('.qa-project-name').text end - def fork_project - click_on 'Fork' + def switch_to_branch(branch_name) + find_element(:branches_select).click + + within_element(:branches_dropdown) do + click_on branch_name + end end - def open_web_ide! - click_element :web_ide_button + def wait_for_import + wait(reload: true) do + has_css?('.tree-holder') + end end end end diff --git a/qa/qa/page/project/wiki/show.rb b/qa/qa/page/project/wiki/show.rb index a7c4455d080..dffbc5d60a2 100644 --- a/qa/qa/page/project/wiki/show.rb +++ b/qa/qa/page/project/wiki/show.rb @@ -5,7 +5,7 @@ module QA module Project module Wiki class Show < Page::Base - include Page::Component::ClonePanel + include Page::Component::LegacyClonePanel view 'app/views/projects/wikis/pages.html.haml' do element :clone_repository_link, 'Clone repository' # rubocop:disable QA/ElementWithPattern diff --git a/qa/qa/resource/api_fabricator.rb b/qa/qa/resource/api_fabricator.rb index 3762a94f312..98eebac0880 100644 --- a/qa/qa/resource/api_fabricator.rb +++ b/qa/qa/resource/api_fabricator.rb @@ -1,13 +1,11 @@ # frozen_string_literal: true -require 'airborne' require 'active_support/core_ext/object/deep_dup' require 'capybara/dsl' module QA module Resource module ApiFabricator - include Airborne include Capybara::DSL HTTP_STATUS_OK = 200 @@ -43,6 +41,7 @@ module QA private + include Support::Api attr_writer :api_resource, :api_response def resource_web_url(resource) @@ -84,10 +83,6 @@ module QA end end - def parse_body(response) - JSON.parse(response.body, symbolize_names: true) - end - def process_api_response(parsed_response) self.api_response = parsed_response self.api_resource = transform_api_resource(parsed_response.deep_dup) diff --git a/qa/qa/resource/base.rb b/qa/qa/resource/base.rb index f3eefb70520..dcea144ab74 100644 --- a/qa/qa/resource/base.rb +++ b/qa/qa/resource/base.rb @@ -2,6 +2,7 @@ require 'forwardable' require 'capybara/dsl' +require 'active_support/core_ext/array/extract_options' module QA module Resource diff --git a/qa/qa/resource/file.rb b/qa/qa/resource/file.rb index effc5a7940b..57e82ac19ad 100644 --- a/qa/qa/resource/file.rb +++ b/qa/qa/resource/file.rb @@ -22,7 +22,7 @@ module QA def fabricate! project.visit! - Page::Project::Show.perform(&:create_new_file!) + Page::Project::Show.perform(&:create_first_new_file!) Page::File::Form.perform do |page| page.add_name(@name) diff --git a/qa/qa/resource/group.rb b/qa/qa/resource/group.rb index dce15e4f10b..a7a6f931e28 100644 --- a/qa/qa/resource/group.rb +++ b/qa/qa/resource/group.rb @@ -51,6 +51,10 @@ module QA "/groups/#{CGI.escape("#{sandbox.path}/#{path}")}" end + def api_members_path + "#{api_get_path}/members" + end + def api_post_path '/groups' end diff --git a/qa/qa/resource/merge_request.rb b/qa/qa/resource/merge_request.rb index 77afb3cfcba..45cb317e0eb 100644 --- a/qa/qa/resource/merge_request.rb +++ b/qa/qa/resource/merge_request.rb @@ -63,6 +63,7 @@ module QA page.fill_title(@title) page.fill_description(@description) page.choose_milestone(@milestone) if @milestone + page.assign_to_me if @assignee == 'me' labels.each do |label| page.select_label(label) end diff --git a/qa/qa/resource/project.rb b/qa/qa/resource/project.rb index 7fdf69278f9..1fafbf5d73e 100644 --- a/qa/qa/resource/project.rb +++ b/qa/qa/resource/project.rb @@ -14,15 +14,13 @@ module QA attribute :repository_ssh_location do Page::Project::Show.perform do |page| - page.choose_repository_clone_ssh - page.repository_location + page.repository_clone_ssh_location end end attribute :repository_http_location do Page::Project::Show.perform do |page| - page.choose_repository_clone_http - page.repository_location + page.repository_clone_http_location end end diff --git a/qa/qa/resource/repository/project_push.rb b/qa/qa/resource/repository/project_push.rb index c9fafe3419f..37feab4ad70 100644 --- a/qa/qa/resource/repository/project_push.rb +++ b/qa/qa/resource/repository/project_push.rb @@ -20,23 +20,11 @@ module QA end def repository_http_uri - @repository_http_uri ||= begin - project.visit! - Page::Project::Show.act do - choose_repository_clone_http - repository_location.uri - end - end + @repository_http_uri ||= project.repository_http_location.uri end def repository_ssh_uri - @repository_ssh_uri ||= begin - project.visit! - Page::Project::Show.act do - choose_repository_clone_ssh - repository_location.uri - end - end + @repository_ssh_uri ||= project.repository_ssh_location.uri end end end diff --git a/qa/qa/resource/user.rb b/qa/qa/resource/user.rb index 16f0b311fa9..c26f0c84a1f 100644 --- a/qa/qa/resource/user.rb +++ b/qa/qa/resource/user.rb @@ -6,7 +6,8 @@ module QA module Resource class User < Base attr_reader :unique_id - attr_writer :username, :password + attr_writer :username, :password, :name, :email + attr_accessor :provider, :extern_uid def initialize @unique_id = SecureRandom.hex(8) @@ -73,11 +74,31 @@ module QA username: username, name: name, skip_confirmation: true - } + }.merge(ldap_post_body) + end + + def self.fabricate_or_use(username, password) + if Runtime::Env.signup_disabled? + self.new.tap do |user| + user.username = username + user.password = password + end + else + self.fabricate! + end end private + def ldap_post_body + return {} unless extern_uid && provider + + { + extern_uid: extern_uid, + provider: provider + } + end + def fetch_id(username) users = parse_body(api_get_from("/users?username=#{username}")) diff --git a/qa/qa/runtime/browser.rb b/qa/qa/runtime/browser.rb index 9aaf57e8d83..b706d6565d2 100644 --- a/qa/qa/runtime/browser.rb +++ b/qa/qa/runtime/browser.rb @@ -70,6 +70,13 @@ module QA options.add_argument("disable-gpu") end + # Use the same profile on QA runs if CHROME_REUSE_PROFILE is true. + # Useful to speed up local QA. + if QA::Runtime::Env.reuse_chrome_profile? + qa_profile_dir = ::File.expand_path('../../tmp/qa-profile', __dir__) + options.add_argument("user-data-dir=#{qa_profile_dir}") + end + # Disable /dev/shm use in CI. See https://gitlab.com/gitlab-org/gitlab-ee/issues/4252 options.add_argument("disable-dev-shm-usage") if QA::Runtime::Env.running_in_ci? @@ -117,6 +124,15 @@ module QA def perform(&block) visit(url) + if QA::Runtime::Env.qa_cookies + browser = Capybara.current_session.driver.browser + QA::Runtime::Env.qa_cookies.each do |cookie| + name, value = cookie.split("=") + value ||= "" + browser.manage.add_cookie name: name, value: value + end + end + yield.tap { clear! } if block_given? end diff --git a/qa/qa/runtime/env.rb b/qa/qa/runtime/env.rb index c4500f9be90..dae5aa3f794 100644 --- a/qa/qa/runtime/env.rb +++ b/qa/qa/runtime/env.rb @@ -5,7 +5,7 @@ module QA module Env extend self - attr_writer :personal_access_token + attr_writer :personal_access_token, :ldap_username, :ldap_password # The environment variables used to indicate if the environment under test # supports the given feature @@ -30,6 +30,11 @@ module QA enabled?(ENV['CHROME_HEADLESS']) end + # set to 'true' to have Chrome use a fixed profile directory + def reuse_chrome_profile? + enabled?(ENV['CHROME_REUSE_PROFILE'], default: false) + end + def accept_insecure_certs? enabled?(ENV['ACCEPT_INSECURE_CERTS']) end @@ -38,6 +43,10 @@ module QA ENV['CI'] || ENV['CI_SERVER'] end + def qa_cookies + ENV['QA_COOKIES'] && ENV['QA_COOKIES'].split(';') + end + def signup_disabled? enabled?(ENV['SIGNUP_DISABLED'], default: false) end @@ -75,18 +84,42 @@ module QA ENV['GITLAB_FORKER_PASSWORD'] end + def gitlab_qa_username_1 + ENV['GITLAB_QA_USERNAME_1'] || 'gitlab-qa-user1' + end + + def gitlab_qa_password_1 + ENV['GITLAB_QA_PASSWORD_1'] + end + + def gitlab_qa_username_2 + ENV['GITLAB_QA_USERNAME_2'] || 'gitlab-qa-user2' + end + + def gitlab_qa_password_2 + ENV['GITLAB_QA_PASSWORD_2'] + end + def ldap_username - ENV['GITLAB_LDAP_USERNAME'] + @ldap_username ||= ENV['GITLAB_LDAP_USERNAME'] end def ldap_password - ENV['GITLAB_LDAP_PASSWORD'] + @ldap_password ||= ENV['GITLAB_LDAP_PASSWORD'] end def sandbox_name ENV['GITLAB_SANDBOX_NAME'] end + def namespace_name + ENV['GITLAB_NAMESPACE_NAME'] + end + + def auto_devops_project_name + ENV['GITLAB_AUTO_DEVOPS_PROJECT_NAME'] + end + def gcloud_account_key ENV.fetch("GCLOUD_ACCOUNT_KEY") end diff --git a/qa/qa/runtime/namespace.rb b/qa/qa/runtime/namespace.rb index f1c8ef11f94..704c65467e0 100644 --- a/qa/qa/runtime/namespace.rb +++ b/qa/qa/runtime/namespace.rb @@ -8,7 +8,7 @@ module QA end def name - "qa-test-#{time.strftime('%Y-%m-%d-%H-%M-%S')}" + Runtime::Env.namespace_name || "qa-test-#{time.strftime('%Y-%m-%d-%H-%M-%S')}" end def path diff --git a/qa/qa/specs/features/browser_ui/1_manage/login/log_in_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/login/log_in_spec.rb index dae2a9e0236..8f24a27b26f 100644 --- a/qa/qa/specs/features/browser_ui/1_manage/login/log_in_spec.rb +++ b/qa/qa/specs/features/browser_ui/1_manage/login/log_in_spec.rb @@ -1,7 +1,7 @@ module QA context 'Manage', :smoke do describe 'basic user login' do - it 'user logs in using basic credentials' do + it 'user logs in using basic credentials and logs out' do Runtime::Browser.visit(:gitlab, Page::Main::Login) Page::Main::Login.act { sign_in_using_credentials } @@ -11,6 +11,15 @@ module QA Page::Main::Menu.perform do |menu| expect(menu).to have_personal_area end + + Page::Main::Menu.perform do |menu| + menu.sign_out + expect(menu).not_to have_personal_area + end + + Page::Main::Login.perform do |form| + expect(form.sign_in_tab?).to be(true) + end end end end diff --git a/qa/qa/specs/features/browser_ui/1_manage/project/add_project_member_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/project/add_project_member_spec.rb index bef89d5be24..4070a225260 100644 --- a/qa/qa/specs/features/browser_ui/1_manage/project/add_project_member_spec.rb +++ b/qa/qa/specs/features/browser_ui/1_manage/project/add_project_member_spec.rb @@ -7,7 +7,7 @@ module QA Runtime::Browser.visit(:gitlab, Page::Main::Login) Page::Main::Login.perform(&:sign_in_using_credentials) - user = Resource::User.fabricate! + user = Resource::User.fabricate_or_use(Runtime::Env.gitlab_qa_username_1, Runtime::Env.gitlab_qa_password_1) project = Resource::Project.fabricate! do |resource| resource.name = 'add-member-project' diff --git a/qa/qa/specs/features/browser_ui/1_manage/project/view_project_activity_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/project/view_project_activity_spec.rb index 275de3d332c..d4cedc9362d 100644 --- a/qa/qa/specs/features/browser_ui/1_manage/project/view_project_activity_spec.rb +++ b/qa/qa/specs/features/browser_ui/1_manage/project/view_project_activity_spec.rb @@ -5,17 +5,17 @@ module QA describe 'Project activity' do it 'user creates an event in the activity page upon Git push' do Runtime::Browser.visit(:gitlab, Page::Main::Login) - Page::Main::Login.act { sign_in_using_credentials } + Page::Main::Login.perform(&:sign_in_using_credentials) - Resource::Repository::ProjectPush.fabricate! do |push| + project_push = Resource::Repository::ProjectPush.fabricate! do |push| push.file_name = 'README.md' push.file_content = '# This is a test project' push.commit_message = 'Add README.md' end + project_push.project.visit! - Page::Project::Menu.act { go_to_activity } - - Page::Project::Activity.act { go_to_push_events } + Page::Project::Menu.perform(&:go_to_activity) + Page::Project::Activity.perform(&:go_to_push_events) expect(page).to have_content('pushed new branch master') end 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 d33947f41da..6ddd7dde2cf 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 @@ -4,6 +4,8 @@ module QA context 'Create' do describe 'Merge request creation' do it 'user creates a new merge request' do + gitlab_account_username = "@#{Runtime::User.username}" + Runtime::Browser.visit(:gitlab, Page::Main::Login) Page::Main::Login.act { sign_in_using_credentials } @@ -27,6 +29,7 @@ module QA merge_request.description = 'Great feature with milestone' merge_request.project = current_project merge_request.milestone = current_milestone + merge_request.assignee = 'me' merge_request.labels.push(new_label) end @@ -34,6 +37,7 @@ module QA expect(merge_request).to have_content('This is a merge request with a milestone') expect(merge_request).to have_content('Great feature with milestone') expect(merge_request).to have_content(/Opened [\w\s]+ ago/) + expect(merge_request).to have_assignee(gitlab_account_username) expect(merge_request).to have_label(new_label.title) end 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 6ff7360c413..4126f967ee2 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 @@ -5,7 +5,7 @@ module QA describe 'Merge request squashing' do it 'user squashes commits while merging' do Runtime::Browser.visit(:gitlab, Page::Main::Login) - Page::Main::Login.act { sign_in_using_credentials } + Page::Main::Login.perform(&:sign_in_using_credentials) project = Resource::Project.fabricate! do |project| project.name = "squash-before-merge" @@ -38,13 +38,12 @@ module QA Git::Repository.perform do |repository| repository.uri = Page::Project::Show.act do - choose_repository_clone_http - repository_location.uri + repository_clone_http_location.uri end repository.use_default_credentials - repository.act { clone } + repository.clone expect(repository.commits.size).to eq 3 end 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 297485dd81e..de5c535c757 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 @@ -7,7 +7,7 @@ module QA def login Runtime::Browser.visit(:gitlab, Page::Main::Login) - Page::Main::Login.act { sign_in_using_credentials } + Page::Main::Login.perform(&:sign_in_using_credentials) end before(:all) do @@ -18,7 +18,15 @@ module QA project.description = 'Add file templates via the Files view' end - Page::Main::Menu.act { sign_out } + # There's no 'New File' dropdown when the project is blank, so we first + # add a dummy file so that the dropdown will appear + Resource::File.fabricate! do |file| + file.project = @project + file.name = 'README.md' + file.content = '# Readme' + end + + Page::Main::Menu.perform(&:sign_out) end templates = [ @@ -55,7 +63,7 @@ module QA login @project.visit! - Page::Project::Show.act { create_new_file! } + Page::Project::Show.perform(&:create_new_file!) Page::File::Form.perform do |page| page.select_template template[:file_name], template[:name] end diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/add_ssh_key_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/add_ssh_key_spec.rb index 94be66782c6..ff879fdeb16 100644 --- a/qa/qa/specs/features/browser_ui/3_create/repository/add_ssh_key_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/repository/add_ssh_key_spec.rb @@ -5,7 +5,7 @@ module QA describe 'SSH keys support' do let(:key_title) { "key for ssh tests #{Time.now.to_f}" } - it 'user adds and then removes an SSH key' do + it 'user adds and then removes an SSH key', :smoke do Runtime::Browser.visit(:gitlab, Page::Main::Login) Page::Main::Login.act { sign_in_using_credentials } diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/clone_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/clone_spec.rb index 6a0add56fe0..571cae4a3c5 100644 --- a/qa/qa/specs/features/browser_ui/3_create/repository/clone_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/repository/clone_spec.rb @@ -4,15 +4,12 @@ module QA context 'Create' do describe 'Git clone over HTTP', :ldap_no_tls do let(:location) do - Page::Project::Show.act do - choose_repository_clone_http - repository_location - end + Page::Project::Show.perform(&:repository_clone_http_location).uri end 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) project = Resource::Project.fabricate! do |scenario| scenario.name = 'project-with-code' @@ -21,7 +18,7 @@ module QA project.visit! Git::Repository.perform do |repository| - repository.uri = location.uri + repository.uri = location repository.use_default_credentials repository.act do @@ -32,14 +29,15 @@ module QA push_changes end end + Page::Project::Show.perform(&:wait_for_push) end it 'user performs a deep clone' do Git::Repository.perform do |repository| - repository.uri = location.uri + repository.uri = location repository.use_default_credentials - repository.act { clone } + repository.clone expect(repository.commits.size).to eq 2 end @@ -47,10 +45,10 @@ module QA it 'user performs a shallow clone' do Git::Repository.perform do |repository| - repository.uri = location.uri + repository.uri = location repository.use_default_credentials - repository.act { shallow_clone } + repository.shallow_clone expect(repository.commits.size).to eq 1 expect(repository.commits.first).to include 'Add Readme' diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/push_mirroring_over_http_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/push_mirroring_over_http_spec.rb new file mode 100644 index 00000000000..2d0e281ab59 --- /dev/null +++ b/qa/qa/specs/features/browser_ui/3_create/repository/push_mirroring_over_http_spec.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +module QA + context 'Create' do + describe 'Push mirror a repository over HTTP' do + it 'configures and syncs a (push) mirrored repository' do + Runtime::Browser.visit(:gitlab, Page::Main::Login) + Page::Main::Login.perform(&:sign_in_using_credentials) + + target_project = Resource::Project.fabricate! do |project| + project.name = 'push-mirror-target-project' + end + target_project_uri = target_project.repository_http_location.uri + target_project_uri.user = Runtime::User.username + + source_project_push = Resource::Repository::ProjectPush.fabricate! do |push| + push.file_name = 'README.md' + push.file_content = '# This is a test project' + push.commit_message = 'Add README.md' + end + source_project_push.project.visit! + + Page::Project::Show.perform(&:wait_for_push) + + Page::Project::Menu.perform(&:click_repository_settings) + Page::Project::Settings::Repository.perform do |settings| + settings.expand_mirroring_repositories do |mirror_settings| + # Configure the source project to push to the target project + mirror_settings.repository_url = target_project_uri + mirror_settings.mirror_direction = :push + mirror_settings.authentication_method = :password + mirror_settings.password = Runtime::User.password + mirror_settings.mirror_repository + mirror_settings.update target_project_uri + end + end + + # Check that the target project has the commit from the source + target_project.visit! + expect(page).to have_content('README.md') + expect(page).to have_content('This is a test project') + end + end + end +end diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_spec.rb index 92f596a44d9..ad6426df420 100644 --- a/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_spec.rb @@ -7,12 +7,12 @@ module QA Runtime::Browser.visit(:gitlab, Page::Main::Login) Page::Main::Login.act { sign_in_using_credentials } - Resource::Repository::ProjectPush.fabricate! do |push| + project_push = Resource::Repository::ProjectPush.fabricate! do |push| push.file_name = 'README.md' push.file_content = '# This is a test project' push.commit_message = 'Add README.md' end - + project_push.project.visit! Page::Project::Show.act { wait_for_push } expect(page).to have_content('README.md') diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/use_ssh_key_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/use_ssh_key_spec.rb index 9c764424129..509a639c130 100644 --- a/qa/qa/specs/features/browser_ui/3_create/repository/use_ssh_key_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/repository/use_ssh_key_spec.rb @@ -16,13 +16,14 @@ module QA resource.title = key_title end - Resource::Repository::ProjectPush.fabricate! do |push| + project_push = Resource::Repository::ProjectPush.fabricate! do |push| push.ssh_key = key push.file_name = 'README.md' push.file_content = '# Test Use SSH Key' push.commit_message = 'Add README.md' end + project_push.project.visit! Page::Project::Show.act { wait_for_push } expect(page).to have_content('README.md') 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 e7374377104..f176ec31abd 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 @@ -7,7 +7,7 @@ module QA def login Runtime::Browser.visit(:gitlab, Page::Main::Login) - Page::Main::Login.act { sign_in_using_credentials } + Page::Main::Login.perform(&:sign_in_using_credentials) end before(:all) do @@ -21,14 +21,14 @@ module QA # Add a file via the regular Files view because the Web IDE isn't # available unless there is a file present - Page::Project::Show.act { create_new_file! } + Page::Project::Show.perform(&:create_first_new_file!) Page::File::Form.perform do |page| page.add_name('dummy') page.add_content('Enable the Web IDE') page.commit_changes end - Page::Main::Menu.act { sign_out } + Page::Main::Menu.perform(&:sign_out) end templates = [ @@ -65,7 +65,7 @@ module QA login @project.visit! - Page::Project::Show.act { open_web_ide! } + Page::Project::Show.perform(&:open_web_ide!) Page::Project::WebIDE::Edit.perform do |page| page.create_new_file_from_template template[:file_name], template[:name] @@ -75,9 +75,7 @@ module QA expect(page).to have_button('Undo') expect(page).to have_content(content[0..100]) - Page::Project::WebIDE::Edit.perform do |page| - page.commit_changes - end + Page::Project::WebIDE::Edit.perform(&:commit_changes) expect(page).to have_content(template[:file_name]) expect(page).to have_content(content[0..100]) diff --git a/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb b/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb index 30ec0665973..b0c277a48c3 100644 --- a/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb +++ b/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb @@ -16,7 +16,7 @@ module QA Page::Main::Login.act { sign_in_using_credentials } project = Resource::Project.fabricate! do |p| - p.name = 'project-with-autodevops' + p.name = Runtime::Env.auto_devops_project_name || 'project-with-autodevops' p.description = 'Project with Auto Devops' end diff --git a/qa/qa/support/api.rb b/qa/qa/support/api.rb new file mode 100644 index 00000000000..1107d43161e --- /dev/null +++ b/qa/qa/support/api.rb @@ -0,0 +1,28 @@ +module QA + module Support + module Api + def post(url, payload) + RestClient::Request.execute( + method: :post, + url: url, + payload: payload, + verify_ssl: false) + rescue RestClient::ExceptionWithResponse => e + e.response + end + + def get(url) + RestClient::Request.execute( + method: :get, + url: url, + verify_ssl: false) + rescue RestClient::ExceptionWithResponse => e + e.response + end + + def parse_body(response) + JSON.parse(response.body, symbolize_names: true) + end + end + end +end diff --git a/qa/qa/support/page/logging.rb b/qa/qa/support/page/logging.rb index cf5cd3a79f8..43bc16d8c9a 100644 --- a/qa/qa/support/page/logging.rb +++ b/qa/qa/support/page/logging.rb @@ -37,8 +37,8 @@ module QA exists end - def find_element(name) - log("finding :#{name}") + def find_element(name, wait: Capybara.default_max_wait_time) + log("finding :#{name} (wait: #{wait})") element = super @@ -71,6 +71,12 @@ module QA super end + def select_element(name, value) + log(%Q(selecting "#{value}" in :#{name})) + + super + end + def has_element?(name) found = super @@ -89,6 +95,16 @@ module QA element end + def within_element_by_index(name, index) + log("within elements :#{name} at index #{index}") + + element = super + + log("end within elements :#{name} at index #{index}") + + element + end + private def log(msg) |