diff options
Diffstat (limited to 'qa/qa')
70 files changed, 1632 insertions, 487 deletions
diff --git a/qa/qa/flow/project.rb b/qa/qa/flow/project.rb index 72b9357a604..db42a3a3594 100644 --- a/qa/qa/flow/project.rb +++ b/qa/qa/flow/project.rb @@ -8,12 +8,20 @@ module QA def add_member(project:, username:) project.visit! - Page::Project::Menu.perform(&:go_to_members_settings) + Page::Project::Menu.perform(&:click_members) - Page::Project::Settings::Members.perform do |member_settings| + Page::Project::Members.perform do |member_settings| member_settings.add_member(username) end end + + def go_to_create_project_from_template + if Page::Project::NewExperiment.perform(&:shown?) + Page::Project::NewExperiment.perform(&:click_create_from_template_link) + else + Page::Project::New.perform(&:click_create_from_template_tab) + end + end end end end diff --git a/qa/qa/git/repository.rb b/qa/qa/git/repository.rb index bd2fbbd80cb..43608827b2e 100644 --- a/qa/qa/git/repository.rb +++ b/qa/qa/git/repository.rb @@ -111,7 +111,7 @@ module QA end def commit_with_gpg(message) - run(%Q{git config user.signingkey #{@gpg_key_id} && git config gpg.program $(which gpg) && git commit -S -m "#{message}"}).to_s + run(%Q{git config user.signingkey #{@gpg_key_id} && git config gpg.program $(command -v gpg) && git commit -S -m "#{message}"}).to_s end def push_changes(branch = 'master') diff --git a/qa/qa/page/base.rb b/qa/qa/page/base.rb index cb3827f8eb1..f0d4ae45ef8 100644 --- a/qa/qa/page/base.rb +++ b/qa/qa/page/base.rb @@ -99,7 +99,16 @@ module QA def find_element(name, **kwargs) wait_for_requests - find(element_selector_css(name), kwargs) + element_selector = element_selector_css(name, reject_capybara_query_keywords(kwargs)) + find(element_selector, only_capybara_query_keywords(kwargs)) + end + + def only_capybara_query_keywords(kwargs) + kwargs.select { |kwarg| Capybara::Queries::SelectorQuery::VALID_KEYS.include?(kwarg) } + end + + def reject_capybara_query_keywords(kwargs) + kwargs.reject { |kwarg| Capybara::Queries::SelectorQuery::VALID_KEYS.include?(kwarg) } end def active_element?(name) @@ -162,11 +171,17 @@ module QA def has_element?(name, **kwargs) wait_for_requests - wait = kwargs.delete(:wait) || Capybara.default_max_wait_time - text = kwargs.delete(:text) - klass = kwargs.delete(:class) + disabled = kwargs.delete(:disabled) - has_css?(element_selector_css(name, kwargs), text: text, wait: wait, class: klass) + if disabled.nil? + wait = kwargs.delete(:wait) || Capybara.default_max_wait_time + text = kwargs.delete(:text) + klass = kwargs.delete(:class) + + has_css?(element_selector_css(name, kwargs), text: text, wait: wait, class: klass) + else + find_element(name, kwargs).disabled? == disabled + end end def has_no_element?(name, **kwargs) diff --git a/qa/qa/page/component/issue_board/show.rb b/qa/qa/page/component/issue_board/show.rb new file mode 100644 index 00000000000..0c840eba7ce --- /dev/null +++ b/qa/qa/page/component/issue_board/show.rb @@ -0,0 +1,138 @@ +# frozen_string_literal: true + +module QA + module Page + module Component + module IssueBoard + class Show < QA::Page::Base + view 'app/assets/javascripts/boards/components/board_card.vue' do + element :board_card + end + + view 'app/assets/javascripts/boards/components/board_form.vue' do + element :board_name_field + end + + view 'app/assets/javascripts/boards/components/board_list.vue' do + element :board_list_cards_area + end + + view 'app/assets/javascripts/boards/components/boards_selector.vue' do + element :boards_dropdown + element :boards_dropdown_content + element :create_new_board_button + end + + view 'app/assets/javascripts/vue_shared/components/deprecated_modal.vue' do + element :save_changes_button + end + + view 'app/assets/javascripts/vue_shared/components/sidebar/labels_select/base.vue' do + element :labels_dropdown_content + end + + view 'app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_title.vue' do + element :labels_edit_button + end + + view 'app/views/shared/boards/_show.html.haml' do + element :boards_list + end + + view 'app/views/shared/boards/components/_board.html.haml' do + element :board_list + element :board_list_header + end + + view 'app/assets/javascripts/boards/toggle_focus.js' do + element :focus_mode_button + end + + # The `focused_board` method does not use `find_element` with an element defined + # with the attribute `data-qa-selector` since such element is not unique when the + # `is-focused` class is not set, and it was not possible to find a better solution. + def focused_board + find('.issue-boards-content.js-focus-mode-board.is-focused') + end + + def boards_dropdown + find_element(:boards_dropdown) + end + + def boards_dropdown_content + find_element(:boards_dropdown_content) + end + + def boards_list_cards_area_with_index(index) + wait_boards_list_finish_loading do + within_element_by_index(:board_list, index) do + find_element(:board_list_cards_area) + end + end + end + + def boards_list_header_with_index(index) + wait_boards_list_finish_loading do + within_element_by_index(:board_list, index) do + find_element(:board_list_header) + end + end + end + + def card_of_list_with_index(index) + wait_boards_list_finish_loading do + within_element_by_index(:board_list, index) do + find_element(:board_card) + end + end + end + + def click_boards_dropdown_button + # The dropdown button comes from the `GlDropdown` component of `@gitlab/ui`, + # so it wasn't possible to add a `data-qa-selector` to it. + find_element(:boards_dropdown).find('button').click + end + + def click_focus_mode_button + click_element(:focus_mode_button) + end + + def configure_by_label(label) + click_boards_config_button + click_element(:labels_edit_button) + find_element(:labels_dropdown_content).find('li', text: label).click + click_element(:save_changes_button) + wait_boards_list_finish_loading + end + + def create_new_board(board_name) + click_boards_dropdown_button + click_element(:create_new_board_button) + set_name(board_name) + end + + def has_modal_board_name_field? + has_element?(:board_name_field, wait: 1) + end + + def set_name(name) + find_element(:board_name_field).set(name) + click_element(:save_changes_button) + end + + private + + def wait_boards_list_finish_loading + within_element(:boards_list) do + wait_until(reload: false, max_duration: 5, sleep_interval: 1) do + finished_loading? && (block_given? ? yield : true) + end + end + end + end + end + end + end +end + +QA::Page::Component::IssueBoard::Show.prepend_if_ee('QA::EE::Page::Component::IssueBoard::Show') diff --git a/qa/qa/page/component/web_ide/modal/create_new_file.rb b/qa/qa/page/component/web_ide/modal/create_new_file.rb new file mode 100644 index 00000000000..48eb32fefd6 --- /dev/null +++ b/qa/qa/page/component/web_ide/modal/create_new_file.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module QA + module Page + module Component + module WebIDE + module Modal + class CreateNewFile < Page::Base + view 'app/assets/javascripts/ide/components/new_dropdown/modal.vue' do + element :file_name_field, required: true + element :new_file_modal, required: true + element :template_list, required: true + end + end + end + end + end + end +end diff --git a/qa/qa/page/dashboard/snippet/new.rb b/qa/qa/page/dashboard/snippet/new.rb index d1a194ba1db..bcfb4734b59 100644 --- a/qa/qa/page/dashboard/snippet/new.rb +++ b/qa/qa/page/dashboard/snippet/new.rb @@ -6,6 +6,7 @@ module QA module Snippet class New < Page::Base view 'app/assets/javascripts/snippets/components/edit.vue' do + element :snippet_title_field, required: true element :submit_button end @@ -14,12 +15,8 @@ module QA element :description_placeholder, required: true end - view 'app/assets/javascripts/snippets/components/snippet_title.vue' do - element :snippet_title, required: true - end - view 'app/assets/javascripts/snippets/components/snippet_blob_edit.vue' do - element :snippet_file_name + element :file_name_field end view 'app/views/shared/form_elements/_description.html.haml' do @@ -29,18 +26,18 @@ module QA view 'app/views/shared/snippets/_form.html.haml' do element :snippet_description_field element :description_placeholder - element :snippet_title - element :snippet_file_name + element :snippet_title_field + element :file_name_field element :submit_button end - view 'app/views/projects/_zen.html.haml' do + view 'app/views/shared/_zen.html.haml' do # This 'element' is here only to ensure the changes in the view source aren't mistakenly changed element :_, "qa_selector = local_assigns.fetch(:qa_selector, '')" # rubocop:disable QA/ElementWithPattern end def fill_title(title) - fill_element :snippet_title, title + fill_element :snippet_title_field, title end def fill_description(description) @@ -54,7 +51,7 @@ module QA def fill_file_name(name) finished_loading? - fill_element :snippet_file_name, name + fill_element :file_name_field, name end def fill_file_content(content) diff --git a/qa/qa/page/dashboard/snippet/show.rb b/qa/qa/page/dashboard/snippet/show.rb index d43b64cd1d4..da494ae70ec 100644 --- a/qa/qa/page/dashboard/snippet/show.rb +++ b/qa/qa/page/dashboard/snippet/show.rb @@ -6,32 +6,26 @@ module QA module Snippet class Show < Page::Base view 'app/assets/javascripts/snippets/components/snippet_description_view.vue' do - element :snippet_description_field + element :snippet_description_content end view 'app/assets/javascripts/snippets/components/snippet_title.vue' do - element :snippet_title, required: true + element :snippet_title_content, required: true end - view 'app/views/shared/snippets/_header.html.haml' do - element :snippet_title, required: true - element :snippet_description_field, required: true - element :snippet_box - end - - view 'app/views/projects/blob/_header_content.html.haml' do - element :file_title_name + view 'app/assets/javascripts/snippets/components/snippet_header.vue' do + element :snippet_container end view 'app/assets/javascripts/blob/components/blob_header_filepath.vue' do - element :file_title_name + element :file_title_content end - view 'app/views/shared/_file_highlight.html.haml' do + view 'app/assets/javascripts/vue_shared/components/blob_viewers/simple_viewer.vue' do element :file_content end - view 'app/assets/javascripts/vue_shared/components/blob_viewers/simple_viewer.vue' do + view 'app/assets/javascripts/blob/components/blob_content.vue' do element :file_content end @@ -50,21 +44,25 @@ module QA end def has_snippet_title?(snippet_title) - has_element? :snippet_title, text: snippet_title + has_element? :snippet_title_content, text: snippet_title end def has_snippet_description?(snippet_description) - has_element? :snippet_description_field, text: snippet_description + has_element? :snippet_description_content, text: snippet_description + end + + def has_no_snippet_description? + has_no_element?(:snippet_description_field) end def has_visibility_type?(visibility_type) - within_element(:snippet_box) do + within_element(:snippet_container) do has_text?(visibility_type) end end def has_file_name?(file_name) - within_element(:file_title_name) do + within_element(:file_title_content) do has_text?(file_name) end end @@ -85,7 +83,11 @@ module QA finished_loading? click_element(:snippet_action_button, action: 'Delete') click_element(:delete_snippet_button) - finished_loading? # wait for the page to reload after deletion + # wait for the page to reload after deletion + wait_until(reload: false) do + has_no_element?(:delete_snippet_button) && + has_no_element?(:snippet_action_button, action: 'Delete') + end end def get_repository_uri_http diff --git a/qa/qa/page/group/new.rb b/qa/qa/page/group/new.rb index e01b8754d55..88e7121dbe0 100644 --- a/qa/qa/page/group/new.rb +++ b/qa/qa/page/group/new.rb @@ -10,7 +10,7 @@ module QA element :group_description_field, 'text_area :description' # rubocop:disable QA/ElementWithPattern end - view 'app/views/groups/new.html.haml' do + view 'app/views/groups/_new_group_fields.html.haml' do element :create_group_button, "submit _('Create group')" # rubocop:disable QA/ElementWithPattern element :visibility_radios, 'visibility_level:' # rubocop:disable QA/ElementWithPattern end diff --git a/qa/qa/page/issuable/new.rb b/qa/qa/page/issuable/new.rb new file mode 100644 index 00000000000..7891074092e --- /dev/null +++ b/qa/qa/page/issuable/new.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +module QA + module Page + module Issuable + class New < Page::Base + view 'app/views/shared/issuable/form/_title.html.haml' do + element :issuable_form_title + end + + view 'app/views/shared/issuable/form/_metadata.html.haml' do + element :issuable_milestone_dropdown + end + + view 'app/views/shared/form_elements/_description.html.haml' do + element :issuable_form_description + end + + view 'app/views/shared/issuable/_milestone_dropdown.html.haml' do + element :issuable_dropdown_menu_milestone + end + + view 'app/views/shared/issuable/_label_dropdown.html.haml' do + element :issuable_label + end + + view 'app/views/shared/issuable/form/_metadata_issuable_assignee.html.haml' do + element :assign_to_me_link + end + + def fill_title(title) + fill_element :issuable_form_title, title + end + + def fill_description(description) + fill_element :issuable_form_description, description + end + + def choose_milestone(milestone) + click_element :issuable_milestone_dropdown + within_element(:issuable_dropdown_menu_milestone) do + click_on milestone.title + end + end + + def select_label(label) + click_element :issuable_label + + click_link label.title + end + + def assign_to_me + click_element :assign_to_me_link + end + end + end + end +end diff --git a/qa/qa/page/merge_request/new.rb b/qa/qa/page/merge_request/new.rb index f877ba76b38..eda7da89a35 100644 --- a/qa/qa/page/merge_request/new.rb +++ b/qa/qa/page/merge_request/new.rb @@ -3,62 +3,13 @@ module QA module Page module MergeRequest - class New < Page::Base + class New < Page::Issuable::New view 'app/views/shared/issuable/_form.html.haml' do element :issuable_create_button end - view 'app/views/shared/issuable/form/_title.html.haml' do - element :issuable_form_title - end - - view 'app/views/shared/issuable/form/_metadata.html.haml' do - element :issuable_milestone_dropdown - end - - view 'app/views/shared/form_elements/_description.html.haml' do - element :issuable_form_description - end - - view 'app/views/shared/issuable/_milestone_dropdown.html.haml' do - element :issuable_dropdown_menu_milestone - end - - view 'app/views/shared/issuable/_label_dropdown.html.haml' do - element :issuable_label - end - - view 'app/views/shared/issuable/form/_metadata_issuable_assignee.html.haml' do - element :assign_to_me_link - end - def create_merge_request - click_element :issuable_create_button - end - - def fill_title(title) - fill_element :issuable_form_title, title - end - - def fill_description(description) - fill_element :issuable_form_description, description - end - - def choose_milestone(milestone) - click_element :issuable_milestone_dropdown - within_element(:issuable_dropdown_menu_milestone) do - click_on milestone.title - end - end - - def select_label(label) - click_element :issuable_label - - click_link label.title - end - - def assign_to_me - click_element :assign_to_me_link + click_element :issuable_create_button, Page::MergeRequest::Show end end end diff --git a/qa/qa/page/merge_request/show.rb b/qa/qa/page/merge_request/show.rb index 0da40b35938..07f8e568b2a 100644 --- a/qa/qa/page/merge_request/show.rb +++ b/qa/qa/page/merge_request/show.rb @@ -73,6 +73,70 @@ module QA element :edit_button end + view 'app/assets/javascripts/batch_comments/components/publish_button.vue' do + element :submit_review + end + + view 'app/assets/javascripts/batch_comments/components/review_bar.vue' do + element :review_bar + element :discard_review + element :modal_delete_pending_comments + end + + view 'app/assets/javascripts/notes/components/note_form.vue' do + element :unresolve_review_discussion_checkbox + element :resolve_review_discussion_checkbox + element :start_review + element :comment_now + end + + view 'app/assets/javascripts/batch_comments/components/preview_dropdown.vue' do + element :review_preview_toggle + end + + def start_review + click_element :start_review + + # After clicking the button, wait for it to disappear + # before moving on to the next part of the test + has_no_element? :start_review + end + + def comment_now + click_element :comment_now + + # After clicking the button, wait for it to disappear + # before moving on to the next part of the test + has_no_element? :comment_now + end + + def submit_pending_reviews + within_element :review_bar do + click_element :review_preview_toggle + click_element :submit_review + + # After clicking the button, wait for it to disappear + # before moving on to the next part of the test + has_no_element? :submit_review + end + end + + def discard_pending_reviews + within_element :review_bar do + click_element :discard_review + end + click_element :modal_delete_pending_comments + end + + def resolve_review_discussion + scroll_to_element :start_review + check_element :resolve_review_discussion_checkbox + end + + def unresolve_review_discussion + check_element :unresolve_review_discussion_checkbox + end + def add_comment_to_diff(text) wait_until(sleep_interval: 5) do has_text?("No newline at end of file") @@ -154,8 +218,8 @@ module QA end def merge! - click_element :merge_button if ready_to_merge? - + wait_until_ready_to_merge + click_element(:merge_button) finished_loading? raise "Merge did not appear to be successful" unless merged? @@ -165,11 +229,18 @@ module QA has_element?(:merged_status_content, text: 'The changes were merged into', wait: 30) end - def ready_to_merge? - # The merge button is disabled on load - wait_until do - has_element?(:merge_button) - end + # Check if the MR is able to be merged + # Waits up 10 seconds and returns false if the MR can't be merged + def mergeable? + # The merge button is enabled via JS, but `has_element?` calls + # `wait_for_requests`, which should ensure the disabled/enabled + # state of the element is reliable + has_element?(:merge_button, disabled: false) + end + + # Waits up 60 seconds and raises an error if unable to merge + def wait_until_ready_to_merge + has_element?(:merge_button) # The merge button is enabled via JS wait_until(reload: false) do @@ -198,7 +269,9 @@ module QA end def try_to_merge! - click_element :merge_button if ready_to_merge? + wait_until_ready_to_merge + + click_element(:merge_button) end def view_email_patches diff --git a/qa/qa/page/project/fork/new.rb b/qa/qa/page/project/fork/new.rb index 1a52c61551d..49c2205fd08 100644 --- a/qa/qa/page/project/fork/new.rb +++ b/qa/qa/page/project/fork/new.rb @@ -6,11 +6,11 @@ module QA module Fork class New < Page::Base view 'app/views/projects/forks/_fork_button.html.haml' do - element :namespace, 'link_to project_forks_path' # rubocop:disable QA/ElementWithPattern + element :fork_namespace_content end def choose_namespace(namespace = Runtime::Namespace.path) - click_on namespace + click_element(:fork_namespace_content, name: namespace) end end end diff --git a/qa/qa/page/project/issue/new.rb b/qa/qa/page/project/issue/new.rb index 65c02801d67..c90a09dce2e 100644 --- a/qa/qa/page/project/issue/new.rb +++ b/qa/qa/page/project/issue/new.rb @@ -4,27 +4,11 @@ module QA module Page module Project module Issue - class New < Page::Base + class New < Page::Issuable::New view 'app/views/shared/issuable/_form.html.haml' do element :issuable_create_button end - view 'app/views/shared/issuable/form/_title.html.haml' do - element :issue_title_textbox, 'form.text_field :title' # rubocop:disable QA/ElementWithPattern - end - - view 'app/views/shared/form_elements/_description.html.haml' do - element :issue_description_textarea, "render 'projects/zen', f: form, attr: :description" # rubocop:disable QA/ElementWithPattern - end - - def add_title(title) - fill_in 'issue_title', with: title - end - - def add_description(description) - fill_in 'issue_description', with: description - end - def create_new_issue click_element :issuable_create_button, Page::Project::Issue::Show end diff --git a/qa/qa/page/project/members.rb b/qa/qa/page/project/members.rb new file mode 100644 index 00000000000..88b05ceb1d1 --- /dev/null +++ b/qa/qa/page/project/members.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +module QA + module Page + module Project + class Members < Page::Base + include QA::Page::Component::Select2 + + view 'app/views/shared/members/_invite_member.html.haml' do + element :member_select_field + element :invite_member_button + end + + view 'app/views/projects/project_members/_team.html.haml' do + element :members_list + end + + view 'app/views/projects/project_members/index.html.haml' do + element :invite_group_tab + end + + view 'app/views/shared/members/_invite_group.html.haml' do + element :group_select_field + element :invite_group_button + end + + view 'app/views/shared/members/_group.html.haml' do + element :group_row + element :delete_group_access_link + end + + def select_group(group_name) + click_element :group_select_field + search_and_select(group_name) + end + + def invite_group(group_name) + click_element :invite_group_tab + select_group(group_name) + click_element :invite_group_button + end + + def add_member(username) + click_element :member_select_field + search_and_select username + click_element :invite_member_button + end + + def remove_group(group_name) + click_element :invite_group_tab + page.accept_alert do + within_element(:group_row, text: group_name) do + click_element :delete_group_access_link + end + end + end + end + end + end +end diff --git a/qa/qa/page/project/menu.rb b/qa/qa/page/project/menu.rb index 5967213a52b..3d4d0ff9d22 100644 --- a/qa/qa/page/project/menu.rb +++ b/qa/qa/page/project/menu.rb @@ -16,6 +16,8 @@ module QA element :activity_link element :merge_requests_link element :wiki_link + element :snippets_link + element :members_link end def click_merge_requests @@ -35,6 +37,18 @@ module QA click_element(:activity_link) end end + + def click_snippets + within_sidebar do + click_element(:snippets_link) + end + end + + def click_members + within_sidebar do + click_element(:members_link) + end + end end end end diff --git a/qa/qa/page/project/new_experiment.rb b/qa/qa/page/project/new_experiment.rb new file mode 100644 index 00000000000..813f7f6cefe --- /dev/null +++ b/qa/qa/page/project/new_experiment.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +module QA + module Page + module Project + class NewExperiment < Page::Base + view 'app/assets/javascripts/projects/experiment_new_project_creation/components/welcome.vue' do + element :blank_project_link, ':data-qa-selector="`${panel.name}_link`"' # rubocop:disable QA/ElementWithPattern + element :create_from_template_link, ':data-qa-selector="`${panel.name}_link`"' # rubocop:disable QA/ElementWithPattern + end + + def shown? + has_element? :blank_project_link + end + + def click_blank_project_link + click_element :blank_project_link + end + + def click_create_from_template_link + click_element :create_from_template_link + end + end + end + end +end diff --git a/qa/qa/page/project/operations/metrics/show.rb b/qa/qa/page/project/operations/metrics/show.rb index 2228cca1d3d..a1c15e72f44 100644 --- a/qa/qa/page/project/operations/metrics/show.rb +++ b/qa/qa/page/project/operations/metrics/show.rb @@ -11,6 +11,9 @@ module QA view 'app/assets/javascripts/monitoring/components/dashboard.vue' do element :prometheus_graphs + end + + view 'app/assets/javascripts/monitoring/components/dashboard_header.vue' do element :dashboards_filter_dropdown element :environments_dropdown element :edit_dashboard_button diff --git a/qa/qa/page/project/settings/advanced.rb b/qa/qa/page/project/settings/advanced.rb index 3bb5181a31c..d6e004e827e 100644 --- a/qa/qa/page/project/settings/advanced.rb +++ b/qa/qa/page/project/settings/advanced.rb @@ -43,7 +43,9 @@ module QA def transfer_project!(project_name, namespace) expand_select_list - select_transfer_option(namespace) + # Workaround for a failure to search when there are no spaces around the / + # https://gitlab.com/gitlab-org/gitlab/-/issues/218965 + select_transfer_option(namespace.gsub(/([^\s])\/([^\s])/, '\1 / \2')) click_element(:transfer_button) fill_confirmation_text(project_name) click_confirm_button diff --git a/qa/qa/page/project/settings/ci_cd.rb b/qa/qa/page/project/settings/ci_cd.rb index aa27c030b78..a7a0f6f57b6 100644 --- a/qa/qa/page/project/settings/ci_cd.rb +++ b/qa/qa/page/project/settings/ci_cd.rb @@ -42,5 +42,3 @@ module QA end end end - -QA::Page::Project::Settings::CICD.prepend_if_ee('QA::EE::Page::Project::Settings::CICD') diff --git a/qa/qa/page/project/settings/integrations.rb b/qa/qa/page/project/settings/integrations.rb index 436a42fb093..e18ff71bcb3 100644 --- a/qa/qa/page/project/settings/integrations.rb +++ b/qa/qa/page/project/settings/integrations.rb @@ -7,13 +7,20 @@ module QA class Integrations < QA::Page::Base view 'app/views/shared/integrations/_index.html.haml' do element :prometheus_link, '{ data: { qa_selector: "#{integration.to_param' # rubocop:disable QA/ElementWithPattern + element :jira_link, '{ data: { qa_selector: "#{integration.to_param' # rubocop:disable QA/ElementWithPattern end def click_on_prometheus_integration click_element :prometheus_link end + + def click_jira_link + click_element :jira_link + end end end end end end + +QA::Page::Project::Settings::Integrations.prepend_if_ee('QA::EE::Page::Project::Settings::Integrations') diff --git a/qa/qa/page/project/settings/main.rb b/qa/qa/page/project/settings/main.rb index efae497b6ba..880711770c0 100644 --- a/qa/qa/page/project/settings/main.rb +++ b/qa/qa/page/project/settings/main.rb @@ -58,3 +58,5 @@ module QA end end end + +QA::Page::Project::Settings::Main.prepend_if_ee("QA::EE::Page::Project::Settings::Main") diff --git a/qa/qa/page/project/settings/members.rb b/qa/qa/page/project/settings/members.rb deleted file mode 100644 index 5dc873750b0..00000000000 --- a/qa/qa/page/project/settings/members.rb +++ /dev/null @@ -1,62 +0,0 @@ -# frozen_string_literal: true - -module QA - module Page - module Project - module Settings - class Members < Page::Base - include QA::Page::Component::Select2 - - view 'app/views/shared/members/_invite_member.html.haml' do - element :member_select_field - element :invite_member_button - end - - view 'app/views/projects/project_members/_team.html.haml' do - element :members_list - end - - view 'app/views/projects/project_members/index.html.haml' do - element :invite_group_tab - end - - view 'app/views/shared/members/_invite_group.html.haml' do - element :group_select_field - element :invite_group_button - end - - view 'app/views/shared/members/_group.html.haml' do - element :group_row - element :delete_group_access_link - end - - def select_group(group_name) - click_element :group_select_field - search_and_select(group_name) - end - - def invite_group(group_name) - click_element :invite_group_tab - select_group(group_name) - click_element :invite_group_button - end - - def add_member(username) - click_element :member_select_field - search_and_select username - click_element :invite_member_button - end - - def remove_group(group_name) - click_element :invite_group_tab - page.accept_alert do - within_element(:group_row, text: group_name) do - click_element :delete_group_access_link - end - end - end - end - end - end - end -end diff --git a/qa/qa/page/project/settings/services/jira.rb b/qa/qa/page/project/settings/services/jira.rb new file mode 100644 index 00000000000..9f9331bac94 --- /dev/null +++ b/qa/qa/page/project/settings/services/jira.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +module QA + module Page + module Project + module Settings + module Services + class Jira < QA::Page::Base + view 'app/views/shared/_field.html.haml' do + element :url_field, 'data: { qa_selector: "#{name.downcase.gsub' # rubocop:disable QA/ElementWithPattern + element :username_field, 'data: { qa_selector: "#{name.downcase.gsub' # rubocop:disable QA/ElementWithPattern + element :password_field, 'data: { qa_selector: "#{name.downcase.gsub' # rubocop:disable QA/ElementWithPattern + element :jira_issue_transition_id_field, 'data: { qa_selector: "#{name.downcase.gsub' # rubocop:disable QA/ElementWithPattern + end + + view 'app/helpers/services_helper.rb' do + element :save_changes_button + end + + def setup_service_with(url:) + QA::Runtime::Logger.info "Setting up JIRA" + + set_jira_server_url(url) + set_username(Runtime::Env.jira_admin_username) + set_password(Runtime::Env.jira_admin_password) + set_transaction_ids('11,21,31,41') + + click_save_changes_button + wait_until(reload: false) do + has_element?(:save_changes_button, wait: 1) ? !find_element(:save_changes_button).disabled? : true + end + end + + private + + def set_jira_server_url(url) + fill_element(:url_field, url) + end + + def set_username(username) + fill_element(:username_field, username) + end + + def set_password(password) + fill_element(:password_field, password) + end + + def set_transaction_ids(transaction_ids) + fill_element(:jira_issue_transition_id_field, transaction_ids) + end + + def click_save_changes_button + click_element :save_changes_button + end + end + end + end + end + end +end diff --git a/qa/qa/page/project/snippet/new.rb b/qa/qa/page/project/snippet/new.rb new file mode 100644 index 00000000000..1463dfc2c7f --- /dev/null +++ b/qa/qa/page/project/snippet/new.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +module QA + module Page + module Project + module Snippet + class New < Page::Dashboard::Snippet::New + include Component::LazyLoader + view 'app/views/shared/empty_states/_snippets.html.haml' do + element :create_first_snippet_link + element :svg_content + end + + def click_create_first_snippet + finished_loading? + # The svg takes a fraction of a second to load after which the + # "New snippet" button shifts up a bit. This can cause + # webdriver to miss the hit so we wait for the svg to load before + # clicking the button. + within_element(:svg_content) do + has_element?(:js_lazy_loaded) + end + click_element(:create_first_snippet_link) + end + end + end + end + end +end diff --git a/qa/qa/page/project/sub_menus/settings.rb b/qa/qa/page/project/sub_menus/settings.rb index 0dd4bd1817a..47274c8db54 100644 --- a/qa/qa/page/project/sub_menus/settings.rb +++ b/qa/qa/page/project/sub_menus/settings.rb @@ -15,7 +15,6 @@ module QA view 'app/views/layouts/nav/sidebar/_project.html.haml' do element :settings_item - element :link_members_settings element :general_settings_link element :integrations_settings_link element :operations_settings_link @@ -31,14 +30,6 @@ module QA end end - def go_to_members_settings - hover_settings do - within_submenu do - click_element :link_members_settings - end - end - end - def go_to_repository_settings hover_settings do within_submenu do diff --git a/qa/qa/page/project/web_ide/edit.rb b/qa/qa/page/project/web_ide/edit.rb index 7809f9246ec..29f431d81df 100644 --- a/qa/qa/page/project/web_ide/edit.rb +++ b/qa/qa/page/project/web_ide/edit.rb @@ -20,12 +20,6 @@ module QA element :file_list end - view 'app/assets/javascripts/ide/components/new_dropdown/modal.vue' do - element :full_file_path - element :new_file_modal - element :template_list - end - view 'app/assets/javascripts/ide/components/file_templates/bar.vue' do element :file_templates_bar element :file_template_dropdown @@ -52,6 +46,10 @@ module QA element :editor_container end + view 'app/assets/javascripts/ide/components/ide.vue' do + element :first_file_button + end + def has_file?(file_name) within_element(:file_list) do page.has_content? file_name @@ -59,10 +57,7 @@ module QA end def create_new_file_from_template(file_name, template) - click_element :new_file - - # Wait for the modal animation to complete before clicking on the file name - wait_for_animated_element(:new_file_modal) + click_element(:new_file, Page::Component::WebIDE::Modal::CreateNewFile) within_element(:template_list) do click_on file_name @@ -130,6 +125,13 @@ module QA find('.modified textarea.inputarea') end end + + def create_first_file(file_name) + finished_loading? + click_element(:first_file_button, Page::Component::WebIDE::Modal::CreateNewFile) + fill_element(:file_name_field, file_name) + click_button('Create file') + end end end end diff --git a/qa/qa/page/project/wiki/edit.rb b/qa/qa/page/project/wiki/edit.rb index f6edc28c41a..96301e33733 100644 --- a/qa/qa/page/project/wiki/edit.rb +++ b/qa/qa/page/project/wiki/edit.rb @@ -5,14 +5,32 @@ module QA module Project module Wiki class Edit < Page::Base - view 'app/views/projects/wikis/_main_links.html.haml' do - element :new_page_link, 'New page' # rubocop:disable QA/ElementWithPattern - element :page_history_link, 'Page history' # rubocop:disable QA/ElementWithPattern - element :edit_page_link, 'Edit' # rubocop:disable QA/ElementWithPattern + view 'app/views/shared/wikis/_form.html.haml' do + element :wiki_title_textbox + element :wiki_content_textarea + element :wiki_message_textbox + element :save_changes_button + element :create_page_button end - def click_edit - click_on 'Edit' + def set_title(title) + fill_element :wiki_title_textbox, title + end + + def set_content(content) + fill_element :wiki_content_textarea, content + end + + def set_message(message) + fill_element :wiki_message_textbox, message + end + + def click_save_changes + click_element :save_changes_button + end + + def click_create_page + click_element :create_page_button end end end diff --git a/qa/qa/page/project/wiki/new.rb b/qa/qa/page/project/wiki/new.rb deleted file mode 100644 index 792eba4bab7..00000000000 --- a/qa/qa/page/project/wiki/new.rb +++ /dev/null @@ -1,61 +0,0 @@ -# frozen_string_literal: true - -module QA - module Page - module Project - module Wiki - class New < Page::Base - include Component::LazyLoader - - view 'app/views/projects/wikis/_form.html.haml' do - element :wiki_title_textbox - element :wiki_content_textarea - element :wiki_message_textbox - element :save_changes_button - element :create_page_button - end - - view 'app/views/shared/empty_states/_wikis.html.haml' do - element :create_first_page_link - end - - view 'app/views/shared/empty_states/_wikis_layout.html.haml' do - element :svg_content - end - - def click_create_your_first_page_button - # The svg takes a fraction of a second to load after which the - # "Create your first page" button shifts up a bit. This can cause - # webdriver to miss the hit so we wait for the svg to load before - # clicking the button. - within_element(:svg_content) do - has_element? :js_lazy_loaded - end - - click_element :create_first_page_link - end - - def set_title(title) - fill_element :wiki_title_textbox, title - end - - def set_content(content) - fill_element :wiki_content_textarea, content - end - - def set_message(message) - fill_element :wiki_message_textbox, message - end - - def save_changes - click_element :save_changes_button - end - - def create_new_page - click_element :create_page_button - end - end - end - end - end -end diff --git a/qa/qa/page/project/wiki/show.rb b/qa/qa/page/project/wiki/show.rb index 44619d177b1..7e4e714f4a6 100644 --- a/qa/qa/page/project/wiki/show.rb +++ b/qa/qa/page/project/wiki/show.rb @@ -5,23 +5,70 @@ module QA module Project module Wiki class Show < Page::Base - include Page::Component::LegacyClonePanel + include Component::LazyLoader - view 'app/views/projects/wikis/pages.html.haml' do - element :clone_repository_link, 'Clone repository' # rubocop:disable QA/ElementWithPattern + view 'app/views/shared/wikis/_sidebar.html.haml' do + element :clone_repository_link end - view 'app/views/projects/wikis/show.html.haml' do + view 'app/views/shared/wikis/show.html.haml' do + element :wiki_page_title element :wiki_page_content end + view 'app/views/shared/wikis/_main_links.html.haml' do + element :new_page_button + element :page_history_button + element :edit_page_button + end + + view 'app/views/shared/empty_states/_wikis.html.haml' do + element :create_first_page_link + end + + view 'app/views/shared/empty_states/_wikis_layout.html.haml' do + element :svg_content + end + + def click_create_your_first_page + # The svg takes a fraction of a second to load after which the + # "Create your first page" button shifts up a bit. This can cause + # webdriver to miss the hit so we wait for the svg to load before + # clicking the button. + within_element(:svg_content) do + has_element? :js_lazy_loaded + end + + click_element :create_first_page_link + end + + def click_new_page + click_element(:new_page_button) + end + + def click_page_history + click_element(:page_history_button) + end + + def click_edit + click_element(:edit_page_button) + end + def click_clone_repository - click_on 'Clone repository' + click_element(:clone_repository_link) end def wiki_text find_element(:wiki_page_content).text end + + def has_title?(title) + has_element?(:wiki_page_title, title) + end + + def has_content?(content) + has_element?(:wiki_page_content, content) + end end end end diff --git a/qa/qa/resource/group.rb b/qa/qa/resource/group.rb index a30bb8cbc77..850d6205305 100644 --- a/qa/qa/resource/group.rb +++ b/qa/qa/resource/group.rb @@ -14,6 +14,7 @@ module QA end end + attribute :full_path attribute :id attribute :name attribute :runners_token @@ -74,10 +75,6 @@ module QA def api_delete_path "/groups/#{id}" 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 0817a9de06f..b4295a35263 100644 --- a/qa/qa/resource/issue.rb +++ b/qa/qa/resource/issue.rb @@ -32,8 +32,8 @@ module QA Page::Project::Show.perform(&:go_to_new_issue) Page::Project::Issue::New.perform do |new_page| - new_page.add_title(@title) - new_page.add_description(@description) + new_page.fill_title(@title) + new_page.fill_description(@description) new_page.create_new_issue end end diff --git a/qa/qa/resource/project.rb b/qa/qa/resource/project.rb index 78e2ba8a248..645f4e97ee0 100644 --- a/qa/qa/resource/project.rb +++ b/qa/qa/resource/project.rb @@ -30,6 +30,8 @@ module QA "#{sandbox_path}#{group.path}/#{name}" if group end + alias_method :full_path, :path_with_namespace + def sandbox_path group.respond_to?('sandbox') ? "#{group.sandbox.path}/" : '' end @@ -54,6 +56,8 @@ module QA @auto_devops_enabled = false @visibility = :public @template_name = nil + + self.name = "the_awesome_project" end def name=(raw_name) @@ -67,12 +71,14 @@ module QA end if @template_name + QA::Flow::Project.go_to_create_project_from_template Page::Project::New.perform do |new_page| - new_page.click_create_from_template_tab new_page.use_template_for_project(@template_name) end end + Page::Project::NewExperiment.perform(&:click_blank_project_link) if Page::Project::NewExperiment.perform(&:shown?) + Page::Project::New.perform do |new_page| new_page.choose_test_namespace new_page.choose_name(@name) @@ -89,6 +95,10 @@ module QA super end + def has_file?(file_path) + repository_tree.any? { |file| file[:path] == file_path } + end + def api_get_path "/projects/#{CGI.escape(path_with_namespace)}" end @@ -109,6 +119,14 @@ module QA "#{api_get_path}/runners" end + def api_repository_branches_path + "#{api_get_path}/repository/branches" + end + + def api_repository_tree_path + "#{api_get_path}/repository/tree" + end + def api_pipelines_path "#{api_get_path}/pipelines" end @@ -149,11 +167,9 @@ module QA raise ResourceUpdateFailedError, "Could not change repository storage to #{new_storage}. Request returned (#{response.code}): `#{response}`." end - wait_until do - reload! - - api_response[:repository_storage] == new_storage - end + wait_until(sleep_interval: 1) { Runtime::API::RepositoryStorageMoves.has_status?(self, 'finished', new_storage) } + rescue Support::Repeater::RepeaterConditionExceededError + raise Runtime::API::RepositoryStorageMoves::RepositoryStorageMovesError, 'Timed out while waiting for the repository storage move to finish' end def import_status @@ -180,6 +196,14 @@ module QA parse_body(response) end + def repository_branches + parse_body(get(Runtime::API::Request.new(api_client, api_repository_branches_path).url)) + end + + def repository_tree + parse_body(get(Runtime::API::Request.new(api_client, api_repository_tree_path).url)) + end + def pipelines parse_body(get(Runtime::API::Request.new(api_client, api_pipelines_path).url)) end diff --git a/qa/qa/resource/project_milestone.rb b/qa/qa/resource/project_milestone.rb index 4d6b37937b4..385b9f0c96b 100644 --- a/qa/qa/resource/project_milestone.rb +++ b/qa/qa/resource/project_milestone.rb @@ -3,6 +3,8 @@ module QA module Resource class ProjectMilestone < Base + attr_writer :start_date, :due_date + attribute :id attribute :title @@ -27,7 +29,10 @@ module QA def api_post_body { title: title - } + }.tap do |hash| + hash[:start_date] = @start_date if @start_date + hash[:due_date] = @due_date if @due_date + end end end end diff --git a/qa/qa/resource/project_snippet.rb b/qa/qa/resource/project_snippet.rb new file mode 100644 index 00000000000..ce4be6445f1 --- /dev/null +++ b/qa/qa/resource/project_snippet.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +module QA + module Resource + class ProjectSnippet < Snippet + attribute :project do + Project.fabricate_via_api! do |resource| + resource.name = 'project-with-snippets' + end + end + + def fabricate! + project.visit! + + Page::Project::Menu.perform { |sidebar| sidebar.click_snippets } + + Page::Project::Snippet::New.perform do |new_snippet| + new_snippet.click_create_first_snippet + new_snippet.fill_title(@title) + new_snippet.fill_description(@description) + new_snippet.set_visibility(@visibility) + new_snippet.fill_file_name(@file_name) + new_snippet.fill_file_content(@file_content) + new_snippet.click_create_snippet_button + end + end + end + end +end diff --git a/qa/qa/resource/protected_branch.rb b/qa/qa/resource/protected_branch.rb index 9c65e0e5a31..9b728fc4c24 100644 --- a/qa/qa/resource/protected_branch.rb +++ b/qa/qa/resource/protected_branch.rb @@ -5,7 +5,12 @@ require 'securerandom' module QA module Resource class ProtectedBranch < Base - attr_accessor :branch_name, :allowed_to_push, :allowed_to_merge, :protected + attr_accessor :branch_name, + :allowed_to_push, + :allowed_to_merge, + :protected, + :new_branch, + :require_code_owner_approval attribute :project do Project.fabricate_via_api! do |resource| @@ -21,11 +26,12 @@ module QA project_push.commit_message = 'Add new file' project_push.branch_name = branch_name project_push.new_branch = true - project_push.remote_branch = @branch_name + project_push.remote_branch = branch_name end end def initialize + @new_branch = true @branch_name = 'test/branch' @allowed_to_push = { roles: Resource::ProtectedBranch::Roles::DEVS_AND_MAINTAINERS @@ -34,22 +40,29 @@ module QA roles: Resource::ProtectedBranch::Roles::DEVS_AND_MAINTAINERS } @protected = false + @require_code_owner_approval = true end def fabricate! - populate(:branch) + if new_branch + populate(:branch) - project.wait_for_push_new_branch @branch_name + project.wait_for_push_new_branch branch_name + end project.visit! Page::Project::Menu.perform(&:go_to_repository_settings) Page::Project::Settings::Repository.perform do |setting| setting.expand_protected_branches do |page| - page.select_branch(branch_name) - page.select_allowed_to_merge(allowed_to_merge) - page.select_allowed_to_push(allowed_to_push) - page.protect_branch + if new_branch + page.select_branch(branch_name) + page.select_allowed_to_merge(allowed_to_merge) + page.select_allowed_to_push(allowed_to_push) + page.protect_branch + else + page.require_code_owner_approval(branch_name) if require_code_owner_approval + end end end end @@ -59,11 +72,11 @@ module QA end def api_get_path - "/projects/#{@project.api_resource[:id]}/protected_branches/#{@branch_name}" + "/projects/#{project.id}/protected_branches/#{branch_name}" end def api_delete_path - "/projects/#{@project.api_resource[:id]}/protected_branches/#{@branch_name}" + "/projects/#{project.id}/protected_branches/#{branch_name}" end class Roles diff --git a/qa/qa/resource/repository/project_push.rb b/qa/qa/resource/repository/project_push.rb index 17596601cf9..c46921ad0c7 100644 --- a/qa/qa/resource/repository/project_push.rb +++ b/qa/qa/resource/repository/project_push.rb @@ -9,8 +9,11 @@ module QA attr_accessor :project_name attr_writer :wait_for_push + attribute :group + attribute :project do Project.fabricate! do |resource| + resource.group = group if @group resource.name = project_name resource.description = 'Project with repository' end @@ -24,6 +27,7 @@ module QA @new_branch = true @project_name = 'project-with-code' @wait_for_push = true + @group = nil end def repository_http_uri diff --git a/qa/qa/resource/repository/push.rb b/qa/qa/resource/repository/push.rb index 902ae9f3135..1e5399fcc59 100644 --- a/qa/qa/resource/repository/push.rb +++ b/qa/qa/resource/repository/push.rb @@ -60,13 +60,13 @@ module QA repository.use_lfs = use_lfs - username = 'GitLab QA' + name = 'GitLab QA' email = 'root@gitlab.com' if user repository.username = user.username repository.password = user.password - username = user.name + name = user.name email = user.email end @@ -75,7 +75,7 @@ module QA end @output += repository.clone - repository.configure_identity(username, email) + repository.configure_identity(name, email) @output += repository.checkout(branch_name, new_branch: new_branch) diff --git a/qa/qa/resource/repository/wiki_push.rb b/qa/qa/resource/repository/wiki_push.rb index e926c00d380..edf76c7cd78 100644 --- a/qa/qa/resource/repository/wiki_push.rb +++ b/qa/qa/resource/repository/wiki_push.rb @@ -5,17 +5,17 @@ module QA module Repository class WikiPush < Repository::Push attribute :wiki do - Wiki.fabricate! do |resource| + # We are using the project based wiki as a standard. + Wiki::ProjectPage.fabricate_via_api! do |resource| resource.title = 'Home' resource.content = '# My First Wiki Content' - resource.message = 'Update home' end end def initialize @file_name = 'Home.md' - @file_content = '# Welcome to My Wiki' - @commit_message = 'Updating Home Page' + @file_content = 'This line was created using git push' + @commit_message = 'Updating using git push' @branch_name = 'master' @new_branch = false end @@ -28,9 +28,12 @@ module QA @repository_ssh_uri ||= wiki.repository_ssh_location.uri end - def fabricate! - super - wiki.visit! + def web_url + # TODO + # workaround + # i.e. This replaces the last occurence of the string (case sensitive) + # and attaches everything before to the new substring + repository_http_uri.to_s.gsub(/(.*)\b\.wiki\.git\b/i, "\\1/-/wikis/#{@file_name.gsub('.md', '')}") end end end diff --git a/qa/qa/resource/sandbox.rb b/qa/qa/resource/sandbox.rb index 7b427af6b74..032ff65c58b 100644 --- a/qa/qa/resource/sandbox.rb +++ b/qa/qa/resource/sandbox.rb @@ -13,6 +13,7 @@ module QA attribute :id attribute :runners_token + attribute :name def initialize @path = Runtime::Namespace.sandbox_name diff --git a/qa/qa/resource/ssh_key.rb b/qa/qa/resource/ssh_key.rb index b948bf3969b..317d70ef2c3 100644 --- a/qa/qa/resource/ssh_key.rb +++ b/qa/qa/resource/ssh_key.rb @@ -6,6 +6,7 @@ module QA extend Forwardable attr_reader :title + attr_accessor :expires_at attribute :id @@ -53,13 +54,27 @@ module QA def api_post_body { title: title, - key: public_key + key: public_key, + expires_at: expires_at } end def api_delete_path "/user/keys/#{id}" end + + def replicated? + api_client = Runtime::API::Client.new(:geo_secondary) + + QA::Runtime::Logger.debug('Checking for SSH key replication') + + Support::Retrier.retry_until(max_duration: QA::EE::Runtime::Geo.max_db_replication_time, sleep_interval: 3) do + response = get Runtime::API::Request.new(api_client, api_get_path).url + + response.code == QA::Support::Api::HTTP_STATUS_OK && + parse_body(response)[:title].include?(title) + end + end end end end diff --git a/qa/qa/resource/wiki.rb b/qa/qa/resource/wiki.rb deleted file mode 100644 index 45d5da9346d..00000000000 --- a/qa/qa/resource/wiki.rb +++ /dev/null @@ -1,48 +0,0 @@ -# frozen_string_literal: true - -module QA - module Resource - class Wiki < Base - attr_accessor :title, :content, :message - - attribute :project do - Project.fabricate! do |resource| - resource.name = 'project-for-wikis' - resource.description = 'project for adding wikis' - end - end - - attribute :repository_http_location do - Page::Project::Wiki::Show.perform(&:click_clone_repository) - - Page::Project::Wiki::GitAccess.perform do |git_access| - git_access.choose_repository_clone_http - git_access.repository_location - end - end - - attribute :repository_ssh_location do - Page::Project::Wiki::Show.perform(&:click_clone_repository) - - Page::Project::Wiki::GitAccess.perform do |git_access| - git_access.choose_repository_clone_ssh - git_access.repository_location - end - end - - def fabricate! - project.visit! - - Page::Project::Menu.perform { |menu_side| menu_side.click_wiki } - - Page::Project::Wiki::New.perform do |wiki_new| - wiki_new.click_create_your_first_page_button - wiki_new.set_title(@title) - wiki_new.set_content(@content) - wiki_new.set_message(@message) - wiki_new.create_new_page - end - end - end - end -end diff --git a/qa/qa/resource/wiki/project_page.rb b/qa/qa/resource/wiki/project_page.rb new file mode 100644 index 00000000000..5d0a0a37765 --- /dev/null +++ b/qa/qa/resource/wiki/project_page.rb @@ -0,0 +1,68 @@ +# frozen_string_literal: true + +module QA + module Resource + module Wiki + class ProjectPage < Base + attribute :title + attribute :content + attribute :slug + attribute :format + + attribute :project do + Project.fabricate_via_api! do |project| + project.name = 'wiki_testing' + project.description = 'project for testing wikis' + end + end + + attribute :repository_http_location do + switching_to_wiki_url project.repository_http_location.git_uri + end + + attribute :repository_ssh_location do + switching_to_wiki_url project.repository_ssh_location.git_uri + end + + def initialize + @title = 'Home' + @content = 'This wiki page is created by the API' + end + + def resource_web_url(resource) + super + rescue ResourceURLMissingError + # TODO + # workaround + project.web_url.concat("/-/wikis/#{slug}") + end + + def api_get_path + "/projects/#{project.id}/wikis/#{slug}" + end + + def api_post_path + "/projects/#{project.id}/wikis" + end + + def api_post_body + { + id: project.id, + content: content, + title: title + } + end + + private + + def switching_to_wiki_url(url) + # TODO + # workaround + # i.e. This replaces the last occurence of the string (case sensitive) + # and attaches everything before to the new substring + Git::Location.new(url.to_s.gsub(/(.*)\bgit\b/i, '\1wiki.git')) + end + end + end + end +end diff --git a/qa/qa/runtime/api/repository_storage_moves.rb b/qa/qa/runtime/api/repository_storage_moves.rb new file mode 100644 index 00000000000..c94a693289f --- /dev/null +++ b/qa/qa/runtime/api/repository_storage_moves.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +module QA + module Runtime + module API + module RepositoryStorageMoves + extend self + extend Support::Api + + RepositoryStorageMovesError = Class.new(RuntimeError) + + def has_status?(project, status, destination_storage = Env.additional_repository_storage) + all.any? do |move| + move[:project][:path_with_namespace] == project.path_with_namespace && + move[:state] == status && + move[:destination_storage_name] == destination_storage + end + end + + def all + Logger.debug('Getting repository storage moves') + parse_body(get(Request.new(api_client, '/project_repository_storage_moves').url)) + end + + private + + def api_client + @api_client ||= Client.as_admin + end + end + end + end +end diff --git a/qa/qa/runtime/env.rb b/qa/qa/runtime/env.rb index 5c23586fb3e..677fba7ced7 100644 --- a/qa/qa/runtime/env.rb +++ b/qa/qa/runtime/env.rb @@ -210,6 +210,18 @@ module QA ENV['GITLAB_QA_1P_GITHUB_UUID'] end + def jira_admin_username + ENV['JIRA_ADMIN_USERNAME'] + end + + def jira_admin_password + ENV['JIRA_ADMIN_PASSWORD'] + end + + def jira_hostname + ENV['JIRA_HOSTNAME'] + end + def knapsack? !!(ENV['KNAPSACK_GENERATE_REPORT'] || ENV['KNAPSACK_REPORT_PATH'] || ENV['KNAPSACK_TEST_FILE_PATTERN']) end diff --git a/qa/qa/runtime/feature.rb b/qa/qa/runtime/feature.rb index 5e948b5b850..579c2293c51 100644 --- a/qa/qa/runtime/feature.rb +++ b/qa/qa/runtime/feature.rb @@ -28,19 +28,11 @@ module QA end def enable_and_verify(key) - Support::Retrier.retry_on_exception(sleep_interval: 2) do - enable(key) - - is_enabled = false - - QA::Support::Waiter.wait_until(sleep_interval: 1) do - is_enabled = enabled?(key) - end - - raise SetFeatureError, "#{key} was not enabled!" unless is_enabled + set_and_verify(key, enable: true) + end - QA::Runtime::Logger.info("Successfully enabled and verified feature flag: #{key}") - end + def disable_and_verify(key) + set_and_verify(key, enable: false) end def enabled?(key) @@ -75,6 +67,27 @@ module QA end end + # Change a feature flag and verify that the change was successful + # Arguments: + # key: The feature flag to set (as a string) + # enable: `true` to enable the flag, `false` to disable it + def set_and_verify(key, enable:) + Support::Retrier.retry_on_exception(sleep_interval: 2) do + enable ? enable(key) : disable(key) + + is_enabled = nil + + QA::Support::Waiter.wait_until(sleep_interval: 1) do + is_enabled = enabled?(key) + is_enabled == enable + end + + raise SetFeatureError, "#{key} was not #{enable ? 'enabled' : 'disabled'}!" unless is_enabled == enable + + QA::Runtime::Logger.info("Successfully #{enable ? 'enabled' : 'disabled'} and verified feature flag: #{key}") + end + end + def set_feature(key, value) request = Runtime::API::Request.new(api_client, "/features/#{key}") response = post(request.url, { value: value }) diff --git a/qa/qa/scenario/test/instance/airgapped.rb b/qa/qa/scenario/test/instance/airgapped.rb new file mode 100644 index 00000000000..556741ec040 --- /dev/null +++ b/qa/qa/scenario/test/instance/airgapped.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module QA + module Scenario + module Test + module Instance + class Airgapped < Template + include Bootable + include SharedAttributes + def perform(address, *rspec_options) + Runtime::Scenario.define(:runner_network, 'airgapped') + + super + end + end + end + end + end +end diff --git a/qa/qa/service/docker_run/base.rb b/qa/qa/service/docker_run/base.rb index b02bbea8ff5..512960e8232 100644 --- a/qa/qa/service/docker_run/base.rb +++ b/qa/qa/service/docker_run/base.rb @@ -8,6 +8,7 @@ module QA def initialize @network = Runtime::Scenario.attributes[:network] || 'test' + @runner_network = Runtime::Scenario.attributes[:runner_network] || @network end def network @@ -18,6 +19,14 @@ module QA @network end + def runner_network + shell "docker network inspect #{@runner_network}" + rescue CommandError + network + else + @runner_network + end + def pull shell "docker pull #{@image}" end diff --git a/qa/qa/service/docker_run/gitlab_runner.rb b/qa/qa/service/docker_run/gitlab_runner.rb index 834f6b430ac..6022ee4ceab 100644 --- a/qa/qa/service/docker_run/gitlab_runner.rb +++ b/qa/qa/service/docker_run/gitlab_runner.rb @@ -1,5 +1,6 @@ # frozen_string_literal: true +require 'resolv' require 'securerandom' module QA @@ -38,11 +39,16 @@ module QA def register! shell <<~CMD.tr("\n", ' ') docker run -d --rm --entrypoint=/bin/sh - --network #{network} --name #{@name} + --network #{runner_network} --name #{@name} #{'-v /var/run/docker.sock:/var/run/docker.sock' if @executor == :docker} --privileged #{@image} -c "#{register_command}" CMD + + # Prove airgappedness + if runner_network == 'airgapped' + shell("docker exec #{@name} sh -c '#{prove_airgap}'") + end end def tags=(tags) @@ -85,6 +91,17 @@ module QA gitlab-runner run CMD end + + # Ping CloudFlare DNS, should fail + # Ping Registry, should fail to resolve + def prove_airgap + gitlab_ip = Resolv.getaddress 'registry.gitlab.com' + <<~CMD + echo "Checking airgapped connectivity..." + nc -zv -w 10 #{gitlab_ip} 80 && (echo "Airgapped network faulty. Connectivity netcat check failed." && exit 1) || (echo "Connectivity netcat check passed." && exit 0) + wget --retry-connrefused --waitretry=1 --read-timeout=15 --timeout=10 -t 2 http://registry.gitlab.com > /dev/null 2>&1 && (echo "Airgapped network faulty. Connectivity wget check failed." && exit 1) || (echo "Airgapped network confirmed. Connectivity wget check passed." && exit 0) + CMD + end end end end diff --git a/qa/qa/service/docker_run/jenkins.rb b/qa/qa/service/docker_run/jenkins.rb index 00b63282484..808fab80c63 100644 --- a/qa/qa/service/docker_run/jenkins.rb +++ b/qa/qa/service/docker_run/jenkins.rb @@ -16,7 +16,9 @@ module QA end def host_name - return 'localhost' unless QA::Runtime::Env.running_in_ci? + if !QA::Runtime::Env.running_in_ci? && !runner_network.equal?('airgapped') + 'localhost' + end super end @@ -33,7 +35,9 @@ module QA #{@image} CMD - command.gsub!("--network #{network} ", '') unless QA::Runtime::Env.running_in_ci? + if !QA::Runtime::Env.running_in_ci? && !runner_network.equal?('airgapped') + command.gsub!("--network #{network} ", '') + end shell command end diff --git a/qa/qa/specs/features/api/3_create/repository/changing_repository_storage_spec.rb b/qa/qa/specs/features/api/3_create/repository/changing_repository_storage_spec.rb new file mode 100644 index 00000000000..d5ab6a3544d --- /dev/null +++ b/qa/qa/specs/features/api/3_create/repository/changing_repository_storage_spec.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +module QA + context 'Create' do + describe 'Changing Gitaly repository storage', :orchestrated, :requires_admin do + shared_examples 'repository storage move' do + it 'confirms a `finished` status after moving project repository storage' do + expect(project).to have_file('README.md') + + project.change_repository_storage(destination_storage) + + expect(Runtime::API::RepositoryStorageMoves).to have_status(project, 'finished', destination_storage) + + Resource::Repository::ProjectPush.fabricate! do |push| + push.project = project + push.file_name = 'new_file' + push.file_content = '# This is a new file' + push.commit_message = 'Add new file' + push.new_branch = false + end + + expect(project).to have_file('README.md') + expect(project).to have_file('new_file') + end + end + + context 'when moving from one Gitaly storage to another', :repository_storage do + let(:project) do + Resource::Project.fabricate_via_api! do |project| + project.name = 'repo-storage-move-status' + project.initialize_with_readme = true + end + end + let(:destination_storage) { QA::Runtime::Env.additional_repository_storage } + + it_behaves_like 'repository storage move' + end + + context 'when moving from Gitaly to Gitaly Cluster', :requires_praefect do + let(:project) do + Resource::Project.fabricate_via_api! do |project| + project.name = 'repo-storage-move' + project.initialize_with_readme = true + project.repository_storage = 'gitaly' + end + end + let(:destination_storage) { QA::Runtime::Env.praefect_repository_storage } + + it_behaves_like 'repository storage move' + end + end + end +end diff --git a/qa/qa/specs/features/browser_ui/1_manage/project/add_project_member_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/project/add_project_member_spec.rb index e30afbf8ae0..67055537567 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 @@ -12,8 +12,8 @@ module QA project.name = 'add-member-project' end.visit! - Page::Project::Menu.perform(&:go_to_members_settings) - Page::Project::Settings::Members.perform do |members| + Page::Project::Menu.perform(&:click_members) + Page::Project::Members.perform do |members| members.add_member(user.username) 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 7b4418191a3..57b0859856e 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 @@ -3,6 +3,8 @@ module QA context 'Plan', :smoke do describe 'Issue creation' do + let(:closed_issue) { Resource::Issue.fabricate_via_api! } + before do Flow::Login.sign_in end @@ -17,6 +19,25 @@ module QA end end + it 'closes an issue' do + closed_issue.visit! + + Page::Project::Issue::Show.perform do |issue_page| + issue_page.click_close_issue_button + + expect(issue_page).to have_element(:reopen_issue_button) + end + + Page::Project::Menu.perform(&:click_issues) + Page::Project::Issue::Index.perform do |index| + expect(index).not_to have_issue(closed_issue) + + index.click_closed_issues_link + + expect(index).to have_issue(closed_issue) + end + end + context 'when using attachments in comments', :object_storage do let(:gif_file_name) { 'banana_sample.gif' } let(:file_to_attach) do 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 9b46a066c8e..623573a1397 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 @@ -16,10 +16,10 @@ module QA it 'shows issue suggestions when creating a new issue' do Page::Project::Show.perform(&:go_to_new_issue) Page::Project::Issue::New.perform do |new_page| - new_page.add_title("issue") + new_page.fill_title("issue") expect(new_page).to have_content(issue_title) - new_page.add_title("Issue Board") + new_page.fill_title("Issue Board") expect(new_page).not_to have_content(issue_title) end end diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue_boards/focus_mode_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue_boards/focus_mode_spec.rb index 409c7d321f0..c81a6f9281c 100644 --- a/qa/qa/specs/features/browser_ui/2_plan/issue_boards/focus_mode_spec.rb +++ b/qa/qa/specs/features/browser_ui/2_plan/issue_boards/focus_mode_spec.rb @@ -17,7 +17,7 @@ module QA project.visit! Page::Project::Menu.perform(&:go_to_boards) - EE::Page::Component::IssueBoard::Show.perform do |show| + Page::Component::IssueBoard::Show.perform do |show| show.click_focus_mode_button expect(show.focused_board).to be_visible diff --git a/qa/qa/specs/features/browser_ui/3_create/jira/jira_basic_integration_spec.rb b/qa/qa/specs/features/browser_ui/3_create/jira/jira_basic_integration_spec.rb new file mode 100644 index 00000000000..05a932fd53e --- /dev/null +++ b/qa/qa/specs/features/browser_ui/3_create/jira/jira_basic_integration_spec.rb @@ -0,0 +1,105 @@ +# frozen_string_literal: true + +module QA + context 'Create' do + include Support::Api + + describe 'Jira integration', :jira, :orchestrated, :requires_admin do + let(:jira_project_key) { 'JITP' } + + before(:all) do + page.visit Vendor::Jira::JiraAPI.perform(&:base_url) + + QA::Support::Retrier.retry_until(sleep_interval: 3, reload_page: page, max_attempts: 20, raise_on_failure: true) do + page.has_text? 'Welcome to Jira' + end + + @project = Resource::Project.fabricate_via_api! do |project| + project.name = "project_with_jira_integration" + end + + # Retry is required because allow_local_requests_from_web_hooks_and_services + # takes some time to get enabled. + # Bug issue: https://gitlab.com/gitlab-org/gitlab/-/issues/217010 + QA::Support::Retrier.retry_on_exception(max_attempts: 5, sleep_interval: 3) do + Runtime::ApplicationSettings.set_application_settings(allow_local_requests_from_web_hooks_and_services: true) + + page.visit Runtime::Scenario.gitlab_address + Flow::Login.sign_in_unless_signed_in + + @project.visit! + + Page::Project::Menu.perform(&:go_to_integrations_settings) + QA::Page::Project::Settings::Integrations.perform(&:click_jira_link) + + QA::Page::Project::Settings::Services::Jira.perform do |jira| + jira.setup_service_with(url: Vendor::Jira::JiraAPI.perform(&:base_url)) + end + + expect(page).not_to have_text("Requests to the local network are not allowed") + end + end + + it 'closes an issue via pushing a commit' do + issue_key = Vendor::Jira::JiraAPI.perform do |jira_api| + jira_api.create_issue(jira_project_key) + end + + push_commit("Closes #{issue_key}") + + expect_issue_done(issue_key) + end + + it 'closes an issue via a merge request' do + issue_key = Vendor::Jira::JiraAPI.perform do |jira_api| + jira_api.create_issue(jira_project_key) + end + + page.visit Runtime::Scenario.gitlab_address + Flow::Login.sign_in_unless_signed_in + + merge_request = create_mr_with_description("Closes #{issue_key}") + + merge_request.visit! + + Page::MergeRequest::Show.perform(&:merge!) + + expect_issue_done(issue_key) + end + + def create_mr_with_description(description) + Resource::MergeRequest.fabricate! do |merge_request| + merge_request.project = @project + merge_request.target_new_branch = !master_branch_exists? + merge_request.description = description + end + end + + def push_commit(commit_message) + Resource::Repository::ProjectPush.fabricate! do |push| + push.branch_name = 'master' + push.commit_message = commit_message + push.file_content = commit_message + push.project = @project + push.new_branch = !master_branch_exists? + end + end + + def expect_issue_done(issue_key) + expect do + Support::Waiter.wait_until(raise_on_failure: true) do + jira_issue = Vendor::Jira::JiraAPI.perform do |jira_api| + jira_api.fetch_issue(issue_key) + end + + jira_issue[:fields][:status][:name] == 'Done' + end + end.not_to raise_error + end + + def master_branch_exists? + @project.repository_branches.map { |item| item[:name] }.include?("master") + end + end + end +end 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 9b504ad76b4..21ae10774c9 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 @@ -3,6 +3,12 @@ module QA context 'Create', :requires_admin do describe 'push after setting the file size limit via admin/application_settings' do + # Note: The file size limits in this test should be greater than the limits in + # ee/browser_ui/3_create/repository/push_rules_spec to prevent that test from + # triggering the limit set in this test (which can happen on Staging where the + # tests are run in parallel). + # See: https://gitlab.com/gitlab-org/gitlab/-/issues/218620#note_361634705 + include Support::Api before(:context) do @@ -31,7 +37,7 @@ module QA end it 'push fails when the file size is above the limit' do - set_file_size_limit(1) + set_file_size_limit(2) retry_on_fail do expect { push_new_file('oversize_file_2.bin', wait_for_push: false) } @@ -52,7 +58,7 @@ module QA 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.file_content = SecureRandom.random_bytes(3000000) p.commit_message = commit_message p.new_branch = false end diff --git a/qa/qa/specs/features/browser_ui/3_create/snippet/clone_push_pull_project_snippet_spec.rb b/qa/qa/specs/features/browser_ui/3_create/snippet/clone_push_pull_project_snippet_spec.rb new file mode 100644 index 00000000000..a3011550db8 --- /dev/null +++ b/qa/qa/specs/features/browser_ui/3_create/snippet/clone_push_pull_project_snippet_spec.rb @@ -0,0 +1,101 @@ +# frozen_string_literal: true + +module QA + context 'Create' do + describe 'Version control for project snippets' do + let(:new_file) { 'new_snippet_file' } + let(:changed_content) { 'changes' } + let(:commit_message) { 'Changes to snippets' } + let(:added_content) { 'updated ' } + let(:branch_name) { 'master' } + + let(:snippet) do + Resource::ProjectSnippet.fabricate! do |snippet| + snippet.file_name = new_file + end + end + + let(:ssh_key) do + Resource::SSHKey.fabricate_via_api! do |resource| + resource.title = "my key title #{Time.now.to_f}" + end + end + + let(:repository_uri_http) do + snippet + Page::Dashboard::Snippet::Show.perform(&:get_repository_uri_http) + end + + let(:repository_uri_ssh) do + ssh_key + snippet + Page::Dashboard::Snippet::Show.perform(&:get_repository_uri_ssh) + end + + before do + Flow::Login.sign_in + end + + it 'clones, pushes, and pulls a project snippet over HTTP, edits via UI' do + Resource::Repository::Push.fabricate! do |push| + push.repository_http_uri = repository_uri_http + push.file_name = new_file + push.file_content = changed_content + push.commit_message = commit_message + push.new_branch = false + end + + page.refresh + verify_changes_in_ui + + Page::Dashboard::Snippet::Show.perform(&:click_edit_button) + + Page::Dashboard::Snippet::Edit.perform do |snippet| + snippet.add_to_file_content(added_content) + snippet.save_changes + end + + Git::Repository.perform do |repository| + repository.init_repository + repository.pull(repository_uri_http, branch_name) + + expect(repository.commits.size).to eq 3 + expect(repository.commits.first).to include 'Update snippet' + expect(repository.file_content(new_file)).to include "#{added_content}#{changed_content}" + end + end + + it 'clones, pushes, and pulls a project snippet over SSH, deletes via UI' do + Resource::Repository::Push.fabricate! do |push| + push.repository_ssh_uri = repository_uri_ssh + push.ssh_key = ssh_key + push.file_name = new_file + push.file_content = changed_content + push.commit_message = commit_message + push.new_branch = false + end + + page.refresh + verify_changes_in_ui + Page::Dashboard::Snippet::Show.perform(&:click_delete_button) + + # attempt to pull a deleted snippet, get a missing repository error + Git::Repository.perform do |repository| + repository.uri = repository_uri_ssh + repository.use_ssh_key(ssh_key) + repository.init_repository + + expect { repository.pull(repository_uri_ssh, branch_name) } + .to raise_error(QA::Git::Repository::RepositoryCommandError, /[fatal: Could not read from remote repository.]+/) + end + end + + def verify_changes_in_ui + Page::Dashboard::Snippet::Show.perform do |snippet| + expect(snippet).to have_file_name(new_file) + expect(snippet).to have_file_content(changed_content) + end + end + end + end +end diff --git a/qa/qa/specs/features/browser_ui/3_create/snippet/create_snippet_spec.rb b/qa/qa/specs/features/browser_ui/3_create/snippet/create_personal_snippet_spec.rb index dfcbf4b44c8..451a7847f8b 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_personal_snippet_spec.rb @@ -2,8 +2,8 @@ module QA context 'Create', :smoke do - describe 'Snippet creation' do - it 'User creates a snippet' do + describe 'Personal snippet creation' do + it 'User creates a personal snippet' do Flow::Login.sign_in Page::Main::Menu.perform(&:go_to_snippets) diff --git a/qa/qa/specs/features/browser_ui/3_create/snippet/create_project_snippet_spec.rb b/qa/qa/specs/features/browser_ui/3_create/snippet/create_project_snippet_spec.rb new file mode 100644 index 00000000000..8fc4427bda7 --- /dev/null +++ b/qa/qa/specs/features/browser_ui/3_create/snippet/create_project_snippet_spec.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +module QA + context 'Create' do # to be converted to a smoke test once proved to be stable + describe 'Project snippet creation' do + it 'User creates a project snippet' do + Flow::Login.sign_in + + Resource::ProjectSnippet.fabricate_via_browser_ui! do |snippet| + snippet.title = 'Project snippet' + snippet.description = ' ' + snippet.visibility = 'Private' + snippet.file_name = 'markdown_file.md' + snippet.file_content = "### Snippet heading\n\n[Gitlab link](https://gitlab.com/)" + end + + Page::Dashboard::Snippet::Show.perform do |snippet| + expect(snippet).to have_snippet_title('Project snippet') + expect(snippet).to have_no_snippet_description + expect(snippet).to have_visibility_type(/private/i) + expect(snippet).to have_file_name('markdown_file.md') + expect(snippet).to have_file_content('Snippet heading') + expect(snippet).to have_file_content('Gitlab link') + expect(snippet).not_to have_file_content('###') + expect(snippet).not_to have_file_content('https://gitlab.com/') + end + end + end + end +end diff --git a/qa/qa/specs/features/browser_ui/3_create/web_ide/create_first_file_in_web_ide_spec.rb b/qa/qa/specs/features/browser_ui/3_create/web_ide/create_first_file_in_web_ide_spec.rb new file mode 100644 index 00000000000..3bf6e156967 --- /dev/null +++ b/qa/qa/specs/features/browser_ui/3_create/web_ide/create_first_file_in_web_ide_spec.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +module QA + context 'Create' do + describe 'First file using Web IDE' do + let(:project) do + Resource::Project.fabricate_via_api! do |project| + project.name = 'empty-project' + project.initialize_with_readme = false + end + end + + let(:web_ide_url) { current_url + '-/ide/project/' + project.path_with_namespace } + let(:file_name) { 'the very first file.txt' } + + before do + Flow::Login.sign_in + end + + it "creates the first file in an empty project via Web IDE" do + # In the first iteration, the test opens Web IDE by modifying the URL to address past regressions. + # Once the Web IDE button is introduced for empty projects, the test will be modified to go through UI. + # See https://gitlab.com/gitlab-org/gitlab/-/issues/27915 and https://gitlab.com/gitlab-org/gitlab/-/issues/27535. + page.visit(web_ide_url) + + Page::Project::WebIDE::Edit.perform do |ide| + ide.create_first_file(file_name) + ide.commit_changes + end + + project.visit! + + Page::Project::Show.perform do |project| + expect(project).to have_file(file_name) + end + end + end + end +end diff --git a/qa/qa/specs/features/browser_ui/3_create/wiki/create_edit_clone_push_wiki_spec.rb b/qa/qa/specs/features/browser_ui/3_create/wiki/create_edit_clone_push_wiki_spec.rb deleted file mode 100644 index 185d10a64ed..00000000000 --- a/qa/qa/specs/features/browser_ui/3_create/wiki/create_edit_clone_push_wiki_spec.rb +++ /dev/null @@ -1,42 +0,0 @@ -# frozen_string_literal: true - -module QA - context 'Create' do - describe 'Wiki management' do - it 'user creates, edits, clones, and pushes to the wiki' do - Flow::Login.sign_in - - wiki = Resource::Wiki.fabricate_via_browser_ui! do |resource| - resource.title = 'Home' - resource.content = '# My First Wiki Content' - resource.message = 'Update home' - end - - validate_content('My First Wiki Content') - - Page::Project::Wiki::Edit.perform(&:click_edit) - Page::Project::Wiki::New.perform do |wiki| - wiki.set_content("My Second Wiki Content") - wiki.save_changes - end - - validate_content('My Second Wiki Content') - - Resource::Repository::WikiPush.fabricate! do |push| - push.wiki = wiki - push.file_name = 'Home.md' - push.file_content = '# My Third Wiki Content' - push.commit_message = 'Update Home.md' - end - Page::Project::Menu.perform(&:click_wiki) - - expect(page).to have_content('My Third Wiki Content') - end - - def validate_content(content) - expect(page).to have_content('Wiki was successfully updated') - expect(page).to have_content(/#{content}/) - end - end - end -end diff --git a/qa/qa/specs/features/browser_ui/3_create/wiki/project_based_content_creation_spec.rb b/qa/qa/specs/features/browser_ui/3_create/wiki/project_based_content_creation_spec.rb new file mode 100644 index 00000000000..1d0c8ee60d4 --- /dev/null +++ b/qa/qa/specs/features/browser_ui/3_create/wiki/project_based_content_creation_spec.rb @@ -0,0 +1,93 @@ +# frozen_string_literal: true + +module QA + context 'Create' do + context 'Wiki' do + describe 'testing wiki content creation inside a project' do + let(:new_wiki_title) { "just_another_wiki_page" } + let(:new_wiki_content) { "this content is changed or added" } + let(:commit_message) { "this is a new addition to the wiki" } + + let(:project) { Resource::Project.fabricate_via_api! } + let(:wiki) { Resource::Wiki::ProjectPage.fabricate_via_api! } + + before do + Flow::Login.sign_in + end + + it 'by adding a home page to the wiki' do + project.visit! + + Page::Project::Menu.perform(&:click_wiki) + Page::Project::Wiki::Show.perform(&:click_create_your_first_page) + + Page::Project::Wiki::Edit.perform do |edit| + edit.set_title new_wiki_title + edit.set_content new_wiki_content + edit.set_message commit_message + end + + Page::Project::Wiki::Edit.perform(&:click_create_page) + + Page::Project::Wiki::Show.perform do |wiki| + expect(wiki).to have_title new_wiki_title + expect(wiki).to have_content new_wiki_content + end + end + + it 'by adding a second page to the wiki' do + wiki.visit! + + Page::Project::Wiki::Show.perform(&:click_new_page) + + Page::Project::Wiki::Edit.perform do |edit| + edit.set_title new_wiki_title + edit.set_content new_wiki_content + edit.set_message commit_message + end + + Page::Project::Wiki::Edit.perform(&:click_create_page) + + Page::Project::Wiki::Show.perform do |wiki| + expect(wiki).to have_title new_wiki_title + expect(wiki).to have_content new_wiki_content + end + end + + it 'by adding a home page to the wiki using git push' do + empty_wiki = Resource::Wiki::ProjectPage.new do |empty_wiki| + empty_wiki.project = project + end + + Resource::Repository::WikiPush.fabricate! do |push| + push.file_name = "#{new_wiki_title}.md" + push.file_content = new_wiki_content + push.commit_message = commit_message + push.wiki = empty_wiki + push.new_branch = true + end.visit! + + Page::Project::Wiki::Show.perform do |wiki| + expect(wiki).to have_title new_wiki_title + expect(wiki).to have_content new_wiki_content + end + end + + it 'by adding a second page to the wiki using git push' do + Resource::Repository::WikiPush.fabricate! do |push| + push.file_name = "#{new_wiki_title}.md" + push.file_content = new_wiki_content + push.commit_message = commit_message + push.wiki = wiki + push.new_branch = false + end.visit! + + Page::Project::Wiki::Show.perform do |wiki| + expect(wiki).to have_title new_wiki_title + expect(wiki).to have_content new_wiki_content + end + end + end + end + end +end diff --git a/qa/qa/specs/features/browser_ui/3_create/wiki/project_based_content_manipulation_spec.rb b/qa/qa/specs/features/browser_ui/3_create/wiki/project_based_content_manipulation_spec.rb new file mode 100644 index 00000000000..10370c80476 --- /dev/null +++ b/qa/qa/specs/features/browser_ui/3_create/wiki/project_based_content_manipulation_spec.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +module QA + context 'Create' do + context 'Wiki' do + describe 'testing wiki content manipulation inside a project' do + let(:new_wiki_title) { "just_another_wiki_page" } + let(:new_wiki_content) { "this content is changed or added" } + let(:commit_message) { "this is a new addition to the wiki" } + + let(:wiki) { Resource::Wiki::ProjectPage.fabricate_via_api! } + + before do + Flow::Login.sign_in + end + + it 'by manipulating content on the page' do + wiki.visit! + + Page::Project::Wiki::Show.perform(&:click_edit) + + Page::Project::Wiki::Edit.perform do |edit| + edit.set_title new_wiki_title + edit.set_content new_wiki_content + edit.set_message commit_message + end + + Page::Project::Wiki::Edit.perform(&:click_save_changes) + + Page::Project::Wiki::Show.perform do |wiki| + expect(wiki).to have_title new_wiki_title + expect(wiki).to have_content new_wiki_content + end + end + + it 'by manipulating content on the page using git push' do + Resource::Repository::WikiPush.fabricate! do |push| + push.file_content = new_wiki_content + push.commit_message = commit_message + push.wiki = wiki + push.new_branch = false + end.visit! + + Page::Project::Wiki::Show.perform do |wiki| + expect(wiki).to have_content new_wiki_content + end + end + end + end + end +end diff --git a/qa/qa/specs/features/browser_ui/6_release/pipeline/parent_child_pipelines_dependent_relationship_spec.rb b/qa/qa/specs/features/browser_ui/6_release/pipeline/parent_child_pipelines_dependent_relationship_spec.rb index b1eb26f0d63..e1d8c50ab75 100644 --- a/qa/qa/specs/features/browser_ui/6_release/pipeline/parent_child_pipelines_dependent_relationship_spec.rb +++ b/qa/qa/specs/features/browser_ui/6_release/pipeline/parent_child_pipelines_dependent_relationship_spec.rb @@ -29,10 +29,12 @@ module QA view_pipelines Page::Project::Pipeline::Show.perform do |parent_pipeline| - parent_pipeline.click_linked_job(project.name) + expect(parent_pipeline).to have_passed + parent_pipeline.retry_on_exception(sleep_interval: 1.0) do + parent_pipeline.click_linked_job(project.name) + end expect(parent_pipeline).to have_job("child_job") - expect(parent_pipeline).to have_passed end end @@ -41,10 +43,12 @@ module QA view_pipelines Page::Project::Pipeline::Show.perform do |parent_pipeline| - parent_pipeline.click_linked_job(project.name) + expect(parent_pipeline).to have_failed + parent_pipeline.retry_on_exception(sleep_interval: 1.0) do + parent_pipeline.click_linked_job(project.name) + end expect(parent_pipeline).to have_job("child_job") - expect(parent_pipeline).to have_failed end end diff --git a/qa/qa/specs/features/browser_ui/6_release/pipeline/parent_child_pipelines_independent_relationship_spec.rb b/qa/qa/specs/features/browser_ui/6_release/pipeline/parent_child_pipelines_independent_relationship_spec.rb index c9a61fc6305..c365e084991 100644 --- a/qa/qa/specs/features/browser_ui/6_release/pipeline/parent_child_pipelines_independent_relationship_spec.rb +++ b/qa/qa/specs/features/browser_ui/6_release/pipeline/parent_child_pipelines_independent_relationship_spec.rb @@ -29,10 +29,12 @@ module QA view_pipelines Page::Project::Pipeline::Show.perform do |parent_pipeline| - parent_pipeline.click_linked_job(project.name) + expect(parent_pipeline).to have_passed + parent_pipeline.retry_on_exception(reload: true, sleep_interval: 1.0) do + parent_pipeline.click_linked_job(project.name) + end expect(parent_pipeline).to have_job("child_job") - expect(parent_pipeline).to have_passed end end @@ -41,10 +43,12 @@ module QA view_pipelines Page::Project::Pipeline::Show.perform do |parent_pipeline| - parent_pipeline.click_linked_job(project.name) + expect(parent_pipeline).to have_passed + parent_pipeline.retry_on_exception(reload: true, sleep_interval: 1.0) do + parent_pipeline.click_linked_job(project.name) + end expect(parent_pipeline).to have_job("child_job") - expect(parent_pipeline).to have_passed end end diff --git a/qa/qa/specs/features/browser_ui/7_configure/kubernetes/kubernetes_integration_spec.rb b/qa/qa/specs/features/browser_ui/7_configure/kubernetes/kubernetes_integration_spec.rb index 04c68598239..0e9c369e97f 100644 --- a/qa/qa/specs/features/browser_ui/7_configure/kubernetes/kubernetes_integration_spec.rb +++ b/qa/qa/specs/features/browser_ui/7_configure/kubernetes/kubernetes_integration_spec.rb @@ -1,10 +1,10 @@ # frozen_string_literal: true module QA - context 'Configure', quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/209085', type: :investigating } do - describe 'Kubernetes Cluster Integration', :orchestrated, :kubernetes, :requires_admin do + context 'Configure' do + describe 'Kubernetes Cluster Integration', :orchestrated, :kubernetes, :requires_admin, :skip_live_env do context 'Project Clusters' do - let(:cluster) { Service::KubernetesCluster.new(provider_class: Service::ClusterProvider::K3s).create! } + let!(:cluster) { Service::KubernetesCluster.new(provider_class: Service::ClusterProvider::K3s).create! } let(:project) do Resource::Project.fabricate_via_api! do |project| project.name = 'project-with-k8s' @@ -13,7 +13,7 @@ module QA end before do - Flow::Login.sign_in + Flow::Login.sign_in_as_admin end after do diff --git a/qa/qa/specs/features/browser_ui/8_monitor/all_monitor_core_features_spec.rb b/qa/qa/specs/features/browser_ui/8_monitor/all_monitor_core_features_spec.rb index 45273655bb6..a9ed6651069 100644 --- a/qa/qa/specs/features/browser_ui/8_monitor/all_monitor_core_features_spec.rb +++ b/qa/qa/specs/features/browser_ui/8_monitor/all_monitor_core_features_spec.rb @@ -1,11 +1,16 @@ # frozen_string_literal: true module QA - context 'Monitor', quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/217705', type: :flaky } do - describe 'with Prometheus Gitlab-managed cluster', :orchestrated, :kubernetes, :docker, :runner do + context 'Monitor' do + describe 'with Prometheus in a Gitlab-managed cluster', :orchestrated, :kubernetes do before :all do - Flow::Login.sign_in - @project, @runner = deploy_project_with_prometheus + @cluster = Service::KubernetesCluster.new.create! + @project = Resource::Project.fabricate_via_api! do |project| + project.name = 'monitoring-project' + project.auto_devops_enabled = true + end + + deploy_project_with_prometheus end before do @@ -14,7 +19,6 @@ module QA end after :all do - @runner.remove_via_api! @cluster.remove! end @@ -27,81 +31,94 @@ module QA it 'duplicates to create dashboard to custom' do Page::Project::Menu.perform(&:go_to_operations_metrics) - Page::Project::Operations::Metrics::Show.perform do |dashboard| - dashboard.duplicate_dashboard + Page::Project::Operations::Metrics::Show.perform do |on_dashboard| + on_dashboard.duplicate_dashboard - expect(dashboard).to have_metrics - expect(dashboard).to have_edit_dashboard_enabled + expect(on_dashboard).to have_metrics + expect(on_dashboard).to have_edit_dashboard_enabled end end it 'verifies data on filtered deployed environment' do Page::Project::Menu.perform(&:go_to_operations_metrics) - Page::Project::Operations::Metrics::Show.perform do |dashboard| - dashboard.filter_environment + Page::Project::Operations::Metrics::Show.perform do |on_dashboard| + on_dashboard.filter_environment - expect(dashboard).to have_metrics + expect(on_dashboard).to have_metrics end end it 'filters using the quick range' do Page::Project::Menu.perform(&:go_to_operations_metrics) - Page::Project::Operations::Metrics::Show.perform do |dashboard| - dashboard.show_last('30 minutes') - expect(dashboard).to have_metrics + Page::Project::Operations::Metrics::Show.perform do |on_dashboard| + on_dashboard.show_last('30 minutes') + expect(on_dashboard).to have_metrics - dashboard.show_last('3 hours') - expect(dashboard).to have_metrics + on_dashboard.show_last('3 hours') + expect(on_dashboard).to have_metrics - dashboard.show_last('1 day') - expect(dashboard).to have_metrics + on_dashboard.show_last('1 day') + expect(on_dashboard).to have_metrics end end private def deploy_project_with_prometheus - project = Resource::Project.fabricate_via_api! do |project| - project.name = 'cluster-with-prometheus' - project.description = 'Cluster with Prometheus' + %w[ + CODE_QUALITY_DISABLED TEST_DISABLED LICENSE_MANAGEMENT_DISABLED + SAST_DISABLED DAST_DISABLED DEPENDENCY_SCANNING_DISABLED + CONTAINER_SCANNING_DISABLED PERFORMANCE_DISABLED + ].each do |key| + Resource::CiVariable.fabricate_via_api! do |resource| + resource.project = @project + resource.key = key + resource.value = '1' + resource.masked = false + end end - runner = Resource::Runner.fabricate_via_api! do |runner| - runner.project = project - runner.name = project.name - end - - @cluster = Service::KubernetesCluster.new.create! + Flow::Login.sign_in - cluster_props = Resource::KubernetesCluster::ProjectCluster.fabricate! do |cluster_settings| - cluster_settings.project = project + Resource::KubernetesCluster::ProjectCluster.fabricate! do |cluster_settings| + cluster_settings.project = @project cluster_settings.cluster = @cluster cluster_settings.install_helm_tiller = true + cluster_settings.install_runner = true cluster_settings.install_ingress = true cluster_settings.install_prometheus = true end - Resource::CiVariable.fabricate_via_api! do |ci_variable| - ci_variable.project = project - ci_variable.key = 'AUTO_DEVOPS_DOMAIN' - ci_variable.value = cluster_props.ingress_ip - ci_variable.masked = false - end - Resource::Repository::ProjectPush.fabricate! do |push| - push.project = project + push.project = @project push.directory = Pathname .new(__dir__) - .join('../../../../fixtures/monitored_auto_devops') + .join('../../../../fixtures/auto_devops_rack') push.commit_message = 'Create AutoDevOps compatible Project for Monitoring' end Page::Project::Menu.perform(&:click_ci_cd_pipelines) - Page::Project::Pipeline::Index.perform(&:wait_for_latest_pipeline_success_or_retry) + Page::Project::Pipeline::Index.perform(&:click_on_latest_pipeline) + + Page::Project::Pipeline::Show.perform do |pipeline| + pipeline.click_job('build') + end + Page::Project::Job::Show.perform do |job| + expect(job).to be_successful(timeout: 600) + + job.click_element(:pipeline_path) + end + + Page::Project::Pipeline::Show.perform do |pipeline| + pipeline.click_job('production') + end + Page::Project::Job::Show.perform do |job| + expect(job).to be_successful(timeout: 1200) - [project, runner] + job.click_element(:pipeline_path) + end end def verify_add_custom_metric @@ -115,8 +132,8 @@ module QA Page::Project::Menu.perform(&:go_to_operations_metrics) - Page::Project::Operations::Metrics::Show.perform do |dashboard| - expect(dashboard).to have_custom_metric('HTTP Requests Total') + Page::Project::Operations::Metrics::Show.perform do |on_dashboard| + expect(on_dashboard).to have_custom_metric('HTTP Requests Total') end end @@ -130,8 +147,8 @@ module QA Page::Project::Menu.perform(&:go_to_operations_metrics) - Page::Project::Operations::Metrics::Show.perform do |dashboard| - expect(dashboard).to have_custom_metric('Throughput') + Page::Project::Operations::Metrics::Show.perform do |on_dashboard| + expect(on_dashboard).to have_custom_metric('Throughput') end end @@ -146,8 +163,8 @@ module QA Page::Project::Menu.perform(&:go_to_operations_metrics) - Page::Project::Operations::Metrics::Show.perform do |dashboard| - expect(dashboard).not_to have_custom_metric('Throughput') + Page::Project::Operations::Metrics::Show.perform do |on_dashboard| + expect(on_dashboard).not_to have_custom_metric('Throughput') end end end diff --git a/qa/qa/support/api.rb b/qa/qa/support/api.rb index f5e4d4e294b..3c46c039eae 100644 --- a/qa/qa/support/api.rb +++ b/qa/qa/support/api.rb @@ -11,22 +11,31 @@ module QA HTTP_STATUS_ACCEPTED = 202 HTTP_STATUS_SERVER_ERROR = 500 - def post(url, payload) - RestClient::Request.execute( + def post(url, payload, args = {}) + default_args = { method: :post, url: url, payload: payload, - verify_ssl: false) + verify_ssl: false + } + + RestClient::Request.execute( + default_args.merge(args) + ) rescue RestClient::ExceptionWithResponse => e return_response_or_raise(e) end - def get(url, raw_response: false) - RestClient::Request.execute( + def get(url, args = {}) + default_args = { method: :get, url: url, - verify_ssl: false, - raw_response: raw_response) + verify_ssl: false + } + + RestClient::Request.execute( + default_args.merge(args) + ) rescue RestClient::ExceptionWithResponse => e return_response_or_raise(e) end diff --git a/qa/qa/support/repeater.rb b/qa/qa/support/repeater.rb index 14771243beb..6f8c4a59566 100644 --- a/qa/qa/support/repeater.rb +++ b/qa/qa/support/repeater.rb @@ -7,8 +7,9 @@ module QA module Repeater DEFAULT_MAX_WAIT_TIME = 60 - RetriesExceededError = Class.new(RuntimeError) - WaitExceededError = Class.new(RuntimeError) + RepeaterConditionExceededError = Class.new(RuntimeError) + RetriesExceededError = Class.new(RepeaterConditionExceededError) + WaitExceededError = Class.new(RepeaterConditionExceededError) def repeat_until(max_attempts: nil, max_duration: nil, reload_page: nil, sleep_interval: 0, raise_on_failure: true, retry_on_exception: false, log: true) attempts = 0 diff --git a/qa/qa/vendor/jira/jira_api.rb b/qa/qa/vendor/jira/jira_api.rb new file mode 100644 index 00000000000..65b080df3d0 --- /dev/null +++ b/qa/qa/vendor/jira/jira_api.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +module QA + module Vendor + module Jira + class JiraAPI + include Scenario::Actable + include Support::Api + + def base_url + host = QA::Runtime::Env.jira_hostname || 'localhost' + + "http://#{host}:8080" + end + + def api_url + "#{base_url}/rest/api/2" + end + + def fetch_issue(issue_key) + response = get("#{api_url}/issue/#{issue_key}", user: Runtime::Env.jira_admin_username, password: Runtime::Env.jira_admin_password) + + parse_body(response) + end + + def create_issue(jira_project_key) + payload = { + fields: { + project: { + key: jira_project_key + }, + summary: 'REST ye merry gentlemen.', + description: 'Creating of an issue using project keys and issue type names using the REST API', + issuetype: { + name: 'Bug' + } + } + } + + response = post("#{api_url}/issue", + payload.to_json, headers: { 'Content-Type': 'application/json' }, + user: Runtime::Env.jira_admin_username, + password: Runtime::Env.jira_admin_password) + + issue_key = parse_body(response)[:key] + + QA::Runtime::Logger.debug("Created JIRA issue with key: '#{issue_key}'") + + issue_key + end + end + end + end +end |