diff options
Diffstat (limited to 'qa')
92 files changed, 1974 insertions, 667 deletions
diff --git a/qa/Dockerfile b/qa/Dockerfile index 81f207cbab9..ad4bffb3bf2 100644 --- a/qa/Dockerfile +++ b/qa/Dockerfile @@ -3,10 +3,6 @@ LABEL maintainer="GitLab Quality Department <quality@gitlab.com>" ENV DEBIAN_FRONTEND="noninteractive" ENV DOCKER_VERSION="17.09.0-ce" -ENV CHROME_VERSION="87.0.4280.141-1" -ENV CHROME_DRIVER_VERSION="87.0.4280.88" -ENV CHROME_DEB="google-chrome-stable_${CHROME_VERSION}_amd64.deb" -ENV CHROME_URL="https://s3.amazonaws.com/gitlab-google-chrome-stable/${CHROME_DEB}" ## # Update APT sources and install dependencies @@ -22,22 +18,6 @@ RUN wget -q "https://download.docker.com/linux/static/stable/x86_64/docker-${DOC rm "docker-${DOCKER_VERSION}.tgz" ## -# Install Google Chrome version with headless support -# Download from our local S3 bucket, populated by https://gitlab.com/gitlab-org/gitlab-build-images/-/blob/master/scripts/cache-google-chrome -# -RUN curl --silent --show-error --fail -O "${CHROME_URL}" && \ - dpkg -i "./${CHROME_DEB}" || true && \ - apt-get install -f -y && \ - rm -f "./${CHROME_DEB}" - -## -# Install chromedriver to make it work with Selenium -# -RUN wget -q "https://chromedriver.storage.googleapis.com/${CHROME_DRIVER_VERSION}/chromedriver_linux64.zip" -RUN unzip chromedriver_linux64.zip -d /usr/local/bin -RUN rm -f chromedriver_linux64.zip - -## # Install client certificate - Bug in Chrome Headless: https://gitlab.com/gitlab-org/gitlab/-/issues/331492 # # RUN apt install -y libnss3-tools @@ -66,8 +46,32 @@ RUN export CLOUD_SDK_REPO="cloud-sdk-$(lsb_release -c -s)" && \ curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add - && \ apt-get update -y && apt-get install google-cloud-sdk kubectl -y +## +# Install Google Chrome version with headless support +# Download from our local S3 bucket, populated by https://gitlab.com/gitlab-org/gitlab-build-images/-/blob/master/scripts/cache-google-chrome +# +# https://s3.amazonaws.com/gitlab-google-chrome-stable +ENV CHROME_VERSION="91.0.4472.77-1" +ENV CHROME_DEB="google-chrome-stable_${CHROME_VERSION}_amd64.deb" +ENV CHROME_URL="https://s3.amazonaws.com/gitlab-google-chrome-stable/${CHROME_DEB}" +RUN curl --silent --show-error --fail -O "${CHROME_URL}" && \ + dpkg -i "./${CHROME_DEB}" || true && \ + apt-get install -f -y && \ + rm -f "./${CHROME_DEB}" + WORKDIR /home/gitlab/qa + +# Install qa dependencies or fetch from cache if unchanged COPY ./qa/Gemfile* /home/gitlab/qa/ +RUN bundle install --jobs=$(nproc) --retry=3 --without=development --quiet + +## +# Fetch chromedriver based on version of chrome +# Copy rakefile first so that webdriver is not reinstalled on every code change +# https://github.com/titusfortner/webdrivers +COPY ./qa/tasks/webdrivers.rake /home/gitlab/qa/tasks/ +RUN bundle exec rake -f tasks/webdrivers.rake webdrivers:chromedriver:update + COPY ./config/initializers/0_inject_enterprise_edition_module.rb /home/gitlab/config/initializers/ # Copy VERSION to ensure the COPY succeeds to copy at least one file since ee/app/models/license.rb isn't present in FOSS # The [b] part makes ./ee/app/models/license.r[b] a pattern that is allowed to return no files (which is the case in FOSS) @@ -75,7 +79,7 @@ COPY VERSION ./ee/app/models/license.r[b] /home/gitlab/ee/app/models/ COPY ./lib/gitlab.rb /home/gitlab/lib/ COPY ./lib/gitlab/utils.rb /home/gitlab/lib/gitlab/ COPY ./INSTALLATION_TYPE ./VERSION /home/gitlab/ -RUN cd /home/gitlab/qa/ && bundle install --jobs=$(nproc) --retry=3 --without=development --quiet + COPY ./qa /home/gitlab/qa ENTRYPOINT ["bin/test"] diff --git a/qa/Gemfile b/qa/Gemfile index 0cb53ca99dc..ff2074b6191 100644 --- a/qa/Gemfile +++ b/qa/Gemfile @@ -9,7 +9,7 @@ gem 'capybara', '~> 3.29.0' gem 'capybara-screenshot', '~> 1.0.23' gem 'rake', '~> 12.3.3' gem 'rspec', '~> 3.7' -gem 'selenium-webdriver', '~> 3.12' +gem 'selenium-webdriver', '~> 4.0.0.beta4' gem 'airborne', '~> 0.3.4' gem 'rest-client', '~> 2.1.0' gem 'nokogiri', '~> 1.11.1' @@ -22,9 +22,10 @@ gem 'rotp', '~> 3.1.0' gem 'timecop', '~> 0.9.1' gem 'parallel', '~> 1.19' gem 'rspec-parameterized', '~> 0.4.2' -gem 'github_api', '~> 0.18.2' +gem "octokit", "~> 4.21" +gem "webdrivers", "~> 4.6" -gem 'chemlab', '~> 0.5' +gem 'chemlab', '~> 0.7' gem 'chemlab-library-www-gitlab-com', '~> 0.1' group :development do diff --git a/qa/Gemfile.lock b/qa/Gemfile.lock index 8b6c4ca0d3e..47dd5ac118e 100644 --- a/qa/Gemfile.lock +++ b/qa/Gemfile.lock @@ -19,10 +19,10 @@ GEM rack-test (>= 1.1.0, < 2.0) rest-client (>= 2.0.2, < 3.0) rspec (~> 3.8) - allure-rspec (2.14.1) - allure-ruby-commons (= 2.14.1) + allure-rspec (2.14.2) + allure-ruby-commons (= 2.14.2) rspec-core (>= 3.8, < 4) - allure-ruby-commons (2.14.1) + allure-ruby-commons (2.14.2) mime-types (>= 3.3, < 4) oj (>= 3.10, < 4) require_all (>= 2, < 4) @@ -41,43 +41,51 @@ GEM capybara-screenshot (1.0.23) capybara (>= 1.0, < 4) launchy - chemlab (0.5.0) - rake (~> 12.3.0) - selenium-webdriver (~> 3.12) - watir (~> 6.17) + chemlab (0.7.2) + colorize (~> 0.8) + i18n (~> 1.8) + rake (>= 12, < 14) + selenium-webdriver (>= 3, < 5) + watir (>= 6, < 8) chemlab-library-www-gitlab-com (0.1.1) chemlab (~> 0.4) - childprocess (3.0.0) + childprocess (4.1.0) coderay (1.1.2) + colorize (0.8.1) concord (0.1.5) adamantium (~> 0.2.0) equalizer (~> 0.0.9) - concurrent-ruby (1.1.8) - descendants_tracker (0.0.4) - thread_safe (~> 0.3, >= 0.3.1) + concurrent-ruby (1.1.9) diff-lcs (1.3) domain_name (0.5.20190701) unf (>= 0.0.5, < 1.0.0) equalizer (0.0.11) faker (1.9.3) i18n (>= 0.7) - faraday (0.17.3) + faraday (1.5.1) + faraday-em_http (~> 1.0) + faraday-em_synchrony (~> 1.0) + faraday-excon (~> 1.1) + faraday-httpclient (~> 1.0.1) + faraday-net_http (~> 1.0) + faraday-net_http_persistent (~> 1.1) + faraday-patron (~> 1.0) multipart-post (>= 1.2, < 3) - github_api (0.18.2) - addressable (~> 2.4) - descendants_tracker (~> 0.0.4) - faraday (~> 0.8) - hashie (~> 3.5, >= 3.5.2) - oauth2 (~> 1.0) + ruby2_keywords (>= 0.0.4) + faraday-em_http (1.0.0) + faraday-em_synchrony (1.0.0) + faraday-excon (1.1.0) + faraday-httpclient (1.0.1) + faraday-net_http (1.0.1) + faraday-net_http_persistent (1.2.0) + faraday-patron (1.0.0) gitlab-qa (4.0.0) - hashie (3.6.0) http-accept (1.7.0) http-cookie (1.0.3) domain_name (~> 0.5) i18n (1.8.10) concurrent-ruby (~> 1.0) ice_nine (0.11.2) - jwt (2.2.2) knapsack (1.17.1) rake launchy (2.4.3) @@ -89,24 +97,19 @@ GEM method_source (0.9.0) mime-types (3.3.1) mime-types-data (~> 3.2015) - mime-types-data (3.2020.0425) + mime-types-data (3.2021.0704) mini_mime (1.0.2) mini_portile2 (2.5.0) minitest (5.14.4) - multi_json (1.15.0) - multi_xml (0.6.0) multipart-post (2.1.1) netrc (0.11.0) nokogiri (1.11.1) mini_portile2 (~> 2.5.0) racc (~> 1.4) - oauth2 (1.4.4) - faraday (>= 0.8, < 2.0) - jwt (>= 1.0, < 3.0) - multi_json (~> 1.3) - multi_xml (~> 0.5) - rack (>= 1.2, < 3) - oj (3.11.5) + octokit (4.21.0) + faraday (>= 0.9) + sawyer (~> 0.8.0, >= 0.5.3) + oj (3.12.1) parallel (1.19.2) parallel_tests (2.29.0) parallel @@ -129,19 +132,20 @@ GEM rack-test (1.1.0) rack (>= 1.0, < 3) rake (12.3.3) - regexp_parser (1.6.0) + regexp_parser (1.8.2) require_all (3.0.0) rest-client (2.1.0) http-accept (>= 1.7.0, < 2.0) http-cookie (>= 1.0.2, < 2.0) mime-types (>= 1.16, < 4.0) netrc (~> 0.8) + rexml (3.2.5) rotp (3.1.0) rspec (3.9.0) rspec-core (~> 3.9.0) rspec-expectations (~> 3.9.0) rspec-mocks (~> 3.9.0) - rspec-core (3.9.2) + rspec-core (3.9.3) rspec-support (~> 3.9.3) rspec-expectations (3.9.1) diff-lcs (>= 1.2.0, < 2.0) @@ -157,14 +161,19 @@ GEM unparser rspec-retry (0.6.1) rspec-core (> 3.3) - rspec-support (3.9.3) + rspec-support (3.9.4) rspec_junit_formatter (0.4.1) rspec-core (>= 2, < 4, != 2.12.0) ruby-debug-ide (0.7.2) rake (>= 0.8.1) - rubyzip (1.3.0) - selenium-webdriver (3.142.6) - childprocess (>= 0.5, < 4.0) + ruby2_keywords (0.0.4) + rubyzip (2.3.2) + sawyer (0.8.2) + addressable (>= 2.3.5) + faraday (> 0.8, < 2.0) + selenium-webdriver (4.0.0.beta4) + childprocess (>= 0.5, < 5.0) + rexml (~> 3.2) rubyzip (>= 1.2.2) systemu (2.6.5) thread_safe (0.3.6) @@ -184,9 +193,13 @@ GEM procto (~> 0.0.2) uuid (2.3.9) macaddr (~> 1.0) - watir (6.18.0) + watir (6.19.1) regexp_parser (>= 1.2, < 3) - selenium-webdriver (>= 3.8) + selenium-webdriver (>= 3.142.7) + webdrivers (4.6.0) + nokogiri (~> 1.6) + rubyzip (>= 1.3.0) + selenium-webdriver (>= 3.0, < 4.0) xpath (3.2.0) nokogiri (~> 1.8) zeitwerk (2.4.2) @@ -200,13 +213,13 @@ DEPENDENCIES allure-rspec (~> 2.14.1) capybara (~> 3.29.0) capybara-screenshot (~> 1.0.23) - chemlab (~> 0.5) + chemlab (~> 0.7) chemlab-library-www-gitlab-com (~> 0.1) faker (~> 1.6, >= 1.6.6) - github_api (~> 0.18.2) gitlab-qa knapsack (~> 1.17) nokogiri (~> 1.11.1) + octokit (~> 4.21) parallel (~> 1.19) parallel_tests (~> 2.29) pry-byebug (~> 3.5.1) @@ -218,8 +231,9 @@ DEPENDENCIES rspec-retry (~> 0.6.1) rspec_junit_formatter (~> 0.4.1) ruby-debug-ide (~> 0.7.0) - selenium-webdriver (~> 3.12) + selenium-webdriver (~> 4.0.0.beta4) timecop (~> 0.9.1) + webdrivers (~> 4.6) BUNDLED WITH - 2.1.4 + 2.2.22 diff --git a/qa/Rakefile b/qa/Rakefile index 0a040f0aee7..f24c81a9ec2 100644 --- a/qa/Rakefile +++ b/qa/Rakefile @@ -1,4 +1,7 @@ # frozen_string_literal: true +# rubocop:disable Rails/RakeEnvironment + +load 'tasks/webdrivers.rake' require_relative 'qa/tools/revoke_all_personal_access_tokens' require_relative 'qa/tools/delete_subgroups' @@ -34,7 +37,9 @@ task :run_artillery_load_tests do urls_file = ENV['URLS_FILE_PATH'] || 'urls.yml' unless File.exist?(urls_file) - raise "\n#{urls_file} file is missing. Please provide correct URLS_FILE_PATH or all of HOST_URL, LARGE_ISSUE_URL and LARGE_MR_URL\n\n" + raise(<<~ERR) + #{urls_file} file is missing. Please provide correct URLS_FILE_PATH or all of HOST_URL, LARGE_ISSUE_URL and LARGE_MR_URL\n + ERR end urls = YAML.safe_load(File.read(urls_file)) @@ -59,3 +64,4 @@ desc "Deletes projects directly under the provided group" task :delete_projects do QA::Tools::DeleteProjects.new.run end +# rubocop:enable Rails/RakeEnvironment @@ -79,6 +79,7 @@ module QA autoload :GroupLabel, 'qa/resource/group_label' autoload :MergeRequest, 'qa/resource/merge_request' autoload :ProjectImportedFromGithub, 'qa/resource/project_imported_from_github' + autoload :ProjectImportedFromURL, 'qa/resource/project_imported_from_url' autoload :MergeRequestFromFork, 'qa/resource/merge_request_from_fork' autoload :DeployKey, 'qa/resource/deploy_key' autoload :DeployToken, 'qa/resource/deploy_token' @@ -87,6 +88,7 @@ module QA autoload :CiVariable, 'qa/resource/ci_variable' autoload :Runner, 'qa/resource/runner' autoload :PersonalAccessToken, 'qa/resource/personal_access_token' + autoload :PersonalAccessTokenCache, 'qa/resource/personal_access_token_cache' autoload :ProjectAccessToken, 'qa/resource/project_access_token' autoload :User, 'qa/resource/user' autoload :ProjectMilestone, 'qa/resource/project_milestone' @@ -106,6 +108,7 @@ module QA autoload :RegistryRepository, 'qa/resource/registry_repository' autoload :Package, 'qa/resource/package' autoload :PipelineSchedules, 'qa/resource/pipeline_schedules' + autoload :ImportProject, 'qa/resource/import_project' module KubernetesCluster autoload :Base, 'qa/resource/kubernetes_cluster/base' @@ -169,6 +172,7 @@ module QA autoload :ObjectStorage, 'qa/scenario/test/integration/object_storage' autoload :SMTP, 'qa/scenario/test/integration/smtp' autoload :SSHTunnel, 'qa/scenario/test/integration/ssh_tunnel' + autoload :Registry, 'qa/scenario/test/integration/registry' end module Sanity @@ -287,6 +291,7 @@ module QA module Import autoload :Github, 'qa/page/project/import/github' + autoload :RepoByURL, 'qa/page/project/import/repo_by_url' end module Pipeline @@ -295,6 +300,10 @@ module QA autoload :New, 'qa/page/project/pipeline/new' end + module PipelineEditor + autoload :Show, 'qa/page/project/pipeline_editor/show' + end + module Tag autoload :Index, 'qa/page/project/tag/index' autoload :New, 'qa/page/project/tag/new' @@ -330,6 +339,7 @@ module QA autoload :MergeRequest, 'qa/page/project/settings/merge_request' autoload :MirroringRepositories, 'qa/page/project/settings/mirroring_repositories' autoload :ProtectedTags, 'qa/page/project/settings/protected_tags' + autoload :DefaultBranch, 'qa/page/project/settings/default_branch' autoload :VisibilityFeaturesPermissions, 'qa/page/project/settings/visibility_features_permissions' autoload :AccessTokens, 'qa/page/project/settings/access_tokens' @@ -529,6 +539,11 @@ module QA autoload :CommitModal, 'qa/page/component/commit_modal' autoload :VisibilitySetting, 'qa/page/component/visibility_setting' + module Import + autoload :Gitlab, 'qa/page/component/import/gitlab' + autoload :Selection, 'qa/page/component/import/selection' + end + module Issuable autoload :Common, 'qa/page/component/issuable/common' autoload :Sidebar, 'qa/page/component/issuable/sidebar' @@ -585,6 +600,7 @@ module QA autoload :Minikube, 'qa/service/cluster_provider/minikube' autoload :K3d, 'qa/service/cluster_provider/k3d' autoload :K3s, 'qa/service/cluster_provider/k3s' + autoload :K3sCilium, 'qa/service/cluster_provider/k3s_cilium' end module DockerRun diff --git a/qa/qa/fixtures/export.tar.gz b/qa/qa/fixtures/export.tar.gz Binary files differnew file mode 100644 index 00000000000..08e4f0c9c43 --- /dev/null +++ b/qa/qa/fixtures/export.tar.gz diff --git a/qa/qa/page/admin/overview/users/show.rb b/qa/qa/page/admin/overview/users/show.rb index f455bd31d14..be73f3d80bf 100644 --- a/qa/qa/page/admin/overview/users/show.rb +++ b/qa/qa/page/admin/overview/users/show.rb @@ -11,12 +11,25 @@ module QA end view 'app/views/admin/users/show.html.haml' do - element :confirm_user_button element :user_id_content end - view 'app/views/admin/users/_approve_user.html.haml' do + view 'app/assets/javascripts/admin/users/components/actions/approve.vue' do element :approve_user_button + element :approve_user_confirm_button + end + + view 'app/assets/javascripts/admin/users/components/user_actions.vue' do + element :user_actions_dropdown_toggle + end + + view 'app/helpers/users_helper.rb' do + element :confirm_user_button + element :confirm_user_confirm_button + end + + def open_user_actions_dropdown(user) + click_element(:user_actions_dropdown_toggle, username: user.username) end def click_impersonate_user @@ -28,15 +41,14 @@ module QA end def confirm_user - accept_confirm do - click_element :confirm_user_button - end + click_element :confirm_user_button + click_element :confirm_user_confirm_button end - def approve_user - accept_confirm do - click_element :approve_user_button - end + def approve_user(user) + open_user_actions_dropdown(user) + click_element :approve_user_button + click_element :approve_user_confirm_button end end end diff --git a/qa/qa/page/base.rb b/qa/qa/page/base.rb index 66040bb9849..6b54d8ab1ac 100644 --- a/qa/qa/page/base.rb +++ b/qa/qa/page/base.rb @@ -104,7 +104,8 @@ module QA end def find_element(name, **kwargs) - wait_for_requests + skip_finished_loading_check = kwargs.delete(:skip_finished_loading_check) + wait_for_requests(skip_finished_loading_check: skip_finished_loading_check) element_selector = element_selector_css(name, reject_capybara_query_keywords(kwargs)) find(element_selector, only_capybara_query_keywords(kwargs)) @@ -196,7 +197,8 @@ module QA # replace with (..., page = self.class) def click_element(name, page = nil, **kwargs) - wait_for_requests + skip_finished_loading_check = kwargs.delete(:skip_finished_loading_check) + wait_for_requests(skip_finished_loading_check: skip_finished_loading_check) wait = kwargs.delete(:wait) || Capybara.default_max_wait_time text = kwargs.delete(:text) diff --git a/qa/qa/page/component/import/gitlab.rb b/qa/qa/page/component/import/gitlab.rb new file mode 100644 index 00000000000..2fd2a45b399 --- /dev/null +++ b/qa/qa/page/component/import/gitlab.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +module QA + module Page + module Component + module Import + module Gitlab + def self.included(base) + super + + base.view 'app/views/import/gitlab_projects/new.html.haml' do + element :import_project_button + end + + base.view 'app/views/import/shared/_new_project_form.html.haml' do + element :project_name_field + element :project_slug_field + end + end + + def set_imported_project_name(name) + fill_element(:project_name_field, name) + end + + def attach_exported_file(path) + page.attach_file("file", path, make_visible: { display: 'block' }) + end + + def click_import_gitlab_project + click_element(:import_project_button) + + wait_until(reload: false) do + has_notice?("The project was successfully imported.") + end + end + end + end + end + end +end diff --git a/qa/qa/page/component/import/selection.rb b/qa/qa/page/component/import/selection.rb new file mode 100644 index 00000000000..6cacdd84f13 --- /dev/null +++ b/qa/qa/page/component/import/selection.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module QA + module Page + module Component + module Import + module Selection + def self.included(base) + super + + base.view 'app/views/projects/_import_project_pane.html.haml' do + element :gitlab_import_button + end + end + + def click_gitlab + click_element(:gitlab_import_button) + end + end + end + end + end +end diff --git a/qa/qa/page/component/invite_members_modal.rb b/qa/qa/page/component/invite_members_modal.rb index 9883ef22029..7cec4588af5 100644 --- a/qa/qa/page/component/invite_members_modal.rb +++ b/qa/qa/page/component/invite_members_modal.rb @@ -19,6 +19,10 @@ module QA element :group_select_dropdown_search_field end + base.view 'app/assets/javascripts/invite_members/components/members_token_select.vue' do + element :members_token_select_input + end + base.view 'app/assets/javascripts/invite_members/components/invite_group_trigger.vue' do element :invite_a_group_button end @@ -42,7 +46,7 @@ module QA within_element(:invite_members_modal_content) do fill_element :access_level_dropdown, with: access_level - fill_in 'Select members or type email addresses', with: username + fill_element :members_token_select_input, username Support::WaitForRequests.wait_for_requests diff --git a/qa/qa/page/component/issuable/sidebar.rb b/qa/qa/page/component/issuable/sidebar.rb index 3a9d316c321..971e7634f6d 100644 --- a/qa/qa/page/component/issuable/sidebar.rb +++ b/qa/qa/page/component/issuable/sidebar.rb @@ -40,16 +40,22 @@ module QA base.view 'app/views/shared/issuable/_sidebar.html.haml' do element :assignee_block - element :edit_milestone_link element :milestone_block - element :milestone_link + end + + base.view 'app/assets/javascripts/sidebar/components/sidebar_dropdown_widget.vue' do + element :milestone_link, 'data-qa-selector="`${issuableAttribute}_link`"' # rubocop:disable QA/ElementWithPattern + end + + base.view 'app/assets/javascripts/sidebar/components/sidebar_editable_item.vue' do + element :edit_link end end def assign_milestone(milestone) - click_element(:edit_milestone_link) within_element(:milestone_block) do - click_link("#{milestone.title}") + click_element(:edit_link) + click_on(milestone.title) end wait_until(reload: false) do @@ -89,7 +95,7 @@ module QA def has_milestone?(milestone_title) wait_milestone_block_finish_loading do - has_element?(:milestone_link, title: milestone_title) + has_element?(:milestone_link, text: milestone_title) end end diff --git a/qa/qa/page/dashboard/snippet/edit.rb b/qa/qa/page/dashboard/snippet/edit.rb index cd8ac77ae04..939413f6d76 100644 --- a/qa/qa/page/dashboard/snippet/edit.rb +++ b/qa/qa/page/dashboard/snippet/edit.rb @@ -23,7 +23,8 @@ module QA end def add_to_file_content(content) - text_area.set content + text_area.click + text_area.send_keys(:home, content) # starts in the beginning of the line text_area.has_text?(content) # wait for changes to take effect end diff --git a/qa/qa/page/group/bulk_import.rb b/qa/qa/page/group/bulk_import.rb index 11741bdf4cb..a0511c9a16c 100644 --- a/qa/qa/page/group/bulk_import.rb +++ b/qa/qa/page/group/bulk_import.rb @@ -10,12 +10,15 @@ module QA view "app/assets/javascripts/import_entities/import_groups/components/import_table_row.vue" do element :import_item - element :target_namespace_selector_dropdown element :target_group_dropdown_item element :import_status_indicator element :import_group_button end + view "app/assets/javascripts/import_entities/components/group_dropdown.vue" do + element :target_namespace_selector_dropdown + end + # Import source group in to target group # # @param [String] source_group_name diff --git a/qa/qa/page/group/menu.rb b/qa/qa/page/group/menu.rb index 9a3b129b6d2..338a135614d 100644 --- a/qa/qa/page/group/menu.rb +++ b/qa/qa/page/group/menu.rb @@ -6,7 +6,7 @@ module QA class Menu < Page::Base include SubMenus::Common - view 'app/views/layouts/nav/sidebar/_group.html.haml' do + view 'app/views/layouts/nav/sidebar/_group_menus.html.haml' do element :general_settings_link element :group_issues_item element :group_members_item diff --git a/qa/qa/page/group/new.rb b/qa/qa/page/group/new.rb index 66afcf7547c..c24712b9418 100644 --- a/qa/qa/page/group/new.rb +++ b/qa/qa/page/group/new.rb @@ -21,6 +21,10 @@ module QA element :connect_instance_button end + view 'app/assets/javascripts/vue_shared/new_namespace/components/welcome.vue' do + element :panel_link + end + def set_path(path) fill_element(:group_path_field, path) fill_element(:group_name_field, path) @@ -62,7 +66,7 @@ module QA end def switch_to_import_tab - click_element("import-group-pane_link") + click_element(:panel_link, panel_name: 'import-group-pane') end end end diff --git a/qa/qa/page/group/settings/general.rb b/qa/qa/page/group/settings/general.rb index 4977e5c7105..2e7ab131225 100644 --- a/qa/qa/page/group/settings/general.rb +++ b/qa/qa/page/group/settings/general.rb @@ -38,7 +38,7 @@ module QA element :project_creation_level_dropdown end - view 'app/views/groups/settings/_advanced.html.haml' do + view 'app/views/groups/settings/_transfer.html.haml' do element :select_group_dropdown element :transfer_group_button end diff --git a/qa/qa/page/group/sub_menus/common.rb b/qa/qa/page/group/sub_menus/common.rb index 86102f70d29..2f8a3fdeb4e 100644 --- a/qa/qa/page/group/sub_menus/common.rb +++ b/qa/qa/page/group/sub_menus/common.rb @@ -12,8 +12,8 @@ module QA super base.class_eval do - view 'app/views/layouts/nav/sidebar/_group.html.haml' do - element :group_sidebar + view 'app/views/shared/nav/_sidebar.html.haml' do + element :group_sidebar, 'qa_selector: sidebar_qa_selector(sidebar.container)' # rubocop:disable QA/ElementWithPattern end end end diff --git a/qa/qa/page/main/login.rb b/qa/qa/page/main/login.rb index 3b3057a9acb..2c7ce69e4e5 100644 --- a/qa/qa/page/main/login.rb +++ b/qa/qa/page/main/login.rb @@ -67,7 +67,7 @@ module QA end def sign_in_using_admin_credentials - admin = QA::Resource::User.new.tap do |user| + admin = QA::Resource::User.init do |user| user.username = QA::Runtime::User.admin_username user.password = QA::Runtime::User.admin_password end diff --git a/qa/qa/page/project/fork/new.rb b/qa/qa/page/project/fork/new.rb index 5a08f6a3cbd..7062702679a 100644 --- a/qa/qa/page/project/fork/new.rb +++ b/qa/qa/page/project/fork/new.rb @@ -9,10 +9,6 @@ module QA element :fork_namespace_button end - view 'app/assets/javascripts/pages/projects/forks/new/components/fork_groups_list.vue' do - element :fork_groups_list_search_field - end - view 'app/assets/javascripts/pages/projects/forks/new/components/fork_form.vue' do element :fork_namespace_dropdown element :fork_project_button @@ -27,8 +23,8 @@ module QA end end - def search_for_group(group_name) - find_element(:fork_groups_list_search_field).set(group_name) + def fork_namespace_dropdown_values + find_element(:fork_namespace_dropdown).all(:option).map { |option| option.text.tr("\n", '').strip } end end end diff --git a/qa/qa/page/project/import/github.rb b/qa/qa/page/project/import/github.rb index dc683f7314b..74bc4cec467 100644 --- a/qa/qa/page/project/import/github.rb +++ b/qa/qa/page/project/import/github.rb @@ -14,13 +14,16 @@ module QA view 'app/assets/javascripts/import_entities/import_projects/components/provider_repo_table_row.vue' do element :project_import_row - element :project_namespace_select element :project_path_field element :import_button element :project_path_content element :go_to_project_button end + view "app/assets/javascripts/import_entities/components/group_dropdown.vue" do + element :target_namespace_selector_dropdown + end + def add_personal_access_token(personal_access_token) # If for some reasons this process is retried, user cannot re-enter github token in the same group # In this case skip this step and proceed to import project row @@ -37,9 +40,18 @@ module QA choose_test_namespace(full_path) set_path(full_path, name) import_project(full_path) + wait_for_success end + # TODO: refactor to use 'go to project' button instead of generic main menu + def go_to_project(name) + Page::Main::Menu.perform(&:go_to_projects) + Page::Dashboard::Projects.perform do |dashboard| + dashboard.go_to_project(name) + end + end + private def within_repo_path(full_path, &block) @@ -50,10 +62,9 @@ module QA def choose_test_namespace(full_path) within_repo_path(full_path) do - click_element :project_namespace_select + within_element(:target_namespace_selector_dropdown) { click_button(class: 'dropdown-toggle') } + click_element(:target_group_dropdown_item, group_name: Runtime::Namespace.path) end - - search_and_select(Runtime::Namespace.path) end def set_path(full_path, name) @@ -77,14 +88,9 @@ module QA reload: true, skip_finished_loading_check_on_refresh: true ) do - page.has_no_content?('Importing 1 repository') - end - end - - def go_to_project(name) - Page::Main::Menu.perform(&:go_to_projects) - Page::Dashboard::Projects.perform do |dashboard| - dashboard.go_to_project(name) + # TODO: Refactor to explicitly wait for specific project import successful status + # This check can create false positive if main importing message appears with delay and check exits early + page.has_no_content?('Importing 1 repository', wait: 3) end end diff --git a/qa/qa/page/project/import/repo_by_url.rb b/qa/qa/page/project/import/repo_by_url.rb new file mode 100644 index 00000000000..0e7524a181a --- /dev/null +++ b/qa/qa/page/project/import/repo_by_url.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +module QA + module Page + module Project + module Import + class RepoByURL < Page::Base + include Page::Component::Select2 + + view 'app/views/projects/_new_project_fields.html.haml' do + element :select_namespace_dropdown + end + + def import!(gitlab_repo_path, name) + fill_git_repository_url_link(gitlab_repo_path) + fill_project_name(name) + choose_test_namespace + click_create_button + + wait_for_success + + go_to_project(name) + end + + private + + def fill_git_repository_url_link(gitlab_repo_path) + fill_in 'project_import_url', with: gitlab_repo_path + end + + def fill_project_name(name) + fill_in 'project_name', with: name + end + + def choose_test_namespace + find('.js-select-namespace').click + search_and_select(Runtime::Namespace.path) + end + + def click_create_button + find('.btn-confirm').click + end + + def wait_for_success + wait_until(max_duration: 60, sleep_interval: 5.0, reload: true, skip_finished_loading_check_on_refresh: true) do + page.has_no_content?('Import in progress', wait: 3.0) + end + end + + def go_to_project(name) + Page::Main::Menu.perform(&:go_to_projects) + Page::Dashboard::Projects.perform do |dashboard| + dashboard.go_to_project(name) + end + end + end + end + end + end +end diff --git a/qa/qa/page/project/infrastructure/kubernetes/show.rb b/qa/qa/page/project/infrastructure/kubernetes/show.rb index e3b4f8fe4d9..62a04a53a2f 100644 --- a/qa/qa/page/project/infrastructure/kubernetes/show.rb +++ b/qa/qa/page/project/infrastructure/kubernetes/show.rb @@ -6,10 +6,6 @@ module QA module Infrastructure module Kubernetes class Show < Page::Base - view 'app/assets/javascripts/clusters/components/applications.vue' do - element :ingress_ip_address, 'id="ingress-endpoint"' # rubocop:disable QA/ElementWithPattern - end - view 'app/assets/javascripts/clusters/forms/components/integration_form.vue' do element :integration_status_toggle, required: true element :base_domain_field, required: true @@ -20,15 +16,6 @@ module QA element :details, required: true end - view 'app/views/clusters/clusters/_applications_tab.html.haml' do - element :applications, required: true - end - - view 'app/assets/javascripts/clusters/components/application_row.vue' do - element :install_button - element :uninstall_button - end - view 'app/views/clusters/clusters/_health.html.haml' do element :cluster_health_section end @@ -42,36 +29,6 @@ module QA click_element :details end - def open_applications - has_element?(:applications, wait: 30) - click_element :applications - end - - def install!(application_name) - within_element(application_name) do - has_element?(:install_button, application: application_name, wait: 30) - click_element :install_button - end - end - - def await_installed(application_name) - within_element(application_name) do - has_element?(:uninstall_button, application: application_name, wait: 300, skip_finished_loading_check: true) - end - end - - def has_application_installed?(application_name) - within_element(application_name) do - has_element?(:uninstall_button, application: application_name, wait: 300) - end - end - - def ingress_ip - # We need to wait longer since it can take some time before the - # ip address is assigned for the ingress controller - page.find('#ingress-endpoint', wait: 1200).value - end - def set_domain(domain) fill_element :base_domain_field, domain end diff --git a/qa/qa/page/project/new.rb b/qa/qa/page/project/new.rb index b14afa90442..170cc14b27f 100644 --- a/qa/qa/page/project/new.rb +++ b/qa/qa/page/project/new.rb @@ -8,6 +8,10 @@ module QA include Page::Component::Select2 include Page::Component::VisibilitySetting + include Layout::Flash + include Page::Component::Import::Selection + include Page::Component::Import::Gitlab + view 'app/views/projects/_new_project_fields.html.haml' do element :initialize_with_readme_checkbox element :project_namespace_select @@ -25,16 +29,15 @@ module QA end view 'app/assets/javascripts/vue_shared/new_namespace/components/welcome.vue' do - element :blank_project_link, ':data-qa-selector="`${panel.name}_link`"' # rubocop:disable QA/ElementWithPattern - element :create_from_template_link, ':data-qa-selector="`${panel.name}_link`"' # rubocop:disable QA/ElementWithPattern + element :panel_link end def click_blank_project_link - click_element :blank_project_link + click_element(:panel_link, panel_name: 'blank_project') end def click_create_from_template_link - click_element :create_from_template_link + click_element(:panel_link, panel_name: 'create_from_template') end def choose_test_namespace @@ -76,6 +79,10 @@ module QA click_link 'GitHub' end + def click_repo_by_url_link + click_button 'Repo by URL' + end + def enable_initialize_with_readme check_element(:initialize_with_readme_checkbox) end diff --git a/qa/qa/page/project/pipeline_editor/show.rb b/qa/qa/page/project/pipeline_editor/show.rb new file mode 100644 index 00000000000..38c87c8daa1 --- /dev/null +++ b/qa/qa/page/project/pipeline_editor/show.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +module QA + module Page + module Project + module PipelineEditor + class Show < QA::Page::Base + view 'app/assets/javascripts/pipeline_editor/components/file_nav/branch_switcher.vue' do + element :branch_selector_button + element :menu_branch_button + end + + view 'app/assets/javascripts/pipeline_editor/components/commit/commit_form.vue' do + element :target_branch_field + end + + def has_branch_selector_button? + has_element? :branch_selector_button + end + + def click_branch_selector_button + wait_until(reload: false) do + has_element?(:branch_selector_button) + end + click_element(:branch_selector_button, skip_finished_loading_check: true) + end + + def select_branch_from_dropdown(branch_to_switch_to) + wait_until(reload: false) do + has_element?(:menu_branch_button) + end + click_element(:menu_branch_button, text: branch_to_switch_to, skip_finished_loading_check: true) + end + + def target_branch_name + wait_until(reload: false) do + has_element?(:target_branch_field) + end + find_element(:target_branch_field, skip_finished_loading_check: true).value + end + end + end + end + end +end diff --git a/qa/qa/page/project/settings/advanced.rb b/qa/qa/page/project/settings/advanced.rb index 9c4b3a3c1c3..0ba856e8a6e 100644 --- a/qa/qa/page/project/settings/advanced.rb +++ b/qa/qa/page/project/settings/advanced.rb @@ -51,7 +51,7 @@ module QA # Workaround for a failure to search when there are no spaces around the / # https://gitlab.com/gitlab-org/gitlab/-/issues/218965 - search_and_select(namespace.gsub(/([^\s])\/([^\s])/, '\1 / \2')) + search_and_select(namespace.gsub(%r{([^\s])/([^\s])}, '\1 / \2')) click_element(:transfer_button) fill_confirmation_text(project_name) diff --git a/qa/qa/page/project/settings/default_branch.rb b/qa/qa/page/project/settings/default_branch.rb new file mode 100644 index 00000000000..cc28b37b88f --- /dev/null +++ b/qa/qa/page/project/settings/default_branch.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +module QA + module Page + module Project + module Settings + class DefaultBranch < Page::Base + include Page::Component::Select2 + + view 'app/views/projects/default_branch/_show.html.haml' do + element :save_changes_button + element :default_branch_dropdown + end + + def set_default_branch(branch) + find('.select2-chosen').click + search_and_select(branch) + end + + def click_save_changes_button + find('.btn-confirm').click + end + end + end + end + end +end + +QA::Page::Project::Settings::DefaultBranch.prepend_mod_with('Page::Project::Settings::DefaultBranch', namespace: QA) diff --git a/qa/qa/page/project/settings/deploy_tokens.rb b/qa/qa/page/project/settings/deploy_tokens.rb index b26cae86d8b..db1f6f68ec6 100644 --- a/qa/qa/page/project/settings/deploy_tokens.rb +++ b/qa/qa/page/project/settings/deploy_tokens.rb @@ -9,6 +9,7 @@ module QA element :deploy_token_name_field element :deploy_token_expires_at_field element :deploy_token_read_repository_checkbox + element :deploy_token_read_package_registry_checkbox element :deploy_token_read_registry_checkbox element :create_deploy_token_button end @@ -27,8 +28,9 @@ module QA fill_element(:deploy_token_expires_at_field, expires_at.to_s + "\n") end - def fill_scopes(read_repository:, read_registry:) + def fill_scopes(read_repository: false, read_registry: false, read_package_registry: false) check_element(:deploy_token_read_repository_checkbox) if read_repository + check_element(:deploy_token_read_package_registry_checkbox) if read_package_registry check_element(:deploy_token_read_registry_checkbox) if read_registry end diff --git a/qa/qa/page/project/settings/mirroring_repositories.rb b/qa/qa/page/project/settings/mirroring_repositories.rb index 5e7b68858c8..582079157f2 100644 --- a/qa/qa/page/project/settings/mirroring_repositories.rb +++ b/qa/qa/page/project/settings/mirroring_repositories.rb @@ -48,7 +48,7 @@ module QA end def mirror_direction=(value) - raise ArgumentError, "Mirror direction must be 'Push' or 'Pull'" unless %w(Push Pull).include? value + raise ArgumentError, "Mirror direction must be 'Push' or 'Pull'" unless %w[Push Pull].include?(value) select_element(:mirror_direction, value) @@ -59,7 +59,9 @@ module QA end def authentication_method=(value) - raise ArgumentError, "Authentication method must be 'SSH public key', 'Password', or 'None'" unless %w(Password None SSH\ public\ key).include? value + unless %w[Password None SSH\ public\ key].include?(value) + raise ArgumentError, "Authentication method must be 'SSH public key', 'Password', or 'None'" + end select_element(:authentication_method, value) end @@ -129,4 +131,7 @@ module QA end end -QA::Page::Project::Settings::MirroringRepositories.prepend_mod_with('Page::Project::Settings::MirroringRepositories', namespace: QA) +QA::Page::Project::Settings::MirroringRepositories.prepend_mod_with( # rubocop:disable Cop/InjectEnterpriseEditionModule + 'Page::Project::Settings::MirroringRepositories', + namespace: QA +) diff --git a/qa/qa/page/project/settings/repository.rb b/qa/qa/page/project/settings/repository.rb index a02b3d6a7d6..de5b4f37076 100644 --- a/qa/qa/page/project/settings/repository.rb +++ b/qa/qa/page/project/settings/repository.rb @@ -56,6 +56,14 @@ module QA ProtectedTags.perform(&block) end end + + def expand_default_branch(&block) + within('#default-branch-settings') do + find('.btn-default').click do + DefaultBranch.perform(&block) + end + end + end end end end diff --git a/qa/qa/page/project/sub_menus/ci_cd.rb b/qa/qa/page/project/sub_menus/ci_cd.rb index 7cb2fd6c655..c8c90df2c1f 100644 --- a/qa/qa/page/project/sub_menus/ci_cd.rb +++ b/qa/qa/page/project/sub_menus/ci_cd.rb @@ -20,6 +20,24 @@ module QA click_element(:sidebar_menu_link, menu_item: 'CI/CD') end end + + def go_to_pipeline_editor + hover_ci_cd_pipelines do + within_submenu do + click_element(:sidebar_menu_item_link, menu_item: 'Editor') + end + end + end + + private + + def hover_ci_cd_pipelines + within_sidebar do + find_element(:sidebar_menu_link, menu_item: 'CI/CD').hover + + yield + end + end end end end diff --git a/qa/qa/page/project/sub_menus/project.rb b/qa/qa/page/project/sub_menus/project.rb index cd08715bcd8..89d4ed578ed 100644 --- a/qa/qa/page/project/sub_menus/project.rb +++ b/qa/qa/page/project/sub_menus/project.rb @@ -12,17 +12,13 @@ module QA base.class_eval do include QA::Page::Project::SubMenus::Common - - view 'app/views/shared/nav/_scope_menu_body.html.haml' do - element :project_scope_link - end end end def click_project retry_on_exception do within_sidebar do - click_element(:project_scope_link) + click_element(:sidebar_menu_link, menu_item: 'Project scope') end end end diff --git a/qa/qa/resource/base.rb b/qa/qa/resource/base.rb index 873ba353051..ca0087cf709 100644 --- a/qa/qa/resource/base.rb +++ b/qa/qa/resource/base.rb @@ -1,70 +1,143 @@ # frozen_string_literal: true -require 'forwardable' require 'capybara/dsl' require 'active_support/core_ext/array/extract_options' module QA module Resource class Base - extend SingleForwardable include ApiFabricator extend Capybara::DSL NoValueError = Class.new(RuntimeError) - def_delegators :evaluator, :attribute + class << self + # Initialize new instance of class without fabrication + # + # @param [Proc] prepare_block + def init(&prepare_block) + new.tap(&prepare_block) + end - def self.fabricate!(*args, &prepare_block) - fabricate_via_api!(*args, &prepare_block) - rescue NotImplementedError - fabricate_via_browser_ui!(*args, &prepare_block) - end + def fabricate!(*args, &prepare_block) + fabricate_via_api!(*args, &prepare_block) + rescue NotImplementedError + fabricate_via_browser_ui!(*args, &prepare_block) + end - def self.fabricate_via_browser_ui!(*args, &prepare_block) - options = args.extract_options! - resource = options.fetch(:resource) { new } - parents = options.fetch(:parents) { [] } + def fabricate_via_browser_ui!(*args, &prepare_block) + options = args.extract_options! + resource = options.fetch(:resource) { new } + parents = options.fetch(:parents) { [] } - do_fabricate!(resource: resource, prepare_block: prepare_block, parents: parents) do - log_fabrication(:browser_ui, resource, parents, args) { resource.fabricate!(*args) } + do_fabricate!(resource: resource, prepare_block: prepare_block, parents: parents) do + log_fabrication(:browser_ui, resource, parents, args) { resource.fabricate!(*args) } - current_url + current_url + end end - end - def self.fabricate_via_api!(*args, &prepare_block) - options = args.extract_options! - resource = options.fetch(:resource) { new } - parents = options.fetch(:parents) { [] } + def fabricate_via_api!(*args, &prepare_block) + options = args.extract_options! + resource = options.fetch(:resource) { new } + parents = options.fetch(:parents) { [] } + + raise NotImplementedError unless resource.api_support? + + resource.eager_load_api_client! + + do_fabricate!(resource: resource, prepare_block: prepare_block, parents: parents) do + log_fabrication(:api, resource, parents, args) { resource.fabricate_via_api! } + end + end + + def remove_via_api!(*args, &prepare_block) + options = args.extract_options! + resource = options.fetch(:resource) { new } + parents = options.fetch(:parents) { [] } + + resource.eager_load_api_client! + + do_fabricate!(resource: resource, prepare_block: prepare_block, parents: parents) do + log_fabrication(:api, resource, parents, args) { resource.remove_via_api! } + end + end - raise NotImplementedError unless resource.api_support? + private - resource.eager_load_api_client! + def do_fabricate!(resource:, prepare_block:, parents: []) + prepare_block.call(resource) if prepare_block - do_fabricate!(resource: resource, prepare_block: prepare_block, parents: parents) do - log_fabrication(:api, resource, parents, args) { resource.fabricate_via_api! } + resource_web_url = yield + resource.web_url = resource_web_url + + resource + end + + def log_fabrication(method, resource, parents, args) + return yield unless Runtime::Env.debug? + + start = Time.now + prefix = "==#{'=' * parents.size}>" + msg = [prefix] + msg << "Built a #{name}" + msg << "as a dependency of #{parents.last}" if parents.any? + msg << "via #{method}" + + yield.tap do + msg << "in #{Time.now - start} seconds" + puts msg.join(' ') + puts if parents.empty? + end + end + + # Define custom attribute + # + # @param [Symbol] name + # @return [void] + def attribute(name, &block) + (@attribute_names ||= []).push(name) # save added attributes + + attr_writer(name) + + define_method(name) do + instance_variable_get("@#{name}") || instance_variable_set("@#{name}", populate_attribute(name, block)) + end + end + + # Define multiple custom attributes + # + # @param [Array] names + # @return [void] + def attributes(*names) + names.each { |name| attribute(name) } end end - def self.remove_via_api!(*args, &prepare_block) - options = args.extract_options! - resource = options.fetch(:resource) { new } - parents = options.fetch(:parents) { [] } + # Override api reload! and update custom attributes from api_resource + # + api_reload = instance_method(:reload!) + define_method(:reload!) do + api_reload.bind_call(self) + return self unless api_resource - resource.eager_load_api_client! + all_attributes.each do |attribute_name| + api_value = api_resource[attribute_name] - do_fabricate!(resource: resource, prepare_block: prepare_block, parents: parents) do - log_fabrication(:api, resource, parents, args) { resource.remove_via_api! } + instance_variable_set("@#{attribute_name}", api_value) if api_value end + + self end + attribute :web_url + def fabricate!(*_args) raise NotImplementedError end def visit! - Runtime::Logger.debug(%Q[Visiting #{self.class.name} at "#{web_url}"]) + Runtime::Logger.debug(%(Visiting #{self.class.name} at "#{web_url}")) # Just in case an async action is not yet complete Support::WaitForRequests.wait_for_requests @@ -78,14 +151,12 @@ module QA Support::WaitForRequests.wait_for_requests end - def populate(*attributes) - attributes.each(&method(:public_send)) + def populate(*attribute_names) + attribute_names.each { |attribute_name| public_send(attribute_name) } end - def wait_until(max_duration: 60, sleep_interval: 0.1) - QA::Support::Waiter.wait_until(max_duration: max_duration, sleep_interval: sleep_interval) do - yield - end + def wait_until(max_duration: 60, sleep_interval: 0.1, &block) + QA::Support::Waiter.wait_until(max_duration: max_duration, sleep_interval: sleep_interval, &block) end private @@ -101,70 +172,27 @@ module QA def attribute_value(name, block) api_value = api_resource&.dig(name) - if api_value && block - log_having_both_api_result_and_block(name, api_value) - end + log_having_both_api_result_and_block(name, api_value) if api_value && block api_value || (block && instance_exec(&block)) end - def log_having_both_api_result_and_block(name, api_value) - QA::Runtime::Logger.info "<#{self.class}> Attribute #{name.inspect} has both API response `#{api_value}` and a block. API response will be picked. Block will be ignored." + # Get all defined attributes across all parents + # + # @return [Array<Symbol>] + def all_attributes + @all_attributes ||= self.class.ancestors + .select { |clazz| clazz <= QA::Resource::Base } + .map { |clazz| clazz.instance_variable_get(:@attribute_names) } + .flatten + .compact end - def self.do_fabricate!(resource:, prepare_block:, parents: []) - prepare_block.call(resource) if prepare_block - - resource_web_url = yield - resource.web_url = resource_web_url - - resource - end - private_class_method :do_fabricate! - - def self.log_fabrication(method, resource, parents, args) - return yield unless Runtime::Env.debug? - - start = Time.now - prefix = "==#{'=' * parents.size}>" - msg = [prefix] - msg << "Built a #{name}" - msg << "as a dependency of #{parents.last}" if parents.any? - msg << "via #{method}" - - yield.tap do - msg << "in #{Time.now - start} seconds" - puts msg.join(' ') - puts if parents.empty? - end - end - private_class_method :log_fabrication - - def self.evaluator - @evaluator ||= Base::DSL.new(self) - end - private_class_method :evaluator - - class DSL - def initialize(base) - @base = base - end - - def attribute(name, &block) - @base.module_eval do - attr_writer(name) - - define_method(name) do - instance_variable_get("@#{name}") || - instance_variable_set( - "@#{name}", - populate_attribute(name, block)) - end - end - end + def log_having_both_api_result_and_block(name, api_value) + QA::Runtime::Logger.info(<<~MSG.strip) + <#{self.class}> Attribute #{name.inspect} has both API response `#{api_value}` and a block. API response will be picked. Block will be ignored. + MSG end - - attribute :web_url end end end diff --git a/qa/qa/resource/deploy_token.rb b/qa/qa/resource/deploy_token.rb index 0ba8dbbf287..cd638ad2f85 100644 --- a/qa/qa/resource/deploy_token.rb +++ b/qa/qa/resource/deploy_token.rb @@ -37,7 +37,7 @@ module QA setting.expand_deploy_tokens do |page| page.fill_token_name(name) page.fill_token_expires_at(expires_at) - page.fill_scopes(read_repository: true, read_registry: false) + page.fill_scopes(read_repository: true, read_package_registry: true) page.add_token end diff --git a/qa/qa/resource/group.rb b/qa/qa/resource/group.rb index 0f06113f85b..ce85273c3b2 100644 --- a/qa/qa/resource/group.rb +++ b/qa/qa/resource/group.rb @@ -3,7 +3,7 @@ module QA module Resource class Group < GroupBase - attr_accessor :description + attributes :require_two_factor_authentication, :description attribute :full_path do determine_full_path @@ -15,8 +15,6 @@ module QA end end - attribute :require_two_factor_authentication - def initialize @path = Runtime::Namespace.name @description = "QA test run at #{Runtime::Namespace.time}" diff --git a/qa/qa/resource/group_base.rb b/qa/qa/resource/group_base.rb index 025d98f50e0..652c6cf7d1e 100644 --- a/qa/qa/resource/group_base.rb +++ b/qa/qa/resource/group_base.rb @@ -9,17 +9,17 @@ module QA attr_accessor :path - attribute :id - attribute :runners_token - attribute :name - attribute :full_path + attributes :id, + :runners_token, + :name, + :full_path # Get group labels # # @return [Array<QA::Resource::GroupLabel>] def labels parse_body(api_get_from("#{api_get_path}/labels")).map do |label| - GroupLabel.new.tap do |resource| + GroupLabel.init do |resource| resource.api_client = api_client resource.group = self resource.id = label[:id] diff --git a/qa/qa/resource/import_project.rb b/qa/qa/resource/import_project.rb new file mode 100644 index 00000000000..105d75285f1 --- /dev/null +++ b/qa/qa/resource/import_project.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +module QA + module Resource + class ImportProject < Resource::Project + attr_writer :file_path + + def initialize + @name = "ImportedProject-#{SecureRandom.hex(8)}" + @file_path = ::File.join('qa', 'fixtures', 'export.tar.gz') + end + + def fabricate! + self.import = true + super + + group.visit! + + Page::Group::Show.perform(&:go_to_new_project) + + Page::Project::New.perform do |new_project| + new_project.click_import_project + new_project.click_gitlab + new_project.set_imported_project_name(@name) + new_project.attach_exported_file(@file_path) + new_project.click_import_gitlab_project + end + end + + def fabricate_via_api! + raise NotImplementedError + end + end + end +end diff --git a/qa/qa/resource/kubernetes_cluster/project_cluster.rb b/qa/qa/resource/kubernetes_cluster/project_cluster.rb index b66a75d978b..b3eba77fc46 100644 --- a/qa/qa/resource/kubernetes_cluster/project_cluster.rb +++ b/qa/qa/resource/kubernetes_cluster/project_cluster.rb @@ -3,6 +3,8 @@ module QA module Resource module KubernetesCluster + # TODO: This resource is currently broken, since one-click apps have been removed. + # See https://gitlab.com/gitlab-org/gitlab/-/issues/333818 class ProjectCluster < Base attr_writer :cluster, :install_ingress, :install_prometheus, :install_runner, :domain @@ -11,8 +13,8 @@ module QA Resource::Project.fabricate! end - attribute :ingress_ip do - Page::Project::Infrastructure::Kubernetes::Show.perform(&:ingress_ip) + def ingress_ip + @ingress_ip ||= @cluster.fetch_external_ip_for_ingress end def fabricate! @@ -40,17 +42,6 @@ module QA # We must wait a few seconds for permissions to be set up correctly for new cluster sleep 25 - # Open applications tab - show.open_applications - - show.install!(:ingress) if @install_ingress - show.install!(:prometheus) if @install_prometheus - show.install!(:runner) if @install_runner - - show.await_installed(:ingress) if @install_ingress - show.await_installed(:prometheus) if @install_prometheus - show.await_installed(:runner) if @install_runner - if @install_ingress populate(:ingress_ip) diff --git a/qa/qa/resource/merge_request.rb b/qa/qa/resource/merge_request.rb index 5a24bb32475..8d9de0ea718 100644 --- a/qa/qa/resource/merge_request.rb +++ b/qa/qa/resource/merge_request.rb @@ -1,17 +1,12 @@ # frozen_string_literal: true require 'securerandom' -require 'active_support/core_ext/object/blank' module QA module Resource class MergeRequest < Base attr_accessor :approval_rules, - :id, - :title, - :description, :source_branch, - :target_branch, :target_new_branch, :assignee, :milestone, @@ -22,9 +17,12 @@ module QA :wait_for_merge, :template - attribute :merge_when_pipeline_succeeds - attribute :merge_status - attribute :state + attributes :iid, + :title, + :description, + :merge_when_pipeline_succeeds, + :merge_status, + :state attribute :project do Project.fabricate! do |resource| @@ -32,11 +30,15 @@ module QA end end + attribute :target_branch do + project.default_branch + end + attribute :target do Repository::ProjectPush.fabricate! do |resource| resource.project = project resource.branch_name = target_branch - resource.new_branch = @target_new_branch + resource.new_branch = target_new_branch resource.remote_branch = target_branch end end @@ -62,13 +64,14 @@ module QA @labels = [] @file_name = "added_file-#{SecureRandom.hex(8)}.txt" @file_content = "File Added" - @target_branch = project.default_branch @target_new_branch = true @no_preparation = false @wait_for_merge = true end def fabricate! + return fabricate_large_merge_request if Runtime::Scenario.large_setup? + populate_target_and_source_if_required project.visit! @@ -89,21 +92,21 @@ module QA end def fabricate_via_api! - raise ResourceNotFoundError unless id + return fabricate_large_merge_request if Runtime::Scenario.large_setup? resource_web_url(api_get) - rescue ResourceNotFoundError + rescue ResourceNotFoundError, NoValueError # rescue if iid not populated populate_target_and_source_if_required super end def api_merge_path - "/projects/#{project.id}/merge_requests/#{id}/merge" + "/projects/#{project.id}/merge_requests/#{iid}/merge" end def api_get_path - "/projects/#{project.id}/merge_requests/#{id}" + "/projects/#{project.id}/merge_requests/#{iid}" end def api_post_path @@ -112,18 +115,22 @@ module QA def api_post_body { - description: @description, - source_branch: @source_branch, - target_branch: @target_branch, - title: @title + description: description, + source_branch: source_branch, + target_branch: target_branch, + title: title } end + def api_comments_path + "#{api_get_path}/notes" + end + def merge_via_api! Support::Waiter.wait_until(sleep_interval: 1) do - QA::Runtime::Logger.debug("Waiting until merge request with id '#{id}' can be merged") + QA::Runtime::Logger.debug("Waiting until merge request with id '#{iid}' can be merged") - reload!.api_resource[:merge_status] == 'can_be_merged' + reload!.merge_status == 'can_be_merged' end Support::Retrier.retry_on_exception do @@ -141,12 +148,21 @@ module QA end end - def reload! - # Refabricate so that we can return a new object with updated attributes - self.class.fabricate_via_api! do |resource| - resource.project = project - resource.id = api_resource[:iid] - end + def fabricate_large_merge_request + @project = Resource::ImportProject.fabricate_via_browser_ui! + # Setting the name here, since otherwise some tests will look for an existing file in + # the proejct without ever knowing what is in it. + @file_name = "github_controller_spec.rb" + visit("#{project.web_url}/-/merge_requests/1") + current_url + end + + # Get MR comments + # + # @return [Array] + def comments + response = get(Runtime::API::Request.new(api_client, api_comments_path).url) + parse_body(response) end private @@ -158,8 +174,6 @@ module QA end def populate_target_and_source_if_required - @target_branch ||= project.default_branch - populate(:target, :source) unless @no_preparation end end diff --git a/qa/qa/resource/package.rb b/qa/qa/resource/package.rb index 1009353a296..0e8c3ee95de 100644 --- a/qa/qa/resource/package.rb +++ b/qa/qa/resource/package.rb @@ -15,11 +15,10 @@ module QA end attribute :id do - packages = project.packages - - return unless (this_package = packages&.find { |package| package[:name] == "#{project.path_with_namespace}/#{name}" }) # rubocop:disable Cop/AvoidReturnFromBlocks + this_package = project.packages + &.find { |package| package[:name] == name } - this_package[:id] + this_package.try(:fetch, :id) end def fabricate! diff --git a/qa/qa/resource/personal_access_token.rb b/qa/qa/resource/personal_access_token.rb index 59ae8f4de7a..924e4206166 100644 --- a/qa/qa/resource/personal_access_token.rb +++ b/qa/qa/resource/personal_access_token.rb @@ -11,21 +11,25 @@ module QA # This *could* be different than the api_client.user or the api_user provided by the QA::Resource::ApiFabricator module attr_writer :user - attribute :token do - Page::Profile::PersonalAccessTokens.perform(&:created_access_token) - end + attribute :token # Only Admins can create PAT via the API. # If Runtime::Env.admin_personal_access_token is provided, fabricate via the API, # else, fabricate via the browser. def fabricate_via_api! - if Runtime::Env.admin_personal_access_token && !@user.nil? - self.api_client = Runtime::API::Client.as_admin + @token = QA::Resource::PersonalAccessTokenCache.get_token_for_username(user.username) + return if @token - super - else - fabricate! - end + resource = if Runtime::Env.admin_personal_access_token && !@user.nil? + self.api_client = Runtime::API::Client.as_admin + + super + else + fabricate! + end + + QA::Resource::PersonalAccessTokenCache.set_token_for_username(user.username, self.token) + resource end # When a user is not provided, use default user @@ -66,6 +70,8 @@ module QA # Expire in 2 days just in case the token is created just before midnight token_page.fill_expiry_date(Time.now.utc.to_date + 2) token_page.click_create_token_button + + self.token = Page::Profile::PersonalAccessTokens.perform(&:created_access_token) end end end diff --git a/qa/qa/resource/personal_access_token_cache.rb b/qa/qa/resource/personal_access_token_cache.rb new file mode 100644 index 00000000000..617779173bd --- /dev/null +++ b/qa/qa/resource/personal_access_token_cache.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module QA + module Resource + class PersonalAccessTokenCache + @personal_access_tokens = {} + + def self.get_token_for_username(username) + @personal_access_tokens[username] + end + + def self.set_token_for_username(username, token) + @personal_access_tokens[username] = token + end + end + end +end diff --git a/qa/qa/resource/project.rb b/qa/qa/resource/project.rb index f8cf816d6e4..d111b070863 100644 --- a/qa/qa/resource/project.rb +++ b/qa/qa/resource/project.rb @@ -9,21 +9,23 @@ module QA include Members include Visibility - attr_accessor :repository_storage # requires admin access - attr_writer :initialize_with_readme, - :auto_devops_enabled, - :github_personal_access_token, - :github_repository_path - - attribute :id - attribute :name - attribute :add_name_uuid - attribute :description - attribute :standalone - attribute :runners_token - attribute :visibility - attribute :template_name - attribute :import + attr_accessor :repository_storage, # requires admin access + :initialize_with_readme, + :auto_devops_enabled, + :github_personal_access_token, + :github_repository_path + + attributes :id, + :name, + :add_name_uuid, + :description, + :standalone, + :runners_token, + :visibility, + :template_name, + :import, + :import_status, + :import_error attribute :group do Group.fabricate! diff --git a/qa/qa/resource/project_imported_from_github.rb b/qa/qa/resource/project_imported_from_github.rb index 93cd166a191..214e8f517bb 100644 --- a/qa/qa/resource/project_imported_from_github.rb +++ b/qa/qa/resource/project_imported_from_github.rb @@ -1,25 +1,82 @@ # frozen_string_literal: true -require 'securerandom' +require 'octokit' module QA module Resource class ProjectImportedFromGithub < Resource::Project + attribute :github_repo_id do + github_client.repository(github_repository_path).id + end + def fabricate! self.import = true Page::Main::Menu.perform(&:go_to_create_project) + go_to_import_page + + Page::Project::Import::Github.perform do |import_page| + import_page.add_personal_access_token(github_personal_access_token) + import_page.import!(github_repository_path, name) + import_page.go_to_project(name) + end + end + + def go_to_import_page Page::Project::New.perform do |project_page| project_page.click_import_project project_page.click_github_link end + end - Page::Project::Import::Github.perform do |import_page| - import_page.add_personal_access_token(@github_personal_access_token) - import_page.import!(@github_repository_path, @name) + def fabricate_via_api! + super + rescue ResourceURLMissingError + "#{Runtime::Scenario.gitlab_address}/#{group.full_path}/#{name}" + end + + def api_post_path + '/import/github' + end + + def api_trigger_mirror_pull_path + "#{api_get_path}/mirror/pull" + end + + def api_post_body + { + repo_id: github_repo_id, + new_name: name, + target_namespace: group.full_path, + personal_access_token: github_personal_access_token, + ci_cd_only: false + } + end + + def transform_api_resource(api_resource) + api_resource + end + + def trigger_project_mirror + Runtime::Logger.info "Triggering pull mirror request" + + Support::Retrier.retry_until(max_attempts: 6, sleep_interval: 10) do + response = post(request_url(api_trigger_mirror_pull_path), nil) + + Runtime::Logger.info "Mirror pull request response: #{response}" + response.code == Support::Api::HTTP_STATUS_OK end end + + private + + # Github client + # + # @return [Octokit::Client] + def github_client + @github_client ||= Octokit::Client.new(access_token: github_personal_access_token) + end end end end diff --git a/qa/qa/resource/project_imported_from_url.rb b/qa/qa/resource/project_imported_from_url.rb new file mode 100644 index 00000000000..f159a174840 --- /dev/null +++ b/qa/qa/resource/project_imported_from_url.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +require 'securerandom' + +module QA + module Resource + class ProjectImportedFromURL < Resource::Project + def fabricate! + self.import = true + super + + group.visit! + + Page::Group::Show.perform(&:go_to_new_project) + + Page::Project::New.perform do |project_page| + project_page.click_import_project + project_page.click_repo_by_url_link + end + + Page::Project::Import::RepoByURL.perform do |import_page| + import_page.import!(@gitlab_repository_path, @name) + end + end + end + end +end diff --git a/qa/qa/resource/repository/wiki_push.rb b/qa/qa/resource/repository/wiki_push.rb index edf76c7cd78..aa69d831bff 100644 --- a/qa/qa/resource/repository/wiki_push.rb +++ b/qa/qa/resource/repository/wiki_push.rb @@ -16,7 +16,6 @@ module QA @file_name = 'Home.md' @file_content = 'This line was created using git push' @commit_message = 'Updating using git push' - @branch_name = 'master' @new_branch = false end diff --git a/qa/qa/resource/user.rb b/qa/qa/resource/user.rb index 8957dbcbe84..c424d7319fe 100644 --- a/qa/qa/resource/user.rb +++ b/qa/qa/resource/user.rb @@ -25,7 +25,7 @@ module QA end def self.default - Resource::User.new.tap do |user| + Resource::User.init do |user| user.username = Runtime::User.ldap_user? ? Runtime::User.ldap_username : Runtime::User.username user.password = Runtime::User.ldap_user? ? Runtime::User.ldap_password : Runtime::User.password end diff --git a/qa/qa/runtime/allure_report.rb b/qa/qa/runtime/allure_report.rb index 5e9ae3e7bbe..bcfdb09e09f 100644 --- a/qa/qa/runtime/allure_report.rb +++ b/qa/qa/runtime/allure_report.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +require 'active_support/core_ext/enumerable' + module QA module Runtime class AllureReport @@ -29,6 +31,7 @@ module QA AllureRspec.configure do |config| config.results_directory = 'tmp/allure-results' config.clean_results_directory = true + config.environment_properties = environment_info if Env.running_in_ci? # Set custom environment name to separate same specs executed on different environments if Env.running_in_ci? && Env.ci_job_name.match?(env_matcher) @@ -66,19 +69,48 @@ module QA RSpec.configure do |config| config.formatter = AllureRspecFormatter - config.before do |example| + config.after do |example| next if example.attempts && example.attempts > 0 testcase = example.metadata[:testcase] example.tms('Testcase', testcase) if testcase - issue = example.metadata.dig(:quarantine, :issue) - example.issue('Issue', issue) if issue + quarantine_issue = example.metadata.dig(:quarantine, :issue) + example.issue('Quarantine issue', quarantine_issue) if quarantine_issue + + spec_file = example.file_path.split('/').last + example.issue( + 'Failure issues', + "https://gitlab.com/gitlab-org/gitlab/-/issues?scope=all&state=opened&search=#{spec_file}" + ) example.add_link(name: "Job(#{Env.ci_job_name})", url: Env.ci_job_url) if Env.running_in_ci? end end end + + # Custom environment info hash + # + # @return [Hash] + def environment_info + %w[ + CI_COMMIT_SHA + CI_MERGE_REQUEST_SOURCE_BRANCH_SHA + CI_MERGE_REQUEST_IID + TOP_UPSTREAM_SOURCE_SHA + TOP_UPSTREAM_MERGE_REQUEST_IID + DEPLOY_VERSION + GITLAB_VERSION + GITLAB_SHELL_VERSION + GITLAB_ELASTICSEARCH_INDEXER_VERSION + GITLAB_KAS_VERSION + GITLAB_WORKHORSE_VERSION + GITLAB_PAGES_VERSION + GITALY_SERVER_VERSION + QA_IMAGE + QA_BROWSER + ].index_with { |val| ENV[val] }.compact_blank + end end end end diff --git a/qa/qa/runtime/browser.rb b/qa/qa/runtime/browser.rb index 3fe8552c063..e13061e2648 100644 --- a/qa/qa/runtime/browser.rb +++ b/qa/qa/runtime/browser.rb @@ -5,6 +5,8 @@ require 'rspec/expectations' require 'capybara/rspec' require 'capybara-screenshot/rspec' require 'selenium-webdriver' +require 'webdrivers/chromedriver' +require 'webdrivers/geckodriver' require 'gitlab_handbook' @@ -66,61 +68,82 @@ module QA return if Capybara.drivers.include?(:chrome) Capybara.register_driver QA::Runtime::Env.browser do |app| - capabilities = Selenium::WebDriver::Remote::Capabilities.send(QA::Runtime::Env.browser, - # This enables access to logs with `page.driver.manage.get_log(:browser)` - loggingPrefs: { - browser: "ALL", - client: "ALL", - driver: "ALL", - server: "ALL" - }) - - if QA::Runtime::Env.accept_insecure_certs? - capabilities['acceptInsecureCerts'] = true - end + capabilities = Selenium::WebDriver::Remote::Capabilities.send(QA::Runtime::Env.browser) - # QA::Runtime::Env.browser.capitalize will work for every driver type except PhantomJS. - # We will have no use to use PhantomJS so this shouldn't be a problem. - options = Selenium::WebDriver.const_get(QA::Runtime::Env.browser.capitalize, false)::Options.new + case QA::Runtime::Env.browser + when :chrome + if QA::Runtime::Env.accept_insecure_certs? + capabilities['acceptInsecureCerts'] = true + end - if QA::Runtime::Env.browser == :chrome - options.add_argument("window-size=1480,2200") + # set logging preferences + # this enables access to logs with `page.driver.manage.get_log(:browser)` + capabilities['goog:loggingPrefs'] = { + browser: 'ALL', + client: 'ALL', + driver: 'ALL', + server: 'ALL' + } # Chrome won't work properly in a Docker container in sandbox mode - options.add_argument("no-sandbox") + capabilities['goog:chromeOptions'] = { + args: %w[no-sandbox] + } - # Run headless by default unless CHROME_HEADLESS is false - if QA::Runtime::Env.chrome_headless? - options.add_argument("headless") + # Run headless by default unless WEBDRIVER_HEADLESS is false + if QA::Runtime::Env.webdriver_headless? + capabilities['goog:chromeOptions'][:args] << 'headless' # Chrome documentation says this flag is needed for now # https://developers.google.com/web/updates/2017/04/headless-chrome#cli - options.add_argument("disable-gpu") + capabilities['goog:chromeOptions'][:args] << 'disable-gpu' end # Disable /dev/shm use in CI. See https://gitlab.com/gitlab-org/gitlab/issues/4252 - options.add_argument("disable-dev-shm-usage") if QA::Runtime::Env.running_in_ci? + capabilities['goog:chromeOptions'][:args] << 'disable-dev-shm-usage' if QA::Runtime::Env.running_in_ci? # Specify the user-agent to allow challenges to be bypassed # See https://gitlab.com/gitlab-com/gl-infra/infrastructure/-/issues/11938 - options.add_argument("user-agent=#{QA::Runtime::Env.user_agent}") if QA::Runtime::Env.user_agent + capabilities['goog:chromeOptions'][:args] << "user-agent=#{QA::Runtime::Env.user_agent}" if QA::Runtime::Env.user_agent + + if QA::Runtime::Env.remote_mobile_device_name + capabilities['platformName'] = 'Android' + capabilities['appium:deviceName'] = QA::Runtime::Env.remote_mobile_device_name + capabilities['appium:platformVersion'] = 'latest' + else + capabilities['goog:chromeOptions'][:args] << 'window-size=1480,2200' + end + + when :safari + if QA::Runtime::Env.remote_mobile_device_name + capabilities['platformName'] = 'iOS' + capabilities['appium:deviceName'] = QA::Runtime::Env.remote_mobile_device_name + capabilities['appium:platformVersion'] = 'latest' + end + + when :firefox + if QA::Runtime::Env.accept_insecure_certs? + capabilities['acceptInsecureCerts'] = true + end 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}") + capabilities['goog:chromeOptions'][:args] << "user-data-dir=#{qa_profile_dir}" end selenium_options = { browser: QA::Runtime::Env.browser, clear_local_storage: true, - desired_capabilities: capabilities, - options: options + capabilities: capabilities } - selenium_options[:url] = QA::Runtime::Env.remote_grid if QA::Runtime::Env.remote_grid + if QA::Runtime::Env.remote_grid + selenium_options[:url] = QA::Runtime::Env.remote_grid + capabilities[:browserVersion] = 'latest' + end Capybara::Selenium::Driver.new( app, @@ -158,6 +181,7 @@ module QA config.browser = Capybara.current_session.driver.browser # reuse Capybara session config.libraries = [GitlabHandbook] config.base_url = Runtime::Scenario.attributes[:gitlab_address] # reuse GitLab address + config.hide_banner = true end end # rubocop: enable Metrics/AbcSize diff --git a/qa/qa/runtime/env.rb b/qa/qa/runtime/env.rb index ccb87147a6e..5cac811d95b 100644 --- a/qa/qa/runtime/env.rb +++ b/qa/qa/runtime/env.rb @@ -1,5 +1,6 @@ # frozen_string_literal: true +require 'active_support/deprecation' require 'gitlab/qa' require 'uri' @@ -64,8 +65,14 @@ module QA ENV['QA_LOG_PATH'] || $stdout end - # set to 'false' to have Chrome run visibly instead of headless - def chrome_headless? + # set to 'false' to have the browser run visibly instead of headless + def webdriver_headless? + if ENV.key?('CHROME_HEADLESS') + ActiveSupport::Deprecation.warn("CHROME_HEADLESS is deprecated. Use WEBDRIVER_HEADLESS instead.") + end + + return enabled?(ENV['WEBDRIVER_HEADLESS']) unless ENV['WEBDRIVER_HEADLESS'].nil? + enabled?(ENV['CHROME_HEADLESS']) end @@ -139,6 +146,10 @@ module QA ENV['QA_BROWSER'].nil? ? :chrome : ENV['QA_BROWSER'].to_sym end + def remote_mobile_device_name + ENV['QA_REMOTE_MOBILE_DEVICE_NAME'] + end + def user_username ENV['GITLAB_USERNAME'] end @@ -327,7 +338,7 @@ module QA # the feature is supported in the environment under test. # All features are supported by default. def can_test?(feature) - raise ArgumentError, %Q(Unknown feature "#{feature}") unless SUPPORTED_FEATURES.include? feature + raise ArgumentError, %(Unknown feature "#{feature}") unless SUPPORTED_FEATURES.include? feature enabled?(ENV[SUPPORTED_FEATURES[feature]], default: true) end @@ -385,11 +396,17 @@ module QA ENV.fetch('GITLAB_QA_TRANSIENT_TRIALS', 10).to_i end + def gitlab_tls_certificate + ENV['GITLAB_TLS_CERTIFICATE'] + end + private def remote_grid_credentials if remote_grid_username - raise ArgumentError, %Q(Please provide an access key for user "#{remote_grid_username}") unless remote_grid_access_key + unless remote_grid_access_key + raise ArgumentError, %(Please provide an access key for user "#{remote_grid_username}") + end return "#{remote_grid_username}:#{remote_grid_access_key}@" end diff --git a/qa/qa/scenario/test/integration/registry.rb b/qa/qa/scenario/test/integration/registry.rb new file mode 100644 index 00000000000..28b74c99ab3 --- /dev/null +++ b/qa/qa/scenario/test/integration/registry.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module QA + module Scenario + module Test + module Integration + class Registry < Test::Instance::All + tags :registry + end + end + end + end +end diff --git a/qa/qa/service/cluster_provider/k3s_cilium.rb b/qa/qa/service/cluster_provider/k3s_cilium.rb new file mode 100644 index 00000000000..5b529caa20b --- /dev/null +++ b/qa/qa/service/cluster_provider/k3s_cilium.rb @@ -0,0 +1,93 @@ +# frozen_string_literal: true + +module QA + module Service + module ClusterProvider + class K3sCilium < K3s + def setup + @k3s = Service::DockerRun::K3s.new.tap do |k3s| + k3s.remove! + k3s.cni_enabled = true + k3s.register! + + shell "kubectl config set-cluster k3s --server https://#{k3s.host_name}:6443 --insecure-skip-tls-verify" + shell 'kubectl config set-credentials default --username=node --password=some-secret' + shell 'kubectl config set-context k3s --cluster=k3s --user=default' + shell 'kubectl config use-context k3s' + + wait_for_server(k3s.host_name) do + shell 'kubectl version' + # install local storage + shell 'kubectl apply -f https://raw.githubusercontent.com/rancher/local-path-provisioner/master/deploy/local-path-storage.yaml' + + # patch local storage + shell %(kubectl patch storageclass local-path -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}') + shell 'kubectl create -f https://raw.githubusercontent.com/cilium/cilium/v1.8/install/kubernetes/quick-install.yaml' + + wait_for_namespaces do + wait_for_cilium + wait_for_coredns do + shell 'kubectl create -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-0.31.0/deploy/static/provider/cloud/deploy.yaml' + wait_for_ingress + end + end + end + end + end + + private + + def wait_for_cilium + QA::Runtime::Logger.info 'Waiting for Cilium pod to be initialized' + + 60.times do + if service_available?('kubectl get pods --all-namespaces -l k8s-app=cilium --no-headers=true | grep -o "cilium-.*1/1"') + return yield if block_given? + + return true + end + + sleep 1 + QA::Runtime::Logger.info '.' + end + + raise 'Cilium pod has not initialized correctly' + end + + def wait_for_coredns + QA::Runtime::Logger.info 'Waiting for CoreDNS pod to be initialized' + + 60.times do + if service_available?('kubectl get pods --all-namespaces --no-headers=true | grep -o "coredns.*1/1"') + return yield if block_given? + + return true + end + + sleep 1 + QA::Runtime::Logger.info '.' + end + + raise 'CoreDNS pod has not been initialized correctly' + end + + def wait_for_ingress + QA::Runtime::Logger.info 'Waiting for Ingress controller pod to be initialized' + + 60.times do + if service_available?('kubectl get pods --all-namespaces -l app.kubernetes.io/component=controller | grep -o "ingress-nginx-controller.*1/1"') + return yield if block_given? + + return true + end + + sleep 1 + QA::Runtime::Logger.info '.' + end + + raise 'Ingress pod has not been initialized correctly' + end + end + end + end +end diff --git a/qa/qa/service/docker_run/gitlab_runner.rb b/qa/qa/service/docker_run/gitlab_runner.rb index a5b129eb1f9..63fbf758231 100644 --- a/qa/qa/service/docker_run/gitlab_runner.rb +++ b/qa/qa/service/docker_run/gitlab_runner.rb @@ -38,11 +38,10 @@ module QA def register! shell <<~CMD.tr("\n", ' ') - docker run -d --rm --entrypoint=/bin/sh - --network #{runner_network} --name #{@name} + docker run -d --rm --network #{runner_network} --name #{@name} #{'-v /var/run/docker.sock:/var/run/docker.sock' if @executor == :docker} --privileged - #{@image} -c "#{register_command}" + #{@image} #{add_gitlab_tls_cert if @address.include? "https"} && docker exec --detach #{@name} sh -c "#{register_command}" CMD # Prove airgappedness @@ -82,6 +81,7 @@ module QA args << '--docker-tlsverify=false' args << '--docker-privileged=true' args << "--docker-network-mode=#{network}" + args << "--docker-volumes=/certs/client" end <<~CMD.strip @@ -102,6 +102,16 @@ module QA wget --retry-connrefused --waitretry=1 --read-timeout=15 --timeout=10 -t 2 http://registry.gitlab.com > /dev/null 2>&1 && (echo "Airgapped network faulty. Connectivity wget check failed." && exit 1) || (echo "Airgapped network confirmed. Connectivity wget check passed." && exit 0) CMD end + + def add_gitlab_tls_cert + gitlab_tls_certificate = Tempfile.new('gitlab-cert') + gitlab_tls_certificate.write(Runtime::Env.gitlab_tls_certificate) + gitlab_tls_certificate.close + + <<~CMD + && docker cp #{gitlab_tls_certificate.path} #{@name}:/etc/gitlab-runner/certs/gitlab.test.crt + CMD + end end end end diff --git a/qa/qa/service/docker_run/k3s.rb b/qa/qa/service/docker_run/k3s.rb index 07211b220f1..a09b62cb613 100644 --- a/qa/qa/service/docker_run/k3s.rb +++ b/qa/qa/service/docker_run/k3s.rb @@ -4,15 +4,20 @@ module QA module Service module DockerRun class K3s < Base + attr_accessor :cni_enabled + def initialize - @image = 'registry.gitlab.com/gitlab-org/cluster-integration/test-utils/k3s-gitlab-ci/releases/v0.6.1' + @image = 'registry.gitlab.com/gitlab-org/cluster-integration/test-utils/k3s-gitlab-ci/releases/v0.9.1' @name = 'k3s' + @cni_enabled = false super end def register! pull start_k3s + # Mount the berkeley packet filter if container network interface is enabled + mount_bpf if @cni_enabled end def host_name @@ -36,12 +41,20 @@ module QA #{@image} server --cluster-secret some-secret --no-deploy traefik + #{@cni_enabled ? '--no-flannel' : ''} CMD command.gsub!("--network #{network} --hostname #{host_name}", '') unless QA::Runtime::Env.running_in_ci? shell command end + + private + + def mount_bpf + shell "docker exec --privileged k3s mount bpffs -t bpf /sys/fs/bpf" + shell "docker exec --privileged k3s mount --make-shared bpffs -t bpf /sys/fs/bpf" + end end end end diff --git a/qa/qa/service/kubernetes_cluster.rb b/qa/qa/service/kubernetes_cluster.rb index ddf97046fb0..adef1b46af2 100644 --- a/qa/qa/service/kubernetes_cluster.rb +++ b/qa/qa/service/kubernetes_cluster.rb @@ -51,6 +51,30 @@ module QA shell('kubectl apply -f -', stdin_data: manifest) end + def add_sample_policy(project, policy_name: 'sample-policy') + namespace = "#{project.name}-#{project.id}-production" + network_policy = <<~YAML + apiVersion: "cilium.io/v2" + kind: CiliumNetworkPolicy + metadata: + name: #{policy_name} + namespace: #{namespace} + spec: + endpointSelector: + matchLabels: + role: backend + ingress: + - fromEndpoints: + - matchLabels: + role: frontend + YAML + shell('kubectl apply -f -', stdin_data: network_policy) + end + + def fetch_external_ip_for_ingress + `kubectl get svc --all-namespaces --no-headers=true -l app.kubernetes.io/name=ingress-nginx -o custom-columns=:'status.loadBalancer.ingress[0].ip' | grep -v 'none'` + end + private def fetch_api_url diff --git a/qa/qa/specs/features/api/1_manage/import_github_repo_spec.rb b/qa/qa/specs/features/api/1_manage/import_github_repo_spec.rb new file mode 100644 index 00000000000..1b873d35d75 --- /dev/null +++ b/qa/qa/specs/features/api/1_manage/import_github_repo_spec.rb @@ -0,0 +1,133 @@ +# frozen_string_literal: true + +module QA + RSpec.describe 'Manage', :github, :requires_admin do + describe 'Project import' do + let!(:api_client) { Runtime::API::Client.as_admin } + let!(:group) { Resource::Group.fabricate_via_api! { |resource| resource.api_client = api_client } } + let!(:user) do + Resource::User.fabricate_via_api! do |resource| + resource.api_client = api_client + resource.hard_delete_on_api_removal = true + end + end + + let(:imported_project) do + Resource::ProjectImportedFromGithub.fabricate_via_api! do |project| + project.name = 'imported-project' + project.group = group + project.github_personal_access_token = Runtime::Env.github_access_token + project.github_repository_path = 'gitlab-qa-github/test-project' + project.api_client = api_client + end + end + + before do + group.add_member(user, Resource::Members::AccessLevel::MAINTAINER) + end + + after do + user.remove_via_api! + end + + it 'imports Github repo via api', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1858' do + imported_project # import the project + + expect { imported_project.reload!.import_status }.to eventually_eq('finished').within(duration: 90) + + aggregate_failures do + verify_repository_import + verify_commits_import + verify_labels_import + verify_issues_import + verify_milestones_import + verify_wikis_import + verify_merge_requests_import + end + end + + def verify_repository_import + expect(imported_project.api_response).to include( + description: 'A new repo for test', + import_error: nil + ) + end + + def verify_commits_import + expect(imported_project.commits.length).to eq(20) + end + + def verify_labels_import + labels = imported_project.labels.map { |label| label.slice(:name, :color) } + + expect(labels).to include( + { name: 'bug', color: '#d73a4a' }, + { name: 'custom new label', color: '#fc8f91' }, + { name: 'documentation', color: '#0075ca' }, + { name: 'duplicate', color: '#cfd3d7' }, + { name: 'enhancement', color: '#a2eeef' }, + { name: 'good first issue', color: '#7057ff' }, + { name: 'help wanted', color: '#008672' }, + { name: 'invalid', color: '#e4e669' }, + { name: 'question', color: '#d876e3' }, + { name: 'wontfix', color: '#ffffff' } + ) + end + + def verify_issues_import + issues = imported_project.issues + + expect(issues.length).to eq(1) + expect(issues.first).to include( + title: 'This is a sample issue', + description: "*Created by: gitlab-qa-github*\n\nThis is a sample first comment", + labels: ['custom new label', 'good first issue', 'help wanted'], + user_notes_count: 1 + ) + end + + def verify_milestones_import + milestones = imported_project.milestones + + expect(milestones.length).to eq(1) + expect(milestones.first).to include(title: 'v1.0', description: nil, state: 'active') + end + + def verify_wikis_import + wikis = imported_project.wikis + + expect(wikis.length).to eq(1) + expect(wikis.first).to include(title: 'Home', format: 'markdown') + end + + def verify_merge_requests_import + merge_requests = imported_project.merge_requests + merge_request = Resource::MergeRequest.init do |mr| + mr.project = imported_project + mr.iid = merge_requests.first[:iid] + mr.api_client = api_client + end.reload! + mr_comments = merge_request.comments.map { |comment| comment[:body] } # rubocop:disable Rails/Pluck + + expect(merge_requests.length).to eq(1) + expect(merge_request.api_resource).to include( + title: 'Improve readme', + state: 'opened', + target_branch: 'main', + source_branch: 'improve-readme', + labels: %w[bug documentation], + description: <<~DSC.strip + *Created by: gitlab-qa-github*\n\nThis improves the README file a bit.\r\n\r\nTODO:\r\n\r\n \r\n\r\n- [ ] Do foo\r\n- [ ] Make bar\r\n - [ ] Think about baz + DSC + ) + expect(mr_comments).to eq( + [ + "*Created by: gitlab-qa-github*\n\n[PR comment by @sliaquat] Nice work! ", + "*Created by: gitlab-qa-github*\n\n[Single diff comment] Nice addition", + "*Created by: gitlab-qa-github*\n\n[Single diff comment] Good riddance" + ] + ) + end + end + end +end diff --git a/qa/qa/specs/features/api/3_create/gitaly/distributed_reads_spec.rb b/qa/qa/specs/features/api/3_create/gitaly/distributed_reads_spec.rb index 08b71258cc6..6c70c09c7ab 100644 --- a/qa/qa/specs/features/api/3_create/gitaly/distributed_reads_spec.rb +++ b/qa/qa/specs/features/api/3_create/gitaly/distributed_reads_spec.rb @@ -5,8 +5,7 @@ require 'parallel' module QA RSpec.describe 'Create' do context 'Gitaly' do - # Issue to track removal of feature flag: https://gitlab.com/gitlab-org/quality/team-tasks/-/issues/602 - describe 'Distributed reads', :orchestrated, :gitaly_cluster, :skip_live_env, :requires_admin, quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/322814', type: :investigating } do + describe 'Distributed reads', :orchestrated, :gitaly_cluster, :skip_live_env, :requires_admin do let(:number_of_reads_per_loop) { 9 } let(:praefect_manager) { Service::PraefectManager.new } let(:project) do @@ -17,14 +16,9 @@ module QA end before do - Runtime::Feature.enable(:gitaly_distributed_reads) praefect_manager.wait_for_replication(project.id) end - after do - Runtime::Feature.disable(:gitaly_distributed_reads) - end - it 'reads from each node', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/979' do pre_read_data = praefect_manager.query_read_distribution diff --git a/qa/qa/specs/features/api/3_create/gitaly/praefect_replication_queue_spec.rb b/qa/qa/specs/features/api/3_create/gitaly/praefect_replication_queue_spec.rb index 396d3f52798..7e924475437 100644 --- a/qa/qa/specs/features/api/3_create/gitaly/praefect_replication_queue_spec.rb +++ b/qa/qa/specs/features/api/3_create/gitaly/praefect_replication_queue_spec.rb @@ -4,7 +4,7 @@ require 'parallel' module QA RSpec.describe 'Create' do - context 'Gitaly Cluster replication queue', :orchestrated, :gitaly_cluster, :skip_live_env, quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/331989', type: :investigating } do + context 'Gitaly Cluster replication queue', :orchestrated, :gitaly_cluster, :skip_live_env do let(:praefect_manager) { Service::PraefectManager.new } let(:project) do Resource::Project.fabricate! do |project| diff --git a/qa/qa/specs/features/api/3_create/merge_request/push_options_mwps_spec.rb b/qa/qa/specs/features/api/3_create/merge_request/push_options_mwps_spec.rb index dde4708874d..157a9e92817 100644 --- a/qa/qa/specs/features/api/3_create/merge_request/push_options_mwps_spec.rb +++ b/qa/qa/specs/features/api/3_create/merge_request/push_options_mwps_spec.rb @@ -64,7 +64,7 @@ module QA merge_request = Resource::MergeRequest.fabricate_via_api! do |mr| mr.project = project - mr.id = merge_request[:iid] + mr.iid = merge_request[:iid] end expect(merge_request.state).to eq('opened') @@ -109,7 +109,7 @@ module QA merge_request = Support::Waiter.wait_until(sleep_interval: 5) do mr = Resource::MergeRequest.fabricate_via_api! do |mr| mr.project = project - mr.id = merge_request[:iid] + mr.iid = merge_request[:iid] end next unless mr.state == 'merged' diff --git a/qa/qa/specs/features/api/3_create/merge_request/push_options_remove_source_branch_spec.rb b/qa/qa/specs/features/api/3_create/merge_request/push_options_remove_source_branch_spec.rb index bf2ecfdb513..eb93f4cd5cb 100644 --- a/qa/qa/specs/features/api/3_create/merge_request/push_options_remove_source_branch_spec.rb +++ b/qa/qa/specs/features/api/3_create/merge_request/push_options_remove_source_branch_spec.rb @@ -34,7 +34,7 @@ module QA merge_request = Resource::MergeRequest.fabricate_via_api! do |mr| mr.project = project - mr.id = merge_request[:iid] + mr.iid = merge_request[:iid] end.merge_via_api! expect(merge_request[:state]).to eq('merged') diff --git a/qa/qa/specs/features/api/3_create/merge_request/push_options_target_branch_spec.rb b/qa/qa/specs/features/api/3_create/merge_request/push_options_target_branch_spec.rb index e02d32bc4c7..799efc243d4 100644 --- a/qa/qa/specs/features/api/3_create/merge_request/push_options_target_branch_spec.rb +++ b/qa/qa/specs/features/api/3_create/merge_request/push_options_target_branch_spec.rb @@ -55,7 +55,7 @@ module QA merge_request = Resource::MergeRequest.fabricate_via_api! do |mr| mr.project = project - mr.id = merge_request[:iid] + mr.iid = merge_request[:iid] end.merge_via_api! expect(merge_request[:state]).to eq('merged') diff --git a/qa/qa/specs/features/api/3_create/merge_request/push_options_title_description_spec.rb b/qa/qa/specs/features/api/3_create/merge_request/push_options_title_description_spec.rb index f49a8a229dc..62e6290183f 100644 --- a/qa/qa/specs/features/api/3_create/merge_request/push_options_title_description_spec.rb +++ b/qa/qa/specs/features/api/3_create/merge_request/push_options_title_description_spec.rb @@ -39,7 +39,7 @@ module QA merge_request = Resource::MergeRequest.fabricate_via_api! do |mr| mr.project = project - mr.id = merge_request[:iid] + mr.iid = merge_request[:iid] end.merge_via_api! expect(merge_request[:state]).to eq('merged') diff --git a/qa/qa/specs/features/api/5_package/container_registry_spec.rb b/qa/qa/specs/features/api/5_package/container_registry_spec.rb index 57b059ffc02..5003d49fe6c 100644 --- a/qa/qa/specs/features/api/5_package/container_registry_spec.rb +++ b/qa/qa/specs/features/api/5_package/container_registry_spec.rb @@ -17,8 +17,8 @@ module QA end let(:registry) do - Resource::RegistryRepository.new.tap do |repository| - repository.name = "#{project.path_with_namespace}" + Resource::RegistryRepository.init do |repository| + repository.name = project.path_with_namespace repository.project = project repository.tag_name = 'master' end diff --git a/qa/qa/specs/features/browser_ui/1_manage/group/bulk_import_group_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/group/bulk_import_group_spec.rb index d4c4ec5611a..6c2ff005f49 100644 --- a/qa/qa/specs/features/browser_ui/1_manage/group/bulk_import_group_spec.rb +++ b/qa/qa/specs/features/browser_ui/1_manage/group/bulk_import_group_spec.rb @@ -3,31 +3,33 @@ module QA RSpec.describe 'Manage', :requires_admin do describe 'Bulk group import' do - let!(:admin_api_client) { Runtime::API::Client.as_admin } - let!(:user) do + let!(:staging?) { Runtime::Scenario.gitlab_address.include?('staging.gitlab.com') } + + let(:admin_api_client) { Runtime::API::Client.as_admin } + let(:user) do Resource::User.fabricate_via_api! do |usr| usr.api_client = admin_api_client usr.hard_delete_on_api_removal = true end end - let!(:api_client) { Runtime::API::Client.new(user: user) } - let!(:personal_access_token) { api_client.personal_access_token } + let(:api_client) { Runtime::API::Client.new(user: user) } + let(:personal_access_token) { api_client.personal_access_token } - let!(:sandbox) do + let(:sandbox) do Resource::Sandbox.fabricate_via_api! do |group| group.api_client = admin_api_client end end - let!(:source_group) do + let(:source_group) do Resource::Sandbox.fabricate_via_api! do |group| group.api_client = api_client group.path = "source-group-for-import-#{SecureRandom.hex(4)}" end end - let!(:subgroup) do + let(:subgroup) do Resource::Group.fabricate_via_api! do |group| group.api_client = api_client group.sandbox = source_group @@ -36,7 +38,7 @@ module QA end let(:imported_group) do - Resource::Group.new.tap do |group| + Resource::Group.init do |group| group.api_client = api_client group.sandbox = sandbox group.path = source_group.path @@ -44,25 +46,23 @@ module QA end let(:imported_subgroup) do - Resource::Group.new.tap do |group| + Resource::Group.init do |group| group.api_client = api_client group.sandbox = imported_group group.path = subgroup.path end end - def staging? - Runtime::Scenario.gitlab_address.include?('staging.gitlab.com') - end - - before(:all) do + before do Runtime::Feature.enable(:bulk_import) unless staging? Runtime::Feature.enable(:top_level_group_creation_enabled) if staging? - end - before do sandbox.add_member(user, Resource::Members::AccessLevel::MAINTAINER) + # create groups explicitly before connecting gitlab instance + source_group + subgroup + Flow::Login.sign_in(as: user) Page::Main::Menu.perform(&:go_to_create_group) Page::Group::New.perform do |group| @@ -73,14 +73,10 @@ module QA # Non blocking issues: # https://gitlab.com/gitlab-org/gitlab/-/issues/331252 + # https://gitlab.com/gitlab-org/gitlab/-/issues/333678 <- can cause 500 when creating user and group back to back it( 'imports group with subgroups and labels', - testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1785', - quarantine: { - only: { job: 'relative_url' }, - issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/330344', - type: :bug - } + testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1785' ) do Resource::GroupLabel.fabricate_via_api! do |label| label.api_client = api_client @@ -96,9 +92,9 @@ module QA Page::Group::BulkImport.perform do |import_page| import_page.import_group(source_group.path, sandbox.path) - aggregate_failures do - expect(import_page).to have_imported_group(source_group.path, wait: 180) + expect(import_page).to have_imported_group(source_group.path, wait: 180) + aggregate_failures do expect { imported_group.reload! }.to eventually_eq(source_group).within(duration: 10) expect { imported_group.labels }.to eventually_include(*source_group.labels).within(duration: 10) @@ -111,9 +107,7 @@ module QA after do user.remove_via_api! - end - - after(:all) do + ensure Runtime::Feature.disable(:bulk_import) unless staging? Runtime::Feature.disable(:top_level_group_creation_enabled) if staging? end diff --git a/qa/qa/specs/features/browser_ui/1_manage/login/register_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/login/register_spec.rb index 23a21d70cc1..696bbc2a7b7 100644 --- a/qa/qa/specs/features/browser_ui/1_manage/login/register_spec.rb +++ b/qa/qa/specs/features/browser_ui/1_manage/login/register_spec.rb @@ -143,7 +143,7 @@ module QA Page::Admin::Overview::Users::Show.perform do |show| user.id = show.user_id.to_i - show.approve_user + show.approve_user(user) end expect(page).to have_text('Successfully approved') diff --git a/qa/qa/specs/features/browser_ui/1_manage/project/create_project_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/project/create_project_spec.rb index e722710ee00..4fffc786c14 100644 --- a/qa/qa/specs/features/browser_ui/1_manage/project/create_project_spec.rb +++ b/qa/qa/specs/features/browser_ui/1_manage/project/create_project_spec.rb @@ -4,7 +4,7 @@ module QA RSpec.describe 'Manage', :smoke do describe 'Project creation' do it 'user creates a new project', - testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1234' do + testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1857' do Flow::Login.sign_in created_project = Resource::Project.fabricate_via_browser_ui! do |project| diff --git a/qa/qa/specs/features/browser_ui/1_manage/project/dashboard_images_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/project/dashboard_images_spec.rb index 95092fe6ba6..6e0ed4adb63 100644 --- a/qa/qa/specs/features/browser_ui/1_manage/project/dashboard_images_spec.rb +++ b/qa/qa/specs/features/browser_ui/1_manage/project/dashboard_images_spec.rb @@ -6,7 +6,7 @@ module QA RSpec.describe 'Manage', quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/212145', type: :stale } do describe 'Check for broken images', :requires_admin do before(:context) do - admin = QA::Resource::User.new.tap do |user| + admin = QA::Resource::User.init do |user| user.username = QA::Runtime::User.admin_username user.password = QA::Runtime::User.admin_password end diff --git a/qa/qa/specs/features/browser_ui/1_manage/project/import_github_repo_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/project/import_github_repo_spec.rb index 55ad8baf872..4f85fa257a2 100644 --- a/qa/qa/specs/features/browser_ui/1_manage/project/import_github_repo_spec.rb +++ b/qa/qa/specs/features/browser_ui/1_manage/project/import_github_repo_spec.rb @@ -18,7 +18,6 @@ module QA project.group = group project.github_personal_access_token = Runtime::Env.github_access_token project.github_repository_path = 'gitlab-qa-github/test-project' - project.api_client = api_client end end @@ -33,91 +32,13 @@ module QA it 'imports a GitHub repo', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1762' do Flow::Login.sign_in(as: user) - imported_project.reload! # import the project and reload all fields + imported_project # import the project - aggregate_failures do - verify_repository_import - verify_commits_import - verify_labels_import - verify_issues_import - verify_milestones_import - verify_wikis_import - verify_merge_requests_import + Page::Project::Show.perform do |project| + expect(project).to have_content(imported_project.name) + expect(project).to have_content('This test project is used for automated GitHub import by GitLab QA.') end end - - def verify_repository_import - expect(imported_project.api_response).to include( - description: 'A new repo for test', - import_status: 'finished', - import_error: nil - ) - end - - def verify_commits_import - expect(imported_project.commits.length).to eq(20) - end - - def verify_labels_import - labels = imported_project.labels.map { |label| label.slice(:name, :color) } - - expect(labels).to eq( - [ - { name: 'bug', color: '#d73a4a' }, - { name: 'custom new label', color: '#fc8f91' }, - { name: 'documentation', color: '#0075ca' }, - { name: 'duplicate', color: '#cfd3d7' }, - { name: 'enhancement', color: '#a2eeef' }, - { name: 'good first issue', color: '#7057ff' }, - { name: 'help wanted', color: '#008672' }, - { name: 'invalid', color: '#e4e669' }, - { name: 'question', color: '#d876e3' }, - { name: 'wontfix', color: '#ffffff' } - ] - ) - end - - def verify_issues_import - issues = imported_project.issues - - expect(issues.length).to eq(1) - expect(issues.first).to include( - title: 'This is a sample issue', - description: "*Created by: gitlab-qa-github*\n\nThis is a sample first comment", - labels: ['custom new label', 'good first issue', 'help wanted'], - user_notes_count: 1 - ) - end - - def verify_milestones_import - milestones = imported_project.milestones - - expect(milestones.length).to eq(1) - expect(milestones.first).to include(title: 'v1.0', description: nil, state: 'active') - end - - def verify_wikis_import - wikis = imported_project.wikis - - expect(wikis.length).to eq(1) - expect(wikis.first).to include(title: 'Home', format: 'markdown') - end - - def verify_merge_requests_import - merge_requests = imported_project.merge_requests - - expect(merge_requests.length).to eq(1) - expect(merge_requests.first).to include( - title: 'Improve readme', - state: 'opened', - target_branch: 'main', - source_branch: 'improve-readme', - labels: %w[bug documentation], - description: <<~DSC.strip - *Created by: gitlab-qa-github*\n\nThis improves the README file a bit.\r\n\r\nTODO:\r\n\r\n \r\n\r\n- [ ] Do foo\r\n- [ ] Make bar\r\n - [ ] Think about baz - DSC - ) - end end end end diff --git a/qa/qa/specs/features/browser_ui/2_plan/email/trigger_email_notification_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/email/trigger_email_notification_spec.rb index d352996f419..d561e5d113c 100644 --- a/qa/qa/specs/features/browser_ui/2_plan/email/trigger_email_notification_spec.rb +++ b/qa/qa/specs/features/browser_ui/2_plan/email/trigger_email_notification_spec.rb @@ -5,7 +5,7 @@ module QA describe 'Email Notification' do include Support::Api - let(:user) do + let!(:user) do Resource::User.fabricate_or_use(Runtime::Env.gitlab_qa_username_1, Runtime::Env.gitlab_qa_password_1) 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 8c0b3da6004..2243437fc71 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 @@ -39,20 +39,23 @@ module QA end context 'when using attachments in comments', :object_storage do - let(:gif_file_name) { 'banana_sample.gif' } + let(:png_file_name) { 'testfile.png' } let(:file_to_attach) do - File.absolute_path(File.join('qa', 'fixtures', 'designs', gif_file_name)) + File.absolute_path(File.join('qa', 'fixtures', 'designs', png_file_name)) end before do Resource::Issue.fabricate_via_api!.visit! end - it 'comments on an issue with an attachment', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1742' do + # The following example is excluded from running in `review-qa-smoke` job + # as it proved to be flaky when running against Review App + # See https://gitlab.com/gitlab-com/www-gitlab-com/-/issues/11568#note_621999351 + it 'comments on an issue with an attachment', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1742', except: { job: 'review-qa-smoke' } do Page::Project::Issue::Show.perform do |show| - show.comment('See attached banana for scale', attachment: file_to_attach) + show.comment('See attached image for scale', attachment: file_to_attach) - expect(show.noteable_note_item.find("img[src$='#{gif_file_name}']")).to be_visible + expect(show.noteable_note_item.find("img[src$='#{png_file_name}']")).to be_visible 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 dd4b3276687..a2b011bc61c 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,7 +1,7 @@ # frozen_string_literal: true module QA - RSpec.describe 'Create' do + RSpec.describe 'Create', quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/332588', type: :investigating } do describe 'Merge request creation from fork' do # TODO: Please add this back to :smoke suite as soon as https://gitlab.com/gitlab-org/gitlab/-/issues/332588 is addressed it 'can merge feature branch fork to mainline', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1701' do diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/revert/reverting_merge_request_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/revert/reverting_merge_request_spec.rb index 3574cdbe4ac..c05a3610b99 100644 --- a/qa/qa/specs/features/browser_ui/3_create/merge_request/revert/reverting_merge_request_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/revert/reverting_merge_request_spec.rb @@ -19,7 +19,7 @@ module QA Flow::Login.sign_in end - it 'can be reverted', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1745' do + it 'can be reverted', :can_use_large_setup, testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1745' do revertable_merge_request.visit! Page::MergeRequest::Show.perform do |merge_request| diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/push_http_private_token_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/push_http_private_token_spec.rb index 67b48d254ac..41fc20cfa5c 100644 --- a/qa/qa/specs/features/browser_ui/3_create/repository/push_http_private_token_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/repository/push_http_private_token_spec.rb @@ -8,7 +8,7 @@ module QA access_token = Resource::PersonalAccessToken.fabricate!.token - user = Resource::User.new.tap do |user| + user = Resource::User.init do |user| user.username = Runtime::User.username user.password = access_token end diff --git a/qa/qa/specs/features/browser_ui/4_verify/pipeline/pipeline_editor_branch_switcher_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/pipeline/pipeline_editor_branch_switcher_spec.rb new file mode 100644 index 00000000000..bb9b5feed2e --- /dev/null +++ b/qa/qa/specs/features/browser_ui/4_verify/pipeline/pipeline_editor_branch_switcher_spec.rb @@ -0,0 +1,90 @@ +# frozen_string_literal: true + +module QA + RSpec.describe 'Verify' do + describe 'Pipeline editor', :requires_admin do + let(:project) do + Resource::Project.fabricate_via_api! do |project| + project.name = 'pipeline-editor-project' + end + end + + let!(:commit) do + Resource::Repository::Commit.fabricate_via_api! do |commit| + commit.project = project + commit.commit_message = 'Add .gitlab-ci.yml' + commit.add_files( + [ + { + file_path: '.gitlab-ci.yml', + content: default_file_content + } + ] + ) + end + end + + let!(:production_push) do + Resource::Repository::Push.fabricate! do |push| + push.repository_http_uri = project.repository_http_location.uri + push.branch_name = 'production' + push.file_name = '.gitlab-ci.yml' + push.file_content = production_file_content + end + end + + let(:default_file_content) do + <<~YAML + stages: + - test + + initialize: + stage: test + script: + - echo "initialized in #{project.default_branch}" + YAML + end + + let(:production_file_content) do + <<~YAML + stages: + - test + + initialize: + stage: test + script: + - echo "initialized in production" + YAML + end + + before do + Runtime::Feature.enable(:pipeline_editor_branch_switcher) + Flow::Login.sign_in + project.visit! + Page::Project::Menu.perform(&:go_to_pipeline_editor) + end + + after do + Runtime::Feature.disable(:pipeline_editor_branch_switcher) + project.remove_via_api! + Page::Main::Menu.perform(&:sign_out) + end + + it 'can switch branches and target branch field updates accordingly', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1856' do + Page::Project::PipelineEditor::Show.perform do |show| + expect(show).to have_branch_selector_button + + show.click_branch_selector_button + show.select_branch_from_dropdown(production_push.branch_name) + + expect(show.target_branch_name).to eq(production_push.branch_name) + + show.click_branch_selector_button + show.select_branch_from_dropdown(project.default_branch) + + expect(show.target_branch_name).to eq(project.default_branch) + end + end + end + end +end diff --git a/qa/qa/specs/features/browser_ui/4_verify/testing/view_code_coverage_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/testing/view_code_coverage_spec.rb index d6d8729114d..07484feb686 100644 --- a/qa/qa/specs/features/browser_ui/4_verify/testing/view_code_coverage_spec.rb +++ b/qa/qa/specs/features/browser_ui/4_verify/testing/view_code_coverage_spec.rb @@ -40,7 +40,7 @@ module QA Page::MergeRequest::Show.perform do |mr_widget| Support::Retrier.retry_until(max_attempts: 5, sleep_interval: 5) do - mr_widget.has_pipeline_status?(/Pipeline #\d+ passed/) + mr_widget.has_pipeline_status?('passed') end expect(mr_widget).to have_content('Test coverage 66.67%') end diff --git a/qa/qa/specs/features/browser_ui/5_package/composer_registry_spec.rb b/qa/qa/specs/features/browser_ui/5_package/composer_registry_spec.rb index 2489545782a..61c71b062ae 100644 --- a/qa/qa/specs/features/browser_ui/5_package/composer_registry_spec.rb +++ b/qa/qa/specs/features/browser_ui/5_package/composer_registry_spec.rb @@ -3,7 +3,7 @@ require 'securerandom' module QA - RSpec.describe 'Package', :orchestrated, :packages do + RSpec.describe 'Package', :orchestrated, :packages, :object_storage do describe 'Composer Repository' do include Runtime::Fixtures @@ -14,7 +14,7 @@ module QA end let(:package) do - Resource::Package.new.tap do |package| + Resource::Package.init do |package| package.name = "my_package-#{SecureRandom.hex(4)}" package.project = project end diff --git a/qa/qa/specs/features/browser_ui/5_package/conan_repository_spec.rb b/qa/qa/specs/features/browser_ui/5_package/conan_repository_spec.rb index a1e2eb1046c..668a1524b1c 100644 --- a/qa/qa/specs/features/browser_ui/5_package/conan_repository_spec.rb +++ b/qa/qa/specs/features/browser_ui/5_package/conan_repository_spec.rb @@ -1,7 +1,11 @@ # frozen_string_literal: true module QA - RSpec.describe 'Package', :orchestrated, :packages do + RSpec.describe 'Package', :orchestrated, :packages, :object_storage, quarantine: { + only: { job: 'object_storage' }, + issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/335981', + type: :bug + } do describe 'Conan Repository' do include Runtime::Fixtures @@ -12,7 +16,7 @@ module QA end let(:package) do - Resource::Package.new.tap do |package| + Resource::Package.init do |package| package.name = 'conantest' package.project = project end diff --git a/qa/qa/specs/features/browser_ui/5_package/container_registry_omnibus_spec.rb b/qa/qa/specs/features/browser_ui/5_package/container_registry_omnibus_spec.rb new file mode 100644 index 00000000000..4b7669810ec --- /dev/null +++ b/qa/qa/specs/features/browser_ui/5_package/container_registry_omnibus_spec.rb @@ -0,0 +1,88 @@ +# frozen_string_literal: true + +module QA + RSpec.describe 'Package', :registry, :orchestrated do + describe 'Self-managed Container Registry' do + let(:project) do + Resource::Project.fabricate_via_api! do |project| + project.name = 'project-with-registry' + project.template_name = 'express' + end + end + + let!(:runner) do + Resource::Runner.fabricate! do |runner| + runner.name = "qa-runner-#{Time.now.to_i}" + runner.tags = ["runner-for-#{project.name}"] + runner.executor = :docker + runner.project = project + end + end + + before do + Flow::Login.sign_in + project.visit! + end + + after do + runner.remove_via_api! + end + + it "pushes image and deletes tag", testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1743' do + Resource::Repository::Commit.fabricate_via_api! do |commit| + commit.project = project + commit.commit_message = 'Add .gitlab-ci.yml' + commit.add_files([{ + file_path: '.gitlab-ci.yml', + content: + <<~YAML + build: + image: docker:19.03.12 + stage: build + services: + - name: docker:19.03.12-dind + command: + - /bin/sh + - -c + - | + apk add --no-cache openssl + true | openssl s_client -showcerts -connect gitlab.test:5050 > /usr/local/share/ca-certificates/gitlab.test.crt + update-ca-certificates + dockerd-entrypoint.sh || exit + variables: + IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG + script: + - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD gitlab.test:5050 + - docker build -t $IMAGE_TAG . + - docker push $IMAGE_TAG + tags: + - "runner-for-#{project.name}" + YAML + }]) + end + + Flow::Pipeline.visit_latest_pipeline + + Page::Project::Pipeline::Show.perform do |pipeline| + pipeline.click_job('build') + end + + Page::Project::Job::Show.perform do |job| + expect(job).to be_successful(timeout: 800) + end + + Page::Project::Menu.perform(&:go_to_container_registry) + + Page::Project::Registry::Show.perform do |registry| + expect(registry).to have_registry_repository(project.path_with_namespace) + + registry.click_on_image(project.path_with_namespace) + expect(registry).to have_tag('master') + + registry.click_delete + expect(registry).not_to have_tag('master') + end + end + end + end +end diff --git a/qa/qa/specs/features/browser_ui/5_package/generic_repository_spec.rb b/qa/qa/specs/features/browser_ui/5_package/generic_repository_spec.rb index bbf8ea8c05e..ef5965b29e5 100644 --- a/qa/qa/specs/features/browser_ui/5_package/generic_repository_spec.rb +++ b/qa/qa/specs/features/browser_ui/5_package/generic_repository_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - RSpec.describe 'Package', :orchestrated, :packages do + RSpec.describe 'Package', :orchestrated, :packages, :object_storage do describe 'Generic Repository' do let(:project) do Resource::Project.fabricate_via_api! do |project| @@ -10,7 +10,7 @@ module QA end let(:package) do - Resource::Package.new.tap do |package| + Resource::Package.init do |package| package.name = "my_package" package.project = project end diff --git a/qa/qa/specs/features/browser_ui/5_package/maven_gradle_repository_spec.rb b/qa/qa/specs/features/browser_ui/5_package/maven_gradle_repository_spec.rb index 4d4f981f021..32a0670e342 100644 --- a/qa/qa/specs/features/browser_ui/5_package/maven_gradle_repository_spec.rb +++ b/qa/qa/specs/features/browser_ui/5_package/maven_gradle_repository_spec.rb @@ -1,41 +1,47 @@ # frozen_string_literal: true module QA - RSpec.describe 'Package', :orchestrated, :packages do + RSpec.describe 'Package', :orchestrated, :packages, :object_storage do describe 'Maven Repository with Gradle' do + using RSpec::Parameterized::TableSyntax include Runtime::Fixtures let(:group_id) { 'com.gitlab.qa' } let(:artifact_id) { 'maven_gradle' } let(:package_name) { "#{group_id}/#{artifact_id}".tr('.', '/') } - let(:auth_token) do - unless Page::Main::Menu.perform(&:signed_in?) - Flow::Login.sign_in - end + let(:package_version) { '1.3.7' } - Resource::PersonalAccessToken.fabricate!.token - end + let(:personal_access_token) { Runtime::Env.personal_access_token } - let(:project) do + let(:package_project) do Resource::Project.fabricate_via_api! do |project| project.name = 'maven-with-gradle-project' project.initialize_with_readme = true + project.visibility = :private + end + end + + let(:client_project) do + Resource::Project.fabricate_via_api! do |client_project| + client_project.name = 'gradle_client' + client_project.initialize_with_readme = true + client_project.group = package_project.group end end let(:package) do - Resource::Package.new.tap do |package| + Resource::Package.init do |package| package.name = package_name - package.project = project + package.project = package_project end end - let!(:runner) do + let(:runner) do Resource::Runner.fabricate! do |runner| runner.name = "qa-runner-#{Time.now.to_i}" - runner.tags = ["runner-for-#{project.name}"] + runner.tags = ["runner-for-#{package_project.group.name}"] runner.executor = :docker - runner.project = project + runner.token = package_project.group.runners_token end end @@ -44,87 +50,200 @@ module QA "#{uri.scheme}://#{uri.host}:#{uri.port}" end + let(:project_deploy_token) do + Resource::DeployToken.fabricate_via_browser_ui! do |deploy_token| + deploy_token.name = 'maven-with-gradle-deploy-token' + deploy_token.project = package_project + end + end + + let(:package_gitlab_ci_file) do + { + file_path: '.gitlab-ci.yml', + content: + <<~YAML + deploy: + image: gradle:6.5-jdk11 + script: + - 'gradle publish' + only: + - "#{package_project.default_branch}" + tags: + - "runner-for-#{package_project.group.name}" + YAML + } + end + + let(:package_build_gradle_file) do + { + file_path: 'build.gradle', + content: + <<~EOF + plugins { + id 'java' + id 'maven-publish' + } + + publishing { + publications { + library(MavenPublication) { + groupId '#{group_id}' + artifactId '#{artifact_id}' + version '#{package_version}' + from components.java + } + } + repositories { + maven { + url "#{gitlab_address_with_port}/api/v4/projects/#{package_project.id}/packages/maven" + credentials(HttpHeaderCredentials) { + name = "Private-Token" + value = "#{personal_access_token}" + } + authentication { + header(HttpHeaderAuthentication) + } + } + } + } + EOF + } + end + + let(:client_gitlab_ci_file) do + { + file_path: '.gitlab-ci.yml', + content: + <<~YAML + build: + image: gradle:6.5-jdk11 + script: + - 'gradle build' + only: + - "#{client_project.default_branch}" + tags: + - "runner-for-#{client_project.group.name}" + YAML + } + end + + before do + Flow::Login.sign_in_unless_signed_in + runner + end + after do runner.remove_via_api! package.remove_via_api! + package_project.remove_via_api! + client_project.remove_via_api! end - it 'publishes a maven package via gradle', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1074' do - Resource::Repository::Commit.fabricate_via_api! do |commit| - commit.project = project - commit.commit_message = 'Add .gitlab-ci.yml' - commit.add_files([{ - file_path: '.gitlab-ci.yml', - content: - <<~YAML - deploy: - image: gradle:6.5-jdk11 - script: - - 'gradle publish' - only: - - "#{project.default_branch}" - tags: - - "runner-for-#{project.name}" - YAML - }, - { - file_path: 'build.gradle', - content: - <<~EOF - plugins { - id 'java' - id 'maven-publish' - } - - publishing { - publications { - library(MavenPublication) { - groupId '#{group_id}' - artifactId '#{artifact_id}' - from components.java - } - } - repositories { - maven { - url "#{gitlab_address_with_port}/api/v4/projects/#{project.id}/packages/maven" - credentials(HttpHeaderCredentials) { - name = "Private-Token" - value = "#{auth_token}" - } - authentication { - header(HttpHeaderAuthentication) - } - } - } - } - EOF - }]) - end - - project.visit! - Flow::Pipeline.visit_latest_pipeline + where(:authentication_token_type, :maven_header_name) do + :personal_access_token | 'Private-Token' + :ci_job_token | 'Job-Token' + :project_deploy_token | 'Deploy-Token' + end - Page::Project::Pipeline::Show.perform do |pipeline| - pipeline.click_job('deploy') + with_them do + let(:token) do + case authentication_token_type + when :personal_access_token + "\"#{personal_access_token}\"" + when :ci_job_token + 'System.getenv("CI_JOB_TOKEN")' + when :project_deploy_token + "\"#{project_deploy_token.password}\"" + end end - Page::Project::Job::Show.perform do |job| - expect(job).to be_successful(timeout: 800) + let(:client_build_gradle_file) do + { + file_path: 'build.gradle', + content: + <<~EOF + plugins { + id 'java' + id 'application' + } + + repositories { + jcenter() + maven { + url "#{gitlab_address_with_port}/api/v4/projects/#{package_project.id}/packages/maven" + name "GitLab" + credentials(HttpHeaderCredentials) { + name = '#{maven_header_name}' + value = #{token} + } + authentication { + header(HttpHeaderAuthentication) + } + } + } + + dependencies { + implementation group: '#{group_id}', name: '#{artifact_id}', version: '#{package_version}' + testImplementation 'junit:junit:4.12' + } + + application { + mainClassName = 'gradle_maven_app.App' + } + EOF + } end - Page::Project::Menu.perform(&:click_packages_link) + it "pushes and pulls a maven package via gradle using #{params[:authentication_token_type]}", testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1074' do + # pushing + Resource::Repository::Commit.fabricate_via_api! do |commit| + commit.project = package_project + commit.commit_message = 'Add .gitlab-ci.yml' + commit.add_files([package_gitlab_ci_file, package_build_gradle_file]) + end - Page::Project::Packages::Index.perform do |index| - expect(index).to have_package(package_name) + package_project.visit! - index.click_package(package_name) - end + Flow::Pipeline.visit_latest_pipeline + + Page::Project::Pipeline::Show.perform do |pipeline| + pipeline.click_job('deploy') + end + + Page::Project::Job::Show.perform do |job| + expect(job).to be_successful(timeout: 800) + end + + Page::Project::Menu.perform(&:click_packages_link) + + Page::Project::Packages::Index.perform do |index| + expect(index).to have_package(package_name) + + index.click_package(package_name) + end + + Page::Project::Packages::Show.perform do |show| + expect(show).to have_package_info(package_name, package_version) + end + + # pulling + Resource::Repository::Commit.fabricate_via_api! do |commit| + commit.project = client_project + commit.commit_message = 'Add .gitlab-ci.yml' + commit.add_files([client_gitlab_ci_file, client_build_gradle_file]) + end + + client_project.visit! + + Flow::Pipeline.visit_latest_pipeline - Page::Project::Packages::Show.perform(&:click_delete) + Page::Project::Pipeline::Show.perform do |pipeline| + pipeline.click_job('build') + end - Page::Project::Packages::Index.perform do |index| - expect(index).to have_content("Package deleted successfully") - expect(index).not_to have_package(package_name) + Page::Project::Job::Show.perform do |job| + expect(job).to be_successful(timeout: 800) + end end end end diff --git a/qa/qa/specs/features/browser_ui/5_package/maven_repository_spec.rb b/qa/qa/specs/features/browser_ui/5_package/maven_repository_spec.rb index 9c00f1f6d17..fb92616ffc5 100644 --- a/qa/qa/specs/features/browser_ui/5_package/maven_repository_spec.rb +++ b/qa/qa/specs/features/browser_ui/5_package/maven_repository_spec.rb @@ -3,7 +3,7 @@ require 'securerandom' module QA - RSpec.describe 'Package', :orchestrated, :packages, :reliable do + RSpec.describe 'Package', :orchestrated, :packages, :reliable, :object_storage do describe 'Maven Repository' do include Runtime::Fixtures @@ -33,7 +33,7 @@ module QA end let(:package) do - Resource::Package.new.tap do |package| + Resource::Package.init do |package| package.name = package_name package.project = project end diff --git a/qa/qa/specs/features/browser_ui/5_package/npm_registry_spec.rb b/qa/qa/specs/features/browser_ui/5_package/npm_registry_spec.rb index c4bfaacca11..2322d18a9ba 100644 --- a/qa/qa/specs/features/browser_ui/5_package/npm_registry_spec.rb +++ b/qa/qa/specs/features/browser_ui/5_package/npm_registry_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - RSpec.describe 'Package', :orchestrated, :packages, :reliable do + RSpec.describe 'Package', :orchestrated, :packages, :reliable, :object_storage do describe 'npm registry' do include Runtime::Fixtures @@ -50,7 +50,7 @@ module QA stages: - deploy - + deploy: stage: deploy script: @@ -72,7 +72,7 @@ module QA stages: - install - + install: stage: install script: @@ -120,7 +120,7 @@ module QA end let(:package) do - Resource::Package.new.tap do |package| + Resource::Package.init do |package| package.name = "@#{registry_scope}/#{project.name}" package.project = project end diff --git a/qa/qa/specs/features/browser_ui/5_package/nuget_repository_spec.rb b/qa/qa/specs/features/browser_ui/5_package/nuget_repository_spec.rb index daf41f1c6ab..1f62b285798 100644 --- a/qa/qa/specs/features/browser_ui/5_package/nuget_repository_spec.rb +++ b/qa/qa/specs/features/browser_ui/5_package/nuget_repository_spec.rb @@ -3,7 +3,7 @@ require 'securerandom' module QA - RSpec.describe 'Package', :orchestrated, :packages do + RSpec.describe 'Package', :orchestrated, :packages, :object_storage do describe 'NuGet Repository' do include Runtime::Fixtures let(:project) do @@ -14,7 +14,7 @@ module QA end let(:package) do - Resource::Package.new.tap do |package| + Resource::Package.init do |package| package.name = "dotnetcore-#{SecureRandom.hex(8)}" package.project = project end diff --git a/qa/qa/specs/features/browser_ui/5_package/online_garbage_collection_spec.rb b/qa/qa/specs/features/browser_ui/5_package/online_garbage_collection_spec.rb new file mode 100644 index 00000000000..65fc12545b7 --- /dev/null +++ b/qa/qa/specs/features/browser_ui/5_package/online_garbage_collection_spec.rb @@ -0,0 +1,108 @@ +# frozen_string_literal: true + +module QA + RSpec.describe 'Package' do + describe 'Container Registry Online Garbage Collection', :registry_gc, only: { subdomain: %i[pre] } do + let(:group) { Resource::Group.fabricate_via_api! } + + let(:imported_project) do + Resource::ProjectImportedFromURL.fabricate_via_browser_ui! do |project| + project.name = 'container-registry' + project.group = group + project.gitlab_repository_path = 'https://gitlab.com/gitlab-org/container-registry.git' + end + end + + let!(:gitlab_ci_yaml) do + <<~YAML + variables: + GOPATH: $CI_PROJECT_DIR/.go + BUILD_CACHE: $CI_PROJECT_DIR/.online-gc-tester + STAGE_ONE_VALIDATION_DELAY: "6m" + STAGE_TWO_VALIDATION_DELAY: "12m" + STAGE_THREE_VALIDATION_DELAY: "6m" + STAGE_FOUR_VALIDATION_DELAY: "12m" + STAGE_FIVE_VALIDATION_DELAY: "12m" + + stages: + - generate + - build + - test + + .base: &base + image: docker:19 + services: + - docker:19-dind + variables: + DOCKER_HOST: tcp://docker:2376 + DOCKER_TLS_CERTDIR: "/certs" + DOCKER_TLS_VERIFY: 1 + DOCKER_CERT_PATH: "$DOCKER_TLS_CERTDIR/client" + before_script: + - until docker info; do sleep 1; done + - mkdir -p $GOPATH + - mkdir -p $BUILD_CACHE + - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY + + test: + stage: generate + extends: .base + script: + - apk add go make git + - make binaries + - ./bin/online-gc-tester generate --base-dir=$BUILD_CACHE + - ./bin/online-gc-tester build --base-dir=$BUILD_CACHE + - ./bin/online-gc-tester push --base-dir=$BUILD_CACHE + - ./bin/online-gc-tester pull --base-dir=$BUILD_CACHE + - ./bin/online-gc-tester test --base-dir=$BUILD_CACHE --stage=1 --delay=$STAGE_ONE_VALIDATION_DELAY + - ./bin/online-gc-tester test --base-dir=$BUILD_CACHE --stage=2 --delay=$STAGE_TWO_VALIDATION_DELAY + - ./bin/online-gc-tester test --base-dir=$BUILD_CACHE --stage=3 --delay=$STAGE_THREE_VALIDATION_DELAY + - ./bin/online-gc-tester test --base-dir=$BUILD_CACHE --stage=4 --delay=$STAGE_FOUR_VALIDATION_DELAY + - ./bin/online-gc-tester test --base-dir=$BUILD_CACHE --stage=5 --delay=$STAGE_FIVE_VALIDATION_DELAY + timeout: 1h 30m + YAML + end + + before do + Flow::Login.sign_in + + imported_project + + Page::Project::Menu.perform(&:go_to_repository_settings) + + Page::Project::Settings::Repository.perform do |setting| + setting.expand_default_branch + end + + Page::Project::Settings::DefaultBranch.perform do |setting| + setting.set_default_branch('online-gc-test-builder-poc') + setting.click_save_changes_button + end + + Resource::Repository::Commit.fabricate_via_api! do |commit| + commit.project = imported_project + commit.branch = 'online-gc-test-builder-poc' + commit.commit_message = 'Update .gitlab-ci.yml' + commit.update_files([{ + file_path: '.gitlab-ci.yml', + content: gitlab_ci_yaml + }]) + end + end + + it 'runs the online garbage collector tool', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1854' do + imported_project.visit! + + Flow::Pipeline.visit_latest_pipeline + + Page::Project::Pipeline::Show.perform do |pipeline| + pipeline.click_job('test') + end + + Page::Project::Job::Show.perform do |job| + expect(job).to be_successful(timeout: 3900) + end + end + end + end +end diff --git a/qa/qa/specs/features/browser_ui/5_package/pypi_repository_spec.rb b/qa/qa/specs/features/browser_ui/5_package/pypi_repository_spec.rb index fb29af43da6..a9034174cab 100644 --- a/qa/qa/specs/features/browser_ui/5_package/pypi_repository_spec.rb +++ b/qa/qa/specs/features/browser_ui/5_package/pypi_repository_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - RSpec.describe 'Package', :orchestrated, :packages do + RSpec.describe 'Package', :orchestrated, :packages, :object_storage do describe 'PyPI Repository' do include Runtime::Fixtures let(:project) do @@ -11,7 +11,7 @@ module QA end let(:package) do - Resource::Package.new.tap do |package| + Resource::Package.init do |package| package.name = 'mypypipackage' package.project = project end diff --git a/qa/qa/specs/features/browser_ui/5_package/rubygems_registry_spec.rb b/qa/qa/specs/features/browser_ui/5_package/rubygems_registry_spec.rb index 5d15885cd67..530a3243766 100644 --- a/qa/qa/specs/features/browser_ui/5_package/rubygems_registry_spec.rb +++ b/qa/qa/specs/features/browser_ui/5_package/rubygems_registry_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - RSpec.describe 'Package', :orchestrated, :requires_admin, :packages do + RSpec.describe 'Package', :orchestrated, :requires_admin, :packages, :object_storage do describe 'RubyGems Repository' do include Runtime::Fixtures @@ -12,7 +12,7 @@ module QA end let(:package) do - Resource::Package.new.tap do |package| + Resource::Package.init do |package| package.name = 'mygem' package.project = project end diff --git a/qa/qa/specs/helpers/context_selector.rb b/qa/qa/specs/helpers/context_selector.rb index 4313f7c34dd..40ecb9b3506 100644 --- a/qa/qa/specs/helpers/context_selector.rb +++ b/qa/qa/specs/helpers/context_selector.rb @@ -13,15 +13,17 @@ module QA config.before do |example| if example.metadata.key?(:only) skip('Test is not compatible with this environment or pipeline') unless ContextSelector.context_matches?(example.metadata[:only]) - elsif example.metadata.key?(:exclude) - skip('Test is excluded in this job') if ContextSelector.exclude?(example.metadata[:exclude]) + elsif example.metadata.key?(:except) + skip('Test is excluded in this job') if ContextSelector.except?(example.metadata[:except]) end end end end - def exclude?(*options) - return false unless Runtime::Env.ci_job_name.present? + def except?(*options) + return false if Runtime::Env.ci_job_name.blank? && options.any? { |o| o.is_a?(Hash) && o[:job].present? } + return false if Runtime::Env.ci_project_name.blank? && options.any? { |o| o.is_a?(Hash) && o[:pipeline].present? } + return false if Runtime::Scenario.attributes[:gitlab_address].blank? context_matches?(*options) end @@ -40,10 +42,14 @@ module QA next unless option.is_a?(Hash) - if option[:pipeline].present? && Runtime::Env.ci_project_name.present? + if option[:pipeline].present? + return true if Runtime::Env.ci_project_name.blank? + return pipeline_matches?(option[:pipeline]) elsif option[:job].present? + return true if Runtime::Env.ci_job_name.blank? + return job_matches?(option[:job]) elsif option[:subdomain].present? diff --git a/qa/qa/specs/runner.rb b/qa/qa/specs/runner.rb index ff690962db8..bd9907611c7 100644 --- a/qa/qa/specs/runner.rb +++ b/qa/qa/specs/runner.rb @@ -65,6 +65,8 @@ module QA args.push(DEFAULT_TEST_PATH_ARGS) unless options.any? { |opt| opt =~ %r{/features/} } end + Runtime::Scenario.define(:large_setup?, args.flatten.include?('can_use_large_setup')) + if Runtime::Scenario.attributes[:parallel] ParallelRunner.run(args.flatten) elsif Runtime::Scenario.attributes[:loop] diff --git a/qa/spec/runtime/env_spec.rb b/qa/spec/runtime/env_spec.rb index 8218ab428b0..1d702b70d10 100644 --- a/qa/spec/runtime/env_spec.rb +++ b/qa/spec/runtime/env_spec.rb @@ -54,10 +54,10 @@ RSpec.describe QA::Runtime::Env do default: false end - describe '.chrome_headless?' do + describe '.webdriver_headless?' do it_behaves_like 'boolean method', - method: :chrome_headless?, - env_key: 'CHROME_HEADLESS', + method: :webdriver_headless?, + env_key: 'WEBDRIVER_HEADLESS', default: true end diff --git a/qa/spec/service/docker_run/gitlab_runner_spec.rb b/qa/spec/service/docker_run/gitlab_runner_spec.rb index 34d95943321..a8838db10cf 100644 --- a/qa/spec/service/docker_run/gitlab_runner_spec.rb +++ b/qa/spec/service/docker_run/gitlab_runner_spec.rb @@ -130,7 +130,7 @@ module QA end it 'mounts the docker socket to the host runner' do - expect(subject).to have_received(:shell).with(/-v \/var\/run\/docker.sock:\/var\/run\/docker.sock /) + expect(subject).to have_received(:shell).with(%r{-v /var/run/docker.sock:/var/run/docker.sock }) end it 'runs in privileged mode' do diff --git a/qa/spec/specs/helpers/context_selector_spec.rb b/qa/spec/specs/helpers/context_selector_spec.rb index 7792d33dcf9..f0250103008 100644 --- a/qa/spec/specs/helpers/context_selector_spec.rb +++ b/qa/spec/specs/helpers/context_selector_spec.rb @@ -49,8 +49,10 @@ RSpec.describe QA::Specs::Helpers::ContextSelector do it 'matches multiple subdomains' do QA::Runtime::Scenario.define(:gitlab_address, "https://staging.gitlab.com") - expect(described_class.context_matches?(subdomain: [:release, :staging])).to be_truthy - expect(described_class.context_matches?(:production, subdomain: [:release, :staging])).to be_truthy + aggregate_failures do + expect(described_class.context_matches?(subdomain: [:release, :staging])).to be_truthy + expect(described_class.context_matches?(:production, subdomain: [:release, :staging])).to be_truthy + end end it 'matches :production' do @@ -87,6 +89,16 @@ RSpec.describe QA::Specs::Helpers::ContextSelector do expect(group.examples[0].execution_result.status).to eq(:passed) end + + context 'when excluding contexts' do + it 'can apply to contexts or descriptions' do + group = describe_successfully 'skips staging', except: { subdomain: :staging } do + it('skips staging') {} + end + + expect(group.examples[0].execution_result.status).to eq(:pending) + end + end end context 'with different environment set' do @@ -102,6 +114,16 @@ RSpec.describe QA::Specs::Helpers::ContextSelector do expect(group.examples[0].execution_result.status).to eq(:pending) end + + context 'when excluding contexts' do + it 'runs against production' do + group = describe_successfully 'Runs in staging', :something, except: { subdomain: :staging } do + it('runs in staging') {} + end + + expect(group.examples[0].execution_result.status).to eq(:passed) + end + end end end @@ -113,10 +135,28 @@ RSpec.describe QA::Specs::Helpers::ContextSelector do it('runs in any env') {} end - expect(group.examples[0].execution_result.status).to eq(:passed) - expect(group.examples[1].execution_result.status).to eq(:pending) - expect(group.examples[2].execution_result.status).to eq(:passed) - expect(group.examples[3].execution_result.status).to eq(:passed) + aggregate_failures do + expect(group.examples[0].execution_result.status).to eq(:passed) + expect(group.examples[1].execution_result.status).to eq(:pending) + expect(group.examples[2].execution_result.status).to eq(:passed) + expect(group.examples[3].execution_result.status).to eq(:passed) + end + end + + context 'when excluding contexts' do + it 'skips staging' do + group = describe_successfully do + it('skips staging', except: { subdomain: :staging }) {} + it('runs in staging', except: :production) {} + it('skips staging also', except: { subdomain: %i[release staging] }) {} + end + + aggregate_failures do + expect(group.examples[0].execution_result.status).to eq(:pending) + expect(group.examples[1].execution_result.status).to eq(:passed) + expect(group.examples[2].execution_result.status).to eq(:pending) + end + end end context 'custom env' do @@ -130,8 +170,24 @@ RSpec.describe QA::Specs::Helpers::ContextSelector do it('does not run on release', only: :production) {} end - expect(group.examples.first.execution_result.status).to eq(:passed) - expect(group.examples.last.execution_result.status).to eq(:pending) + aggregate_failures do + expect(group.examples.first.execution_result.status).to eq(:passed) + expect(group.examples.last.execution_result.status).to eq(:pending) + end + end + + context 'when excluding contexts' do + it 'skips a custom environment' do + group = describe_successfully do + it('skips release gitlab net', except: { tld: '.net', subdomain: :release, domain: 'gitlab' }) {} + it('runs on release', except: :production) {} + end + + aggregate_failures do + expect(group.examples.first.execution_result.status).to eq(:pending) + expect(group.examples.last.execution_result.status).to eq(:passed) + end + end end end @@ -147,9 +203,27 @@ RSpec.describe QA::Specs::Helpers::ContextSelector do it('runs in prod and staging', only: { subdomain: /(staging.)?/, domain: 'gitlab' }) {} end - expect(group.examples[0].execution_result.status).to eq(:passed) - expect(group.examples[1].execution_result.status).to eq(:pending) - expect(group.examples[2].execution_result.status).to eq(:passed) + aggregate_failures do + expect(group.examples[0].execution_result.status).to eq(:passed) + expect(group.examples[1].execution_result.status).to eq(:pending) + expect(group.examples[2].execution_result.status).to eq(:passed) + end + end + + context 'when excluding contexts' do + it 'skips production' do + group = describe_successfully do + it('skips prod', except: :production) {} + it('runs on prod', except: { subdomain: :staging }) {} + it('skips prod and staging', except: { subdomain: /(staging.)?/, domain: 'gitlab' }) {} + end + + aggregate_failures do + expect(group.examples[0].execution_result.status).to eq(:pending) + expect(group.examples[1].execution_result.status).to eq(:passed) + expect(group.examples[2].execution_result.status).to eq(:pending) + end + end end end @@ -179,6 +253,21 @@ RSpec.describe QA::Specs::Helpers::ContextSelector do expect(group.examples[1].execution_result.status).to eq(:passed) end end + + context 'when excluding contexts' do + it 'runs in any pipeline' do + group = describe_successfully do + it('runs given a single named pipeline', except: { pipeline: :nightly }) {} + it('runs given an array of pipelines', except: { pipeline: [:canary, :not_nightly] }) {} + end + + aggregate_failures do + group.examples.each do |example| + expect(example.execution_result.status).to eq(:passed) + end + end + end + end end context 'when a pipeline triggered from the default branch runs in gitlab-qa' do @@ -200,6 +289,22 @@ RSpec.describe QA::Specs::Helpers::ContextSelector do expect(group.examples[2].execution_result.status).to eq(:pending) end end + + context 'when excluding contexts' do + it 'skips default branch pipelines' do + group = describe_successfully do + it('skips main pipeline given a single pipeline', except: { pipeline: :main }) {} + it('skips main given an array of pipelines', except: { pipeline: [:canary, :main] }) {} + it('runs non-default pipelines', except: { pipeline: [:nightly, :not_nightly, :not_main] }) {} + end + + aggregate_failures do + expect(group.examples[0].execution_result.status).to eq(:pending) + expect(group.examples[1].execution_result.status).to eq(:pending) + expect(group.examples[2].execution_result.status).to eq(:passed) + end + end + end end context 'with CI_PROJECT_NAME set' do @@ -223,24 +328,42 @@ RSpec.describe QA::Specs::Helpers::ContextSelector do expect(group.examples[3].execution_result.status).to eq(:pending) end end + + context 'when excluding contexts' do + it 'skips designated pipeline' do + group = describe_successfully do + it('skips nightly', except: { pipeline: :nightly }) {} + it('runs in not_nightly', except: { pipeline: :not_nightly }) {} + it('skips on nightly given an array', except: { pipeline: [:canary, :nightly] }) {} + it('runs in not_nightly given an array', except: { pipeline: [:not_nightly, :canary] }) {} + end + + aggregate_failures do + expect(group.examples[0].execution_result.status).to eq(:pending) + expect(group.examples[1].execution_result.status).to eq(:passed) + expect(group.examples[2].execution_result.status).to eq(:pending) + expect(group.examples[3].execution_result.status).to eq(:passed) + end + end + end end end - context 'when excluding contexts' do - context 'with job constraints' do - context 'without CI_JOB_NAME set' do - before do - stub_env('CI_JOB_NAME', nil) - described_class.configure_rspec - end + context 'with job constraints' do + context 'without CI_JOB_NAME set' do + before do + stub_env('CI_JOB_NAME', nil) + described_class.configure_rspec + end + context 'when excluding contexts' do it 'runs in any job' do group = describe_successfully do - it('runs given a single named job', exclude: { job: 'ee:instance-image' }) {} - it('runs given a single regex pattern', exclude: { job: '.*:instance-image' }) {} - it('runs given an array of jobs', exclude: { job: %w[ee:instance-image qa-schedules-browser_ui-3_create] }) {} - it('runs given an array of regex patterns', exclude: { job: %w[ee:.* qa-schedules-browser_ui.*] }) {} - it('runs given a mix of strings and regex patterns', exclude: { job: %w[ee:instance-image qa-schedules-browser_ui.*] }) {} + it('runs given a single named job', except: { job: 'ee:instance-image' }) {} + it('runs given a single regex pattern', except: { job: '.*:instance-image' }) {} + it('runs given an array of jobs', except: { job: %w[ee:instance-image qa-schedules-browser_ui-3_create] }) {} + it('runs given an array of regex patterns', except: { job: %w[ee:.* qa-schedules-browser_ui.*] }) {} + it('runs given a mix of strings and regex patterns', except: { job: %w[ee:instance-image qa-schedules-browser_ui.*] }) {} end aggregate_failures do @@ -251,19 +374,39 @@ RSpec.describe QA::Specs::Helpers::ContextSelector do end end - context 'with CI_JOB_NAME set' do - before do - stub_env('CI_JOB_NAME', 'ee:instance-image') - described_class.configure_rspec + context 'when including only specific contexts' do + it 'runs in any job' do + group = describe_successfully do + it('runs given a single named job', only: { job: 'ee:instance-image' }) {} + it('runs given a single regex pattern', only: { job: '.*:instance-image' }) {} + it('runs given an array of jobs', only: { job: %w[ee:instance-image qa-schedules-browser_ui-3_create] }) {} + it('runs given an array of regex patterns', only: { job: %w[ee:.* qa-schedules-browser_ui.*] }) {} + it('runs given a mix of strings and regex patterns', only: { job: %w[ee:instance-image qa-schedules-browser_ui.*] }) {} + end + + aggregate_failures do + group.examples.each do |example| + expect(example.execution_result.status).to eq(:passed) + end + end end + end + end + + context 'with CI_JOB_NAME set' do + before do + stub_env('CI_JOB_NAME', 'ee:instance-image') + described_class.configure_rspec + end + context 'when excluding contexts' do it 'does not run in the specified job' do group = describe_successfully do - it('skips given a single named job', exclude: { job: 'ee:instance-image' }) {} - it('skips given a single regex pattern', exclude: { job: '.*:instance-image' }) {} - it('skips given an array of jobs', exclude: { job: %w[ee:instance-image qa-schedules-browser_ui-3_create] }) {} - it('skips given an array of regex patterns', exclude: { job: %w[ee:.* qa-schedules-browser_ui.*] }) {} - it('skips given a mix of strings and regex patterns', exclude: { job: %w[ee:instance-image qa-schedules-browser_ui.*] }) {} + it('skips given a single named job', except: { job: 'ee:instance-image' }) {} + it('skips given a single regex pattern', except: { job: '.*:instance-image' }) {} + it('skips given an array of jobs', except: { job: %w[ee:instance-image qa-schedules-browser_ui-3_create] }) {} + it('skips given an array of regex patterns', except: { job: %w[ee:.* qa-schedules-browser_ui.*] }) {} + it('skips given a mix of strings and regex patterns', except: { job: %w[ee:instance-image qa-schedules-browser_ui.*] }) {} end aggregate_failures do @@ -275,11 +418,11 @@ RSpec.describe QA::Specs::Helpers::ContextSelector do it 'runs in jobs that do not match' do group = describe_successfully do - it('runs given a single named job', exclude: { job: 'ce:instance-image' }) {} - it('runs given a single regex pattern', exclude: { job: '.*:instance-image-quarantine' }) {} - it('runs given an array of jobs', exclude: { job: %w[ce:instance-image qa-schedules-browser_ui-3_create] }) {} - it('runs given an array of regex patterns', exclude: { job: %w[ce:.* qa-schedules-browser_ui.*] }) {} - it('runs given a mix of strings and regex patterns', exclude: { job: %w[ce:instance-image qa-schedules-browser_ui.*] }) {} + it('runs given a single named job', except: { job: 'ce:instance-image' }) {} + it('runs given a single regex pattern', except: { job: '.*:instance-image-quarantine' }) {} + it('runs given an array of jobs', except: { job: %w[ce:instance-image qa-schedules-browser_ui-3_create] }) {} + it('runs given an array of regex patterns', except: { job: %w[ce:.* qa-schedules-browser_ui.*] }) {} + it('runs given a mix of strings and regex patterns', except: { job: %w[ce:instance-image qa-schedules-browser_ui.*] }) {} end aggregate_failures do @@ -289,6 +432,40 @@ RSpec.describe QA::Specs::Helpers::ContextSelector do end end end + + context 'when including only specific contexts' do + it 'runs only in the specified jobs' do + group = describe_successfully do + it('runs given a single named job', only: { job: 'ee:instance-image' }) {} + it('runs given a single regex pattern', only: { job: '.*:instance-image' }) {} + it('runs given an array of jobs', only: { job: %w[ee:instance-image qa-schedules-browser_ui-3_create] }) {} + it('runs given an array of regex patterns', only: { job: %w[ee:.* qa-schedules-browser_ui.*] }) {} + it('runs given a mix of strings and regex patterns', only: { job: %w[ee:instance-image qa-schedules-browser_ui.*] }) {} + end + + aggregate_failures do + group.examples.each do |example| + expect(example.execution_result.status).to eq(:passed) + end + end + end + + it 'does not run in jobs that do not match' do + group = describe_successfully do + it('skips given a single named job', only: { job: 'ce:instance-image' }) {} + it('skips given a single regex pattern', only: { job: '.*:instance-image-quarantine' }) {} + it('skips given an array of jobs', only: { job: %w[ce:instance-image qa-schedules-browser_ui-3_create] }) {} + it('skips given an array of regex patterns', only: { job: %w[ce:.* qa-schedules-browser_ui.*] }) {} + it('skips given a mix of strings and regex patterns', only: { job: %w[ce:instance-image qa-schedules-browser_ui.*] }) {} + end + + aggregate_failures do + group.examples.each do |example| + expect(example.execution_result.status).to eq(:pending) + end + end + end + end end end end diff --git a/qa/tasks/webdrivers.rake b/qa/tasks/webdrivers.rake new file mode 100644 index 00000000000..f4fa3fab555 --- /dev/null +++ b/qa/tasks/webdrivers.rake @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +require 'webdrivers' +load 'webdrivers/Rakefile' |