diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2019-11-19 22:11:55 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2019-11-19 22:11:55 +0000 |
commit | 5a8431feceba47fd8e1804d9aa1b1730606b71d5 (patch) | |
tree | e5df8e0ceee60f4af8093f5c4c2f934b8abced05 /qa | |
parent | 4d477238500c347c6553d335d920bedfc5a46869 (diff) | |
download | gitlab-ce-5a8431feceba47fd8e1804d9aa1b1730606b71d5.tar.gz |
Add latest changes from gitlab-org/gitlab@12-5-stable-ee
Diffstat (limited to 'qa')
71 files changed, 934 insertions, 142 deletions
diff --git a/qa/Gemfile b/qa/Gemfile index f04ecb13879..5266fc57b0a 100644 --- a/qa/Gemfile +++ b/qa/Gemfile @@ -2,16 +2,21 @@ source 'https://rubygems.org' gem 'gitlab-qa' gem 'activesupport', '5.2.3' # This should stay in sync with the root's Gemfile -gem 'pry-byebug', '~> 3.5.1', platform: :mri gem 'capybara', '~> 2.16.1' gem 'capybara-screenshot', '~> 1.0.18' gem 'rake', '~> 12.3.0' gem 'rspec', '~> 3.7' gem 'selenium-webdriver', '~> 3.12' gem 'airborne', '~> 0.2.13' -gem 'nokogiri', '~> 1.10.4' +gem 'nokogiri', '~> 1.10.5' gem 'rspec-retry', '~> 0.6.1' gem 'rspec_junit_formatter', '~> 0.4.1' gem 'faker', '~> 1.6', '>= 1.6.6' gem 'knapsack', '~> 1.17' gem 'parallel_tests', '~> 2.29' + +group :test do + gem 'pry-byebug', '~> 3.5.1', platform: :mri + gem "ruby-debug-ide", "~> 0.7.0" + gem "debase", "~> 0.2.4.1" +end diff --git a/qa/Gemfile.lock b/qa/Gemfile.lock index d582d77c5cd..84eab990c95 100644 --- a/qa/Gemfile.lock +++ b/qa/Gemfile.lock @@ -29,6 +29,9 @@ GEM ffi (~> 1.0, >= 1.0.11) coderay (1.1.2) concurrent-ruby (1.1.5) + debase (0.2.4.1) + debase-ruby_core_source (>= 0.10.2) + debase-ruby_core_source (0.10.6) diff-lcs (1.3) domain_name (0.5.20170404) unf (>= 0.0.5, < 1.0.0) @@ -52,7 +55,7 @@ GEM mini_portile2 (2.4.0) minitest (5.11.3) netrc (0.11.0) - nokogiri (1.10.4) + nokogiri (1.10.5) mini_portile2 (~> 2.4.0) parallel (1.17.0) parallel_tests (2.29.0) @@ -67,7 +70,7 @@ GEM rack (2.0.6) rack-test (0.8.2) rack (>= 1.0, < 3) - rake (12.3.0) + rake (12.3.3) rest-client (2.0.2) http-cookie (>= 1.0.2, < 2.0) mime-types (>= 1.16, < 4.0) @@ -89,6 +92,8 @@ GEM rspec-support (3.7.0) rspec_junit_formatter (0.4.1) rspec-core (>= 2, < 4, != 2.12.0) + ruby-debug-ide (0.7.0) + rake (>= 0.8.1) rubyzip (1.2.2) selenium-webdriver (3.141.0) childprocess (~> 0.5) @@ -110,16 +115,18 @@ DEPENDENCIES airborne (~> 0.2.13) capybara (~> 2.16.1) capybara-screenshot (~> 1.0.18) + debase (~> 0.2.4.1) faker (~> 1.6, >= 1.6.6) gitlab-qa knapsack (~> 1.17) - nokogiri (~> 1.10.4) + nokogiri (~> 1.10.5) parallel_tests (~> 2.29) pry-byebug (~> 3.5.1) rake (~> 12.3.0) rspec (~> 3.7) rspec-retry (~> 0.6.1) rspec_junit_formatter (~> 0.4.1) + ruby-debug-ide (~> 0.7.0) selenium-webdriver (~> 3.12) BUNDLED WITH @@ -10,6 +10,14 @@ require_relative '../config/initializers/0_inject_enterprise_edition_module' module QA ## + # Helper classes to represent frequently used sequences of actions + # (e.g., login) + # + module Flow + autoload :Login, 'qa/flow/login' + end + + ## # GitLab QA runtime classes, mostly singletons. # module Runtime @@ -157,6 +165,7 @@ module QA module Dashboard autoload :Projects, 'qa/page/dashboard/projects' autoload :Groups, 'qa/page/dashboard/groups' + autoload :Welcome, 'qa/page/dashboard/welcome' module Snippet autoload :New, 'qa/page/dashboard/snippet/new' @@ -331,6 +340,7 @@ module QA module Component autoload :IpLimits, 'qa/page/admin/settings/component/ip_limits' + autoload :OutboundRequests, 'qa/page/admin/settings/component/outbound_requests' autoload :RepositoryStorage, 'qa/page/admin/settings/component/repository_storage' autoload :AccountAndLimit, 'qa/page/admin/settings/component/account_and_limit' autoload :PerformanceBar, 'qa/page/admin/settings/component/performance_bar' @@ -406,7 +416,9 @@ module QA module DockerRun autoload :Base, 'qa/service/docker_run/base' + autoload :Jenkins, 'qa/service/docker_run/jenkins' autoload :LDAP, 'qa/service/docker_run/ldap' + autoload :Maven, 'qa/service/docker_run/maven' autoload :NodeJs, 'qa/service/docker_run/node_js' autoload :GitlabRunner, 'qa/service/docker_run/gitlab_runner' end @@ -419,6 +431,7 @@ module QA autoload :Config, 'qa/specs/config' autoload :Runner, 'qa/specs/runner' autoload :ParallelRunner, 'qa/specs/parallel_runner' + autoload :LoopRunner, 'qa/specs/loop_runner' module Helpers autoload :Quarantine, 'qa/specs/helpers/quarantine' @@ -436,6 +449,17 @@ module QA end end + module Jenkins + module Page + autoload :Base, 'qa/vendor/jenkins/page/base' + autoload :Login, 'qa/vendor/jenkins/page/login' + autoload :Configure, 'qa/vendor/jenkins/page/configure' + autoload :NewCredentials, 'qa/vendor/jenkins/page/new_credentials' + autoload :NewJob, 'qa/vendor/jenkins/page/new_job' + autoload :ConfigureJob, 'qa/vendor/jenkins/page/configure_job' + end + end + module Github module Page autoload :Base, 'qa/vendor/github/page/base' diff --git a/qa/qa/flow/login.rb b/qa/qa/flow/login.rb new file mode 100644 index 00000000000..d84dfaa9377 --- /dev/null +++ b/qa/qa/flow/login.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +module QA + module Flow + module Login + module_function + + def while_signed_in(as: nil) + Page::Main::Menu.perform(&:sign_out_if_signed_in) + + sign_in(as: as) + + yield + + Page::Main::Menu.perform(&:sign_out) + end + + def while_signed_in_as_admin + while_signed_in(as: Runtime::User.admin) do + yield + end + end + + def sign_in(as: nil) + Runtime::Browser.visit(:gitlab, Page::Main::Login) + Page::Main::Login.perform { |login| login.sign_in_using_credentials(user: as) } + end + + def sign_in_as_admin + sign_in(as: Runtime::User.admin) + end + + def sign_in_unless_signed_in(as: nil) + sign_in(as: as) unless Page::Main::Menu.perform(&:signed_in?) + end + end + end +end diff --git a/qa/qa/page/admin/overview/users/show.rb b/qa/qa/page/admin/overview/users/show.rb index 11ea7bcabc8..f15ef0492fc 100644 --- a/qa/qa/page/admin/overview/users/show.rb +++ b/qa/qa/page/admin/overview/users/show.rb @@ -10,9 +10,19 @@ module QA element :impersonate_user_link end + view 'app/views/admin/users/show.html.haml' do + element :confirm_user_button + end + def click_impersonate_user click_element(:impersonate_user_link) end + + def confirm_user + accept_confirm do + click_element :confirm_user_button + end + end end end end diff --git a/qa/qa/page/admin/settings/component/outbound_requests.rb b/qa/qa/page/admin/settings/component/outbound_requests.rb new file mode 100644 index 00000000000..248ea5b6715 --- /dev/null +++ b/qa/qa/page/admin/settings/component/outbound_requests.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +module QA + module Page + module Admin + module Settings + module Component + class OutboundRequests < Page::Base + view 'app/views/admin/application_settings/_outbound.html.haml' do + element :allow_requests_from_services_checkbox + element :save_changes_button + end + + def allow_requests_to_local_network_from_services + check_allow_requests_to_local_network_from_services_checkbox + click_save_changes_button + end + + private + + def check_allow_requests_to_local_network_from_services_checkbox + check_element :allow_requests_from_services_checkbox + end + + def click_save_changes_button + click_element :save_changes_button + end + end + end + end + end + end +end diff --git a/qa/qa/page/admin/settings/network.rb b/qa/qa/page/admin/settings/network.rb index fdb8fcda281..83566d3d1ca 100644 --- a/qa/qa/page/admin/settings/network.rb +++ b/qa/qa/page/admin/settings/network.rb @@ -9,6 +9,7 @@ module QA view 'app/views/admin/application_settings/network.html.haml' do element :ip_limits_section + element :outbound_requests_section end def expand_ip_limits(&block) @@ -16,6 +17,12 @@ module QA Component::IpLimits.perform(&block) end end + + def expand_outbound_requests(&block) + expand_section(:outbound_requests_section) do + Component::OutboundRequests.perform(&block) + end + end end end end diff --git a/qa/qa/page/base.rb b/qa/qa/page/base.rb index 71df90f2f42..ed4d33dc7a3 100644 --- a/qa/qa/page/base.rb +++ b/qa/qa/page/base.rb @@ -111,12 +111,18 @@ module QA element.select value end - def has_element?(name, text: nil, wait: Capybara.default_max_wait_time) - has_css?(element_selector_css(name), wait: wait, text: text) + def has_element?(name, **kwargs) + wait = kwargs[:wait] ? kwargs[:wait] && kwargs.delete(:wait) : Capybara.default_max_wait_time + text = kwargs[:text] ? kwargs[:text] && kwargs.delete(:text) : nil + + has_css?(element_selector_css(name, kwargs), text: text, wait: wait) end - def has_no_element?(name, text: nil, wait: Capybara.default_max_wait_time) - has_no_css?(element_selector_css(name), wait: wait, text: text) + def has_no_element?(name, **kwargs) + wait = kwargs[:wait] ? kwargs[:wait] && kwargs.delete(:wait) : Capybara.default_max_wait_time + text = kwargs[:text] ? kwargs[:text] && kwargs.delete(:text) : nil + + has_no_css?(element_selector_css(name, kwargs), wait: wait, text: text) end def has_text?(text) @@ -135,6 +141,40 @@ module QA has_no_css?('.fa-spinner.block-loading', wait: Capybara.default_max_wait_time) end + def has_loaded_all_images? + # I don't know of a foolproof way to wait for all images to load + # This loop gives time for the img tags to be rendered and for + # images to start loading. + previous_total_images = 0 + wait(interval: 1) do + current_total_images = all("img").size + result = previous_total_images == current_total_images + previous_total_images = current_total_images + result + end + + # Retry until all images found can be fetched via HTTP, and + # check that the image has a non-zero natural width (a broken + # img tag could have a width, but wouldn't have a natural width) + + # Unfortunately, this doesn't account for SVGs. They're rendered + # as HTML, so there doesn't seem to be a way to check that they + # display properly via Selenium. However, if the SVG couldn't be + # rendered (e.g., because the file doesn't exist), the whole page + # won't display properly, so we should catch that with the test + # this method is called from. + + # The user's avatar is an img, which could be a gravatar image, + # so we skip that by only checking for images hosted internally + retry_until(sleep_interval: 1) do + all("img").all? do |image| + next true unless URI(image['src']).host == URI(page.current_url).host + + asset_exists?(image['src']) && image['naturalWidth'].to_i > 0 + end + end + end + def wait_for_animated_element(name) # It would be ideal if we could detect when the animation is complete # but in some cases there's nothing we can easily access via capybara @@ -165,8 +205,8 @@ module QA scroll_to(element_selector_css(name), *args) end - def element_selector_css(name) - Page::Element.new(name).selector_css + def element_selector_css(name, *attributes) + Page::Element.new(name, *attributes).selector_css end def click_link_with_text(text) diff --git a/qa/qa/page/component/select2.rb b/qa/qa/page/component/select2.rb index d05c44d22b2..8fe6a4a75b3 100644 --- a/qa/qa/page/component/select2.rb +++ b/qa/qa/page/component/select2.rb @@ -20,12 +20,20 @@ module QA def search_and_select(item_text) find('.select2-input').set(item_text) + + wait_for_search_to_complete + select_item(item_text) end def expand_select_list find('span.select2-arrow').click end + + def wait_for_search_to_complete + has_css?('.select2-active') + has_no_css?('.select2-active', wait: 30) + end end end end diff --git a/qa/qa/page/dashboard/projects.rb b/qa/qa/page/dashboard/projects.rb index 378ac793f7b..c103bc26a36 100644 --- a/qa/qa/page/dashboard/projects.rb +++ b/qa/qa/page/dashboard/projects.rb @@ -18,6 +18,10 @@ module QA '/' end + def clear_project_filter + fill_element(:project_filter_form, "") + end + private def filter_by_name(name) diff --git a/qa/qa/page/dashboard/welcome.rb b/qa/qa/page/dashboard/welcome.rb new file mode 100644 index 00000000000..b54205780d9 --- /dev/null +++ b/qa/qa/page/dashboard/welcome.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module QA + module Page + module Dashboard + class Welcome < Page::Base + view 'app/views/dashboard/projects/_zero_authorized_projects.html.haml' do + element :welcome_title_content + end + + def has_welcome_title?(text) + has_element?(:welcome_title_content, text: text) + end + end + end + end +end diff --git a/qa/qa/page/element.rb b/qa/qa/page/element.rb index 9e6fd2fdd4f..6bfdf98587b 100644 --- a/qa/qa/page/element.rb +++ b/qa/qa/page/element.rb @@ -28,7 +28,7 @@ module QA end def selector_css - %Q([data-qa-selector="#{@name}"],.#{selector}) + %Q([data-qa-selector="#{@name}"]#{additional_selectors},.#{selector}) end def expression @@ -42,6 +42,14 @@ module QA def matches?(line) !!(line =~ /["']#{name}['"]|#{expression}/) end + + private + + def additional_selectors + @attributes.dup.delete_if { |attr| attr == :pattern || attr == :required }.map do |key, value| + %Q([data-qa-#{key.to_s.tr('_', '-')}="#{value}"]) + end.join + end end end end diff --git a/qa/qa/page/file/shared/commit_button.rb b/qa/qa/page/file/shared/commit_button.rb index d8e751dd7b6..559b4c6ceea 100644 --- a/qa/qa/page/file/shared/commit_button.rb +++ b/qa/qa/page/file/shared/commit_button.rb @@ -13,6 +13,10 @@ module QA def commit_changes click_element(:commit_button) + + wait(reload: false, max: 60) do + finished_loading? + end end end end diff --git a/qa/qa/page/group/show.rb b/qa/qa/page/group/show.rb index d4c4be0d6ca..e1f319da134 100644 --- a/qa/qa/page/group/show.rb +++ b/qa/qa/page/group/show.rb @@ -18,6 +18,10 @@ module QA element :no_result_text, 'No groups or projects matched your search' # rubocop:disable QA/ElementWithPattern end + view 'app/views/shared/members/_access_request_links.html.haml' do + element :leave_group_link + end + def click_subgroup(name) click_link name end @@ -42,6 +46,12 @@ module QA click_element :new_in_group_button end + def leave_group + accept_alert do + click_element :leave_group_link + end + end + private def select_kind(kind) diff --git a/qa/qa/page/main/menu.rb b/qa/qa/page/main/menu.rb index 024f56db8e2..49c48568e68 100644 --- a/qa/qa/page/main/menu.rb +++ b/qa/qa/page/main/menu.rb @@ -20,7 +20,7 @@ module QA element :admin_area_link element :projects_dropdown, required: true element :groups_dropdown, required: true - element :more_dropdown, required: true + element :more_dropdown element :snippets_link end diff --git a/qa/qa/page/project/issue/index.rb b/qa/qa/page/project/issue/index.rb index befee25b37a..a6ccee4353b 100644 --- a/qa/qa/page/project/issue/index.rb +++ b/qa/qa/page/project/issue/index.rb @@ -36,6 +36,10 @@ module QA def click_closed_issues_link click_element :closed_issues_link end + + def has_issue?(issue) + has_element? :issue, issue_title: issue.to_s + end end end end diff --git a/qa/qa/page/project/issue/show.rb b/qa/qa/page/project/issue/show.rb index d2732eb7dd2..6ec80b7c9cc 100644 --- a/qa/qa/page/project/issue/show.rb +++ b/qa/qa/page/project/issue/show.rb @@ -108,6 +108,10 @@ module QA find_element(:more_assignees_link) end + def noteable_note_item + find_element(:noteable_note_item) + end + def select_all_activities_filter select_filter_with_text('Show all activity') end @@ -161,7 +165,15 @@ module QA def select_user(username) find("#{element_selector_css(:assignee_block)} input").set(username) - find('.dropdown-menu-user-link', text: "@#{username}").click + + dropdown_menu_user_link_selector = '.dropdown-menu-user-link' + at_username = "@#{username}" + ten_seconds = 10 + + wait(reload: false, max: ten_seconds, interval: 1) do + has_css?(dropdown_menu_user_link_selector, wait: ten_seconds, text: at_username) + end + find(dropdown_menu_user_link_selector, text: at_username).click end def wait_assignees_block_finish_loading diff --git a/qa/qa/page/project/pipeline/index.rb b/qa/qa/page/project/pipeline/index.rb index fae7818f871..b52f3e99a36 100644 --- a/qa/qa/page/project/pipeline/index.rb +++ b/qa/qa/page/project/pipeline/index.rb @@ -7,6 +7,10 @@ module QA::Page element :pipeline_link, 'class="js-pipeline-url-link' # rubocop:disable QA/ElementWithPattern end + view 'app/assets/javascripts/pipelines/components/pipelines_table_row.vue' do + element :pipeline_commit_status + end + def click_on_latest_pipeline css = '.js-pipeline-url-link' @@ -16,6 +20,14 @@ module QA::Page link.click end + + def wait_for_latest_pipeline_success + wait(reload: false, max: 300) do + within_element_by_index(:pipeline_commit_status, 0) do + has_text?('passed') + end + end + end end end end diff --git a/qa/qa/page/project/settings/ci_cd.rb b/qa/qa/page/project/settings/ci_cd.rb index 45040cf4660..46f93fad61e 100644 --- a/qa/qa/page/project/settings/ci_cd.rb +++ b/qa/qa/page/project/settings/ci_cd.rb @@ -35,3 +35,5 @@ module QA end end end + +QA::Page::Project::Settings::CICD.prepend_if_ee('QA::EE::Page::Project::Settings::CICD') diff --git a/qa/qa/page/project/sub_menus/settings.rb b/qa/qa/page/project/sub_menus/settings.rb index 1cd39fcff58..8be442ba35d 100644 --- a/qa/qa/page/project/sub_menus/settings.rb +++ b/qa/qa/page/project/sub_menus/settings.rb @@ -13,6 +13,7 @@ module QA element :settings_item element :link_members_settings element :general_settings_link + element :integrations_settings_link end end end @@ -55,6 +56,14 @@ module QA end end + def go_to_integrations_settings + hover_settings do + within_submenu do + click_element :integrations_settings_link + end + end + end + private def hover_settings diff --git a/qa/qa/resource/base.rb b/qa/qa/resource/base.rb index 88069df6ade..ae20ca1a98e 100644 --- a/qa/qa/resource/base.rb +++ b/qa/qa/resource/base.rb @@ -64,7 +64,12 @@ module QA end def visit! - visit(web_url) + Runtime::Logger.debug(%Q[Visiting #{self.class.name} at "#{web_url}"]) if Runtime::Env.debug? + + Support::Retrier.retry_until do + visit(web_url) + wait { current_url.include?(URI.parse(web_url).path.split('/').last || web_url) } + end end def populate(*attributes) @@ -72,7 +77,9 @@ module QA end def wait(max: 60, interval: 0.1) - QA::Support::Waiter.wait(max: max, interval: interval) + QA::Support::Waiter.wait(max: max, interval: interval) do + yield + end end private diff --git a/qa/qa/resource/group.rb b/qa/qa/resource/group.rb index e11bd5728fb..7511396251d 100644 --- a/qa/qa/resource/group.rb +++ b/qa/qa/resource/group.rb @@ -8,7 +8,10 @@ module QA attr_accessor :path, :description attribute :sandbox do - Sandbox.fabricate! + Sandbox.fabricate_via_api! do |sandbox| + sandbox.user = user + sandbox.api_client = api_client + end end attribute :id diff --git a/qa/qa/resource/issue.rb b/qa/qa/resource/issue.rb index 0817a9de06f..3bcff6a10ac 100644 --- a/qa/qa/resource/issue.rb +++ b/qa/qa/resource/issue.rb @@ -38,6 +38,10 @@ module QA end end + def to_s + @title + end + def api_get_path "/projects/#{project.id}/issues/#{id}" end diff --git a/qa/qa/resource/members.rb b/qa/qa/resource/members.rb index d70a2907523..c738a91a77f 100644 --- a/qa/qa/resource/members.rb +++ b/qa/qa/resource/members.rb @@ -11,6 +11,10 @@ module QA post Runtime::API::Request.new(api_client, api_members_path).url, { user_id: user.id, access_level: access_level } end + def list_members + JSON.parse(get(Runtime::API::Request.new(api_client, api_members_path).url).body) + end + def api_members_path "#{api_get_path}/members" end diff --git a/qa/qa/resource/merge_request.rb b/qa/qa/resource/merge_request.rb index fe7eeeed37a..1a6de8de456 100644 --- a/qa/qa/resource/merge_request.rb +++ b/qa/qa/resource/merge_request.rb @@ -26,8 +26,6 @@ module QA end attribute :target do - project.visit! - Repository::ProjectPush.fabricate! do |resource| resource.project = project resource.branch_name = 'master' diff --git a/qa/qa/resource/project.rb b/qa/qa/resource/project.rb index caaa766e982..3bebe2aaeda 100644 --- a/qa/qa/resource/project.rb +++ b/qa/qa/resource/project.rb @@ -9,6 +9,7 @@ module QA include Members attr_writer :initialize_with_readme + attr_writer :auto_devops_enabled attr_writer :visibility attribute :id @@ -47,6 +48,7 @@ module QA @standalone = false @description = 'My awesome project' @initialize_with_readme = false + @auto_devops_enabled = true @visibility = 'public' end @@ -101,7 +103,8 @@ module QA name: name, description: description, visibility: @visibility, - initialize_with_readme: @initialize_with_readme + initialize_with_readme: @initialize_with_readme, + auto_devops_enabled: @auto_devops_enabled } unless @standalone diff --git a/qa/qa/resource/runner.rb b/qa/qa/resource/runner.rb index 1be2429bc04..102c1ec83f5 100644 --- a/qa/qa/resource/runner.rb +++ b/qa/qa/resource/runner.rb @@ -36,7 +36,6 @@ module QA runner.tags = tags runner.image = image runner.config = config if config - runner.run_untagged = true runner.register! end end diff --git a/qa/qa/resource/sandbox.rb b/qa/qa/resource/sandbox.rb index 6ee3dcf350f..6c87fcb377a 100644 --- a/qa/qa/resource/sandbox.rb +++ b/qa/qa/resource/sandbox.rb @@ -7,6 +7,8 @@ module QA # creating it if it doesn't yet exist. # class Sandbox < Base + include Members + attr_accessor :path attribute :id diff --git a/qa/qa/resource/user.rb b/qa/qa/resource/user.rb index dcf145c9882..bdbe5f3ef51 100644 --- a/qa/qa/resource/user.rb +++ b/qa/qa/resource/user.rb @@ -7,16 +7,21 @@ module QA class User < Base attr_reader :unique_id attr_writer :username, :password - attr_accessor :provider, :extern_uid + attr_accessor :admin, :provider, :extern_uid attribute :id attribute :name attribute :email def initialize + @admin = false @unique_id = SecureRandom.hex(8) end + def admin? + api_resource&.dig(:is_admin) || false + end + def username @username || "qa-user-#{unique_id}" end @@ -71,6 +76,16 @@ module QA super end + def api_delete + super + + QA::Runtime::Logger.debug("Deleted user '#{username}'") if Runtime::Env.debug? + end + + def api_delete_path + "/users/#{id}" + end + def api_get_path "/users/#{fetch_id(username)}" end @@ -81,6 +96,7 @@ module QA def api_post_body { + admin: admin, email: email, password: password, username: username, @@ -93,6 +109,7 @@ module QA if Runtime::Env.signup_disabled? self.fabricate_via_api! do |user| user.username = username + user.password = password end else self.fabricate! diff --git a/qa/qa/runtime/api/client.rb b/qa/qa/runtime/api/client.rb index 1b0adbc9053..83fbb8f15d2 100644 --- a/qa/qa/runtime/api/client.rb +++ b/qa/qa/runtime/api/client.rb @@ -46,19 +46,24 @@ module QA end def create_personal_access_token - Page::Main::Menu.perform(&:sign_out) if @is_new_session && Page::Main::Menu.perform { |p| p.has_personal_area?(wait: 0) } + signed_in_initially = Page::Main::Menu.perform(&:signed_in?) - unless Page::Main::Menu.perform { |p| p.has_personal_area?(wait: 0) } - Runtime::Browser.visit(@address, Page::Main::Login) - Page::Main::Login.perform { |login| login.sign_in_using_credentials(user: @user) } - end + Page::Main::Menu.perform(&:sign_out) if @is_new_session && signed_in_initially + + Flow::Login.sign_in_unless_signed_in(as: @user) token = Resource::PersonalAccessToken.fabricate!.access_token # If this is a new session, that tests that follow could fail if they - # try to sign in without starting a new session + # try to sign in without starting a new session. + # Also, if the browser wasn't already signed in, leaving it + # signed in could cause tests to fail when they try to sign + # in again. For example, that would happen if a test has a + # before(:context) block that fabricates via the API, and + # it's the first test to run so it creates an access token + # # Sign out so the tests can successfully sign in - Page::Main::Menu.perform(&:sign_out) if @is_new_session + Page::Main::Menu.perform(&:sign_out) if @is_new_session || !signed_in_initially token end diff --git a/qa/qa/runtime/browser.rb b/qa/qa/runtime/browser.rb index 4789b380377..7e45e5e86ea 100644 --- a/qa/qa/runtime/browser.rb +++ b/qa/qa/runtime/browser.rb @@ -19,6 +19,12 @@ module QA self.class.configure! end + def self.blank_page? + ['', 'about:blank', 'data:,'].include?(Capybara.current_session.driver.browser.current_url) + rescue + true + end + ## # Visit a page that belongs to a GitLab instance under given address. # @@ -51,13 +57,13 @@ module QA 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" - }) + # 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 diff --git a/qa/qa/runtime/env.rb b/qa/qa/runtime/env.rb index b4047ef5088..bcd2a225469 100644 --- a/qa/qa/runtime/env.rb +++ b/qa/qa/runtime/env.rb @@ -261,6 +261,10 @@ module QA ENV['QA_RUNTIME_SCENARIO_ATTRIBUTES'] end + def gitlab_qa_loop_runner_minutes + ENV.fetch('GITLAB_QA_LOOP_RUNNER_MINUTES', 1).to_i + end + private def remote_grid_credentials diff --git a/qa/qa/runtime/feature.rb b/qa/qa/runtime/feature.rb index b74f343ba7b..8c19436ee12 100644 --- a/qa/qa/runtime/feature.rb +++ b/qa/qa/runtime/feature.rb @@ -7,6 +7,7 @@ module QA extend Support::Api SetFeatureError = Class.new(RuntimeError) + AuthorizationError = Class.new(RuntimeError) def enable(key) QA::Runtime::Logger.info("Enabling feature: #{key}") @@ -18,6 +19,28 @@ module QA set_feature(key, false) end + def remove(key) + request = Runtime::API::Request.new(api_client, "/features/#{key}") + response = delete(request.url) + unless response.code == QA::Support::Api::HTTP_STATUS_NO_CONTENT + raise SetFeatureError, "Deleting feature flag #{key} failed with `#{response}`." + end + end + + def enable_and_verify(key) + Support::Retrier.retry_on_exception(sleep_interval: 2) do + enable(key) + + is_enabled = false + + QA::Support::Waiter.wait(interval: 1) do + is_enabled = enabled?(key) + end + + raise SetFeatureError, "#{key} was not enabled!" unless is_enabled + end + end + def enabled?(key) feature = JSON.parse(get_features).find { |flag| flag["name"] == key } feature && feature["state"] == "on" @@ -26,7 +49,22 @@ module QA private def api_client - @api_client ||= Runtime::API::Client.new(:gitlab) + @api_client ||= begin + if Runtime::Env.admin_personal_access_token + Runtime::API::Client.new(:gitlab, personal_access_token: Runtime::Env.admin_personal_access_token) + else + user = Resource::User.fabricate_via_api! do |user| + user.username = Runtime::User.admin_username + user.password = Runtime::User.admin_password + end + + unless user.admin? + raise AuthorizationError, "Administrator access is required to enable/disable feature flags. User '#{user.username}' is not an administrator." + end + + Runtime::API::Client.new(:gitlab, user: user) + end + end end def set_feature(key, value) diff --git a/qa/qa/runtime/fixtures.rb b/qa/qa/runtime/fixtures.rb index f91218ea0b5..ed051b18a9a 100644 --- a/qa/qa/runtime/fixtures.rb +++ b/qa/qa/runtime/fixtures.rb @@ -30,7 +30,7 @@ module QA yield dir ensure - FileUtils.remove_entry(dir) + FileUtils.remove_entry(dir, true) end private diff --git a/qa/qa/runtime/user.rb b/qa/qa/runtime/user.rb index 3c26a3ad691..c50fcc25304 100644 --- a/qa/qa/runtime/user.rb +++ b/qa/qa/runtime/user.rb @@ -5,6 +5,10 @@ module QA module User extend self + def admin + Struct.new(:username, :password).new(admin_username, admin_password) + end + def default_username 'root' end diff --git a/qa/qa/scenario/shared_attributes.rb b/qa/qa/scenario/shared_attributes.rb index 52f50ec8c27..bb45c4ce4cb 100644 --- a/qa/qa/scenario/shared_attributes.rb +++ b/qa/qa/scenario/shared_attributes.rb @@ -8,6 +8,7 @@ module QA attribute :gitlab_address, '--address URL', 'Address of the instance to test' attribute :enable_feature, '--enable-feature FEATURE_FLAG', 'Enable a feature before running tests' attribute :parallel, '--parallel', 'Execute tests in parallel' + attribute :loop, '--loop', 'Execute test repeatedly' end end end diff --git a/qa/qa/service/docker_run/jenkins.rb b/qa/qa/service/docker_run/jenkins.rb new file mode 100644 index 00000000000..00b63282484 --- /dev/null +++ b/qa/qa/service/docker_run/jenkins.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +module QA + module Service + module DockerRun + class Jenkins < Base + def initialize + @image = 'registry.gitlab.com/gitlab-org/gitlab-qa/jenkins-gitlab:version1' + @name = 'jenkins-server' + @port = '8080' + super() + end + + def host_address + "http://#{host_name}:#{@port}" + end + + def host_name + return 'localhost' unless QA::Runtime::Env.running_in_ci? + + super + end + + def register! + command = <<~CMD.tr("\n", ' ') + docker run -d --rm + --network #{network} + --hostname #{host_name} + --name #{@name} + --env JENKINS_HOME=jenkins_home + --publish #{@port}:8080 + --publish 50000:50000 + #{@image} + CMD + + command.gsub!("--network #{network} ", '') unless QA::Runtime::Env.running_in_ci? + + shell command + end + end + end + end +end diff --git a/qa/qa/service/docker_run/maven.rb b/qa/qa/service/docker_run/maven.rb new file mode 100644 index 00000000000..8bdea20963d --- /dev/null +++ b/qa/qa/service/docker_run/maven.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +module QA + module Service + module DockerRun + class Maven < Base + def initialize(volume_host_path) + @image = 'maven:3.6.2-ibmjava-8-alpine' + @name = "qa-maven-#{SecureRandom.hex(8)}" + @volume_host_path = volume_host_path + + super() + end + + def publish! + # When we run the tests via gitlab-qa, we use docker-in-docker + # which means that host of a volume mount would be the host that + # started the gitlab-qa QA container (e.g., the CI runner), + # not the gitlab-qa container itself. That means we can't + # mount a volume from the file system inside the gitlab-qa + # container. + # + # Instead, we copy the files into the container. + shell <<~CMD.tr("\n", ' ') + docker run -d --rm + --network #{network} + --hostname #{host_name} + --name #{@name} + --volume #{@volume_host_path}:/home/maven + #{@image} sh -c "sleep 300" + CMD + shell "docker cp #{@volume_host_path}/. #{@name}:/home/maven" + shell "docker exec -t #{@name} sh -c 'cd /home/maven && mvn deploy -s settings.xml'" + + # Stop the container when `mvn deploy` is finished otherwise + # the sleeping container will hold onto the files in @volume_host_path, + # which causes problems when they're created in a tmp dir + # that we want to delete + shell "docker stop #{@name}" + end + end + end + end +end diff --git a/qa/qa/specs/features/browser_ui/1_manage/login/login_via_instance_wide_saml_sso_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/login/login_via_instance_wide_saml_sso_spec.rb index 101143399f6..ad67f02eaca 100644 --- a/qa/qa/specs/features/browser_ui/1_manage/login/login_via_instance_wide_saml_sso_spec.rb +++ b/qa/qa/specs/features/browser_ui/1_manage/login/login_via_instance_wide_saml_sso_spec.rb @@ -8,7 +8,9 @@ module QA Page::Main::Login.perform(&:sign_in_with_saml) - Vendor::SAMLIdp::Page::Login.perform(&:login) + Vendor::SAMLIdp::Page::Login.perform do |login_page| + login_page.login('user1', 'user1pass') + end expect(page).to have_content('Welcome to GitLab') end 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 new file mode 100644 index 00000000000..6a5bc6173e0 --- /dev/null +++ b/qa/qa/specs/features/browser_ui/1_manage/project/dashboard_images_spec.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +require 'nokogiri' + +module QA + context 'Manage' do + describe 'Check for broken images', :requires_admin do + before(:context) do + admin = QA::Resource::User.new.tap do |user| + user.username = QA::Runtime::User.admin_username + user.password = QA::Runtime::User.admin_password + end + @api_client = Runtime::API::Client.new(:gitlab, user: admin) + @new_user = Resource::User.fabricate_via_api! do |user| + user.api_client = @api_client + end + @new_admin = Resource::User.fabricate_via_api! do |user| + user.admin = true + user.api_client = @api_client + end + + Page::Main::Menu.perform(&:sign_out_if_signed_in) + end + + after(:context) do + @new_user.remove_via_api! + @new_admin.remove_via_api! + end + + shared_examples 'loads all images' do + it 'loads all images' do + Runtime::Browser.visit(:gitlab, Page::Main::Login) + Page::Main::Login.perform { |login| login.sign_in_using_credentials(user: new_user) } + + Page::Dashboard::Welcome.perform do |welcome| + expect(welcome).to have_welcome_title("Welcome to GitLab") + + # This would be better if it were a visual validation test + expect(welcome).to have_loaded_all_images + end + end + end + + context 'when logged in as a new user' do + it_behaves_like 'loads all images' do + let(:new_user) { @new_user } + end + end + + context 'when logged in as a new admin' do + it_behaves_like 'loads all images' do + let(:new_user) { @new_admin } + end + end + end + end +end diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/check_mentions_for_xss_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/check_mentions_for_xss_spec.rb index 55e15b19200..69389672a6d 100644 --- a/qa/qa/specs/features/browser_ui/2_plan/issue/check_mentions_for_xss_spec.rb +++ b/qa/qa/specs/features/browser_ui/2_plan/issue/check_mentions_for_xss_spec.rb @@ -2,13 +2,12 @@ module QA context 'Plan' do - describe 'check xss occurence in @mentions in issues' do + describe 'check xss occurence in @mentions in issues', :requires_admin do it 'user mentions a user in comment' do QA::Runtime::Env.personal_access_token = QA::Runtime::Env.admin_personal_access_token unless QA::Runtime::Env.personal_access_token - Runtime::Browser.visit(:gitlab, Page::Main::Login) - Page::Main::Login.perform(&:sign_in_using_admin_credentials) + Flow::Login.sign_in_as_admin end user = Resource::User.fabricate_via_api! do |user| @@ -20,9 +19,7 @@ module QA Page::Main::Menu.perform(&:sign_out) if Page::Main::Menu.perform { |p| p.has_personal_area?(wait: 0) } - Runtime::Browser.visit(:gitlab, Page::Main::Login) - - Page::Main::Login.perform(&:sign_in_using_credentials) + Flow::Login.sign_in project = Resource::Project.fabricate_via_api! do |resource| resource.name = 'xss-test-for-mentions-project' @@ -42,7 +39,7 @@ module QA Page::Project::Issue::Show.perform do |show| show.select_all_activities_filter - show.comment('cc-ing you here @eve') + show.comment("cc-ing you here @#{user.username}") expect do expect(show).to have_content("cc-ing you here") diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/close_issue_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/close_issue_spec.rb index 2bcc89cb338..dc7fa9f3859 100644 --- a/qa/qa/specs/features/browser_ui/2_plan/issue/close_issue_spec.rb +++ b/qa/qa/specs/features/browser_ui/2_plan/issue/close_issue_spec.rb @@ -7,8 +7,7 @@ module QA let(:commit_message) { 'Closes' } before do - Runtime::Browser.visit(:gitlab, Page::Main::Login) - Page::Main::Login.perform(&:sign_in_using_credentials) + Flow::Login.sign_in issue = Resource::Issue.fabricate_via_api! do |issue| issue.title = issue_title diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/collapse_comments_in_discussions_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/collapse_comments_in_discussions_spec.rb index ad70f6813fb..77fcc4e9b6a 100644 --- a/qa/qa/specs/features/browser_ui/2_plan/issue/collapse_comments_in_discussions_spec.rb +++ b/qa/qa/specs/features/browser_ui/2_plan/issue/collapse_comments_in_discussions_spec.rb @@ -6,8 +6,7 @@ module QA let(:my_first_reply) { 'My first reply' } before do - Runtime::Browser.visit(:gitlab, Page::Main::Login) - Page::Main::Login.perform(&:sign_in_using_credentials) + Flow::Login.sign_in issue = Resource::Issue.fabricate_via_api! do |issue| issue.title = 'issue title' diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/comment_issue_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/comment_issue_spec.rb index 0b1bd00ac8d..77489c0ecf5 100644 --- a/qa/qa/specs/features/browser_ui/2_plan/issue/comment_issue_spec.rb +++ b/qa/qa/specs/features/browser_ui/2_plan/issue/comment_issue_spec.rb @@ -4,8 +4,7 @@ module QA context 'Plan' do describe 'Issue comments' do before do - Runtime::Browser.visit(:gitlab, Page::Main::Login) - Page::Main::Login.perform(&:sign_in_using_credentials) + Flow::Login.sign_in issue = Resource::Issue.fabricate_via_api! do |issue| issue.title = 'issue title' 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 04ae4963d3a..254efb741b3 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 @@ -6,23 +6,25 @@ module QA let(:issue_title) { 'issue title' } before do - Runtime::Browser.visit(:gitlab, Page::Main::Login) - Page::Main::Login.perform(&:sign_in_using_credentials) + Flow::Login.sign_in end it 'user creates an issue' do - Resource::Issue.fabricate_via_browser_ui! do |issue| + issue = Resource::Issue.fabricate_via_browser_ui! do |issue| issue.title = issue_title end Page::Project::Menu.perform(&:click_issues) - expect(page).to have_content(issue_title) + Page::Project::Issue::Index.perform do |index| + expect(index).to have_issue(issue) + end end context 'when using attachments in comments', :object_storage do + let(:gif_file_name) { 'banana_sample.gif' } let(:file_to_attach) do - File.absolute_path(File.join('spec', 'fixtures', 'banana_sample.gif')) + File.absolute_path(File.join('spec', 'fixtures', gif_file_name)) end before do @@ -37,15 +39,7 @@ module QA Page::Project::Issue::Show.perform do |show| show.comment('See attached banana for scale', attachment: file_to_attach) - show.refresh - - image_url = find('a[href$="banana_sample.gif"]')[:href] - - found = show.wait(reload: false) do - show.asset_exists?(image_url) - end - - expect(found).to be_truthy + expect(show.noteable_note_item.find("img[src$='#{gif_file_name}']")).to be_visible end end end diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/filter_issue_comments_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/filter_issue_comments_spec.rb index 317e31feea8..a4f6b0bb1bf 100644 --- a/qa/qa/specs/features/browser_ui/2_plan/issue/filter_issue_comments_spec.rb +++ b/qa/qa/specs/features/browser_ui/2_plan/issue/filter_issue_comments_spec.rb @@ -6,8 +6,7 @@ module QA let(:issue_title) { 'issue title' } before do - Runtime::Browser.visit(:gitlab, Page::Main::Login) - Page::Main::Login.perform(&:sign_in_using_credentials) + Flow::Login.sign_in issue = Resource::Issue.fabricate_via_api! do |issue| issue.title = issue_title diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/issue_suggestions_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/issue_suggestions_spec.rb index c42c2cedde0..e15afd1f576 100644 --- a/qa/qa/specs/features/browser_ui/2_plan/issue/issue_suggestions_spec.rb +++ b/qa/qa/specs/features/browser_ui/2_plan/issue/issue_suggestions_spec.rb @@ -6,8 +6,7 @@ module QA let(:issue_title) { 'Issue Lists are awesome' } before do - Runtime::Browser.visit(:gitlab, Page::Main::Login) - Page::Main::Login.perform(&:sign_in_using_credentials) + Flow::Login.sign_in project = Resource::Project.fabricate_via_api! do |resource| resource.name = 'project-for-issue-suggestions' diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/mentions_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/mentions_spec.rb index 45c14d0537c..b1a80ad75cd 100644 --- a/qa/qa/specs/features/browser_ui/2_plan/issue/mentions_spec.rb +++ b/qa/qa/specs/features/browser_ui/2_plan/issue/mentions_spec.rb @@ -4,8 +4,7 @@ module QA context 'Plan', :smoke do describe 'mention' do before do - Runtime::Browser.visit(:gitlab, Page::Main::Login) - Page::Main::Login.perform(&:sign_in_using_credentials) + Flow::Login.sign_in @user = Resource::User.fabricate_or_use(Runtime::Env.gitlab_qa_username_1, Runtime::Env.gitlab_qa_password_1) diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/view_merge_request_diff_patch_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/view_merge_request_diff_patch_spec.rb index 891cef6c420..0eaec61b2fa 100644 --- a/qa/qa/specs/features/browser_ui/3_create/merge_request/view_merge_request_diff_patch_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/view_merge_request_diff_patch_spec.rb @@ -4,9 +4,6 @@ module QA context 'Create' do describe 'Download merge request patch and diff' do before(:context) do - Runtime::Browser.visit(:gitlab, Page::Main::Login) - Page::Main::Login.perform(&:sign_in_using_credentials) - project = Resource::Project.fabricate_via_api! do |project| project.name = 'project' end @@ -19,6 +16,8 @@ module QA end it 'views the merge request email patches' do + Flow::Login.sign_in + @merge_request.visit! Page::MergeRequest::Show.perform(&:view_email_patches) @@ -28,6 +27,8 @@ module QA end it 'views the merge request plain diff' do + Flow::Login.sign_in + @merge_request.visit! Page::MergeRequest::Show.perform(&:view_plain_diff) diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/add_file_template_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/add_file_template_spec.rb index e42d538fdf8..d2fd1d743fb 100644 --- a/qa/qa/specs/features/browser_ui/3_create/repository/add_file_template_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/repository/add_file_template_spec.rb @@ -1,27 +1,17 @@ # frozen_string_literal: true module QA - context 'Create' do + # Failure issue: https://gitlab.com/gitlab-org/gitlab/issues/34551 + context 'Create', :quarantine do describe 'File templates' do include Runtime::Fixtures - def login - unless Page::Main::Menu.perform(&:signed_in?) - Runtime::Browser.visit(:gitlab, Page::Main::Login) - Page::Main::Login.perform(&:sign_in_using_credentials) - end - end - before(:all) do - login - - @project = Resource::Project.fabricate! do |project| + @project = Resource::Project.fabricate_via_api! do |project| project.name = 'file-template-project' project.description = 'Add file templates via the Files view' project.initialize_with_readme = true end - - Page::Main::Menu.perform(&:sign_out) end templates = [ @@ -55,7 +45,8 @@ module QA it "user adds #{template[:file_name]} via file template #{template[:name]}" do content = fetch_template_from_api(template[:api_path], template[:api_key]) - login + Flow::Login.sign_in + @project.visit! Page::Project::Show.perform(&:create_new_file!) diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/add_list_delete_branches_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/add_list_delete_branches_spec.rb index bb1e3ced333..3306c5f5c50 100644 --- a/qa/qa/specs/features/browser_ui/3_create/repository/add_list_delete_branches_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/repository/add_list_delete_branches_spec.rb @@ -66,24 +66,22 @@ module QA expect(page).to have_content(commit_message_of_second_branch) expect(page).to have_content(commit_message_of_third_branch) - Page::Project::Branches::Show.perform do |branches| - expect(branches).to have_branch_with_badge(second_branch, 'merged') - end + Page::Project::Branches::Show.perform do |branches_page| + expect(branches_page).to have_branch_with_badge(second_branch, 'merged') - Page::Project::Branches::Show.perform do |branches_view| - branches_view.delete_branch(third_branch) - expect(branches_view).to have_no_branch(third_branch) - end + branches_page.delete_branch(third_branch) + + expect(branches_page).to have_no_branch(third_branch) + + branches_page.delete_merged_branches - Page::Project::Branches::Show.perform(&:delete_merged_branches) + expect(branches_page).to have_content( + 'Merged branches are being deleted. This can take some time depending on the number of branches. Please refresh the page to see changes.' + ) - expect(page).to have_content( - 'Merged branches are being deleted. This can take some time depending on the number of branches. Please refresh the page to see changes.' - ) + branches_page.refresh - page.refresh - Page::Project::Branches::Show.perform do |branches_view| - expect(branches_view).to have_no_branch(second_branch, reload: true) + expect(branches_page).to have_no_branch(second_branch, reload: true) end end end diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/clone_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/clone_spec.rb index f2584f55a60..0650c8395c7 100644 --- a/qa/qa/specs/features/browser_ui/3_create/repository/clone_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/repository/clone_spec.rb @@ -4,14 +4,10 @@ module QA context 'Create' do describe 'Git clone over HTTP', :ldap_no_tls do before(:all) do - Runtime::Browser.visit(:gitlab, Page::Main::Login) - Page::Main::Login.perform(&:sign_in_using_credentials) - - @project = Resource::Project.fabricate! do |scenario| + @project = Resource::Project.fabricate_via_api! do |scenario| scenario.name = 'project-with-code' scenario.description = 'project for git clone tests' end - @project.visit! Git::Repository.perform do |repository| repository.uri = @project.repository_http_location.uri diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/user_views_commit_diff_patch_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/user_views_commit_diff_patch_spec.rb index b2eef38f896..aee62bacfa8 100644 --- a/qa/qa/specs/features/browser_ui/3_create/repository/user_views_commit_diff_patch_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/repository/user_views_commit_diff_patch_spec.rb @@ -4,9 +4,6 @@ module QA context 'Create' do describe 'Commit data' do before(:context) do - Runtime::Browser.visit(:gitlab, Page::Main::Login) - Page::Main::Login.perform(&:sign_in_using_credentials) - # Get the user's details to confirm they're included in the email patch @user = Resource::User.fabricate_via_api! do |user| user.username = Runtime::User.username @@ -34,9 +31,11 @@ module QA end def view_commit + Flow::Login.sign_in + @project.visit! - Page::Project::Show.perform do |page| # rubocop:disable QA/AmbiguousPageObjectName - page.click_commit(@commit_message) + Page::Project::Show.perform do |show| + show.click_commit(@commit_message) end end diff --git a/qa/qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb b/qa/qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb index 0a89f0c9d41..318adc3c272 100644 --- a/qa/qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb @@ -1,25 +1,17 @@ # frozen_string_literal: true module QA - context 'Create' do + # Failure issue: https://gitlab.com/gitlab-org/gitlab/issues/34551 + context 'Create', :quarantine do describe 'Web IDE file templates' do include Runtime::Fixtures - def login - Runtime::Browser.visit(:gitlab, Page::Main::Login) - Page::Main::Login.perform(&:sign_in_using_credentials) - end - before(:all) do - login - - @project = Resource::Project.fabricate! do |project| + @project = Resource::Project.fabricate_via_api! do |project| project.name = 'file-template-project' project.description = 'Add file templates via the Web IDE' project.initialize_with_readme = true end - - Page::Main::Menu.perform(&:sign_out) end templates = [ @@ -53,7 +45,8 @@ module QA it "user adds #{template[:file_name]} via file template #{template[:name]}" do content = fetch_template_from_api(template[:api_path], template[:api_key]) - login + Flow::Login.sign_in + @project.visit! Page::Project::Show.perform(&:open_web_ide!) diff --git a/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb b/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb index e45ce438fc2..9dc4bcc8a03 100644 --- a/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb +++ b/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb @@ -6,6 +6,10 @@ module QA context 'Release', :docker do describe 'Git clone using a deploy key' do before do + # Handle WIP Job Logs flag - https://gitlab.com/gitlab-org/gitlab/issues/31162 + @job_log_json_flag_enabled = Runtime::Feature.enabled?('job_log_json') + Runtime::Feature.disable('job_log_json') if @job_log_json_flag_enabled + Runtime::Browser.visit(:gitlab, Page::Main::Login) Page::Main::Login.perform(&:sign_in_using_credentials) @@ -26,6 +30,7 @@ module QA end after do + Runtime::Feature.enable('job_log_json') if @job_log_json_flag_enabled Service::DockerRun::GitlabRunner.new(@runner_name).remove! end diff --git a/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb b/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb index 3f99ae644c7..e9a3b0f75e6 100644 --- a/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb +++ b/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb @@ -55,7 +55,8 @@ module QA end end - describe 'Auto DevOps support', :orchestrated, :kubernetes do + # https://gitlab.com/gitlab-org/gitlab/issues/35156 + describe 'Auto DevOps support', :orchestrated, :kubernetes, :quarantine do context 'when rbac is enabled' do before(:all) do @cluster = Service::KubernetesCluster.new.create! diff --git a/qa/qa/specs/features/browser_ui/non_devops/performance_bar_spec.rb b/qa/qa/specs/features/browser_ui/non_devops/performance_bar_spec.rb index 4fca2db3d0f..187c4a2a248 100644 --- a/qa/qa/specs/features/browser_ui/non_devops/performance_bar_spec.rb +++ b/qa/qa/specs/features/browser_ui/non_devops/performance_bar_spec.rb @@ -2,7 +2,7 @@ module QA context 'Performance bar' do - context 'when logged in as an admin user' do + context 'when logged in as an admin user', :requires_admin do before do Runtime::Browser.visit(:gitlab, Page::Main::Login) Page::Main::Login.perform(&:sign_in_using_admin_credentials) diff --git a/qa/qa/specs/loop_runner.rb b/qa/qa/specs/loop_runner.rb new file mode 100644 index 00000000000..f97f5cbbd81 --- /dev/null +++ b/qa/qa/specs/loop_runner.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module QA + module Specs + module LoopRunner + module_function + + def run(args) + start = Time.now + loop_duration = 60 * QA::Runtime::Env.gitlab_qa_loop_runner_minutes + + while Time.now - start < loop_duration + RSpec::Core::Runner.run(args.flatten, $stderr, $stdout).tap do |status| + abort if status.nonzero? + end + RSpec.clear_examples + end + end + end + end +end diff --git a/qa/qa/specs/runner.rb b/qa/qa/specs/runner.rb index 6aa08cf77b4..ac73cc00dbf 100644 --- a/qa/qa/specs/runner.rb +++ b/qa/qa/specs/runner.rb @@ -63,6 +63,8 @@ module QA if Runtime::Scenario.attributes[:parallel] ParallelRunner.run(args.flatten) + elsif Runtime::Scenario.attributes[:loop] + LoopRunner.run(args.flatten) else RSpec::Core::Runner.run(args.flatten, $stderr, $stdout).tap do |status| abort if status.nonzero? diff --git a/qa/qa/support/api.rb b/qa/qa/support/api.rb index d0ff1f8bc2c..cd496efb4db 100644 --- a/qa/qa/support/api.rb +++ b/qa/qa/support/api.rb @@ -14,7 +14,7 @@ module QA payload: payload, verify_ssl: false) rescue RestClient::ExceptionWithResponse => e - e.response + return_response_or_raise(e) end def get(url, raw_response: false) @@ -24,7 +24,7 @@ module QA verify_ssl: false, raw_response: raw_response) rescue RestClient::ExceptionWithResponse => e - e.response + return_response_or_raise(e) end def put(url, payload) @@ -34,7 +34,7 @@ module QA payload: payload, verify_ssl: false) rescue RestClient::ExceptionWithResponse => e - e.response + return_response_or_raise(e) end def delete(url) @@ -43,7 +43,7 @@ module QA url: url, verify_ssl: false) rescue RestClient::ExceptionWithResponse => e - e.response + return_response_or_raise(e) end def head(url) @@ -52,12 +52,18 @@ module QA url: url, verify_ssl: false) rescue RestClient::ExceptionWithResponse => e - e.response + return_response_or_raise(e) end def parse_body(response) JSON.parse(response.body, symbolize_names: true) end + + def return_response_or_raise(error) + raise error unless error.respond_to?(:response) && error.response + + error.response + end end end end diff --git a/qa/qa/vendor/github/page/login.rb b/qa/qa/vendor/github/page/login.rb index f6e72bb01f9..e581edcb7c7 100644 --- a/qa/qa/vendor/github/page/login.rb +++ b/qa/qa/vendor/github/page/login.rb @@ -12,11 +12,15 @@ module QA fill_in 'password', with: QA::Runtime::Env.github_password click_on 'Sign in' - otp = OnePassword::CLI.new.otp + Support::Retrier.retry_until(exit_on_failure: true, sleep_interval: 35) do + otp = OnePassword::CLI.new.otp - fill_in 'otp', with: otp + fill_in 'otp', with: otp - click_on 'Verify' + click_on 'Verify' + + !has_text?('Two-factor authentication failed', wait: 1.0) + end click_on 'Authorize gitlab-qa' if has_button?('Authorize gitlab-qa') end diff --git a/qa/qa/vendor/jenkins/page/base.rb b/qa/qa/vendor/jenkins/page/base.rb new file mode 100644 index 00000000000..8dfbe7570f8 --- /dev/null +++ b/qa/qa/vendor/jenkins/page/base.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module QA + module Vendor + module Jenkins + module Page + class Base + include Capybara::DSL + include Scenario::Actable + + attr_reader :path + + class << self + attr_accessor :host + end + + def visit! + page.visit URI.join(Base.host, path).to_s + end + end + end + end + end +end diff --git a/qa/qa/vendor/jenkins/page/configure.rb b/qa/qa/vendor/jenkins/page/configure.rb new file mode 100644 index 00000000000..8851a2564fd --- /dev/null +++ b/qa/qa/vendor/jenkins/page/configure.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +require 'capybara/dsl' + +module QA + module Vendor + module Jenkins + module Page + class Configure < Page::Base + def initialize + @path = 'configure' + end + + def visit_and_setup_gitlab_connection(gitlab_host, token_description) + visit! + fill_in '_.name', with: 'GitLab' + find('.setting-name', text: "Gitlab host URL").find(:xpath, "..").find('input').set gitlab_host + + dropdown_element = find('.setting-name', text: "Credentials").find(:xpath, "..").find('select') + + QA::Support::Retrier.retry_until(exit_on_failure: true) do + dropdown_element.select "GitLab API token (#{token_description})" + dropdown_element.value != '' + end + + yield if block_given? + + click_save + end + + def click_test_connection + click_on 'Test Connection' + end + + def has_success? + has_css?('div.ok', text: "Success") + end + + private + + def click_save + click_on 'Save' + end + end + end + end + end +end diff --git a/qa/qa/vendor/jenkins/page/configure_job.rb b/qa/qa/vendor/jenkins/page/configure_job.rb new file mode 100644 index 00000000000..ab16e895fa9 --- /dev/null +++ b/qa/qa/vendor/jenkins/page/configure_job.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +require 'capybara/dsl' + +module QA + module Vendor + module Jenkins + module Page + class ConfigureJob < Page::Base + attr_accessor :job_name + + def initialize + @path = "/job/#{@job_name}/configure" + end + + def configure(scm_url:) + set_git_source_code_management_url(scm_url) + click_build_when_change_is_pushed_to_gitlab + set_publish_status_to_gitlab + click_save + end + + private + + def set_git_source_code_management_url(repository_url) + select_git_source_code_management + set_repository_url(repository_url) + end + + def click_build_when_change_is_pushed_to_gitlab + find('label', text: 'Build when a change is pushed to GitLab').find(:xpath, "..").find('input').click + end + + def set_publish_status_to_gitlab + click_add_post_build_action + select_publish_build_status_to_gitlab + end + + def click_save + click_on 'Save' + end + + def select_git_source_code_management + find('#radio-block-1').click + end + + def set_repository_url(repository_url) + find('.setting-name', text: "Repository URL").find(:xpath, "..").find('input').set repository_url + end + + def click_add_post_build_action + click_on "Add post-build action" + end + + def select_publish_build_status_to_gitlab + click_link "Publish build status to GitLab" + end + end + end + end + end +end diff --git a/qa/qa/vendor/jenkins/page/login.rb b/qa/qa/vendor/jenkins/page/login.rb new file mode 100644 index 00000000000..7b3558b25e2 --- /dev/null +++ b/qa/qa/vendor/jenkins/page/login.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +require 'capybara/dsl' + +module QA + module Vendor + module Jenkins + module Page + class Login < Page::Base + def initialize + @path = 'login' + end + + def visit! + super + + QA::Support::Retrier.retry_until(sleep_interval: 3, reload_page: page, max_attempts: 20, exit_on_failure: true) do + page.has_text? 'Welcome to Jenkins!' + end + end + + def login + fill_in 'j_username', with: 'admin' + fill_in 'j_password', with: 'password' + click_on 'Sign in' + end + end + end + end + end +end diff --git a/qa/qa/vendor/jenkins/page/new_credentials.rb b/qa/qa/vendor/jenkins/page/new_credentials.rb new file mode 100644 index 00000000000..bdef1a13fd4 --- /dev/null +++ b/qa/qa/vendor/jenkins/page/new_credentials.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +require 'capybara/dsl' + +module QA + module Vendor + module Jenkins + module Page + class NewCredentials < Page::Base + def initialize + @path = 'credentials/store/system/domain/_/newCredentials' + end + + def visit_and_set_gitlab_api_token(api_token, description) + visit! + wait_for_page_to_load + select_gitlab_api_token + set_api_token(api_token) + set_description(description) + click_ok + end + + private + + def select_gitlab_api_token + find('.setting-name', text: "Kind").find(:xpath, "..").find('select').select "GitLab API token" + end + + def set_api_token(api_token) + fill_in '_.apiToken', with: api_token + end + + def set_description(description) + fill_in '_.description', with: description + end + + def click_ok + click_on 'OK' + end + + def wait_for_page_to_load + QA::Support::Waiter.wait(interval: 1.0) do + page.has_css?('.setting-name', text: "Description") + end + end + end + end + end + end +end diff --git a/qa/qa/vendor/jenkins/page/new_job.rb b/qa/qa/vendor/jenkins/page/new_job.rb new file mode 100644 index 00000000000..11fa4ca8a53 --- /dev/null +++ b/qa/qa/vendor/jenkins/page/new_job.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +require 'capybara/dsl' + +module QA + module Vendor + module Jenkins + module Page + class NewJob < Page::Base + def initialize + @path = 'newJob' + end + + def visit_and_create_new_job_with_name(new_job_name) + visit! + set_new_job_name(new_job_name) + click_free_style_project + click_ok + end + + private + + def set_new_job_name(new_job_name) + fill_in 'name', with: new_job_name + end + + def click_free_style_project + find('.hudson_model_FreeStyleProject').click + end + + def click_ok + click_on 'OK' + end + end + end + end + end +end diff --git a/qa/qa/vendor/saml_idp/page/login.rb b/qa/qa/vendor/saml_idp/page/login.rb index 1b8c926532a..9ebcabe15fc 100644 --- a/qa/qa/vendor/saml_idp/page/login.rb +++ b/qa/qa/vendor/saml_idp/page/login.rb @@ -7,18 +7,22 @@ module QA module SAMLIdp module Page class Login < Page::Base - def login - fill_in 'username', with: 'user1' - fill_in 'password', with: 'user1pass' + def login(username, password) + QA::Runtime::Logger.debug("Logging into SAMLIdp with username: #{username} and password:#{password}") if QA::Runtime::Env.debug? + + fill_in 'username', with: username + fill_in 'password', with: password click_on 'Login' end - def login_if_required - login if login_required? + def login_if_required(username, password) + login(username, password) if login_required? end def login_required? - page.has_text?('Enter your username and password') + login_required = page.has_text?('Enter your username and password') + QA::Runtime::Logger.debug("login_required: #{login_required}") if QA::Runtime::Env.debug? + login_required end end end diff --git a/qa/spec/page/element_spec.rb b/qa/spec/page/element_spec.rb index ff5e118cefa..3f64743ffac 100644 --- a/qa/spec/page/element_spec.rb +++ b/qa/spec/page/element_spec.rb @@ -117,5 +117,23 @@ describe QA::Page::Element do it 'properly translates to a data-qa-selector' do expect(subject.selector_css).to include(%q([data-qa-selector="my_element"])) end + + context 'additional selectors' do + let(:element) { described_class.new(:my_element, index: 3, another_match: 'something') } + let(:required_element) { described_class.new(:my_element, required: true, index: 3) } + + it 'matches on additional data-qa properties' do + expect(element.selector_css).to include(%q([data-qa-selector="my_element"][data-qa-index="3"])) + end + + it 'doesnt conflict with element requirement' do + expect(required_element).to be_required + expect(required_element.selector_css).not_to include(%q(data-qa-required)) + end + + it 'translates snake_case to kebab-case' do + expect(element.selector_css).to include(%q(data-qa-another-match)) + end + end end end diff --git a/qa/spec/resource/base_spec.rb b/qa/spec/resource/base_spec.rb index 4a6b76c869f..fe84b3d024a 100644 --- a/qa/spec/resource/base_spec.rb +++ b/qa/spec/resource/base_spec.rb @@ -269,6 +269,8 @@ describe QA::Resource::Base do end it 'calls #visit with the underlying #web_url' do + allow(resource).to receive(:current_url).and_return(subject.current_url) + resource.web_url = subject.current_url resource.visit! diff --git a/qa/spec/spec_helper.rb b/qa/spec/spec_helper.rb index 363980acc33..42f1e6f292a 100644 --- a/qa/spec/spec_helper.rb +++ b/qa/spec/spec_helper.rb @@ -20,7 +20,25 @@ RSpec.configure do |config| QA::Specs::Helpers::Quarantine.configure_rspec config.before do |example| - QA::Runtime::Logger.debug("Starting test: #{example.full_description}") if QA::Runtime::Env.debug? + QA::Runtime::Logger.debug("\nStarting test: #{example.full_description}\n") if QA::Runtime::Env.debug? + end + + config.after(:context) do + if !QA::Runtime::Browser.blank_page? && QA::Page::Main::Menu.perform(&:signed_in?) + QA::Page::Main::Menu.perform(&:sign_out) + raise( + <<~ERROR + The test left the browser signed in. + + Usually, Capybara prevents this from happening but some things can + interfere. For example, if it has an `after(:context)` block that logs + in, the browser will stay logged in and this will cause the next test + to fail. + + Please make sure the test does not leave the browser signed in. + ERROR + ) + end end config.expect_with :rspec do |expectations| |