diff options
| author | Achilleas Pipinellis <axil@gitlab.com> | 2019-08-20 20:22:43 +0200 |
|---|---|---|
| committer | Achilleas Pipinellis <axil@gitlab.com> | 2019-08-20 20:22:43 +0200 |
| commit | e61308ce1d82e12e5087371469baea4a452875d1 (patch) | |
| tree | 62551a3ae4eab75e5af7e3b35358c07a51b2132f /qa | |
| parent | 4f323bb62fbe71a4352de25cab141f361a3fe1a6 (diff) | |
| parent | 2989ed078c1d45b0959dcecb1bc3c8f4740a3c0d (diff) | |
| download | gitlab-ce-docs-patch-71.tar.gz | |
Merge branch 'master' into docs-patch-71docs-patch-71
Diffstat (limited to 'qa')
120 files changed, 1678 insertions, 534 deletions
diff --git a/qa/.rspec_parallel b/qa/.rspec_parallel new file mode 100644 index 00000000000..e5927927eaa --- /dev/null +++ b/qa/.rspec_parallel @@ -0,0 +1,5 @@ +--color +--format documentation +--format ParallelTests::RSpec::SummaryLogger --out tmp/spec_summary.log +--format ParallelTests::RSpec::RuntimeLogger --out tmp/parallel_runtime_rspec.log +--require spec_helper diff --git a/qa/Dockerfile b/qa/Dockerfile index 74be373b8e8..3309f5b6ce3 100644 --- a/qa/Dockerfile +++ b/qa/Dockerfile @@ -47,9 +47,13 @@ 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 -WORKDIR /home/qa -COPY ./Gemfile* ./ -RUN bundle install -COPY ./ ./ +WORKDIR /home/gitlab/qa +COPY ./qa/Gemfile* /home/gitlab/qa/ +COPY ./config/initializers/0_inject_enterprise_edition_module.rb /home/gitlab/config/initializers/ +COPY ./lib/gitlab.rb /home/gitlab/lib/ +COPY ./INSTALLATION_TYPE /home/gitlab/ +COPY ./VERSION /home/gitlab/ +RUN cd /home/gitlab/qa/ && bundle install +COPY ./qa /home/gitlab/qa ENTRYPOINT ["bin/test"] diff --git a/qa/Gemfile b/qa/Gemfile index 12994b85322..f04ecb13879 100644 --- a/qa/Gemfile +++ b/qa/Gemfile @@ -1,5 +1,7 @@ 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' @@ -7,7 +9,9 @@ gem 'rake', '~> 12.3.0' gem 'rspec', '~> 3.7' gem 'selenium-webdriver', '~> 3.12' gem 'airborne', '~> 0.2.13' -gem 'nokogiri', '~> 1.10.3' +gem 'nokogiri', '~> 1.10.4' 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' diff --git a/qa/Gemfile.lock b/qa/Gemfile.lock index 6b0635ed0e2..d582d77c5cd 100644 --- a/qa/Gemfile.lock +++ b/qa/Gemfile.lock @@ -1,9 +1,9 @@ GEM remote: https://rubygems.org/ specs: - activesupport (5.1.4) + activesupport (5.2.3) concurrent-ruby (~> 1.0, >= 1.0.2) - i18n (~> 0.7) + i18n (>= 0.7, < 2) minitest (~> 5.1) tzinfo (~> 1.1) addressable (2.5.2) @@ -28,16 +28,17 @@ GEM childprocess (0.9.0) ffi (~> 1.0, >= 1.0.11) coderay (1.1.2) - concurrent-ruby (1.0.5) + concurrent-ruby (1.1.5) diff-lcs (1.3) domain_name (0.5.20170404) unf (>= 0.0.5, < 1.0.0) faker (1.9.3) i18n (>= 0.7) ffi (1.9.25) + gitlab-qa (4.0.0) http-cookie (1.0.3) domain_name (~> 0.5) - i18n (0.9.1) + i18n (1.6.0) concurrent-ruby (~> 1.0) knapsack (1.17.1) rake @@ -49,10 +50,13 @@ GEM mime-types-data (3.2016.0521) mini_mime (1.0.0) mini_portile2 (2.4.0) - minitest (5.11.1) + minitest (5.11.3) netrc (0.11.0) - nokogiri (1.10.3) + nokogiri (1.10.4) mini_portile2 (~> 2.4.0) + parallel (1.17.0) + parallel_tests (2.29.0) + parallel pry (0.11.3) coderay (~> 1.1.0) method_source (~> 0.9.0) @@ -83,12 +87,14 @@ GEM rspec-retry (0.6.1) rspec-core (> 3.3) rspec-support (3.7.0) + rspec_junit_formatter (0.4.1) + rspec-core (>= 2, < 4, != 2.12.0) rubyzip (1.2.2) selenium-webdriver (3.141.0) childprocess (~> 0.5) rubyzip (~> 1.2, >= 1.2.2) thread_safe (0.3.6) - tzinfo (1.2.4) + tzinfo (1.2.5) thread_safe (~> 0.1) unf (0.1.4) unf_ext @@ -100,16 +106,20 @@ PLATFORMS ruby DEPENDENCIES + activesupport (= 5.2.3) airborne (~> 0.2.13) capybara (~> 2.16.1) capybara-screenshot (~> 1.0.18) faker (~> 1.6, >= 1.6.6) + gitlab-qa knapsack (~> 1.17) - nokogiri (~> 1.10.3) + nokogiri (~> 1.10.4) + 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) selenium-webdriver (~> 3.12) BUNDLED WITH diff --git a/qa/README.md b/qa/README.md index ef6f202464d..97555f8d0c2 100644 --- a/qa/README.md +++ b/qa/README.md @@ -34,8 +34,23 @@ using `package-and-qa` manual action, to test if everything works fine. ## How can I use it? -You can use GitLab QA to exercise tests on any live instance! For example, the -following call would login to a local [GDK] instance and run all specs in +You can use GitLab QA to exercise tests on any live instance! If you don't +have an instance available you can follow the instructions below to use +the [GitLab Development Kit (GDK)][GDK]. +This is the recommended option if you would like to contribute to the tests. + +### Run the end-to-end tests in a local development environment + +Follow the GDK instructions to [prepare](https://gitlab.com/gitlab-org/gitlab-development-kit/blob/master/doc/prepare.md) +and [install](https://gitlab.com/gitlab-org/gitlab-development-kit/blob/master/doc/set-up-gdk.md) +your local GitLab development environment. + +Once you have GDK running, switch to the `qa` directory. E.g., if you setup +GDK to develop in the main `gitlab-ce` repo, the GitLab source code will be +in a `gitlab` directory and so the end-to-end test code will be in `gitlab/qa`. + +From there you can run the tests. For example, the +following call would login to the GDK instance and run all specs in `qa/specs/features`: ``` @@ -60,14 +75,14 @@ You can also supply specific tests to run as another parameter. For example, to run the repository-related specs, you can execute: ``` -bundle exec bin/qa Test::Instance::All http://localhost -- qa/specs/features/browser_ui/3_create/repository +bundle exec bin/qa Test::Instance::All http://localhost:3000 -- qa/specs/features/browser_ui/3_create/repository ``` Since the arguments would be passed to `rspec`, you could use all `rspec` options there. For example, passing `--backtrace` and also line number: ``` -bundle exec bin/qa Test::Instance::All http://localhost -- qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb:6 --backtrace +bundle exec bin/qa Test::Instance::All http://localhost:3000 -- qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb:6 --backtrace ``` Note that the separator `--` is required; all subsequent options will be @@ -108,10 +123,11 @@ To set multiple cookies, separate them with the `;` character, for example: `QA_ Once you have made changes to the CE/EE repositories, you may want to build a Docker image to test locally instead of waiting for the `gitlab-ce-qa` or -`gitlab-ee-qa` nightly builds. To do that, you can run from this directory: +`gitlab-ee-qa` nightly builds. To do that, you can run **from the top `gitlab` +directory** (one level up from this directory): ```sh -docker build -t gitlab/gitlab-ce-qa:nightly . +docker build -t gitlab/gitlab-ce-qa:nightly --file ./qa/Dockerfile ./ ``` [GDK]: https://gitlab.com/gitlab-org/gitlab-development-kit/ @@ -125,7 +141,7 @@ tests that are expected to fail while a fix is in progress (similar to how can be used). ``` -bundle exec bin/qa Test::Instance::All http://localhost -- --tag quarantine +bundle exec bin/qa Test::Instance::All http://localhost:3000 -- --tag quarantine ``` If `quarantine` is used with other tags, tests will only be run if they have at @@ -144,7 +160,7 @@ option `--enable-feature FEATURE_FLAG`. For example, to enable the feature flag that enforces Gitaly request limits, you would use the command: ``` -bundle exec bin/qa Test::Instance::All http://localhost --enable-feature gitaly_enforce_requests_limits +bundle exec bin/qa Test::Instance::All http://localhost:3000 --enable-feature gitaly_enforce_requests_limits ``` This will instruct the QA framework to enable the `gitaly_enforce_requests_limits` diff --git a/qa/Rakefile b/qa/Rakefile index 7ac018f7286..7ba8a6d68ba 100644 --- a/qa/Rakefile +++ b/qa/Rakefile @@ -13,8 +13,9 @@ task :delete_subgroups do end desc "Generate Performance Testdata" -task :generate_perf_testdata do - QA::Tools::GeneratePerfTestdata.new.run +task :generate_perf_testdata, :type do |t, args| + args.with_defaults(type: :all) + QA::Tools::GeneratePerfTestdata.new.method(args[:type]).call end desc "Run artillery load tests" @@ -4,6 +4,9 @@ $: << File.expand_path(File.dirname(__FILE__)) Encoding.default_external = 'UTF-8' +require_relative '../lib/gitlab' +require_relative '../config/initializers/0_inject_enterprise_edition_module' + module QA ## # GitLab QA runtime classes, mostly singletons. @@ -162,9 +165,12 @@ module QA module File autoload :Form, 'qa/page/file/form' autoload :Show, 'qa/page/file/show' + autoload :Edit, 'qa/page/file/edit' module Shared autoload :CommitMessage, 'qa/page/file/shared/commit_message' + autoload :CommitButton, 'qa/page/file/shared/commit_button' + autoload :Editor, 'qa/page/file/shared/editor' end end @@ -218,6 +224,7 @@ module QA autoload :Operations, 'qa/page/project/sub_menus/operations' autoload :Repository, 'qa/page/project/sub_menus/repository' autoload :Settings, 'qa/page/project/sub_menus/settings' + autoload :Project, 'qa/page/project/sub_menus/project' end module Issue @@ -310,6 +317,10 @@ module QA autoload :Login, 'qa/page/mattermost/login' end + module Search + autoload :Results, 'qa/page/search/results' + end + ## # Classes describing components that are used by several pages. # @@ -323,6 +334,7 @@ module QA autoload :DropdownFilter, 'qa/page/component/dropdown_filter' autoload :UsersSelect, 'qa/page/component/users_select' autoload :Note, 'qa/page/component/note' + autoload :ConfirmModal, 'qa/page/component/confirm_modal' module Issuable autoload :Common, 'qa/page/component/issuable/common' @@ -355,6 +367,7 @@ module QA module Specs autoload :Config, 'qa/specs/config' autoload :Runner, 'qa/specs/runner' + autoload :ParallelRunner, 'qa/specs/parallel_runner' module Helpers autoload :Quarantine, 'qa/specs/helpers/quarantine' diff --git a/qa/qa/ce/knapsack/nightly_master_report.json b/qa/qa/ce/knapsack/nightly_master_report.json new file mode 100644 index 00000000000..08694f706de --- /dev/null +++ b/qa/qa/ce/knapsack/nightly_master_report.json @@ -0,0 +1,46 @@ +{ + "qa/specs/features/api/1_manage/users_spec.rb": 0.6089541912078857, + "qa/specs/features/api/3_create/repository/files_spec.rb": 5.015859127044678, + "qa/specs/features/api/3_create/repository/project_archive_compare_spec.rb": 1.0199065208435059, + "qa/specs/features/browser_ui/1_manage/group/transfer_project_spec.rb": 33.54091453552246, + "qa/specs/features/browser_ui/1_manage/login/log_in_spec.rb": 3.438166856765747, + "qa/specs/features/browser_ui/1_manage/login/login_via_oauth_spec.rb": 20.58603596687317, + "qa/specs/features/browser_ui/1_manage/login/register_spec.rb": 22.320587396621704, + "qa/specs/features/browser_ui/1_manage/project/add_project_member_spec.rb": 8.490083694458008, + "qa/specs/features/browser_ui/1_manage/project/create_project_spec.rb": 10.214765310287476, + "qa/specs/features/browser_ui/1_manage/project/import_github_repo_spec.rb": 100.28881478309631, + "qa/specs/features/browser_ui/1_manage/project/view_project_activity_spec.rb": 7.882027864456177, + "qa/specs/features/browser_ui/2_plan/issue/check_mentions_for_xss_spec.rb": 13.739388942718506, + "qa/specs/features/browser_ui/2_plan/issue/collapse_comments_in_discussions_spec.rb": 13.403101205825806, + "qa/specs/features/browser_ui/2_plan/issue/comment_issue_spec.rb": 10.989444971084595, + "qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb": 10.811973810195923, + "qa/specs/features/browser_ui/2_plan/issue/filter_issue_comments_spec.rb": 12.63524317741394, + "qa/specs/features/browser_ui/2_plan/issue/issue_suggestions_spec.rb": 11.280649185180664, + "qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb": 41.76726770401001, + "qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb": 32.5517954826355, + "qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb": 46.54227638244629, + "qa/specs/features/browser_ui/3_create/merge_request/squash_merge_request_spec.rb": 27.943300485610962, + "qa/specs/features/browser_ui/3_create/merge_request/view_merge_request_diff_patch_spec.rb": 3.705310821533203, + "qa/specs/features/browser_ui/3_create/repository/add_file_template_spec.rb": 40.09336972236633, + "qa/specs/features/browser_ui/3_create/repository/add_list_delete_branches_spec.rb": 31.49540114402771, + "qa/specs/features/browser_ui/3_create/repository/add_ssh_key_spec.rb": 16.18057894706726, + "qa/specs/features/browser_ui/3_create/repository/clone_spec.rb": 0.7397980690002441, + "qa/specs/features/browser_ui/3_create/repository/create_edit_delete_file_via_web_spec.rb": 18.047621726989746, + "qa/specs/features/browser_ui/3_create/repository/push_http_private_token_spec.rb": 9.48607873916626, + "qa/specs/features/browser_ui/3_create/repository/push_mirroring_over_http_spec.rb": 23.710937023162842, + "qa/specs/features/browser_ui/3_create/repository/push_over_http_file_size_spec.rb": 19.459370374679565, + "qa/specs/features/browser_ui/3_create/repository/push_over_http_spec.rb": 7.730542182922363, + "qa/specs/features/browser_ui/3_create/repository/push_protected_branch_spec.rb": 29.76174831390381, + "qa/specs/features/browser_ui/3_create/repository/use_ssh_key_spec.rb": 22.800872802734375, + "qa/specs/features/browser_ui/3_create/repository/user_views_commit_diff_patch_spec.rb": 6.731764793395996, + "qa/specs/features/browser_ui/3_create/snippet/create_snippet_spec.rb": 5.812374591827393, + "qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb": 25.460349321365356, + "qa/specs/features/browser_ui/3_create/wiki/create_edit_clone_push_wiki_spec.rb": 17.273863554000854, + "qa/specs/features/browser_ui/4_verify/ci_variable/add_ci_variable_spec.rb": 8.31815505027771, + "qa/specs/features/browser_ui/4_verify/runner/register_runner_spec.rb": 18.679633855819702, + "qa/specs/features/browser_ui/6_release/deploy_key/add_deploy_key_spec.rb": 15.342933893203735, + "qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb": 92.46774697303772, + "qa/specs/features/browser_ui/6_release/deploy_token/add_deploy_token_spec.rb": 20.252174615859985, + "qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb": 8.281434059143066, + "qa/specs/features/browser_ui/non_devops/performance_bar_spec.rb": 8.810423135757446 +}
\ No newline at end of file diff --git a/qa/qa/page/admin/menu.rb b/qa/qa/page/admin/menu.rb index eea5717f5a7..61ec9854726 100644 --- a/qa/qa/page/admin/menu.rb +++ b/qa/qa/page/admin/menu.rb @@ -13,6 +13,10 @@ module QA element :admin_settings_metrics_and_profiling_item end + view 'app/views/layouts/nav/sidebar/_admin.html.haml' do + element :integration_settings_link + end + def go_to_repository_settings hover_settings do within_submenu do @@ -21,6 +25,14 @@ module QA end end + def go_to_integration_settings + hover_settings do + within_submenu do + click_element :integration_settings_link + end + end + end + def go_to_general_settings hover_settings do within_submenu do diff --git a/qa/qa/page/base.rb b/qa/qa/page/base.rb index d247a273637..45496c6b245 100644 --- a/qa/qa/page/base.rb +++ b/qa/qa/page/base.rb @@ -25,19 +25,10 @@ module QA end end - def retry_until(max_attempts: 3, reload: false) - attempts = 0 - - while attempts < max_attempts - result = yield - return result if result - - refresh if reload - - attempts += 1 + def retry_until(max_attempts: 3, reload: false, sleep_interval: 0) + QA::Support::Retrier.retry_until(max_attempts: max_attempts, reload_page: (reload && self), sleep_interval: sleep_interval) do + yield end - - false end def retry_on_exception(max_attempts: 3, reload: false, sleep_interval: 0.5) @@ -190,11 +181,15 @@ module QA return ["Page class does not have views / elements defined!"] end - views.map(&:errors).flatten + views.flat_map(&:errors) end def self.elements - views.map(&:elements).flatten + views.flat_map(&:elements) + end + + def send_keys_to_element(name, keys) + find_element(name).send_keys(keys) end class DSL diff --git a/qa/qa/page/component/confirm_modal.rb b/qa/qa/page/component/confirm_modal.rb new file mode 100644 index 00000000000..355e2783fb7 --- /dev/null +++ b/qa/qa/page/component/confirm_modal.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +module QA + module Page + module Component + module ConfirmModal + def self.included(base) + base.view 'app/views/shared/_confirm_modal.html.haml' do + element :confirm_modal + element :confirm_input + element :confirm_button + end + end + + def fill_confirmation_text(text) + fill_element :confirm_input, text + end + + def click_confirm_button + click_element :confirm_button + end + end + end + end +end diff --git a/qa/qa/page/component/note.rb b/qa/qa/page/component/note.rb index 07e191f1c9b..34fde045091 100644 --- a/qa/qa/page/component/note.rb +++ b/qa/qa/page/component/note.rb @@ -10,19 +10,27 @@ module QA element :discussion_option end + base.view 'app/assets/javascripts/notes/components/note_actions.vue' do + element :note_edit_button + end + base.view 'app/assets/javascripts/notes/components/note_form.vue' do element :reply_input element :reply_comment_button end base.view 'app/assets/javascripts/notes/components/discussion_actions.vue' do - element :discussion_reply + element :discussion_reply_tab end base.view 'app/assets/javascripts/notes/components/toggle_replies_widget.vue' do element :expand_replies element :collapse_replies end + + base.view 'app/assets/javascripts/diffs/components/diff_file_header.vue' do + element :toggle_comments_button + end end def start_discussion(text) @@ -32,8 +40,12 @@ module QA click_element :comment_button end + def toggle_comments + all_elements(:toggle_comments_button).last.click + end + def type_reply_to_discussion(reply_text) - all_elements(:discussion_reply).last.click + all_elements(:discussion_reply_tab).last.click fill_element :reply_input, reply_text end @@ -49,6 +61,12 @@ module QA def expand_replies click_element :expand_replies end + + def edit_comment(text) + click_element :note_edit_button + fill_element :reply_input, text + click_element :reply_comment_button + end end end end diff --git a/qa/qa/page/component/select2.rb b/qa/qa/page/component/select2.rb index e40bc4b1d3e..d05c44d22b2 100644 --- a/qa/qa/page/component/select2.rb +++ b/qa/qa/page/component/select2.rb @@ -8,6 +8,10 @@ module QA find('.select2-result-label', text: item_text, match: :prefer_exact).click end + def current_selection + find('.select2-chosen').text + end + def clear_current_selection_if_present if has_css?('a > abbr.select2-search-choice-close', wait: 1.0) find('a > abbr.select2-search-choice-close').click @@ -18,6 +22,10 @@ module QA find('.select2-input').set(item_text) select_item(item_text) end + + def expand_select_list + find('span.select2-arrow').click + end end end end diff --git a/qa/qa/page/dashboard/projects.rb b/qa/qa/page/dashboard/projects.rb index 7ab8ee39f72..0c23d7cffbb 100644 --- a/qa/qa/page/dashboard/projects.rb +++ b/qa/qa/page/dashboard/projects.rb @@ -5,7 +5,7 @@ module QA module Dashboard class Projects < Page::Base view 'app/views/shared/projects/_search_form.html.haml' do - element :form_filter_by_name, /form_tag.+id: 'project-filter-form'/ # rubocop:disable QA/ElementWithPattern + element :project_filter_form, required: true end def go_to_project(name) @@ -14,10 +14,14 @@ module QA find_link(text: name).click end + def self.path + '/' + end + private def filter_by_name(name) - page.within('form#project-filter-form') do + within_element(:project_filter_form) do fill_in :name, with: name end end diff --git a/qa/qa/page/element.rb b/qa/qa/page/element.rb index 7a01320901d..9e6fd2fdd4f 100644 --- a/qa/qa/page/element.rb +++ b/qa/qa/page/element.rb @@ -28,7 +28,7 @@ module QA end def selector_css - ".#{selector}" + %Q([data-qa-selector="#{@name}"],.#{selector}) end def expression @@ -40,7 +40,7 @@ module QA end def matches?(line) - !!(line =~ expression) + !!(line =~ /["']#{name}['"]|#{expression}/) end end end diff --git a/qa/qa/page/file/edit.rb b/qa/qa/page/file/edit.rb new file mode 100644 index 00000000000..3a4a1837b1c --- /dev/null +++ b/qa/qa/page/file/edit.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module QA + module Page + module File + class Edit < Page::Base + include Shared::CommitMessage + include Shared::CommitButton + include Shared::Editor + end + end + end +end diff --git a/qa/qa/page/file/form.rb b/qa/qa/page/file/form.rb index e42de7d65c5..a6251f185f9 100644 --- a/qa/qa/page/file/form.rb +++ b/qa/qa/page/file/form.rb @@ -6,14 +6,11 @@ module QA class Form < Page::Base include Shared::CommitMessage include Page::Component::DropdownFilter + include Shared::CommitButton + include Shared::Editor view 'app/views/projects/blob/_editor.html.haml' do element :file_name, "text_field_tag 'file_name'" # rubocop:disable QA/ElementWithPattern - element :editor, '#editor' # rubocop:disable QA/ElementWithPattern - end - - view 'app/views/projects/_commit_button.html.haml' do - element :commit_changes, "button_tag 'Commit changes'" # rubocop:disable QA/ElementWithPattern end view 'app/views/projects/blob/_template_selectors.html.haml' do @@ -28,20 +25,6 @@ module QA fill_in 'file_name', with: name end - def add_content(content) - text_area.set content - end - - def remove_content - text_area.send_keys([:command, 'a'], :backspace) - end - - def commit_changes - click_on 'Commit changes' - - finished_loading? - end - def select_template(template_type, template) click_element :template_type_dropdown click_link template_type @@ -60,12 +43,6 @@ module QA end filter_and_select template end - - private - - def text_area - find('#editor>textarea', visible: false) - end end end end diff --git a/qa/qa/page/file/shared/commit_button.rb b/qa/qa/page/file/shared/commit_button.rb new file mode 100644 index 00000000000..d8e751dd7b6 --- /dev/null +++ b/qa/qa/page/file/shared/commit_button.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module QA + module Page + module File + module Shared + module CommitButton + def self.included(base) + base.view 'app/views/projects/_commit_button.html.haml' do + element :commit_button + end + end + + def commit_changes + click_element(:commit_button) + end + end + end + end + end +end diff --git a/qa/qa/page/file/shared/editor.rb b/qa/qa/page/file/shared/editor.rb new file mode 100644 index 00000000000..448c09cfbca --- /dev/null +++ b/qa/qa/page/file/shared/editor.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +module QA + module Page + module File + module Shared + module Editor + def self.included(base) + base.view 'app/views/projects/blob/_editor.html.haml' do + element :editor + end + end + + def add_content(content) + text_area.set content + end + + def remove_content + text_area.send_keys([:command, 'a'], :backspace) + end + + private + + def text_area + within_element :editor do + find('textarea', visible: false) + end + end + end + end + end + end +end diff --git a/qa/qa/page/file/show.rb b/qa/qa/page/file/show.rb index eaf88c6e69e..92f9181f99d 100644 --- a/qa/qa/page/file/show.rb +++ b/qa/qa/page/file/show.rb @@ -5,6 +5,8 @@ module QA module File class Show < Page::Base include Shared::CommitMessage + include Project::SubMenus::Settings + include Project::SubMenus::Common view 'app/helpers/blob_helper.rb' do element :edit_button, "_('Edit')" # rubocop:disable QA/ElementWithPattern diff --git a/qa/qa/page/main/login.rb b/qa/qa/page/main/login.rb index 8970eeb6678..94245bbfcba 100644 --- a/qa/qa/page/main/login.rb +++ b/qa/qa/page/main/login.rb @@ -6,7 +6,7 @@ module QA class Login < Page::Base view 'app/views/devise/passwords/edit.html.haml' do element :password_field - element :password_confirmation + element :password_confirmation_field element :change_password_button end @@ -44,7 +44,7 @@ module QA def sign_in_using_credentials(user = nil) # Don't try to log-in if we're already logged-in - return if Page::Main::Menu.act { has_personal_area?(wait: 0) } + return if Page::Main::Menu.perform { |menu| menu.has_personal_area?(wait: 0) } using_wait_time 0 do set_initial_password_if_present @@ -52,13 +52,11 @@ module QA raise NotImplementedError if Runtime::User.ldap_user? && user&.credentials_given? if Runtime::User.ldap_user? - sign_in_using_ldap_credentials + sign_in_using_ldap_credentials(user || Runtime::User) else sign_in_using_gitlab_credentials(user || Runtime::User) end end - - Page::Main::Menu.act { has_personal_area? } end def sign_in_using_admin_credentials @@ -73,7 +71,26 @@ module QA sign_in_using_gitlab_credentials(admin) end - Page::Main::Menu.act { has_personal_area? } + Page::Main::Menu.perform(&:has_personal_area?) + end + + def sign_in_using_ldap_credentials(user) + # Log out if already logged in + Page::Main::Menu.perform do |menu| + menu.sign_out if menu.has_personal_area?(wait: 0) + end + + using_wait_time 0 do + set_initial_password_if_present + + switch_to_ldap_tab + + fill_element :username_field, user.ldap_username + fill_element :password_field, user.ldap_password + click_element :sign_in_button + end + + Page::Main::Menu.perform(&:has_personal_area?) end def self.path @@ -133,14 +150,6 @@ module QA private - def sign_in_using_ldap_credentials - switch_to_ldap_tab - - fill_element :username_field, Runtime::User.ldap_username - fill_element :password_field, Runtime::User.ldap_password - click_element :sign_in_button - end - def sign_in_using_gitlab_credentials(user) switch_to_sign_in_tab if has_sign_in_tab? switch_to_standard_tab if has_standard_tab? @@ -154,7 +163,7 @@ module QA return unless has_content?('Change your password') fill_element :password_field, Runtime::User.password - fill_element :password_confirmation, Runtime::User.password + fill_element :password_confirmation_field, Runtime::User.password click_element :change_password_button end end diff --git a/qa/qa/page/main/menu.rb b/qa/qa/page/main/menu.rb index 5eb24d2d2ba..e3039149ab4 100644 --- a/qa/qa/page/main/menu.rb +++ b/qa/qa/page/main/menu.rb @@ -5,14 +5,14 @@ module QA module Main class Menu < Page::Base view 'app/views/layouts/header/_current_user_dropdown.html.haml' do - element :user_sign_out_link, 'link_to _("Sign out")' # rubocop:disable QA/ElementWithPattern - element :settings_link, 'link_to s_("CurrentUser|Settings")' # rubocop:disable QA/ElementWithPattern + element :sign_out_link + element :settings_link end view 'app/views/layouts/header/_default.html.haml' do element :navbar, required: true element :user_avatar, required: true - element :user_menu, '.dropdown-menu' # rubocop:disable QA/ElementWithPattern + element :user_menu, required: true end view 'app/views/layouts/nav/_dashboard.html.haml' do @@ -27,6 +27,10 @@ module QA element :your_projects_link end + view 'app/views/layouts/_search.html.haml' do + element :search_term_field + end + def go_to_groups within_top_menu do click_element :groups_dropdown @@ -53,7 +57,7 @@ module QA def sign_out within_user_menu do - click_link 'Sign out' + click_element :sign_out_link end end @@ -71,6 +75,10 @@ module QA click_element :snippets_link end + def search_for(term) + fill_element :search_term_field, "#{term}\n" + end + def has_personal_area?(wait: Capybara.default_max_wait_time) has_element?(:user_avatar, wait: wait) end @@ -82,7 +90,7 @@ module QA private def within_top_menu - page.within('.qa-navbar') do + within_element(:navbar) do yield end end @@ -91,7 +99,7 @@ module QA within_top_menu do click_element :user_avatar - page.within('.dropdown-menu') do + within_element(:user_menu) do yield end end diff --git a/qa/qa/page/main/oauth.rb b/qa/qa/page/main/oauth.rb index 5f6ddb9a114..2b1a9ab2b6a 100644 --- a/qa/qa/page/main/oauth.rb +++ b/qa/qa/page/main/oauth.rb @@ -5,7 +5,7 @@ module QA module Main class OAuth < Page::Base view 'app/views/doorkeeper/authorizations/new.html.haml' do - element :authorization_button, 'submit_tag _("Authorize")' # rubocop:disable QA/ElementWithPattern + element :authorization_button end def needs_authorization? @@ -13,7 +13,7 @@ module QA end def authorize! - click_button 'Authorize' + click_element :authorization_button end end end diff --git a/qa/qa/page/main/sign_up.rb b/qa/qa/page/main/sign_up.rb index 46a105003d0..c47d2ce9c74 100644 --- a/qa/qa/page/main/sign_up.rb +++ b/qa/qa/page/main/sign_up.rb @@ -5,28 +5,28 @@ module QA module Main class SignUp < Page::Base view 'app/views/devise/shared/_signup_box.html.haml' do - element :new_user_name - element :new_user_username - element :new_user_email - element :new_user_email_confirmation - element :new_user_password + element :new_user_name_field + element :new_user_username_field + element :new_user_email_field + element :new_user_email_confirmation_field + element :new_user_password_field element :new_user_register_button - element :new_user_accept_terms + element :new_user_accept_terms_checkbox end def sign_up!(user) - fill_element :new_user_name, user.name - fill_element :new_user_username, user.username - fill_element :new_user_email, user.email - fill_element :new_user_email_confirmation, user.email - fill_element :new_user_password, user.password + fill_element :new_user_name_field, user.name + fill_element :new_user_username_field, user.username + fill_element :new_user_email_field, user.email + fill_element :new_user_email_confirmation_field, user.email + fill_element :new_user_password_field, user.password - check_element :new_user_accept_terms if has_element?(:new_user_accept_terms) + check_element :new_user_accept_terms_checkbox if has_element?(:new_user_accept_terms_checkbox) signed_in = retry_until do click_element :new_user_register_button - Page::Main::Menu.act { has_personal_area? } + Page::Main::Menu.perform(&:has_personal_area?) end raise "Failed to register and sign in" unless signed_in diff --git a/qa/qa/page/merge_request/show.rb b/qa/qa/page/merge_request/show.rb index 5aef868a805..6a415b56e50 100644 --- a/qa/qa/page/merge_request/show.rb +++ b/qa/qa/page/merge_request/show.rb @@ -111,6 +111,14 @@ module QA end end + def has_title?(title) + has_element?(:title, text: title) + end + + def has_description?(description) + has_element?(:description, text: description) + end + def merge! # The merge button is disabled on load wait do diff --git a/qa/qa/page/project/issue/new.rb b/qa/qa/page/project/issue/new.rb index 0d138417176..65c02801d67 100644 --- a/qa/qa/page/project/issue/new.rb +++ b/qa/qa/page/project/issue/new.rb @@ -6,7 +6,7 @@ module QA module Issue class New < Page::Base view 'app/views/shared/issuable/_form.html.haml' do - element :submit_issue_button, 'form.submit "Submit' # rubocop:disable QA/ElementWithPattern + element :issuable_create_button end view 'app/views/shared/issuable/form/_title.html.haml' do @@ -26,7 +26,7 @@ module QA end def create_new_issue - click_on 'Submit issue' + click_element :issuable_create_button, Page::Project::Issue::Show end end end diff --git a/qa/qa/page/project/issue/show.rb b/qa/qa/page/project/issue/show.rb index 77bad7481d8..45dad9bc0ae 100644 --- a/qa/qa/page/project/issue/show.rb +++ b/qa/qa/page/project/issue/show.rb @@ -8,18 +8,13 @@ module QA include Page::Component::Issuable::Common include Page::Component::Note - view 'app/views/shared/notes/_form.html.haml' do - element :new_note_form, 'new-note' # rubocop:disable QA/ElementWithPattern - element :new_note_form, 'attr: :note' # rubocop:disable QA/ElementWithPattern - end - view 'app/assets/javascripts/notes/components/comment_form.vue' do element :comment_button element :comment_input end view 'app/assets/javascripts/notes/components/discussion_filter.vue' do - element :discussion_filter + element :discussion_filter, required: true element :filter_options end @@ -27,6 +22,21 @@ module QA element :noteable_note_item end + view 'app/helpers/dropdowns_helper.rb' do + element :dropdown_input_field + end + + view 'app/views/shared/notes/_form.html.haml' do + element :new_note_form, 'new-note' # rubocop:disable QA/ElementWithPattern + element :new_note_form, 'attr: :note' # rubocop:disable QA/ElementWithPattern + end + + view 'app/views/shared/issuable/_sidebar.html.haml' do + element :labels_block + element :edit_link_labels + element :dropdown_menu_labels + end + # Adds a comment to an issue # attachment option should be an absolute path def comment(text, attachment: nil, filter: :all_activities) @@ -47,6 +57,10 @@ module QA end end + def select_all_activities_filter + select_filter_with_text('Show all activity') + end + def select_comments_only_filter select_filter_with_text('Show comments only') end @@ -55,8 +69,29 @@ module QA select_filter_with_text('Show history only') end - def select_all_activities_filter - select_filter_with_text('Show all activity') + def select_labels_and_refresh(labels) + Support::Retrier.retry_until do + click_element(:edit_link_labels) + has_element?(:dropdown_menu_labels, text: labels.first) + end + + labels.each do |label| + within_element(:dropdown_menu_labels, text: label) do + send_keys_to_element(:dropdown_input_field, [label, :enter]) + end + end + + click_body + + labels.each do |label| + has_element?(:labels_block, text: label) + end + + refresh + end + + def text_of_labels_block + find_element(:labels_block) end private diff --git a/qa/qa/page/project/menu.rb b/qa/qa/page/project/menu.rb index 3fe048f752a..838d59b59cb 100644 --- a/qa/qa/page/project/menu.rb +++ b/qa/qa/page/project/menu.rb @@ -5,7 +5,7 @@ module QA module Project class Menu < Page::Base include SubMenus::Common - + include SubMenus::Project include SubMenus::CiCd include SubMenus::Issues include SubMenus::Operations diff --git a/qa/qa/page/project/new.rb b/qa/qa/page/project/new.rb index defd85a5740..64aab9be056 100644 --- a/qa/qa/page/project/new.rb +++ b/qa/qa/page/project/new.rb @@ -18,7 +18,7 @@ module QA element :project_name, 'text_field :name' # rubocop:disable QA/ElementWithPattern element :project_path, 'text_field :path' # rubocop:disable QA/ElementWithPattern element :project_description, 'text_area :description' # rubocop:disable QA/ElementWithPattern - element :project_create_button, "submit 'Create project'" # rubocop:disable QA/ElementWithPattern + element :project_create_button, "submit _('Create project')" # rubocop:disable QA/ElementWithPattern element :visibility_radios, 'visibility_level:' # rubocop:disable QA/ElementWithPattern end @@ -59,7 +59,7 @@ module QA end def set_visibility(visibility) - choose visibility + choose visibility.capitalize end def click_github_link diff --git a/qa/qa/page/project/operations/kubernetes/show.rb b/qa/qa/page/project/operations/kubernetes/show.rb index c81e13e9b91..eb30e0ea02a 100644 --- a/qa/qa/page/project/operations/kubernetes/show.rb +++ b/qa/qa/page/project/operations/kubernetes/show.rb @@ -30,7 +30,7 @@ module QA def await_installed(application_name) within(".js-cluster-application-row-#{application_name}") do - page.has_text?('Installed', wait: 300) + page.has_text?(/Installed|Uninstall/, wait: 300) end end diff --git a/qa/qa/page/project/pipeline/index.rb b/qa/qa/page/project/pipeline/index.rb index 68850d989b1..fae7818f871 100644 --- a/qa/qa/page/project/pipeline/index.rb +++ b/qa/qa/page/project/pipeline/index.rb @@ -4,7 +4,7 @@ module QA::Page module Project::Pipeline class Index < QA::Page::Base view 'app/assets/javascripts/pipelines/components/pipeline_url.vue' do - element :pipeline_link, 'class="js-pipeline-url-link"' # rubocop:disable QA/ElementWithPattern + element :pipeline_link, 'class="js-pipeline-url-link' # rubocop:disable QA/ElementWithPattern end def click_on_latest_pipeline diff --git a/qa/qa/page/project/settings/advanced.rb b/qa/qa/page/project/settings/advanced.rb index 75530832860..ab4e3d757b6 100644 --- a/qa/qa/page/project/settings/advanced.rb +++ b/qa/qa/page/project/settings/advanced.rb @@ -5,9 +5,13 @@ module QA module Project module Settings class Advanced < Page::Base + include Component::Select2 + include Component::ConfirmModal + view 'app/views/projects/edit.html.haml' do element :project_path_field element :change_path_button + element :transfer_button end def update_project_path_to(path) @@ -22,6 +26,18 @@ module QA def click_change_path_button click_element :change_path_button end + + def select_transfer_option(namespace) + search_and_select(namespace) + end + + def transfer_project!(project_name, namespace) + expand_select_list + select_transfer_option(namespace) + click_element(:transfer_button) + fill_confirmation_text(project_name) + click_confirm_button + end end end end diff --git a/qa/qa/page/project/settings/ci_cd.rb b/qa/qa/page/project/settings/ci_cd.rb index b8c5c563da6..ae826fb3a32 100644 --- a/qa/qa/page/project/settings/ci_cd.rb +++ b/qa/qa/page/project/settings/ci_cd.rb @@ -8,33 +8,32 @@ module QA include Common view 'app/views/projects/settings/ci_cd/show.html.haml' do - element :autodevops_settings - element :runners_settings - element :variables_settings + element :autodevops_settings_content + element :runners_settings_content + element :variables_settings_content end view 'app/views/projects/settings/ci_cd/_autodevops_form.html.haml' do - element :enable_auto_devops_field, 'check_box :enabled' # rubocop:disable QA/ElementWithPattern - element :enable_auto_devops_button, "%strong= s_('CICD|Default to Auto DevOps pipeline')" # rubocop:disable QA/ElementWithPattern - element :save_changes_button, "submit _('Save changes')" # rubocop:disable QA/ElementWithPattern + element :enable_autodevops_checkbox + element :save_changes_button end def expand_runners_settings(&block) - expand_section(:runners_settings) do + expand_section(:runners_settings_content) do Settings::Runners.perform(&block) end end def expand_ci_variables(&block) - expand_section(:variables_settings) do + expand_section(:variables_settings_content) do Settings::CiVariables.perform(&block) end end def enable_auto_devops - expand_section(:autodevops_settings) do - check 'Default to Auto DevOps pipeline' - click_on 'Save changes' + expand_section(:autodevops_settings_content) do + check_element :enable_autodevops_checkbox + click_element :save_changes_button end end end diff --git a/qa/qa/page/project/settings/main.rb b/qa/qa/page/project/settings/main.rb index d1f3b15f950..dbbe62e3b1d 100644 --- a/qa/qa/page/project/settings/main.rb +++ b/qa/qa/page/project/settings/main.rb @@ -6,6 +6,8 @@ module QA module Settings class Main < Page::Base include Common + include Component::Select2 + include SubMenus::Project view 'app/views/projects/edit.html.haml' do element :advanced_settings diff --git a/qa/qa/page/project/show.rb b/qa/qa/page/project/show.rb index 1a9a2fd413f..9fd668f812b 100644 --- a/qa/qa/page/project/show.rb +++ b/qa/qa/page/project/show.rb @@ -5,6 +5,7 @@ module QA module Project class Show < Page::Base include Page::Component::ClonePanel + include Page::Project::SubMenus::Settings view 'app/views/layouts/header/_new_dropdown.haml' do element :new_menu_toggle diff --git a/qa/qa/page/project/sub_menus/ci_cd.rb b/qa/qa/page/project/sub_menus/ci_cd.rb index adae2ce08c4..2f0bc8b9ba6 100644 --- a/qa/qa/page/project/sub_menus/ci_cd.rb +++ b/qa/qa/page/project/sub_menus/ci_cd.rb @@ -5,6 +5,8 @@ module QA module Project module SubMenus module CiCd + include Page::Project::SubMenus::Common + def self.included(base) base.class_eval do view 'app/views/layouts/nav/sidebar/_project.html.haml' do diff --git a/qa/qa/page/project/sub_menus/common.rb b/qa/qa/page/project/sub_menus/common.rb index c94e1e85256..3c9e8085748 100644 --- a/qa/qa/page/project/sub_menus/common.rb +++ b/qa/qa/page/project/sub_menus/common.rb @@ -12,7 +12,11 @@ module QA end def within_submenu - within('.fly-out-list') do + if has_css?('.fly-out-list') + within('.fly-out-list') do + yield + end + else yield end end diff --git a/qa/qa/page/project/sub_menus/issues.rb b/qa/qa/page/project/sub_menus/issues.rb index f81e4f34909..8fb8fa06346 100644 --- a/qa/qa/page/project/sub_menus/issues.rb +++ b/qa/qa/page/project/sub_menus/issues.rb @@ -5,6 +5,8 @@ module QA module Project module SubMenus module Issues + include Page::Project::SubMenus::Common + def self.included(base) base.class_eval do view 'app/views/layouts/nav/sidebar/_project.html.haml' do diff --git a/qa/qa/page/project/sub_menus/operations.rb b/qa/qa/page/project/sub_menus/operations.rb index 24a99a9464c..d266cb21417 100644 --- a/qa/qa/page/project/sub_menus/operations.rb +++ b/qa/qa/page/project/sub_menus/operations.rb @@ -5,6 +5,8 @@ module QA module Project module SubMenus module Operations + include Page::Project::SubMenus::Common + def self.included(base) base.class_eval do view 'app/views/layouts/nav/sidebar/_project.html.haml' do diff --git a/qa/qa/page/project/sub_menus/project.rb b/qa/qa/page/project/sub_menus/project.rb new file mode 100644 index 00000000000..6f1bc131f84 --- /dev/null +++ b/qa/qa/page/project/sub_menus/project.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +module QA + module Page + module Project + module SubMenus + module Project + include Common + + def self.included(base) + base.class_eval do + view 'app/views/layouts/nav/sidebar/_project.html.haml' do + element :project_link + end + end + end + + def click_project + retry_on_exception do + within_sidebar do + click_element(:project_link) + end + end + end + end + end + end + end +end diff --git a/qa/qa/page/project/sub_menus/repository.rb b/qa/qa/page/project/sub_menus/repository.rb index 4cc73a6b25a..c53d805c61d 100644 --- a/qa/qa/page/project/sub_menus/repository.rb +++ b/qa/qa/page/project/sub_menus/repository.rb @@ -5,6 +5,8 @@ module QA module Project module SubMenus module Repository + include Page::Project::SubMenus::Common + def self.included(base) base.class_eval do view 'app/views/layouts/nav/sidebar/_project.html.haml' do diff --git a/qa/qa/page/project/sub_menus/settings.rb b/qa/qa/page/project/sub_menus/settings.rb index 22743ebd0a1..1cd39fcff58 100644 --- a/qa/qa/page/project/sub_menus/settings.rb +++ b/qa/qa/page/project/sub_menus/settings.rb @@ -5,11 +5,14 @@ module QA module Project module SubMenus module Settings + include Page::Project::SubMenus::Common + def self.included(base) base.class_eval do view 'app/views/layouts/nav/sidebar/_project.html.haml' do element :settings_item element :link_members_settings + element :general_settings_link end end end @@ -38,6 +41,14 @@ module QA end end + def go_to_general_settings + hover_settings do + within_submenu do + click_element :general_settings_link + end + end + end + def click_settings within_sidebar do click_on 'Settings' diff --git a/qa/qa/page/search/results.rb b/qa/qa/page/search/results.rb new file mode 100644 index 00000000000..b9b18abf660 --- /dev/null +++ b/qa/qa/page/search/results.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +module QA::Page + module Search + class Results < QA::Page::Base + view 'app/views/search/_category.html.haml' do + element :code_tab + end + + view 'app/views/search/results/_blob_data.html.haml' do + element :result_item_content + element :file_title_content + element :file_text_content + end + + def switch_to_code + click_element(:code_tab) + end + + def has_file_in_project?(file_name, project_name) + has_element? :result_item_content, text: "#{project_name}: #{file_name}" + end + + def has_file_with_content?(file_name, file_text) + within_element_by_index :result_item_content, 0 do + false unless has_element? :file_title_content, text: file_name + + has_element? :file_text_content, text: file_text + end + end + end + end +end diff --git a/qa/qa/page/settings/common.rb b/qa/qa/page/settings/common.rb index 8cd0b6bb49c..bede3fde105 100644 --- a/qa/qa/page/settings/common.rb +++ b/qa/qa/page/settings/common.rb @@ -11,7 +11,7 @@ module QA within_element(element_name) do # Because it is possible to click the button before the JS toggle code is bound wait(reload: false) do - click_button 'Expand' unless first('button', text: 'Collapse') + click_button 'Expand' unless has_css?('button', text: 'Collapse') has_content?('Collapse') end diff --git a/qa/qa/page/validator.rb b/qa/qa/page/validator.rb index edd12665f1e..9b2d0a1a41d 100644 --- a/qa/qa/page/validator.rb +++ b/qa/qa/page/validator.rb @@ -22,16 +22,14 @@ module QA end def descendants - @descendants ||= constants.map do |const| + @descendants ||= constants.flat_map do |const| case const when Class const if const < Page::Base when Module Page::Validator.new(const).descendants end - end - - @descendants.flatten.compact + end.compact end def errors diff --git a/qa/qa/resource/api_fabricator.rb b/qa/qa/resource/api_fabricator.rb index de04467ff5b..d1d75b6179e 100644 --- a/qa/qa/resource/api_fabricator.rb +++ b/qa/qa/resource/api_fabricator.rb @@ -13,6 +13,8 @@ module QA ResourceURLMissingError = Class.new(RuntimeError) attr_reader :api_resource, :api_response + attr_writer :api_client + attr_accessor :user def api_support? respond_to?(:api_get_path) && @@ -29,9 +31,12 @@ module QA end def eager_load_api_client! + return unless api_client.nil? + api_client.tap do |client| # Eager-load the API client so that the personal token creation isn't # taken in account in the actual resource creation timing. + client.user = user client.personal_access_token end end @@ -76,7 +81,7 @@ module QA def api_client @api_client ||= begin - Runtime::API::Client.new(:gitlab, is_new_session: !current_url.start_with?('http')) + Runtime::API::Client.new(:gitlab, is_new_session: !current_url.start_with?('http'), user: user) end end diff --git a/qa/qa/resource/group.rb b/qa/qa/resource/group.rb index 0b567a474c8..44d9dc8f296 100644 --- a/qa/qa/resource/group.rb +++ b/qa/qa/resource/group.rb @@ -67,6 +67,10 @@ module QA visibility: 'public' } end + + def full_path + sandbox.path + ' / ' + path + end end end end diff --git a/qa/qa/resource/issue.rb b/qa/qa/resource/issue.rb index 2c2f27fe231..51b2af8b4ef 100644 --- a/qa/qa/resource/issue.rb +++ b/qa/qa/resource/issue.rb @@ -12,8 +12,14 @@ module QA end end + attribute :id + attribute :labels attribute :title + def initialize + @labels = [] + end + def fabricate! project.visit! @@ -25,6 +31,21 @@ module QA page.create_new_issue end end + + def api_get_path + "/projects/#{project.id}/issues/#{id}" + end + + def api_post_path + "/projects/#{project.id}/issues" + end + + def api_post_body + { + labels: labels, + title: title + } + end end end end diff --git a/qa/qa/resource/label.rb b/qa/qa/resource/label.rb index 7c899db31f3..3750725c440 100644 --- a/qa/qa/resource/label.rb +++ b/qa/qa/resource/label.rb @@ -34,6 +34,27 @@ module QA page.click_label_create_button end end + + def resource_web_url(resource) + super + rescue ResourceURLMissingError + # this particular resource does not expose a web_url property + end + + def api_get_path + raise NotImplementedError, "The Labels API doesn't expose a single-resource endpoint so this method cannot be properly implemented." + end + + def api_post_path + "/projects/#{project.id}/labels" + end + + def api_post_body + { + color: @color, + name: @title + } + end end end end diff --git a/qa/qa/resource/merge_request.rb b/qa/qa/resource/merge_request.rb index 45cb317e0eb..45ab2396a04 100644 --- a/qa/qa/resource/merge_request.rb +++ b/qa/qa/resource/merge_request.rb @@ -5,10 +5,12 @@ require 'securerandom' module QA module Resource class MergeRequest < Base - attr_accessor :title, + attr_accessor :id, + :title, :description, :source_branch, :target_branch, + :target_new_branch, :assignee, :milestone, :labels, @@ -27,6 +29,7 @@ module QA Repository::ProjectPush.fabricate! do |resource| resource.project = project resource.branch_name = 'master' + resource.new_branch = @target_new_branch resource.remote_branch = target_branch end end @@ -52,6 +55,7 @@ module QA @labels = [] @file_name = "added_file.txt" @file_content = "File Added" + @target_new_branch = true end def fabricate! @@ -71,6 +75,28 @@ module QA page.create_merge_request end end + + def fabricate_via_api! + populate(:target, :source) + super + end + + def api_get_path + "/projects/#{project.id}/merge_requests/#{id}" + end + + def api_post_path + "/projects/#{project.id}/merge_requests" + end + + def api_post_body + { + description: @description, + source_branch: @source_branch, + target_branch: @target_branch, + title: @title + } + end end end end diff --git a/qa/qa/resource/merge_request_from_fork.rb b/qa/qa/resource/merge_request_from_fork.rb index 5d20a6e9c75..6c9a096289b 100644 --- a/qa/qa/resource/merge_request_from_fork.rb +++ b/qa/qa/resource/merge_request_from_fork.rb @@ -21,7 +21,7 @@ module QA def fabricate! populate(:push) - fork.visit! + fork.project.visit! Page::Project::Show.perform(&:new_merge_request) Page::MergeRequest::New.perform(&:create_merge_request) diff --git a/qa/qa/resource/project.rb b/qa/qa/resource/project.rb index d706439a891..93a82094776 100644 --- a/qa/qa/resource/project.rb +++ b/qa/qa/resource/project.rb @@ -8,17 +8,24 @@ module QA include Events::Project attr_writer :initialize_with_readme + attr_writer :visibility attribute :id attribute :name + attribute :add_name_uuid attribute :description + attribute :standalone attribute :group do Group.fabricate! end attribute :path_with_namespace do - "#{group.sandbox.path}/#{group.path}/#{name}" if group + "#{sandbox_path}#{group.path}/#{name}" if group + end + + def sandbox_path + group.respond_to?('sandbox') ? "#{group.sandbox.path}/" : '' end attribute :repository_ssh_location do @@ -34,24 +41,28 @@ module QA end def initialize + @add_name_uuid = true + @standalone = false @description = 'My awesome project' @initialize_with_readme = false + @visibility = 'public' end def name=(raw_name) - @name = "#{raw_name}-#{SecureRandom.hex(8)}" + @name = @add_name_uuid ? "#{raw_name}-#{SecureRandom.hex(8)}" : raw_name end def fabricate! - group.visit! - - Page::Group::Show.perform(&:go_to_new_project) + unless @standalone + group.visit! + Page::Group::Show.perform(&:go_to_new_project) + end Page::Project::New.perform do |page| page.choose_test_namespace page.choose_name(@name) page.add_description(@description) - page.set_visibility('Public') + page.set_visibility(@visibility) page.enable_initialize_with_readme if @initialize_with_readme page.create_new_project end @@ -67,19 +78,28 @@ module QA "/projects/#{CGI.escape(path_with_namespace)}" end + def api_get_archive_path(type = 'tar.gz') + "#{api_get_path}/repository/archive.#{type}" + end + def api_post_path '/projects' end def api_post_body - { - namespace_id: group.id, - path: name, + post_body = { name: name, description: description, - visibility: 'public', + visibility: @visibility, initialize_with_readme: @initialize_with_readme } + + unless @standalone + post_body[:namespace_id] = group.id + post_body[:path] = name + end + + post_body end private diff --git a/qa/qa/resource/project_milestone.rb b/qa/qa/resource/project_milestone.rb index 8ace75f695a..70640eac095 100644 --- a/qa/qa/resource/project_milestone.rb +++ b/qa/qa/resource/project_milestone.rb @@ -31,6 +31,21 @@ module QA milestone_new.click_milestone_create_button end end + + def api_get_path + "/projects/#{project.id}/milestones/#{id}" + end + + def api_post_path + "/projects/#{project.id}/milestones" + end + + def api_post_body + { + description: @description, + title: @title + } + end end end end diff --git a/qa/qa/resource/sandbox.rb b/qa/qa/resource/sandbox.rb index 942eea5cc40..e2b1c4c0831 100644 --- a/qa/qa/resource/sandbox.rb +++ b/qa/qa/resource/sandbox.rb @@ -44,6 +44,10 @@ module QA "/groups/#{path}" end + def api_members_path + "#{api_get_path}/members" + end + def api_post_path '/groups' end diff --git a/qa/qa/resource/user.rb b/qa/qa/resource/user.rb index 6c5e91b6488..eec46f46d99 100644 --- a/qa/qa/resource/user.rb +++ b/qa/qa/resource/user.rb @@ -88,7 +88,7 @@ module QA }.merge(ldap_post_body) end - def self.fabricate_or_use(username, password) + def self.fabricate_or_use(username = nil, password = nil) if Runtime::Env.signup_disabled? self.new.tap do |user| user.username = username diff --git a/qa/qa/runtime/address.rb b/qa/qa/runtime/address.rb index 98d042fb43a..c622051bb6d 100644 --- a/qa/qa/runtime/address.rb +++ b/qa/qa/runtime/address.rb @@ -5,7 +5,7 @@ module QA class Address attr_reader :address - def initialize(instance, page = nil) + def initialize(instance, page) @instance = instance @address = host + (page.is_a?(String) ? page : page&.path) end diff --git a/qa/qa/runtime/api/client.rb b/qa/qa/runtime/api/client.rb index 40a3bc85195..663be27a849 100644 --- a/qa/qa/runtime/api/client.rb +++ b/qa/qa/runtime/api/client.rb @@ -6,31 +6,34 @@ module QA module Runtime module API class Client - attr_reader :address + attr_reader :address, :user - def initialize(address = :gitlab, personal_access_token: nil, is_new_session: true) + def initialize(address = :gitlab, personal_access_token: nil, is_new_session: true, user: nil) @address = address @personal_access_token = personal_access_token @is_new_session = is_new_session + @user = user end def personal_access_token @personal_access_token ||= begin # you can set the environment variable GITLAB_QA_ACCESS_TOKEN # to use a specific access token rather than create one from the UI - Runtime::Env.personal_access_token ||= create_personal_access_token + # unless a specific user has been passed + @user.nil? ? Runtime::Env.personal_access_token ||= create_personal_access_token : create_personal_access_token end end private def create_personal_access_token - Runtime::Browser.visit(@address, Page::Main::Login) if @is_new_session - do_create_personal_access_token - end + Page::Main::Menu.perform(&:sign_out) if @is_new_session && Page::Main::Menu.perform { |p| p.has_personal_area?(wait: 0) } + + unless Page::Main::Menu.perform { |p| p.has_personal_area?(wait: 0) } + Runtime::Browser.visit(@address, Page::Main::Login) + Page::Main::Login.perform { |login| login.sign_in_using_credentials(@user) } + end - def do_create_personal_access_token - Page::Main::Login.perform(&:sign_in_using_credentials) Resource::PersonalAccessToken.fabricate!.access_token end end diff --git a/qa/qa/runtime/browser.rb b/qa/qa/runtime/browser.rb index 3bf4b3bbbfb..2987bb1a213 100644 --- a/qa/qa/runtime/browser.rb +++ b/qa/qa/runtime/browser.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require 'rspec/core' +require 'rspec/expectations' require 'capybara/rspec' require 'capybara-screenshot/rspec' require 'selenium-webdriver' @@ -12,6 +13,8 @@ module QA NotRespondingError = Class.new(RuntimeError) + CAPYBARA_MAX_WAIT_TIME = 10 + def initialize self.class.configure! end @@ -27,13 +30,12 @@ module QA # In case of an address that is a symbol we will try to guess address # based on `Runtime::Scenario#something_address`. # - def visit(address, page = nil, &block) - Browser::Session.new(address, page).perform(&block) + def visit(address, page_class, &block) + Browser::Session.new(address, page_class).perform(&block) end - def self.visit(address, page = nil, &block) - new.visit(address, page, &block) - page.validate_elements_present! + def self.visit(address, page_class, &block) + new.visit(address, page_class, &block) end def self.configure! @@ -43,6 +45,8 @@ module QA end end + Capybara.server_port = 9887 + ENV['TEST_ENV_NUMBER'].to_i + return if Capybara.drivers.include?(:chrome) Capybara.register_driver QA::Runtime::Env.browser do |app| @@ -119,7 +123,7 @@ module QA Capybara.configure do |config| config.default_driver = QA::Runtime::Env.browser config.javascript_driver = QA::Runtime::Env.browser - config.default_max_wait_time = 10 + config.default_max_wait_time = CAPYBARA_MAX_WAIT_TIME # https://github.com/mattheworiordan/capybara-screenshot/issues/164 config.save_path = ::File.expand_path('../../tmp', __dir__) end @@ -128,8 +132,11 @@ module QA class Session include Capybara::DSL - def initialize(instance, page = nil) - @session_address = Runtime::Address.new(instance, page) + attr_reader :page_class + + def initialize(instance, page_class) + @session_address = Runtime::Address.new(instance, page_class) + @page_class = page_class end def url @@ -139,6 +146,8 @@ module QA def perform(&block) visit(url) + page_class.validate_elements_present! + if QA::Runtime::Env.qa_cookies browser = Capybara.current_session.driver.browser QA::Runtime::Env.qa_cookies.each do |cookie| diff --git a/qa/qa/runtime/env.rb b/qa/qa/runtime/env.rb index 82510dfa03c..b184eeb1701 100644 --- a/qa/qa/runtime/env.rb +++ b/qa/qa/runtime/env.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +require 'gitlab/qa' + module QA module Runtime module Env @@ -7,16 +9,31 @@ module QA attr_writer :personal_access_token, :ldap_username, :ldap_password + ENV_VARIABLES = Gitlab::QA::Runtime::Env::ENV_VARIABLES + # The environment variables used to indicate if the environment under test # supports the given feature SUPPORTED_FEATURES = { - git_protocol_v2: 'QA_CAN_TEST_GIT_PROTOCOL_V2' + git_protocol_v2: 'QA_CAN_TEST_GIT_PROTOCOL_V2', + admin: 'QA_CAN_TEST_ADMIN_FEATURES' }.freeze def supported_features SUPPORTED_FEATURES end + def admin_password + ENV['GITLAB_ADMIN_PASSWORD'] + end + + def admin_username + ENV['GITLAB_ADMIN_USERNAME'] + end + + def admin_personal_access_token + ENV['GITLAB_QA_ADMIN_ACCESS_TOKEN'] + end + def debug? enabled?(ENV['QA_DEBUG'], default: false) end @@ -92,14 +109,6 @@ module QA ENV['GITLAB_PASSWORD'] end - def admin_username - ENV['GITLAB_ADMIN_USERNAME'] - end - - def admin_password - ENV['GITLAB_ADMIN_PASSWORD'] - end - def github_username ENV['GITHUB_USERNAME'] end @@ -168,8 +177,12 @@ module QA ENV.fetch("GCLOUD_ACCOUNT_EMAIL") end - def gcloud_zone - ENV.fetch('GCLOUD_ZONE') + def gcloud_region + ENV.fetch('GCLOUD_REGION') + end + + def gcloud_num_nodes + ENV.fetch('GCLOUD_NUM_NODES', 1) end def has_gcloud_credentials? @@ -196,6 +209,10 @@ module QA enabled?(ENV[SUPPORTED_FEATURES[feature]], default: true) end + def runtime_scenario_attributes + ENV['QA_RUNTIME_SCENARIO_ATTRIBUTES'] + end + private def remote_grid_credentials diff --git a/qa/qa/runtime/logger.rb b/qa/qa/runtime/logger.rb index bd5c4fe5bf5..7f73f1bd01b 100644 --- a/qa/qa/runtime/logger.rb +++ b/qa/qa/runtime/logger.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require 'logger' +require 'forwardable' module QA module Runtime diff --git a/qa/qa/runtime/scenario.rb b/qa/qa/runtime/scenario.rb index 5067322804b..3662ebe671b 100644 --- a/qa/qa/runtime/scenario.rb +++ b/qa/qa/runtime/scenario.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +require 'json' + module QA module Runtime ## @@ -24,6 +26,10 @@ module QA end end + def from_env(var) + JSON.parse(Runtime::Env.runtime_scenario_attributes).each { |k, v| define(k, v) } + end + def method_missing(name, *) raise ArgumentError, "Scenario attribute `#{name}` not defined!" end diff --git a/qa/qa/scenario/shared_attributes.rb b/qa/qa/scenario/shared_attributes.rb index 40d5c6b1ff1..52f50ec8c27 100644 --- a/qa/qa/scenario/shared_attributes.rb +++ b/qa/qa/scenario/shared_attributes.rb @@ -7,6 +7,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' end end end diff --git a/qa/qa/scenario/test/sanity/selectors.rb b/qa/qa/scenario/test/sanity/selectors.rb index e05269e8d55..632a0f5f2a9 100644 --- a/qa/qa/scenario/test/sanity/selectors.rb +++ b/qa/qa/scenario/test/sanity/selectors.rb @@ -7,14 +7,16 @@ module QA class Selectors < Scenario::Template include Scenario::Bootable - PAGES = [QA::Page].freeze + def pages + @pages ||= [QA::Page] + end def perform(*) - validators = PAGES.map do |pages| - Page::Validator.new(pages) + validators = pages.map do |page| + Page::Validator.new(page) end - validators.map(&:errors).flatten.tap do |errors| + validators.flat_map(&:errors).tap do |errors| break if errors.none? warn <<~EOS diff --git a/qa/qa/service/kubernetes_cluster.rb b/qa/qa/service/kubernetes_cluster.rb index 16a89591eb2..ac0b6313167 100644 --- a/qa/qa/service/kubernetes_cluster.rb +++ b/qa/qa/service/kubernetes_cluster.rb @@ -28,17 +28,19 @@ module QA create #{cluster_name} #{auth_options} --enable-basic-auth - --zone #{Runtime::Env.gcloud_zone} + --region #{Runtime::Env.gcloud_region} + --disk-size 10GB + --num-nodes #{Runtime::Env.gcloud_num_nodes} && gcloud container clusters get-credentials - --zone #{Runtime::Env.gcloud_zone} + --region #{Runtime::Env.gcloud_region} #{cluster_name} CMD @api_url = `kubectl config view --minify -o jsonpath='{.clusters[].cluster.server}'` @admin_user = "#{cluster_name}-admin" - master_auth = JSON.parse(`gcloud container clusters describe #{cluster_name} --zone #{Runtime::Env.gcloud_zone} --format 'json(masterAuth.username, masterAuth.password)'`) + master_auth = JSON.parse(`gcloud container clusters describe #{cluster_name} --region #{Runtime::Env.gcloud_region} --format 'json(masterAuth.username, masterAuth.password)'`) shell <<~CMD.tr("\n", ' ') kubectl config set-credentials #{@admin_user} --username #{master_auth['masterAuth']['username']} @@ -66,10 +68,10 @@ module QA def remove! shell <<~CMD.tr("\n", ' ') gcloud container clusters delete - --zone #{Runtime::Env.gcloud_zone} - #{cluster_name} - --quiet --async - CMD + --region #{Runtime::Env.gcloud_region} + #{cluster_name} + --quiet --async + CMD end private diff --git a/qa/qa/service/omnibus.rb b/qa/qa/service/omnibus.rb index b54fd5628f2..c5cddff56cd 100644 --- a/qa/qa/service/omnibus.rb +++ b/qa/qa/service/omnibus.rb @@ -11,11 +11,12 @@ module QA end def gitlab_ctl(command, input: nil) - if input.nil? - shell "docker exec #{@name} gitlab-ctl #{command}" - else - shell "docker exec #{@name} bash -c '#{input} | gitlab-ctl #{command}'" - end + docker_exec("gitlab-ctl #{command}", input: input) + end + + def docker_exec(command, input: nil) + command = "#{input} | #{command}" if input + shell "docker exec #{@name} bash -c '#{command}'" end end end diff --git a/qa/qa/service/shellout.rb b/qa/qa/service/shellout.rb index 7065ab0e7f3..217df669db3 100644 --- a/qa/qa/service/shellout.rb +++ b/qa/qa/service/shellout.rb @@ -19,7 +19,7 @@ module QA Open3.popen2e(*command) do |stdin, out, wait| stdin.puts(stdin_data) if stdin_data stdin.close if stdin_data - out.each { |line| puts line } + out.each_char { |char| print char } if wait.value.exited? && wait.value.exitstatus.nonzero? raise CommandError, "Command `#{command}` failed!" diff --git a/qa/qa/specs/features/api/3_create/repository/project_archive_compare_spec.rb b/qa/qa/specs/features/api/3_create/repository/project_archive_compare_spec.rb new file mode 100644 index 00000000000..3fe04e8b835 --- /dev/null +++ b/qa/qa/specs/features/api/3_create/repository/project_archive_compare_spec.rb @@ -0,0 +1,75 @@ +# frozen_string_literal: true + +require 'securerandom' +require 'digest' + +module QA + context 'Create' do + describe 'Compare archives of different user projects with the same name and check they\'re different' do + include Support::Api + + before(:all) do + @project_name = "project-archive-download-#{SecureRandom.hex(8)}" + @archive_types = %w(tar.gz tar.bz2 tar zip) + @users = { + user1: { username: Runtime::Env.gitlab_qa_username_1, password: Runtime::Env.gitlab_qa_password_1 }, + user2: { username: Runtime::Env.gitlab_qa_username_2, password: Runtime::Env.gitlab_qa_password_2 } + } + + @users.each do |_, user_info| + user_info[:user] = Resource::User.fabricate_or_use(user_info[:username], user_info[:password]) + user_info[:api_client] = Runtime::API::Client.new(:gitlab, user: user_info[:user]) + user_info[:api_client].personal_access_token + user_info[:project] = create_project(user_info[:user], user_info[:api_client], @project_name) + Page::Main::Menu.perform(&:sign_out) + end + end + + it 'download archives of each user project then check they are different' do + archive_checksums = {} + + @users.each do |user_key, user_info| + archive_checksums[user_key] = {} + + @archive_types.each do |type| + archive_path = download_project_archive_via_api(user_info[:api_client], user_info[:project], type).path + archive_checksums[user_key][type] = Digest::MD5.hexdigest(File.read(archive_path)) + end + end + + QA::Runtime::Logger.debug("Archive checksums are #{archive_checksums}") + + expect(archive_checksums[:user1]).not_to include(archive_checksums[:user2]) + end + + def create_project(user, api_client, project_name) + project = Resource::Project.fabricate! do |project| + project.standalone = true + project.add_name_uuid = false + project.name = project_name + project.path_with_namespace = "#{user.name}/#{project_name}" + project.user = user + project.api_client = api_client + end + + Resource::Repository::ProjectPush.fabricate! do |push| + push.project = project + push.file_name = 'README.md' + push.file_content = '# This is a test project' + push.commit_message = 'Add README.md' + push.user = user + end + + project + end + + def download_project_archive_via_api(api_client, project, type = 'tar.gz') + get_project_archive_zip = Runtime::API::Request.new(api_client, project.api_get_archive_path(type)) + project_archive_download = get(get_project_archive_zip.url, raw_response: true) + expect(project_archive_download.code).to eq(200) + + project_archive_download.file + end + end + end +end diff --git a/qa/qa/specs/features/browser_ui/7_configure/mattermost/create_group_with_mattermost_team_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/group/create_group_with_mattermost_team_spec.rb index 8383dcdb983..94d20106de4 100644 --- a/qa/qa/specs/features/browser_ui/7_configure/mattermost/create_group_with_mattermost_team_spec.rb +++ b/qa/qa/specs/features/browser_ui/1_manage/group/create_group_with_mattermost_team_spec.rb @@ -5,8 +5,8 @@ module QA describe 'Mattermost support' do it 'user creates a group with a mattermost team' do Runtime::Browser.visit(:gitlab, Page::Main::Login) - Page::Main::Login.act { sign_in_using_credentials } - Page::Main::Menu.act { go_to_groups } + Page::Main::Login.perform(&:sign_in_using_credentials) + Page::Main::Menu.perform(&:go_to_groups) Page::Dashboard::Groups.perform do |page| page.click_new_group diff --git a/qa/qa/specs/features/browser_ui/1_manage/group/transfer_project_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/group/transfer_project_spec.rb new file mode 100644 index 00000000000..c9acd7df776 --- /dev/null +++ b/qa/qa/specs/features/browser_ui/1_manage/group/transfer_project_spec.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +module QA + context 'Manage' do + describe 'Project transfer between groups' do + it 'user transfers a project between groups' do + Runtime::Browser.visit(:gitlab, Page::Main::Login) + Page::Main::Login.perform(&:sign_in_using_credentials) + + source_group = Resource::Group.fabricate_via_api! do |group| + group.path = 'source-group' + end + + target_group = Resource::Group.fabricate_via_api! do |group| + group.path = 'target-group' + end + + project = Resource::Project.fabricate_via_api! do |project| + project.group = source_group + project.name = 'transfer-project' + project.initialize_with_readme = true + end + + project.visit! + + Page::Project::Show.perform do |project| + project.click_file('README.md') + end + + Page::File::Show.perform(&:click_edit) + + edited_readme_content = 'Here is the edited content.' + + Page::File::Edit.perform do |file| + file.remove_content + file.add_content(edited_readme_content) + file.commit_changes + end + + Page::File::Show.perform(&:go_to_general_settings) + + Page::Project::Settings::Main.perform(&:expand_advanced_settings) + + Page::Project::Settings::Advanced.perform do |advanced| + advanced.transfer_project!(project.name, target_group.full_path) + end + + Page::Project::Settings::Main.perform(&:click_project) + + Page::Project::Show.perform do |project| + expect(project).to have_text(target_group.path) + expect(project).to have_text(edited_readme_content) + end + end + end + end +end diff --git a/qa/qa/specs/features/browser_ui/1_manage/login/log_in_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/login/log_in_spec.rb index cf225a639b6..6556c28ccab 100644 --- a/qa/qa/specs/features/browser_ui/1_manage/login/log_in_spec.rb +++ b/qa/qa/specs/features/browser_ui/1_manage/login/log_in_spec.rb @@ -5,14 +5,16 @@ module QA describe 'basic user login' do it 'user logs in using basic credentials and logs out' do Runtime::Browser.visit(:gitlab, Page::Main::Login) - Page::Main::Login.act { sign_in_using_credentials } + Page::Main::Login.perform(&:sign_in_using_credentials) Page::Main::Menu.perform do |menu| expect(menu).to have_personal_area end - Page::Main::Menu.perform do |menu| - menu.sign_out + Support::Retrier.retry_until(sleep_interval: 0.5) do + Page::Main::Menu.perform(&:sign_out) + + Page::Main::Login.perform(&:has_sign_in_tab?) end Page::Main::Login.perform do |form| diff --git a/qa/qa/specs/features/browser_ui/1_manage/login/log_into_gitlab_via_ldap_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/login/log_into_gitlab_via_ldap_spec.rb index 72dde4e5bd8..10cd8470a8f 100644 --- a/qa/qa/specs/features/browser_ui/1_manage/login/log_into_gitlab_via_ldap_spec.rb +++ b/qa/qa/specs/features/browser_ui/1_manage/login/log_into_gitlab_via_ldap_spec.rb @@ -5,7 +5,7 @@ module QA describe 'LDAP login' do it 'user logs into GitLab using LDAP credentials' do Runtime::Browser.visit(:gitlab, Page::Main::Login) - Page::Main::Login.act { sign_in_using_credentials } + Page::Main::Login.perform(&:sign_in_using_credentials) Page::Main::Menu.perform do |menu| expect(menu).to have_personal_area diff --git a/qa/qa/specs/features/browser_ui/1_manage/login/log_into_mattermost_via_gitlab_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/login/log_into_mattermost_via_gitlab_spec.rb index 67610b62ed7..0a999cf00fa 100644 --- a/qa/qa/specs/features/browser_ui/1_manage/login/log_into_mattermost_via_gitlab_spec.rb +++ b/qa/qa/specs/features/browser_ui/1_manage/login/log_into_mattermost_via_gitlab_spec.rb @@ -7,11 +7,13 @@ module QA Runtime::Browser.visit(:gitlab, Page::Main::Login) Page::Main::Login.perform(&:sign_in_using_credentials) - Runtime::Browser.visit(:mattermost, Page::Mattermost::Login) - Page::Mattermost::Login.perform(&:sign_in_using_oauth) + Support::Retrier.retry_on_exception do + Runtime::Browser.visit(:mattermost, Page::Mattermost::Login) + Page::Mattermost::Login.perform(&:sign_in_using_oauth) - Page::Mattermost::Main.perform do |page| - expect(page).to have_content(/(Welcome to: Mattermost|Logout GitLab Mattermost)/) + Page::Mattermost::Main.perform do |page| + expect(page).to have_content(/(Welcome to: Mattermost|Logout GitLab Mattermost)/) + 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 87f0e9030d2..101143399f6 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 @@ -6,9 +6,9 @@ module QA it 'User logs in to gitlab with SAML SSO' do Runtime::Browser.visit(:gitlab, Page::Main::Login) - Page::Main::Login.act { sign_in_with_saml } + Page::Main::Login.perform(&:sign_in_with_saml) - Vendor::SAMLIdp::Page::Login.act { login } + Vendor::SAMLIdp::Page::Login.perform(&:login) expect(page).to have_content('Welcome to GitLab') end diff --git a/qa/qa/specs/features/browser_ui/1_manage/login/login_via_oauth_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/login/login_via_oauth_spec.rb index 15cd59f041b..db99488160b 100644 --- a/qa/qa/specs/features/browser_ui/1_manage/login/login_via_oauth_spec.rb +++ b/qa/qa/specs/features/browser_ui/1_manage/login/login_via_oauth_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - # https://gitlab.com/gitlab-org/quality/nightly/issues/100 + # Failure issue: https://gitlab.com/gitlab-org/quality/nightly/issues/121 context 'Manage', :orchestrated, :oauth, :quarantine do describe 'OAuth login' do it 'User logs in to GitLab with GitHub OAuth' do diff --git a/qa/qa/specs/features/browser_ui/1_manage/project/add_project_member_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/project/add_project_member_spec.rb index d1747280227..f51c16f472c 100644 --- a/qa/qa/specs/features/browser_ui/1_manage/project/add_project_member_spec.rb +++ b/qa/qa/specs/features/browser_ui/1_manage/project/add_project_member_spec.rb @@ -9,7 +9,7 @@ module QA user = Resource::User.fabricate_or_use(Runtime::Env.gitlab_qa_username_1, Runtime::Env.gitlab_qa_password_1) - project = Resource::Project.fabricate! do |resource| + project = Resource::Project.fabricate_via_api! do |resource| resource.name = 'add-member-project' end project.visit! 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 6632c2977ef..fbe857dc2a5 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 @@ -5,7 +5,7 @@ module QA describe 'Project creation' do it 'user creates a new project' do Runtime::Browser.visit(:gitlab, Page::Main::Login) - Page::Main::Login.act { sign_in_using_credentials } + Page::Main::Login.perform(&:sign_in_using_credentials) created_project = Resource::Project.fabricate_via_browser_ui! do |project| project.name = 'awesome-project' 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 a9eafd61a91..4f8c46cbd5f 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 @@ -24,16 +24,16 @@ module QA it 'user imports a GitHub repo' do Runtime::Browser.visit(:gitlab, Page::Main::Login) - Page::Main::Login.act { sign_in_using_credentials } + Page::Main::Login.perform(&:sign_in_using_credentials) imported_project # import the project - Page::Main::Menu.act { go_to_projects } + Page::Main::Menu.perform(&:go_to_projects) Page::Dashboard::Projects.perform do |dashboard| dashboard.go_to_project(imported_project.name) end - Page::Project::Show.act { wait_for_import } + Page::Project::Show.perform(&:wait_for_import) verify_repository_import verify_issues_import @@ -50,7 +50,7 @@ module QA def verify_issues_import QA::Support::Retrier.retry_on_exception do - Page::Project::Menu.act { click_issues } + Page::Project::Menu.perform(&:click_issues) expect(page).to have_content('This is a sample issue') click_link 'This is a sample issue' @@ -73,7 +73,7 @@ module QA end def verify_merge_requests_import - Page::Project::Menu.act { click_merge_requests } + Page::Project::Menu.perform(&:click_merge_requests) expect(page).to have_content('Improve README.md') click_link 'Improve README.md' @@ -108,7 +108,7 @@ module QA end def verify_wiki_import - Page::Project::Menu.act { click_wiki } + Page::Project::Menu.perform(&:click_wiki) expect(page).to have_content('Welcome to the test-project wiki!') 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 new file mode 100644 index 00000000000..70c03e10449 --- /dev/null +++ b/qa/qa/specs/features/browser_ui/2_plan/issue/check_mentions_for_xss_spec.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +module QA + context 'Plan' do + describe 'check xss occurence in @mentions in issues' 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) + end + + user = Resource::User.fabricate_via_api! do |user| + user.name = "eve <img src=x onerror=alert(2)<img src=x onerror=alert(1)>" + user.password = "test1234" + end + + QA::Runtime::Env.personal_access_token = nil + + 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) + + project = Resource::Project.fabricate_via_api! do |resource| + resource.name = 'xss-test-for-mentions-project' + end + project.visit! + + Page::Project::Show.perform(&:go_to_members_settings) + Page::Project::Settings::Members.perform do |page| + page.add_member(user.username) + end + + issue = Resource::Issue.fabricate_via_api! do |issue| + issue.title = 'issue title' + issue.project = project + end + issue.visit! + + Page::Project::Issue::Show.perform do |show| + show.select_all_activities_filter + show.comment('cc-ing you here @eve') + + expect do + expect(show).to have_content("cc-ing you here") + end.not_to raise_error # Selenium::WebDriver::Error::UnhandledAlertError + end + end + end + end +end diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/collapse_comments_in_discussions_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/collapse_comments_in_discussions_spec.rb index fa779bd1f4e..ad70f6813fb 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 @@ -3,33 +3,40 @@ module QA context 'Plan' do describe 'collapse comments in issue discussions' do - let(:issue_title) { 'issue title' } + let(:my_first_reply) { 'My first reply' } - it 'user collapses reply for comments in an issue' do + before do Runtime::Browser.visit(:gitlab, Page::Main::Login) Page::Main::Login.perform(&:sign_in_using_credentials) - Resource::Issue.fabricate! do |issue| - issue.title = issue_title + issue = Resource::Issue.fabricate_via_api! do |issue| + issue.title = 'issue title' end - expect(page).to have_content(issue_title) + issue.visit! - Page::Project::Issue::Show.perform do |show_page| - show_page.select_all_activities_filter - show_page.start_discussion("My first discussion") - expect(show_page).to have_content("My first discussion") + Page::Project::Issue::Show.perform do |show| + my_first_discussion = 'My first discussion' - show_page.reply_to_discussion("My First Reply") - expect(show_page).to have_content("My First Reply") + show.select_all_activities_filter + show.start_discussion(my_first_discussion) + page.assert_text(my_first_discussion) + show.reply_to_discussion(my_first_reply) + page.assert_text(my_first_reply) + end + end + + it 'user collapses and expands reply for comments in an issue' do + Page::Project::Issue::Show.perform do |show| + one_reply = "1 reply" - show_page.collapse_replies - expect(show_page).to have_content("1 reply") - expect(show_page).not_to have_content("My First Reply") + show.collapse_replies + expect(show).to have_content(one_reply) + expect(show).not_to have_content(my_first_reply) - show_page.expand_replies - expect(show_page).to have_content("My First Reply") - expect(show_page).not_to have_content("1 reply") + show.expand_replies + expect(show).to have_content(my_first_reply) + expect(show).not_to have_content(one_reply) end end end 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 new file mode 100644 index 00000000000..0b1bd00ac8d --- /dev/null +++ b/qa/qa/specs/features/browser_ui/2_plan/issue/comment_issue_spec.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +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) + + issue = Resource::Issue.fabricate_via_api! do |issue| + issue.title = 'issue title' + end + issue.visit! + end + + it 'user comments on an issue and edits the comment' do + Page::Project::Issue::Show.perform do |show| + first_version_of_comment = 'First version of the comment' + second_version_of_comment = 'Second version of the comment' + + show.comment(first_version_of_comment) + + expect(show).to have_content(first_version_of_comment) + + show.edit_comment(second_version_of_comment) + + expect(show).to have_content(second_version_of_comment) + expect(show).not_to have_content(first_version_of_comment) + end + end + end + end +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 00094161f61..04ae4963d3a 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 @@ -5,23 +5,35 @@ module QA describe 'Issue creation' do let(:issue_title) { 'issue title' } + before do + Runtime::Browser.visit(:gitlab, Page::Main::Login) + Page::Main::Login.perform(&:sign_in_using_credentials) + end + it 'user creates an issue' do - create_issue + Resource::Issue.fabricate_via_browser_ui! do |issue| + issue.title = issue_title + end - Page::Project::Menu.act { click_issues } + Page::Project::Menu.perform(&:click_issues) expect(page).to have_content(issue_title) end - # Failure issue: https://gitlab.com/gitlab-org/quality/nightly/issues/101 - context 'when using attachments in comments', :object_storage, :quarantine do + context 'when using attachments in comments', :object_storage do let(:file_to_attach) do File.absolute_path(File.join('spec', 'fixtures', 'banana_sample.gif')) end - it 'user comments on an issue with an attachment' do - create_issue + before do + issue = Resource::Issue.fabricate_via_api! do |issue| + issue.title = issue_title + end + + issue.visit! + end + it 'user comments on an issue with an attachment' do Page::Project::Issue::Show.perform do |show| show.comment('See attached banana for scale', attachment: file_to_attach) @@ -37,15 +49,6 @@ module QA end end end - - def create_issue - Runtime::Browser.visit(:gitlab, Page::Main::Login) - Page::Main::Login.act { sign_in_using_credentials } - - Resource::Issue.fabricate! do |issue| - issue.title = issue_title - end - end 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 b2164bb5fab..317e31feea8 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 @@ -5,32 +5,37 @@ module QA describe 'filter issue comments activities' do let(:issue_title) { 'issue title' } - it 'user filters comments and activities in an issue' do + before do Runtime::Browser.visit(:gitlab, Page::Main::Login) - Page::Main::Login.act { sign_in_using_credentials } + Page::Main::Login.perform(&:sign_in_using_credentials) - Resource::Issue.fabricate! do |issue| + issue = Resource::Issue.fabricate_via_api! do |issue| issue.title = issue_title end - expect(page).to have_content(issue_title) + issue.visit! + end + + it 'user filters comments and activities in an issue' do + Page::Project::Issue::Show.perform do |show| + my_own_comment = "My own comment" + made_the_issue_confidential = "made the issue confidential" - Page::Project::Issue::Show.perform do |show_page| - show_page.comment('/confidential', filter: :comments_only) - show_page.comment('My own comment', filter: :comments_only) + show.comment('/confidential', filter: :comments_only) + show.comment(my_own_comment, filter: :comments_only) - expect(show_page).not_to have_content("made the issue confidential") - expect(show_page).to have_content("My own comment") + expect(show).not_to have_content(made_the_issue_confidential) + expect(show).to have_content(my_own_comment) - show_page.select_all_activities_filter + show.select_all_activities_filter - expect(show_page).to have_content("made the issue confidential") - expect(show_page).to have_content("My own comment") + expect(show).to have_content(made_the_issue_confidential) + expect(show).to have_content(my_own_comment) - show_page.select_history_only_filter + show.select_history_only_filter - expect(show_page).to have_content("made the issue confidential") - expect(show_page).not_to have_content("My own comment") + expect(show).to have_content(made_the_issue_confidential) + expect(show).not_to have_content(my_own_comment) end end end 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 530fc684437..c42c2cedde0 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 @@ -5,7 +5,7 @@ module QA describe 'issue suggestions' do let(:issue_title) { 'Issue Lists are awesome' } - it 'user sees issue suggestions when creating a new issue' do + before do Runtime::Browser.visit(:gitlab, Page::Main::Login) Page::Main::Login.perform(&:sign_in_using_credentials) @@ -14,20 +14,22 @@ module QA resource.description = 'project for issue suggestions' end - Resource::Issue.fabricate_via_browser_ui! do |issue| + Resource::Issue.fabricate_via_api! do |issue| issue.title = issue_title issue.project = project end project.visit! + end + it 'user sees issue suggestions when creating a new issue' do Page::Project::Show.perform(&:go_to_new_issue) - Page::Project::Issue::New.perform do |new_issue_page| - new_issue_page.add_title("issue") - expect(new_issue_page).to have_content(issue_title) + Page::Project::Issue::New.perform do |new| + new.add_title("issue") + expect(new).to have_content(issue_title) - new_issue_page.add_title("Issue Board") - expect(new_issue_page).not_to have_content(issue_title) + new.add_title("Issue Board") + expect(new).not_to have_content(issue_title) end end end diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb index 0ff71baed90..6969f123f95 100644 --- a/qa/qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb @@ -1,74 +1,64 @@ # frozen_string_literal: true module QA - # Failure issue: https://gitlab.com/gitlab-org/quality/staging/issues/50 - context 'Create', :quarantine do - describe 'Merge request creation' do - it 'user creates a new merge request', :smoke do + context 'Create' do + describe 'Create a new merge request' do + before do Runtime::Browser.visit(:gitlab, Page::Main::Login) - Page::Main::Login.act { sign_in_using_credentials } + Page::Main::Login.perform(&:sign_in_using_credentials) - current_project = Resource::Project.fabricate! do |project| - project.name = 'project-with-merge-request' + @project = Resource::Project.fabricate_via_api! do |project| + project.name = 'project' end - merge_request_title = 'This is a merge request' - merge_request_description = 'Great feature' + @merge_request_title = 'One merge request to rule them all' + @merge_request_description = '... to find them, to bring them all, and in the darkness bind them' + end - Resource::MergeRequest.fabricate! do |merge_request| - merge_request.title = merge_request_title - merge_request.description = merge_request_description - merge_request.project = current_project + it 'creates a basic merge request', :smoke do + Resource::MergeRequest.fabricate_via_browser_ui! do |merge_request| + merge_request.project = @project + merge_request.title = @merge_request_title + merge_request.description = @merge_request_description end - expect(page).to have_content(merge_request_title) - expect(page).to have_content(merge_request_description) - expect(page).to have_content(/Opened [\w\s]+ ago/) + Page::MergeRequest::Show.perform do |merge_request| + expect(merge_request).to have_title(@merge_request_title) + expect(merge_request).to have_description(@merge_request_description) + end end - it 'user creates a new merge request with a milestone and label' do + it 'creates a merge request with a milestone and label' do gitlab_account_username = "@#{Runtime::User.username}" - Runtime::Browser.visit(:gitlab, Page::Main::Login) - Page::Main::Login.act { sign_in_using_credentials } - - current_project = Resource::Project.fabricate! do |project| - project.name = 'project-with-merge-request-and-milestone' - end - - current_milestone = Resource::ProjectMilestone.fabricate! do |milestone| - milestone.title = 'unique-milestone' - milestone.project = current_project + milestone = Resource::ProjectMilestone.fabricate_via_api! do |milestone| + milestone.project = @project + milestone.title = 'milestone' end - new_label = Resource::Label.fabricate! do |label| - label.project = current_project - label.title = 'qa-mr-test-label' - label.description = 'Merge Request label' + label = Resource::Label.fabricate_via_api! do |label| + label.project = @project + label.title = 'label' end - merge_request_title = 'This is a merge request with a milestone and a label' - merge_request_description = 'Great feature with milestone' - - Resource::MergeRequest.fabricate! do |merge_request| - merge_request.title = merge_request_title - merge_request.description = merge_request_description - merge_request.project = current_project - merge_request.milestone = current_milestone + Resource::MergeRequest.fabricate_via_browser_ui! do |merge_request| + merge_request.title = @merge_request_title + merge_request.description = @merge_request_description + merge_request.project = @project + merge_request.milestone = milestone merge_request.assignee = 'me' - merge_request.labels.push(new_label) + merge_request.labels.push(label) end Page::MergeRequest::Show.perform do |merge_request| - expect(merge_request).to have_content(merge_request_title) - expect(merge_request).to have_content(merge_request_description) - expect(merge_request).to have_content(/Opened [\w\s]+ ago/) + expect(merge_request).to have_title(@merge_request_title) + expect(merge_request).to have_description(@merge_request_description) expect(merge_request).to have_assignee(gitlab_account_username) - expect(merge_request).to have_label(new_label.title) + expect(merge_request).to have_label(label.title) end Page::Issuable::Sidebar.perform do |sidebar| - expect(sidebar).to have_milestone(current_milestone.title) + expect(sidebar).to have_milestone(milestone.title) end end end diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb index c7db595284e..6ca7af8a3af 100644 --- a/qa/qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb @@ -1,8 +1,7 @@ # frozen_string_literal: true module QA - # Failure issue: https://gitlab.com/gitlab-org/quality/nightly/issues/94 - context 'Create', :quarantine do + context 'Create' do describe 'Merge request creation from fork' do it 'user forks a project, submits a merge request and maintainer merges it' do Runtime::Browser.visit(:gitlab, Page::Main::Login) diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb index 7a36f9ea420..3ce291bf8bc 100644 --- a/qa/qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb @@ -1,7 +1,8 @@ # frozen_string_literal: true module QA - context 'Create' do + # Failure issue: https://gitlab.com/gitlab-org/quality/staging/issues/66 + context 'Create', :quarantine do describe 'Merge request rebasing' do it 'user rebases source branch of merge request' do Runtime::Browser.visit(:gitlab, Page::Main::Login) diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/squash_merge_request_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/squash_merge_request_spec.rb index 86692623790..a93f2695ec2 100644 --- a/qa/qa/specs/features/browser_ui/3_create/merge_request/squash_merge_request_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/squash_merge_request_spec.rb @@ -1,8 +1,7 @@ # frozen_string_literal: true module QA - # Failure issue: https://gitlab.com/gitlab-org/quality/nightly/issues/93 - context 'Create', :quarantine do + context 'Create' do describe 'Merge request squashing' do it 'user squashes commits while merging' do Runtime::Browser.visit(:gitlab, Page::Main::Login) diff --git a/qa/qa/specs/features/browser_ui/3_create/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 db33c6330ff..891cef6c420 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 @@ -1,20 +1,24 @@ # frozen_string_literal: true module QA - # https://gitlab.com/gitlab-org/quality/staging/issues/55 - context 'Create', :quarantine do + 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) - @merge_request = Resource::MergeRequest.fabricate! do |merge_request| + project = Resource::Project.fabricate_via_api! do |project| + project.name = 'project' + end + + @merge_request = Resource::MergeRequest.fabricate_via_api! do |merge_request| + merge_request.project = project merge_request.title = 'This is a merge request' - merge_request.description = 'For downloading patches and diffs' + merge_request.description = '... for downloading patches and diffs' end end - it 'user views merge request email patches' do + it 'views the merge request email patches' do @merge_request.visit! Page::MergeRequest::Show.perform(&:view_email_patches) @@ -23,7 +27,7 @@ module QA expect(page).to have_content('diff --git a/added_file.txt b/added_file.txt') end - it 'user views merge request plain diff' do + it 'views the merge request plain diff' do @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 2750b171a85..458072b1507 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,9 +1,9 @@ # frozen_string_literal: true module QA - context 'Create' do - # Issue: https://gitlab.com/gitlab-org/quality/nightly/issues/97 - describe 'File templates', :quarantine do + # Failure issue: https://gitlab.com/gitlab-org/quality/nightly/issues/127 + context 'Create', :quarantine do + describe 'File templates' do include Runtime::Fixtures def login diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/add_list_delete_branches_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/add_list_delete_branches_spec.rb index 37a784248d4..36cbd1b81f0 100644 --- a/qa/qa/specs/features/browser_ui/3_create/repository/add_list_delete_branches_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/repository/add_list_delete_branches_spec.rb @@ -1,8 +1,7 @@ # frozen_string_literal: true module QA - # Failure issue: https://gitlab.com/gitlab-org/quality/nightly/issues/62 - context 'Create', :quarantine do + context 'Create' do describe 'Create, list, and delete branches via web' do master_branch = 'master' second_branch = 'second-branch' @@ -57,7 +56,7 @@ module QA project.visit! end - it 'branches are correctly listed after CRUD operations' do + it 'lists branches correctly after CRUD operations' do Page::Project::Menu.perform(&:go_to_repository_branches) expect(page).to have_content(master_branch) diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/add_ssh_key_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/add_ssh_key_spec.rb index f41240b7605..56a7a04e840 100644 --- a/qa/qa/specs/features/browser_ui/3_create/repository/add_ssh_key_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/repository/add_ssh_key_spec.rb @@ -7,7 +7,7 @@ module QA it 'user adds and then removes an SSH key', :smoke do Runtime::Browser.visit(:gitlab, Page::Main::Login) - Page::Main::Login.act { sign_in_using_credentials } + Page::Main::Login.perform(&:sign_in_using_credentials) key = Resource::SSHKey.fabricate! do |resource| resource.title = key_title @@ -16,8 +16,8 @@ module QA expect(page).to have_content("Title: #{key_title}") expect(page).to have_content(key.fingerprint) - Page::Main::Menu.act { click_settings_link } - Page::Profile::Menu.act { click_ssh_keys } + Page::Main::Menu.perform(&:click_settings_link) + Page::Profile::Menu.perform(&:click_ssh_keys) Page::Profile::SSHKeys.perform do |ssh_keys| ssh_keys.remove_key(key_title) diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/create_edit_delete_file_via_web_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/create_edit_delete_file_via_web_spec.rb index d345fbfe995..51a1c19f0f7 100644 --- a/qa/qa/specs/features/browser_ui/3_create/repository/create_edit_delete_file_via_web_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/repository/create_edit_delete_file_via_web_spec.rb @@ -5,7 +5,7 @@ module QA describe 'Files management' do it 'user creates, edits and deletes a file via the Web' do Runtime::Browser.visit(:gitlab, Page::Main::Login) - Page::Main::Login.act { sign_in_using_credentials } + Page::Main::Login.perform(&:sign_in_using_credentials) # Create file_name = 'QA Test - File name' @@ -27,7 +27,7 @@ module QA updated_file_content = 'QA Test - Updated file content' commit_message_for_update = 'QA Test - Update file' - Page::File::Show.act { click_edit } + Page::File::Show.perform(&:click_edit) Page::File::Form.act do remove_content diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/push_mirroring_over_http_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/push_mirroring_over_http_spec.rb index 23008a58af8..448d4980727 100644 --- a/qa/qa/specs/features/browser_ui/3_create/repository/push_mirroring_over_http_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/repository/push_mirroring_over_http_spec.rb @@ -1,8 +1,7 @@ # frozen_string_literal: true module QA - # https://gitlab.com/gitlab-org/quality/staging/issues/40 - context 'Create', :quarantine do + context 'Create' do describe 'Push mirror a repository over HTTP' do it 'configures and syncs a (push) mirrored repository' do Runtime::Browser.visit(:gitlab, Page::Main::Login) diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_file_size_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_file_size_spec.rb index 5bfafdfa041..2027a3c16aa 100644 --- a/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_file_size_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_file_size_spec.rb @@ -1,74 +1,80 @@ # frozen_string_literal: true module QA - # Failure issue: https://gitlab.com/gitlab-org/quality/staging/issues/37 - context 'Create', :quarantine do + context 'Create', :requires_admin do describe 'push after setting the file size limit via admin/application_settings' do - before(:all) do - push = Resource::Repository::ProjectPush.fabricate! do |p| - p.file_name = 'README.md' - p.file_content = '# This is a test project' - p.commit_message = 'Add README.md' + before(:context) do + @project = Resource::Project.fabricate_via_api! do |p| + p.name = 'project-test-push-limit' + p.initialize_with_readme = true end - @project = push.project + @api_client = Runtime::API::Client.new(:gitlab, personal_access_token: Runtime::Env.admin_personal_access_token) end - before do - Runtime::Browser.visit(:gitlab, Page::Main::Login) - Page::Main::Login.perform(&:sign_in_using_credentials) - end - - after(:all) do + after(:context) do # need to set the default value after test # default value for file size limit is empty - Runtime::Browser.visit(:gitlab, Page::Main::Login) - Page::Main::Login.perform(&:sign_in_using_credentials) - - set_file_size_limit('') - - Page::Main::Menu.perform(&:sign_out) + set_file_size_limit(nil) end it 'push successful when the file size is under the limit' do set_file_size_limit(5) - expect(page).to have_content("Application settings saved successfully") - push = push_new_file('oversize_file_1.bin', wait_for_push: true) - expect(push.output).not_to have_content 'remote: fatal: pack exceeds maximum allowed size' + retry_on_fail do + push = push_new_file('oversize_file_1.bin', wait_for_push: true) + + expect(push.output).not_to have_content 'remote: fatal: pack exceeds maximum allowed size' + end end it 'push fails when the file size is above the limit' do set_file_size_limit(1) - expect(page).to have_content("Application settings saved successfully") - expect { push_new_file('oversize_file_2.bin', wait_for_push: false) } - .to raise_error(QA::Git::Repository::RepositoryCommandError, /remote: fatal: pack exceeds maximum allowed size/) + retry_on_fail do + expect { push_new_file('oversize_file_2.bin', wait_for_push: false) } + .to raise_error(QA::Git::Repository::RepositoryCommandError, /remote: fatal: pack exceeds maximum allowed size/) + end end def set_file_size_limit(limit) - Page::Main::Menu.perform(&:click_admin_area) - Page::Admin::Menu.perform(&:go_to_general_settings) + request = Runtime::API::Request.new(@api_client, '/application/settings') + put request.url, receive_max_input_size: limit - Page::Admin::Settings::General.perform do |setting| - setting.expand_account_and_limit do |page| - page.set_max_file_size(limit) - page.save_settings - end - end + expect_status(200) + expect(json_body).to match( + a_hash_including(receive_max_input_size: limit) + ) end def push_new_file(file_name, wait_for_push: true) - @project.visit! - - Resource::Repository::ProjectPush.fabricate! do |p| - p.project = @project + commit_message = 'Adding a new file' + output = Resource::Repository::Push.fabricate! do |p| + p.repository_http_uri = @project.repository_http_location.uri p.file_name = file_name p.file_content = SecureRandom.random_bytes(2000000) - p.commit_message = 'Adding a new file' - p.wait_for_push = wait_for_push + p.commit_message = commit_message p.new_branch = false end + @project.wait_for_push commit_message + + output + end + + # Application settings are cached for up to a minute. So when we change + # the `receive_max_input_size` setting, the setting might not be applied + # for minute. This caused the tests to intermittently fail. + # See https://gitlab.com/gitlab-org/quality/nightly/issues/113 + # + # Instead of waiting a minute after changing the setting, we retry the + # attempt to push if it fails. Most of the time the setting is updated in + # under a minute, i.e., in fewer than 6 attempts with a 10 second sleep + # between attempts. + # See https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/30233#note_188616863 + def retry_on_fail + Support::Retrier.retry_on_exception(max_attempts: 6, reload_page: nil, sleep_interval: 10) do + yield + end end end end diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/push_protected_branch_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/push_protected_branch_spec.rb index 6aebd04af03..e159e517cbb 100644 --- a/qa/qa/specs/features/browser_ui/3_create/repository/push_protected_branch_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/repository/push_protected_branch_spec.rb @@ -13,7 +13,7 @@ module QA before do Runtime::Browser.visit(:gitlab, Page::Main::Login) - Page::Main::Login.act { sign_in_using_credentials } + Page::Main::Login.perform(&:sign_in_using_credentials) end after do 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 680c5e21fa4..f915d412bf3 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 @@ -2,8 +2,7 @@ module QA context 'Create' do - # failure reported: https://gitlab.com/gitlab-org/quality/nightly/issues/42 - describe 'Commit data', :quarantine do + describe 'Commit data' do before(:context) do Runtime::Browser.visit(:gitlab, Page::Main::Login) Page::Main::Login.perform(&:sign_in_using_credentials) diff --git a/qa/qa/specs/features/browser_ui/3_create/snippet/create_snippet_spec.rb b/qa/qa/specs/features/browser_ui/3_create/snippet/create_snippet_spec.rb index f6f0468e76e..796de44a012 100644 --- a/qa/qa/specs/features/browser_ui/3_create/snippet/create_snippet_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/snippet/create_snippet_spec.rb @@ -1,8 +1,7 @@ # frozen_string_literal: true module QA - # Failure issue: https://gitlab.com/gitlab-org/quality/staging/issues/49 - context 'Create', :smoke, :quarantine do + context 'Create', :smoke do describe 'Snippet creation' do it 'User creates a snippet' do Runtime::Browser.visit(:gitlab, Page::Main::Login) @@ -13,7 +12,7 @@ module QA Resource::Snippet.fabricate_via_browser_ui! do |snippet| snippet.title = 'Snippet title' snippet.description = 'Snippet description' - snippet.visibility = 'Public' + snippet.visibility = 'Private' snippet.file_name = 'New snippet file name' snippet.file_content = 'Snippet file text' end @@ -21,8 +20,7 @@ module QA Page::Dashboard::Snippet::Show.perform do |snippet| expect(snippet).to have_snippet_title('Snippet title') expect(snippet).to have_snippet_description('Snippet description') - expect(snippet).to have_embed_type('Embed') - expect(snippet).to have_visibility_type('Public') + expect(snippet).to have_visibility_type('Private') expect(snippet).to have_file_name('New snippet file name') expect(snippet).to have_file_content('Snippet file text') end diff --git a/qa/qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb b/qa/qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb index 078d3b2b5b1..9ff7919f199 100644 --- a/qa/qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true module QA - # Failure issue: https://gitlab.com/gitlab-org/quality/staging/issues/46 context 'Create', :quarantine do + # Failure issue: https://gitlab.com/gitlab-org/quality/nightly/issues/127 describe 'Web IDE file templates' do include Runtime::Fixtures diff --git a/qa/qa/specs/features/browser_ui/4_verify/ci_variable/add_ci_variable_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/ci_variable/add_ci_variable_spec.rb index b060f15168c..b2c70547421 100644 --- a/qa/qa/specs/features/browser_ui/4_verify/ci_variable/add_ci_variable_spec.rb +++ b/qa/qa/specs/features/browser_ui/4_verify/ci_variable/add_ci_variable_spec.rb @@ -7,12 +7,12 @@ module QA Runtime::Browser.visit(:gitlab, Page::Main::Login) Page::Main::Login.perform(&:sign_in_using_credentials) - project = Resource::Project.fabricate! do |project| + project = Resource::Project.fabricate_via_api! do |project| project.name = 'project-with-ci-variables' project.description = 'project with CI variables' end - Resource::CiVariable.fabricate! do |resource| + Resource::CiVariable.fabricate_via_api! do |resource| resource.project = project resource.key = 'VARIABLE_KEY' resource.value = 'some_CI_variable' diff --git a/qa/qa/specs/features/browser_ui/4_verify/runner/register_runner_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/runner/register_runner_spec.rb index 3af7db751e7..33744236dd4 100644 --- a/qa/qa/specs/features/browser_ui/4_verify/runner/register_runner_spec.rb +++ b/qa/qa/specs/features/browser_ui/4_verify/runner/register_runner_spec.rb @@ -11,7 +11,7 @@ module QA it 'user registers a new specific runner' do Runtime::Browser.visit(:gitlab, Page::Main::Login) - Page::Main::Login.act { sign_in_using_credentials } + Page::Main::Login.perform(&:sign_in_using_credentials) Resource::Runner.fabricate! do |runner| runner.name = executor diff --git a/qa/qa/specs/features/browser_ui/6_release/deploy_key/add_deploy_key_spec.rb b/qa/qa/specs/features/browser_ui/6_release/deploy_key/add_deploy_key_spec.rb index aa01e5a618e..6f39a755392 100644 --- a/qa/qa/specs/features/browser_ui/6_release/deploy_key/add_deploy_key_spec.rb +++ b/qa/qa/specs/features/browser_ui/6_release/deploy_key/add_deploy_key_spec.rb @@ -1,8 +1,7 @@ # frozen_string_literal: true module QA - # Failure issue: https://gitlab.com/gitlab-org/quality/staging/issues/26 - context 'Release', :quarantine do + context 'Release' do describe 'Deploy key creation' do it 'user adds a deploy key' do Runtime::Browser.visit(:gitlab, Page::Main::Login) 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 2fe4e4d9d1f..f6411d8c5ad 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 @@ -3,36 +3,29 @@ require 'digest/sha1' module QA - # Failure issue: https://gitlab.com/gitlab-org/quality/nightly/issues/70 - context 'Release', :docker, :quarantine do + context 'Release', :docker do describe 'Git clone using a deploy key' do - def login + before do Runtime::Browser.visit(:gitlab, Page::Main::Login) Page::Main::Login.perform(&:sign_in_using_credentials) - end - - before(:all) do - login @runner_name = "qa-runner-#{Time.now.to_i}" - @project = Resource::Project.fabricate! do |resource| + @project = Resource::Project.fabricate_via_api! do |resource| resource.name = 'deploy-key-clone-project' end @repository_location = @project.repository_ssh_location - Resource::Runner.fabricate! do |resource| + Resource::Runner.fabricate_via_browser_ui! do |resource| resource.project = @project resource.name = @runner_name resource.tags = %w[qa docker] resource.image = 'gitlab/gitlab-runner:ubuntu' end - - Page::Main::Menu.perform(&:sign_out) end - after(:all) do + after do Service::Runner.new(@runner_name).remove! end @@ -46,9 +39,7 @@ module QA it "user sets up a deploy key with #{key_class}(#{bits}) to clone code using pipelines" do key = key_class.new(*bits) - login - - Resource::DeployKey.fabricate! do |resource| + Resource::DeployKey.fabricate_via_browser_ui! do |resource| resource.project = @project resource.title = "deploy key #{key.name}(#{key.bits})" resource.key = key.public_key @@ -56,7 +47,7 @@ module QA deploy_key_name = "DEPLOY_KEY_#{key.name}_#{key.bits}" - Resource::CiVariable.fabricate! do |resource| + Resource::CiVariable.fabricate_via_browser_ui! do |resource| resource.project = @project resource.key = deploy_key_name resource.value = key.private_key diff --git a/qa/qa/specs/features/browser_ui/6_release/deploy_token/add_deploy_token_spec.rb b/qa/qa/specs/features/browser_ui/6_release/deploy_token/add_deploy_token_spec.rb index caa9be341b4..ec0c45652fd 100644 --- a/qa/qa/specs/features/browser_ui/6_release/deploy_token/add_deploy_token_spec.rb +++ b/qa/qa/specs/features/browser_ui/6_release/deploy_token/add_deploy_token_spec.rb @@ -5,12 +5,12 @@ module QA describe 'Deploy token creation' do it 'user adds a deploy token' do Runtime::Browser.visit(:gitlab, Page::Main::Login) - Page::Main::Login.act { sign_in_using_credentials } + Page::Main::Login.perform(&:sign_in_using_credentials) deploy_token_name = 'deploy token name' one_week_from_now = Date.today + 7 - deploy_token = Resource::DeployToken.fabricate! do |resource| + deploy_token = Resource::DeployToken.fabricate_via_browser_ui! do |resource| resource.name = deploy_token_name resource.expires_at = one_week_from_now 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 5ca9ddb6b19..60c1e105ae6 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 @@ -9,8 +9,53 @@ module QA Page::Main::Login.perform(&:sign_in_using_credentials) end - # Failure issue: https://gitlab.com/gitlab-org/quality/nightly/issues/108 - describe 'Auto DevOps support', :orchestrated, :kubernetes, :quarantine do + def disable_optional_jobs(project) + # Disable code_quality check in Auto DevOps pipeline as it takes + # too long and times out the test + Resource::CiVariable.fabricate_via_api! do |resource| + resource.project = project + resource.key = 'CODE_QUALITY_DISABLED' + resource.value = '1' + resource.masked = false + end + + Resource::CiVariable.fabricate_via_api! do |resource| + resource.project = project + resource.key = 'LICENSE_MANAGEMENT_DISABLED' + resource.value = '1' + resource.masked = false + end + + Resource::CiVariable.fabricate_via_api! do |resource| + resource.project = project + resource.key = 'SAST_DISABLED' + resource.value = '1' + resource.masked = false + end + + Resource::CiVariable.fabricate_via_api! do |resource| + resource.project = project + resource.key = 'DEPENDENCY_SCANNING_DISABLED' + resource.value = '1' + resource.masked = false + end + + Resource::CiVariable.fabricate_via_api! do |resource| + resource.project = project + resource.key = 'CONTAINER_SCANNING_DISABLED' + resource.value = '1' + resource.masked = false + end + + Resource::CiVariable.fabricate_via_api! do |resource| + resource.project = project + resource.key = 'DAST_DISABLED' + resource.value = '1' + resource.masked = false + end + end + + describe 'Auto DevOps support', :orchestrated, :kubernetes do context 'when rbac is enabled' do before(:all) do @cluster = Service::KubernetesCluster.new.create! @@ -28,14 +73,7 @@ module QA p.description = 'Project with Auto DevOps' end - # Disable code_quality check in Auto DevOps pipeline as it takes - # too long and times out the test - Resource::CiVariable.fabricate! do |resource| - resource.project = @project - resource.key = 'CODE_QUALITY_DISABLED' - resource.value = '1' - resource.masked = false - end + disable_optional_jobs(@project) # Set an application secret CI variable (prefixed with K8S_SECRET_) Resource::CiVariable.fabricate! do |resource| diff --git a/qa/qa/specs/features/browser_ui/non_devops/performance_bar_spec.rb b/qa/qa/specs/features/browser_ui/non_devops/performance_bar_spec.rb index fc4ff364fd4..a04efb94def 100644 --- a/qa/qa/specs/features/browser_ui/non_devops/performance_bar_spec.rb +++ b/qa/qa/specs/features/browser_ui/non_devops/performance_bar_spec.rb @@ -19,7 +19,7 @@ module QA it 'shows results for the original request and AJAX requests' do # Issue pages always make AJAX requests - Resource::Issue.fabricate! do |issue| + Resource::Issue.fabricate_via_browser_ui! do |issue| issue.title = 'Performance bar test' end diff --git a/qa/qa/specs/parallel_runner.rb b/qa/qa/specs/parallel_runner.rb new file mode 100644 index 00000000000..b92fdb610b6 --- /dev/null +++ b/qa/qa/specs/parallel_runner.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +require 'open3' + +module QA + module Specs + module ParallelRunner + module_function + + def run(args) + unless args.include?('--') + index = args.index { |opt| opt.include?('features') } + + args.insert(index, '--') if index + end + + env = {} + Runtime::Env::ENV_VARIABLES.each_key do |key| + env[key] = ENV[key] if ENV[key] + end + env['QA_RUNTIME_SCENARIO_ATTRIBUTES'] = Runtime::Scenario.attributes.to_json + env['GITLAB_QA_ACCESS_TOKEN'] = Runtime::API::Client.new(:gitlab).personal_access_token unless env['GITLAB_QA_ACCESS_TOKEN'] + + cmd = "bundle exec parallel_test -t rspec --combine-stderr --serialize-stdout -- #{args.flatten.join(' ')}" + ::Open3.popen2e(env, cmd) do |_, out, wait| + out.each { |line| puts line } + + exit wait.value.exitstatus + end + end + end + end +end diff --git a/qa/qa/specs/runner.rb b/qa/qa/specs/runner.rb index f1cb9378de8..6aa08cf77b4 100644 --- a/qa/qa/specs/runner.rb +++ b/qa/qa/specs/runner.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true +require 'knapsack' require 'rspec/core' require 'rspec/expectations' -require 'knapsack' module QA module Specs @@ -17,44 +17,56 @@ module QA @options = [] end - def perform - args = [] - args.push('--tty') if tty + def paths_from_knapsack + allocator = Knapsack::AllocatorBuilder.new(Knapsack::Adapters::RSpecAdapter).allocator + + QA::Runtime::Logger.info '' + QA::Runtime::Logger.info 'Report specs:' + QA::Runtime::Logger.info allocator.report_node_tests.join(', ') + QA::Runtime::Logger.info '' + QA::Runtime::Logger.info 'Leftover specs:' + QA::Runtime::Logger.info allocator.leftover_node_tests.join(', ') + QA::Runtime::Logger.info '' + + ['--', allocator.node_tests] + end + + def rspec_tags + tags_for_rspec = [] if tags.any? - tags.each { |tag| args.push(['--tag', tag.to_s]) } + tags.each { |tag| tags_for_rspec.push(['--tag', tag.to_s]) } else - args.push(%w[--tag ~orchestrated]) unless (%w[-t --tag] & options).any? + tags_for_rspec.push(%w[--tag ~orchestrated]) unless (%w[-t --tag] & options).any? end - args.push(%w[--tag ~skip_signup_disabled]) if QA::Runtime::Env.signup_disabled? + tags_for_rspec.push(%w[--tag ~skip_signup_disabled]) if QA::Runtime::Env.signup_disabled? QA::Runtime::Env.supported_features.each_key do |key| - args.push(["--tag", "~requires_#{key}"]) unless QA::Runtime::Env.can_test? key + tags_for_rspec.push(%W[--tag ~requires_#{key}]) unless QA::Runtime::Env.can_test? key end - args.push(options) + tags_for_rspec + end - Runtime::Browser.configure! + def perform + args = [] + args.push('--tty') if tty + args.push(rspec_tags) + args.push(options) if Runtime::Env.knapsack? - allocator = Knapsack::AllocatorBuilder.new(Knapsack::Adapters::RSpecAdapter).allocator - - QA::Runtime::Logger.info '' - QA::Runtime::Logger.info 'Report specs:' - QA::Runtime::Logger.info allocator.report_node_tests.join(', ') - QA::Runtime::Logger.info '' - QA::Runtime::Logger.info 'Leftover specs:' - QA::Runtime::Logger.info allocator.leftover_node_tests.join(', ') - QA::Runtime::Logger.info '' - - args.push(['--', allocator.node_tests]) + args.push(paths_from_knapsack) else args.push(DEFAULT_TEST_PATH_ARGS) unless options.any? { |opt| opt =~ %r{/features/} } end - RSpec::Core::Runner.run(args.flatten, $stderr, $stdout).tap do |status| - abort if status.nonzero? + if Runtime::Scenario.attributes[:parallel] + ParallelRunner.run(args.flatten) + else + RSpec::Core::Runner.run(args.flatten, $stderr, $stdout).tap do |status| + abort if status.nonzero? + end end end end diff --git a/qa/qa/support/api.rb b/qa/qa/support/api.rb index a5c86425465..203064b2665 100644 --- a/qa/qa/support/api.rb +++ b/qa/qa/support/api.rb @@ -16,11 +16,12 @@ module QA e.response end - def get(url) + def get(url, raw_response: false) RestClient::Request.execute( method: :get, url: url, - verify_ssl: false) + verify_ssl: false, + raw_response: raw_response) rescue RestClient::ExceptionWithResponse => e e.response end diff --git a/qa/qa/support/page/logging.rb b/qa/qa/support/page/logging.rb index 02ebd96ad49..93d8fa99c0a 100644 --- a/qa/qa/support/page/logging.rb +++ b/qa/qa/support/page/logging.rb @@ -125,7 +125,7 @@ module QA super end - def within_element(name) + def within_element(name, text: nil) log("within element :#{name}") element = super diff --git a/qa/qa/support/retrier.rb b/qa/qa/support/retrier.rb index 8be4e9f5365..720f1d17037 100644 --- a/qa/qa/support/retrier.rb +++ b/qa/qa/support/retrier.rb @@ -23,6 +23,25 @@ module QA raise end end + + def retry_until(max_attempts: 3, reload_page: nil, sleep_interval: 0) + QA::Runtime::Logger.debug("with retry_until: max_attempts #{max_attempts}; sleep_interval #{sleep_interval}; reload_page:#{reload_page}") + attempts = 0 + + while attempts < max_attempts + QA::Runtime::Logger.debug("Attempt number #{attempts + 1}") + result = yield + return result if result + + sleep sleep_interval + + reload_page.refresh if reload_page + + attempts += 1 + end + + false + end end end end diff --git a/qa/qa/tools/generate_perf_testdata.rb b/qa/qa/tools/generate_perf_testdata.rb index de8cfa1aed9..26bcb2fe958 100644 --- a/qa/qa/tools/generate_perf_testdata.rb +++ b/qa/qa/tools/generate_perf_testdata.rb @@ -19,26 +19,30 @@ module QA raise ArgumentError, "Please provide GITLAB_QA_ACCESS_TOKEN" unless ENV['GITLAB_QA_ACCESS_TOKEN'] @api_client = Runtime::API::Client.new(ENV['GITLAB_ADDRESS'], personal_access_token: ENV['GITLAB_QA_ACCESS_TOKEN']) - @group_name = "gitlab-qa-perf-sandbox-#{SecureRandom.hex(8)}" - @project_name = "my-test-project-#{SecureRandom.hex(8)}" + @group_name = ENV['GROUP_NAME'] || "gitlab-qa-perf-sandbox-#{SecureRandom.hex(8)}" + @project_name = ENV['PROJECT_NAME'] || "my-test-project-#{SecureRandom.hex(8)}" @visibility = "public" @urls = { host: ENV['GITLAB_ADDRESS'] } end - def run + def all STDOUT.puts 'Running...' group_id = create_group create_project(group_id) - create_branch - add_new_file + + create_many_branches + create_many_new_files + create_mr_with_many_commits + methods_arr = [ - method(:create_issues), - method(:create_labels), - method(:create_todos), - method(:create_merge_requests), - method(:create_issue_with_500_discussions), - method(:create_mr_with_large_files) + method(:create_many_issues), + method(:create_many_labels), + method(:create_many_todos), + method(:create_many_merge_requests), + method(:create_an_issue_with_many_discussions), + method(:create_an_mr_with_large_files_and_many_mr_discussions) ] + threads_arr = [] methods_arr.each do |m| @@ -51,103 +55,102 @@ module QA STDOUT.puts "\nDone" end - private - def create_group - group_search_response = post Runtime::API::Request.new(@api_client, "/groups").url, "name=#{@group_name}&path=#{@group_name}&visibility=#{@visibility}" + group_search_response = create_a_group_api_req(@group_name, @visibility) group = JSON.parse(group_search_response.body) @urls[:group_page] = group["web_url"] + STDOUT.puts "Created a group: #{@urls[:group_page]}" group["id"] end def create_project(group_id) - create_project_response = post Runtime::API::Request.new(@api_client, "/projects").url, "name=#{@project_name}&namespace_id=#{group_id}&visibility=#{@visibility}" + create_project_response = create_a_project_api_req(@project_name, group_id, @visibility) @urls[:project_page] = JSON.parse(create_project_response.body)["web_url"] + STDOUT.puts "Created a project: #{@urls[:project_page]}" end - def create_issues + def create_many_issues 30.times do |i| - post Runtime::API::Request.new(@api_client, "/projects/#{@group_name}%2F#{@project_name}/issues").url, "title=issue#{i}&description=desc#{i}" + create_an_issue_api_req("#{@group_name}%2F#{@project_name}", "issue#{i}", "desc#{i}") end @urls[:issues_list_page] = @urls[:project_page] + "/issues" - STDOUT.puts "Created Issues" + STDOUT.puts "Created many issues: #{@urls[:issues_list_page]}" end - def create_todos + def create_many_todos 30.times do |i| - post Runtime::API::Request.new(@api_client, "/projects/#{@group_name}%2F#{@project_name}/issues/#{i + 1}/todo").url, nil + create_a_todo_api_req("#{@group_name}%2F#{@project_name}", "#{i + 1}") end @urls[:todos_page] = ENV['GITLAB_ADDRESS'] + "/dashboard/todos" - STDOUT.puts "Created todos" + STDOUT.puts "Created many todos: #{@urls[:todos_page]}" end - def create_labels + def create_many_labels 30.times do |i| - post Runtime::API::Request.new(@api_client, "/projects/#{@group_name}%2F#{@project_name}/labels").url, - "name=label#{i}&color=#{Faker::Color.hex_color}" + create_a_label_api_req("#{@group_name}%2F#{@project_name}", "label#{i}", "#{Faker::Color.hex_color}") end @urls[:labels_page] = @urls[:project_page] + "/labels" - STDOUT.puts "Created labels" + STDOUT.puts "Created many labels: #{@urls[:labels_page]}" end - def create_merge_requests + def create_many_merge_requests 30.times do |i| - post Runtime::API::Request.new(@api_client, "/projects/#{@group_name}%2F#{@project_name}/merge_requests").url, "source_branch=branch#{i}&target_branch=master&title=MR#{i}" + create_a_merge_request_api_req("#{@group_name}%2F#{@project_name}", "branch#{i}", "master", "MR#{i}") end @urls[:mr_list_page] = @urls[:project_page] + "/merge_requests" - STDOUT.puts "Created MRs" + STDOUT.puts "Created many MRs: #{@urls[:mr_list_page]}" end - def add_new_file - post Runtime::API::Request.new(@api_client, "/projects/#{@group_name}%2F#{@project_name}/repository/files/hello.txt").url, "branch=master&commit_message=\"hello\"&content=\"my new content\"" + def create_many_new_files + create_a_new_file_api_req("hello.txt", "master", "#{@group_name}%2F#{@project_name}", "hello", "my new content") 30.times do |i| - post Runtime::API::Request.new(@api_client, "/projects/#{@group_name}%2F#{@project_name}/repository/files/hello#{i}.txt").url, "branch=branch#{i}&commit_message=\"hello\"&content=\"my new content\"" + create_a_new_file_api_req("hello#{i}.txt", "branch#{i}", "#{@group_name}%2F#{@project_name}", "hello", "my new content") end - STDOUT.puts "Added Files" + + @urls[:files_page] = @urls[:project_page] + "/tree/master" + STDOUT.puts "Added many new files: #{@urls[:files_page]}" end - def create_branch + def create_many_branches 30.times do |i| - post Runtime::API::Request.new(@api_client, "/projects/#{@group_name}%2F#{@project_name}/repository/branches").url, "branch=branch#{i}&ref=master" + create_a_branch_api_req("branch#{i}", "#{@group_name}%2F#{@project_name}") end - STDOUT.puts "Created branches" + @urls[:branches_page] = @urls[:project_page] + "/-/branches" + STDOUT.puts "Created many branches: #{@urls[:branches_page]}" end - def create_issue_with_500_discussions + def create_an_issue_with_many_discussions issue_id = 1 500.times do - post Runtime::API::Request.new(@api_client, "/projects/#{@group_name}%2F#{@project_name}/issues/#{issue_id}/discussions").url, "body=\"Let us discuss\"" + create_a_discussion_on_issue_api_req("#{@group_name}%2F#{@project_name}", issue_id, "Let us discuss") end - labels_list = (0..15).map {|i| "label#{i}"}.join(',') + labels_list = (0..15).map { |i| "label#{i}" }.join(',') # Add description and labels - put Runtime::API::Request.new(@api_client, "/projects/#{@group_name}%2F#{@project_name}/issues/#{issue_id}").url, "description=#{Faker::Lorem.sentences(500).join(" ")}&labels=#{labels_list}" + update_an_issue_api_req("#{@group_name}%2F#{@project_name}", issue_id, "#{Faker::Lorem.sentences(500).join(" ")}", labels_list) @urls[:large_issue] = @urls[:project_page] + "/issues/#{issue_id}" - STDOUT.puts "Created Issue with 500 Discussions" + STDOUT.puts "Created an issue with many discussions: #{@urls[:large_issue]}" end - def create_mr_with_large_files + def create_an_mr_with_large_files_and_many_mr_discussions content_arr = [] 16.times do |i| faker_line_arr = Faker::Lorem.sentences(1500) content = faker_line_arr.join("\n\r") - post Runtime::API::Request.new(@api_client, "/projects/#{@group_name}%2F#{@project_name}/repository/files/hello#{i}.txt").url, - "branch=master&commit_message=\"Add hello#{i}.txt\"&content=#{content}" + create_a_new_file_api_req("hello#{i}.txt", "master", "#{@group_name}%2F#{@project_name}", "Add hello#{i}.txt", content) content_arr[i] = faker_line_arr end - post Runtime::API::Request.new(@api_client, "/projects/#{@group_name}%2F#{@project_name}/repository/branches").url, - "branch=performance&ref=master" + create_a_branch_api_req("performance", "#{@group_name}%2F#{@project_name}") 16.times do |i| missed_line_array = content_arr[i].each_slice(2).map(&:first) content = missed_line_array.join("\n\rIm new!:D \n\r ") - put Runtime::API::Request.new(@api_client, "/projects/#{@group_name}%2F#{@project_name}/repository/files/hello#{i}.txt").url, - "branch=performance&commit_message=\"Update hello#{i}.txt\"&content=#{content}" + + update_file_api_req("hello#{i}.txt", "performance", "#{@group_name}%2F#{@project_name}", "Update hello#{i}.txt", content) end - create_mr_response = post Runtime::API::Request.new(@api_client, """/projects/#{@group_name}%2F#{@project_name}/merge_requests""").url, - "source_branch=performance&target_branch=master&title=Large_MR" + create_mr_response = create_a_merge_request_api_req("#{@group_name}%2F#{@project_name}", "performance", "master", "Large_MR") iid = JSON.parse(create_mr_response.body)["iid"] diff_refs = JSON.parse(create_mr_response.body)["diff_refs"] @@ -161,8 +164,8 @@ module QA if should_resolve discussion_id = JSON.parse(create_diff_note_response.body)["id"] - put Runtime::API::Request.new(@api_client, """/projects/#{@group_name}%2F#{@project_name}/merge_requests/#{iid}/discussions/#{discussion_id}""").url, - "resolved=true" + + update_a_discussion_on_issue_api_req("#{@group_name}%2F#{@project_name}", iid, discussion_id, "true") end should_resolve ^= true @@ -171,23 +174,126 @@ module QA # Add discussions to main tab 100.times do - post Runtime::API::Request.new(@api_client, "/projects/#{@group_name}%2F#{@project_name}/merge_requests/#{iid}/discussions").url, - "body=\"Let us discuss\"" + create_a_discussion_on_mr_api_req("#{@group_name}%2F#{@project_name}", iid, "Let us discuss") end @urls[:large_mr] = JSON.parse(create_mr_response.body)["web_url"] - STDOUT.puts "Created MR with 500 Discussions and 20 Very Large Files" + STDOUT.puts "Created an MR with many discussions and many very large Files: #{@urls[:large_mr]}" end def create_diff_note(iid, file_count, line_count, head_sha, start_sha, base_sha, line_type) post Runtime::API::Request.new(@api_client, "/projects/#{@group_name}%2F#{@project_name}/merge_requests/#{iid}/discussions").url, - """body=\"Let us discuss\"& + "" "body=\"Let us discuss\"& position[position_type]=text& position[new_path]=hello#{file_count}.txt& position[old_path]=hello#{file_count}.txt& position[#{line_type}]=#{line_count * 100}& position[head_sha]=#{head_sha}& position[start_sha]=#{start_sha}& - position[base_sha]=#{base_sha}""" + position[base_sha]=#{base_sha}" "" + end + + def create_mr_with_many_commits + project_path = "#{@group_name}%2F#{@project_name}" + branch_name = "branch_with_many_commits-#{SecureRandom.hex(8)}" + file_name = "file_for_many_commits.txt" + + create_a_branch_api_req(branch_name, project_path) + create_a_new_file_api_req(file_name, branch_name, project_path, "Initial commit for new file", "Initial file content") + create_mr_response = create_a_merge_request_api_req(project_path, branch_name, "master", "MR with many commits-#{SecureRandom.hex(8)}") + @urls[:mr_with_many_commits] = JSON.parse(create_mr_response.body)["web_url"] + 100.times do |i| + update_file_api_req(file_name, branch_name, project_path, Faker::Lorem.sentences(5).join(" "), Faker::Lorem.sentences(500).join("\n")) + end + STDOUT.puts "Using branch: #{branch_name}, created an MR with many commits: #{@urls[:mr_with_many_commits]}" + end + + private + + # API Requests + + def create_a_discussion_on_issue_api_req(project_path_or_id, issue_id, body) + call_api(expected_response_code: 201) do + post Runtime::API::Request.new(@api_client, "/projects/#{project_path_or_id}/issues/#{issue_id}/discussions").url, "body=\"#{body}\"" + end + end + + def update_a_discussion_on_issue_api_req(project_path_or_id, mr_iid, discussion_id, resolved_status) + call_api(expected_response_code: 200) do + put Runtime::API::Request.new(@api_client, "/projects/#{project_path_or_id}/merge_requests/#{mr_iid}/discussions/#{discussion_id}").url, "resolved=#{resolved_status}" + end + end + + def create_a_discussion_on_mr_api_req(project_path_or_id, mr_iid, body) + call_api(expected_response_code: 201) do + post Runtime::API::Request.new(@api_client, "/projects/#{project_path_or_id}/merge_requests/#{mr_iid}/discussions").url, "body=\"#{body}\"" + end + end + + def create_a_label_api_req(project_path_or_id, name, color) + call_api(expected_response_code: 201) do + post Runtime::API::Request.new(@api_client, "/projects/#{project_path_or_id}/labels").url, "name=#{name}&color=#{color}" + end + end + + def create_a_todo_api_req(project_path_or_id, issue_id) + call_api(expected_response_code: 201) do + post Runtime::API::Request.new(@api_client, "/projects/#{project_path_or_id}/issues/#{issue_id}/todo").url, nil + end + end + + def create_an_issue_api_req(project_path_or_id, title, description) + call_api(expected_response_code: 201) do + post Runtime::API::Request.new(@api_client, "/projects/#{project_path_or_id}/issues").url, "title=#{title}&description=#{description}" + end + end + + def update_an_issue_api_req(project_path_or_id, issue_id, description, labels_list) + call_api(expected_response_code: 200) do + put Runtime::API::Request.new(@api_client, "/projects/#{project_path_or_id}/issues/#{issue_id}").url, "description=#{description}&labels=#{labels_list}" + end + end + + def create_a_project_api_req(project_name, group_id, visibility) + call_api(expected_response_code: 201) do + post Runtime::API::Request.new(@api_client, "/projects").url, "name=#{project_name}&namespace_id=#{group_id}&visibility=#{visibility}" + end + end + + def create_a_group_api_req(group_name, visibility) + call_api(expected_response_code: 201) do + post Runtime::API::Request.new(@api_client, "/groups").url, "name=#{group_name}&path=#{group_name}&visibility=#{visibility}" + end + end + + def create_a_branch_api_req(branch_name, project_path_or_id) + call_api(expected_response_code: 201) do + post Runtime::API::Request.new(@api_client, "/projects/#{project_path_or_id}/repository/branches").url, "branch=#{branch_name}&ref=master" + end + end + + def create_a_new_file_api_req(file_path, branch_name, project_path_or_id, commit_message, content) + call_api(expected_response_code: 201) do + post Runtime::API::Request.new(@api_client, "/projects/#{project_path_or_id}/repository/files/#{file_path}").url, "branch=#{branch_name}&commit_message=\"#{commit_message}\"&content=\"#{content}\"" + end + end + + def create_a_merge_request_api_req(project_path_or_id, source_branch, target_branch, mr_title) + call_api(expected_response_code: 201) do + post Runtime::API::Request.new(@api_client, "/projects/#{project_path_or_id}/merge_requests").url, "source_branch=#{source_branch}&target_branch=#{target_branch}&title=#{mr_title}" + end + end + + def update_file_api_req(file_path, branch_name, project_path_or_id, commit_message, content) + call_api(expected_response_code: 200) do + put Runtime::API::Request.new(@api_client, "/projects/#{project_path_or_id}/repository/files/#{file_path}").url, "branch=#{branch_name}&commit_message=\"#{commit_message}\"&content=\"#{content}\"" + end + end + + def call_api(expected_response_code: 200) + response = yield + raise "API call failed with response code: #{response.code} and body: #{response.body}" unless response.code == expected_response_code + + response end end end diff --git a/qa/qa/vendor/saml_idp/page/login.rb b/qa/qa/vendor/saml_idp/page/login.rb index 9c1f9904a7a..1b8c926532a 100644 --- a/qa/qa/vendor/saml_idp/page/login.rb +++ b/qa/qa/vendor/saml_idp/page/login.rb @@ -12,6 +12,14 @@ module QA fill_in 'password', with: 'user1pass' click_on 'Login' end + + def login_if_required + login if login_required? + end + + def login_required? + page.has_text?('Enter your username and password') + end end end end diff --git a/qa/spec/page/element_spec.rb b/qa/spec/page/element_spec.rb index f746fe06e40..20d4a00c020 100644 --- a/qa/spec/page/element_spec.rb +++ b/qa/spec/page/element_spec.rb @@ -11,7 +11,7 @@ describe QA::Page::Element do describe '#selector_css' do it 'transforms element name into QA-specific clickable css selector' do expect(described_class.new(:sign_in_button).selector_css) - .to eq '.qa-sign-in-button' + .to include('.qa-sign-in-button') end end @@ -49,6 +49,10 @@ describe QA::Page::Element do it 'does not match if QA selector is not there' do expect(subject.matches?('some_name selector')).to be false end + + it 'matches when element name is specified' do + expect(subject.matches?('data:{qa:{selector:"some_name"}}')).to be true + end end describe 'attributes' do @@ -106,4 +110,11 @@ describe QA::Page::Element do end end end + + describe 'data-qa selectors' do + subject { described_class.new(:my_element) } + it 'properly translates to a data-qa-selector' do + expect(subject.selector_css).to include(%q([data-qa-selector="my_element"])) + end + end end diff --git a/qa/spec/page/logging_spec.rb b/qa/spec/page/logging_spec.rb index 092c6a17c9c..92a4f7b40e6 100644 --- a/qa/spec/page/logging_spec.rb +++ b/qa/spec/page/logging_spec.rb @@ -91,26 +91,26 @@ describe QA::Support::Page::Logging do it 'logs has_element?' do expect { subject.has_element?(:element) } - .to output(/has_element\? :element \(wait: 2\) returned: true/).to_stdout_from_any_process + .to output(/has_element\? :element \(wait: #{QA::Runtime::Browser::CAPYBARA_MAX_WAIT_TIME}\) returned: true/).to_stdout_from_any_process end it 'logs has_element? with text' do expect { subject.has_element?(:element, text: "some text") } - .to output(/has_element\? :element with text \"some text\" \(wait: 2\) returned: true/).to_stdout_from_any_process + .to output(/has_element\? :element with text \"some text\" \(wait: #{QA::Runtime::Browser::CAPYBARA_MAX_WAIT_TIME}\) returned: true/).to_stdout_from_any_process end it 'logs has_no_element?' do allow(page).to receive(:has_no_css?).and_return(true) expect { subject.has_no_element?(:element) } - .to output(/has_no_element\? :element \(wait: 2\) returned: true/).to_stdout_from_any_process + .to output(/has_no_element\? :element \(wait: #{QA::Runtime::Browser::CAPYBARA_MAX_WAIT_TIME}\) returned: true/).to_stdout_from_any_process end it 'logs has_no_element? with text' do allow(page).to receive(:has_no_css?).and_return(true) expect { subject.has_no_element?(:element, text: "more text") } - .to output(/has_no_element\? :element with text \"more text\" \(wait: 2\) returned: true/).to_stdout_from_any_process + .to output(/has_no_element\? :element with text \"more text\" \(wait: #{QA::Runtime::Browser::CAPYBARA_MAX_WAIT_TIME}\) returned: true/).to_stdout_from_any_process end it 'logs has_text?' do @@ -135,9 +135,9 @@ describe QA::Support::Page::Logging do end it 'logs within_element' do - expect { subject.within_element(:element) } + expect { subject.within_element(:element, text: nil) } .to output(/within element :element/).to_stdout_from_any_process - expect { subject.within_element(:element) } + expect { subject.within_element(:element, text: nil) } .to output(/end within element :element/).to_stdout_from_any_process end diff --git a/qa/spec/resource/repository/push_spec.rb b/qa/spec/resource/repository/push_spec.rb index bf3ebce0cfe..2f9e4958ae1 100644 --- a/qa/spec/resource/repository/push_spec.rb +++ b/qa/spec/resource/repository/push_spec.rb @@ -19,7 +19,11 @@ describe QA::Resource::Repository::Push do expect { subject.files = [] }.to raise_error(ArgumentError) end - it 'does not raise if files is an array' do + it 'raises an error if files is not an array of hashes with :name and :content keys' do + expect { subject.files = [{ foo: 'foo' }] }.to raise_error(ArgumentError) + end + + it 'does not raise if files is an array of hashes with :name and :content keys' do expect { subject.files = files }.not_to raise_error end end diff --git a/qa/spec/runtime/api/client_spec.rb b/qa/spec/runtime/api/client_spec.rb index cf19b52700b..6f7020d6595 100644 --- a/qa/spec/runtime/api/client_spec.rb +++ b/qa/spec/runtime/api/client_spec.rb @@ -16,26 +16,56 @@ describe QA::Runtime::API::Client do end describe '#personal_access_token' do - context 'when QA::Runtime::Env.personal_access_token is present' do + context 'when user is nil and QA::Runtime::Env.personal_access_token is present' do before do allow(QA::Runtime::Env).to receive(:personal_access_token).and_return('a_token') end it 'returns specified token from env' do - expect(described_class.new.personal_access_token).to eq 'a_token' + expect(subject.personal_access_token).to eq 'a_token' end end - context 'when QA::Runtime::Env.personal_access_token is nil' do + context 'when user is present and QA::Runtime::Env.personal_access_token is nil' do before do allow(QA::Runtime::Env).to receive(:personal_access_token).and_return(nil) end it 'returns a created token' do + subject { described_class.new(user: { username: 'foo' }) } + expect(subject).to receive(:create_personal_access_token).and_return('created_token') expect(subject.personal_access_token).to eq 'created_token' end end + + context 'when user is nil and QA::Runtime::Env.personal_access_token is nil' do + before do + allow(QA::Runtime::Env).to receive(:personal_access_token).and_return(nil) + end + + it 'returns a created token' do + client = described_class.new + + expect(client).to receive(:create_personal_access_token).and_return('created_token') + + expect(client.personal_access_token).to eq 'created_token' + end + end + + context 'when user is present and QA::Runtime::Env.personal_access_token is present' do + before do + allow(QA::Runtime::Env).to receive(:personal_access_token).and_return('a_token') + end + + it 'returns a created token' do + client = described_class.new(user: { username: 'foo' }) + + expect(client).to receive(:create_personal_access_token).and_return('created_token') + + expect(client.personal_access_token).to eq 'created_token' + end + end end end diff --git a/qa/spec/runtime/env_spec.rb b/qa/spec/runtime/env_spec.rb index 2560695ef2e..340831aa06d 100644 --- a/qa/spec/runtime/env_spec.rb +++ b/qa/spec/runtime/env_spec.rb @@ -192,6 +192,30 @@ describe QA::Runtime::Env do end end + describe '.knapsack?' do + it 'returns true if KNAPSACK_GENERATE_REPORT is defined' do + stub_env('KNAPSACK_GENERATE_REPORT', 'true') + + expect(described_class.knapsack?).to be_truthy + end + + it 'returns true if KNAPSACK_REPORT_PATH is defined' do + stub_env('KNAPSACK_REPORT_PATH', '/a/path') + + expect(described_class.knapsack?).to be_truthy + end + + it 'returns true if KNAPSACK_TEST_FILE_PATTERN is defined' do + stub_env('KNAPSACK_TEST_FILE_PATTERN', '/a/**/pattern') + + expect(described_class.knapsack?).to be_truthy + end + + it 'returns false if neither KNAPSACK_GENERATE_REPORT nor KNAPSACK_REPORT_PATH nor KNAPSACK_TEST_FILE_PATTERN are defined' do + expect(described_class.knapsack?).to be_falsey + end + end + describe '.require_github_access_token!' do it 'raises ArgumentError if GITHUB_ACCESS_TOKEN is not defined' do stub_env('GITHUB_ACCESS_TOKEN', nil) @@ -227,6 +251,12 @@ describe QA::Runtime::Env do env_key: 'QA_CAN_TEST_GIT_PROTOCOL_V2', default: true + it_behaves_like 'boolean method with parameter', + method: :can_test?, + param: :admin, + env_key: 'QA_CAN_TEST_ADMIN_FEATURES', + default: true + it 'raises ArgumentError if feature is unknown' do expect { described_class.can_test? :foo }.to raise_error(ArgumentError, 'Unknown feature "foo"') end diff --git a/qa/spec/spec_helper.rb b/qa/spec/spec_helper.rb index f25dbf3a8ab..363980acc33 100644 --- a/qa/spec/spec_helper.rb +++ b/qa/spec/spec_helper.rb @@ -8,6 +8,10 @@ if ENV['CI'] && QA::Runtime::Env.knapsack? && !ENV['NO_KNAPSACK'] Knapsack::Adapters::RSpecAdapter.bind end +QA::Runtime::Browser.configure! + +QA::Runtime::Scenario.from_env(QA::Runtime::Env.runtime_scenario_attributes) if QA::Runtime::Env.runtime_scenario_attributes + %w[helpers shared_examples].each do |d| Dir[::File.join(__dir__, d, '**', '*.rb')].each { |f| require f } end @@ -42,7 +46,7 @@ RSpec.configure do |config| if ENV['CI'] config.around do |example| - retry_times = example.metadata.keys.include?(:quarantine) ? 1 : 2 + retry_times = example.metadata.key?(:quarantine) ? 1 : 2 example.run_with_retry retry: retry_times end end diff --git a/qa/spec/specs/parallel_runner_spec.rb b/qa/spec/specs/parallel_runner_spec.rb new file mode 100644 index 00000000000..67d94a1f648 --- /dev/null +++ b/qa/spec/specs/parallel_runner_spec.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +describe QA::Specs::ParallelRunner do + include Helpers::StubENV + + before do + allow(QA::Runtime::Scenario).to receive(:attributes).and_return(parallel: true) + stub_env('GITLAB_QA_ACCESS_TOKEN', 'skip_token_creation') + end + + it 'passes args to parallel_tests' do + expect_cli_arguments(['--tag', '~orchestrated', *QA::Specs::Runner::DEFAULT_TEST_PATH_ARGS]) + + subject.run(['--tag', '~orchestrated', *QA::Specs::Runner::DEFAULT_TEST_PATH_ARGS]) + end + + it 'passes a given test path to parallel_tests and adds a separator' do + expect_cli_arguments(%w[-- qa/specs/features/foo]) + + subject.run(%w[qa/specs/features/foo]) + end + + it 'passes tags and test paths to parallel_tests and adds a separator' do + expect_cli_arguments(%w[--tag smoke -- qa/specs/features/foo qa/specs/features/bar]) + + subject.run(%w[--tag smoke qa/specs/features/foo qa/specs/features/bar]) + end + + it 'passes tags and test paths with separators to parallel_tests' do + expect_cli_arguments(%w[-- --tag smoke -- qa/specs/features/foo qa/specs/features/bar]) + + subject.run(%w[-- --tag smoke -- qa/specs/features/foo qa/specs/features/bar]) + end + + it 'passes supported environment variables' do + # Test only env vars starting with GITLAB because some of the others + # affect how the runner behaves, and we're not concerned with those + # behaviors in this test + gitlab_env_vars = QA::Runtime::Env::ENV_VARIABLES.reject { |v| !v.start_with?('GITLAB') } + + gitlab_env_vars.each do |k, v| + stub_env(k, v) + end + + gitlab_env_vars['QA_RUNTIME_SCENARIO_ATTRIBUTES'] = '{"parallel":true}' + + expect_cli_arguments([], gitlab_env_vars) + + subject.run([]) + end + + def expect_cli_arguments(arguments, env = { 'QA_RUNTIME_SCENARIO_ATTRIBUTES' => '{"parallel":true}' }) + cmd = "bundle exec parallel_test -t rspec --combine-stderr --serialize-stdout -- #{arguments.join(' ')}" + expect(Open3).to receive(:popen2e) + .with(hash_including(env), cmd) + .and_return(0) + end +end diff --git a/qa/spec/specs/runner_spec.rb b/qa/spec/specs/runner_spec.rb index 5c86c102105..3d98f03a982 100644 --- a/qa/spec/specs/runner_spec.rb +++ b/qa/spec/specs/runner_spec.rb @@ -1,16 +1,22 @@ # frozen_string_literal: true +require 'active_support/core_ext/hash' + describe QA::Specs::Runner do + shared_examples 'excludes orchestrated' do + it 'excludes the orchestrated tag and includes default args' do + expect_rspec_runner_arguments(['--tag', '~orchestrated', *described_class::DEFAULT_TEST_PATH_ARGS]) + + subject.perform + end + end + context '#perform' do before do allow(QA::Runtime::Browser).to receive(:configure!) end - it 'excludes the orchestrated tag by default' do - expect_rspec_runner_arguments(['--tag', '~orchestrated', *described_class::DEFAULT_TEST_PATH_ARGS]) - - subject.perform - end + it_behaves_like 'excludes orchestrated' context 'when tty is set' do subject { described_class.new.tap { |runner| runner.tty = true } } @@ -52,11 +58,11 @@ describe QA::Specs::Runner do end end - context 'when "-- qa/specs/features/foo" is set as options' do - subject { described_class.new.tap { |runner| runner.options = %w[-- qa/specs/features/foo] } } + context 'when "--tag smoke" and "qa/specs/features/foo" are set as options' do + subject { described_class.new.tap { |runner| runner.options = %w[--tag smoke qa/specs/features/foo] } } - it 'passes the given tests path and excludes the orchestrated tag' do - expect_rspec_runner_arguments(['--tag', '~orchestrated', '--', 'qa/specs/features/foo']) + it 'focuses on the given tag and includes the path without excluding the orchestrated tag' do + expect_rspec_runner_arguments(['--tag', 'smoke', 'qa/specs/features/foo']) subject.perform end @@ -67,8 +73,6 @@ describe QA::Specs::Runner do allow(QA::Runtime::Env).to receive(:signup_disabled?).and_return(true) end - subject { described_class.new } - it 'includes default args and excludes the skip_signup_disabled tag' do expect_rspec_runner_arguments(['--tag', '~orchestrated', '--tag', '~skip_signup_disabled', *described_class::DEFAULT_TEST_PATH_ARGS]) @@ -76,17 +80,53 @@ describe QA::Specs::Runner do end end - context 'when git protocol v2 is not supported' do - before do - allow(QA::Runtime::Env).to receive(:can_test?).with(:git_protocol_v2).and_return(false) + context 'testable features' do + shared_examples 'one supported feature' do |feature| + before do + QA::Runtime::Env.supported_features.each do |tag, _| + allow(QA::Runtime::Env).to receive(:can_test?).with(tag).and_return(false) + end + + allow(QA::Runtime::Env).to receive(:can_test?).with(feature).and_return(true) unless feature.nil? + end + + it 'includes default args and excludes all unsupported tags' do + expect_rspec_runner_arguments(['--tag', '~orchestrated', *excluded_feature_tags_except(feature), *described_class::DEFAULT_TEST_PATH_ARGS]) + + subject.perform + end end - subject { described_class.new } + context 'when only git protocol 2 is supported' do + it_behaves_like 'one supported feature', :git_protocol_v2 + end - it 'includes default args and excludes the requires_git_protocol_v2 tag' do - expect_rspec_runner_arguments(['--tag', '~orchestrated', '--tag', '~requires_git_protocol_v2', *described_class::DEFAULT_TEST_PATH_ARGS]) + context 'when only admin features are supported' do + it_behaves_like 'one supported feature', :admin + end - subject.perform + context 'when no features are supported' do + it_behaves_like 'one supported feature', nil + end + + context 'when all features are supported' do + before do + QA::Runtime::Env.supported_features.each do |tag, _| + allow(QA::Runtime::Env).to receive(:can_test?).with(tag).and_return(true) + end + end + + it_behaves_like 'excludes orchestrated' + end + + context 'when features are not specified' do + it_behaves_like 'excludes orchestrated' + end + end + + def excluded_feature_tags_except(tag) + QA::Runtime::Env.supported_features.except(tag).flat_map do |tag, _| + ['--tag', "~requires_#{tag}"] end end |
