diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-06-01 03:07:59 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-06-01 03:07:59 +0000 |
commit | 72b4a0c010f3eb160c18d94050f5057131db4408 (patch) | |
tree | 5f3e44d7d45d8b2b05faf7016ba64a678d39d6d3 | |
parent | e0a8496a094971dae746dae511b6a41d0cdc92c7 (diff) | |
download | gitlab-ce-72b4a0c010f3eb160c18d94050f5057131db4408.tar.gz |
Add latest changes from gitlab-org/gitlab@master
-rw-r--r-- | app/assets/javascripts/ide/components/ide.vue | 1 | ||||
-rw-r--r-- | app/assets/javascripts/ide/components/new_dropdown/modal.vue | 5 | ||||
-rw-r--r-- | doc/user/application_security/dast/index.md | 2 | ||||
-rw-r--r-- | qa/qa.rb | 4 | ||||
-rw-r--r-- | qa/qa/page/base.rb | 25 | ||||
-rw-r--r-- | qa/qa/page/component/web_ide/modal/create_new_file.rb | 19 | ||||
-rw-r--r-- | qa/qa/page/merge_request/show.rb | 25 | ||||
-rw-r--r-- | qa/qa/page/project/settings/advanced.rb | 4 | ||||
-rw-r--r-- | qa/qa/page/project/settings/main.rb | 2 | ||||
-rw-r--r-- | qa/qa/page/project/web_ide/edit.rb | 22 | ||||
-rw-r--r-- | qa/qa/resource/group.rb | 5 | ||||
-rw-r--r-- | qa/qa/resource/project.rb | 2 | ||||
-rw-r--r-- | qa/qa/resource/protected_branch.rb | 33 | ||||
-rw-r--r-- | qa/qa/resource/sandbox.rb | 1 | ||||
-rw-r--r-- | qa/qa/runtime/feature.rb | 37 | ||||
-rw-r--r-- | qa/qa/specs/features/browser_ui/3_create/web_ide/create_first_file_in_web_ide_spec.rb | 39 | ||||
-rw-r--r-- | qa/spec/runtime/feature_spec.rb | 31 | ||||
-rw-r--r-- | qa/spec/support/shared_examples/merge_with_code_owner_shared_examples.rb | 71 |
18 files changed, 275 insertions, 53 deletions
diff --git a/app/assets/javascripts/ide/components/ide.vue b/app/assets/javascripts/ide/components/ide.vue index 562231f8002..e9f84eb8648 100644 --- a/app/assets/javascripts/ide/components/ide.vue +++ b/app/assets/javascripts/ide/components/ide.vue @@ -125,6 +125,7 @@ export default { variant="success" :title="__('New file')" :aria-label="__('New file')" + data-qa-selector="first_file_button" @click="createNewFile()" > {{ __('New file') }} diff --git a/app/assets/javascripts/ide/components/new_dropdown/modal.vue b/app/assets/javascripts/ide/components/new_dropdown/modal.vue index 4766a2fe6ae..ceaf7058c5a 100644 --- a/app/assets/javascripts/ide/components/new_dropdown/modal.vue +++ b/app/assets/javascripts/ide/components/new_dropdown/modal.vue @@ -133,7 +133,7 @@ export default { <gl-modal ref="modal" modal-id="ide-new-entry" - modal-class="qa-new-file-modal" + data-qa-selector="new_file_modal" :title="modalTitle" :ok-title="buttonLabel" ok-variant="success" @@ -148,7 +148,8 @@ export default { ref="fieldName" v-model.trim="entryName" type="text" - class="form-control qa-full-file-path" + class="form-control" + data-qa-selector="file_name_field" :placeholder="placeholder" /> <ul diff --git a/doc/user/application_security/dast/index.md b/doc/user/application_security/dast/index.md index 2202d7567cd..03c7236c280 100644 --- a/doc/user/application_security/dast/index.md +++ b/doc/user/application_security/dast/index.md @@ -471,7 +471,7 @@ DAST can be [configured](#customizing-the-dast-settings) using environment varia | `DAST_INCLUDE_ALPHA_VULNERABILITIES` | no | Include alpha passive and active scan rules. Boolean. `true`, `True`, or `1` are considered as true value, otherwise false. Defaults to `false`. | | `DAST_USE_AJAX_SPIDER` | no | Use the AJAX spider in addition to the traditional spider, useful for crawling sites that require JavaScript. Boolean. `true`, `True`, or `1` are considered as true value, otherwise false. Defaults to `false`. | | `DAST_ZAP_CLI_OPTIONS` | no | ZAP Server command-line options. For example, `-Xmx3072m` would set the Java maximum memory allocation pool size. | -| `DAST_ZAP_GENERATE_CONFIG` | no | Generate sample ZAP config file for use with `DAST_ZAP_CONFIG_FILE`. Boolean. `true`, `True`, or `1` are considered as true value, otherwise false. Defaults to `false`. | +| `DAST_ZAP_GENERATE_CONFIG` | no | The file name of the generated sample ZAP config file for use with `DAST_ZAP_CONFIG_FILE`. | | `DAST_ZAP_CONFIG_FILE` | no | Name of config file used to determine thresholds of vulnerability rules. | | `DAST_ZAP_CONFIG_URL` | no | URL of config file used to determine thresholds of vulnerability rules. | @@ -440,6 +440,10 @@ module QA module WebIDE autoload :Alert, 'qa/page/component/web_ide/alert' + + module Modal + autoload :CreateNewFile, 'qa/page/component/web_ide/modal/create_new_file' + end end module Project 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/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/merge_request/show.rb b/qa/qa/page/merge_request/show.rb index 0da40b35938..175579dc994 100644 --- a/qa/qa/page/merge_request/show.rb +++ b/qa/qa/page/merge_request/show.rb @@ -154,8 +154,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 +165,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 +205,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/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/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/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/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/project.rb b/qa/qa/resource/project.rb index 76da7ad7d9c..6424807be74 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 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/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/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/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/spec/runtime/feature_spec.rb b/qa/spec/runtime/feature_spec.rb index 94638d99b01..db3c2f65963 100644 --- a/qa/spec/runtime/feature_spec.rb +++ b/qa/spec/runtime/feature_spec.rb @@ -25,6 +25,21 @@ describe QA::Runtime::Feature do end end + describe '.enable_and_verify' do + it 'enables a feature flag' do + allow(described_class).to receive(:get).and_return(response_get) + + expect(QA::Runtime::API::Request).to receive(:new) + .with(api_client, "/features/a-flag").and_return(request) + expect(described_class).to receive(:post) + .with(request.url, { value: true }).and_return(response_post) + expect(QA::Runtime::API::Request).to receive(:new) + .with(api_client, "/features").and_return(request) + + subject.enable_and_verify('a-flag') + end + end + describe '.disable' do it 'disables a feature flag' do expect(QA::Runtime::API::Request) @@ -40,6 +55,22 @@ describe QA::Runtime::Feature do end end + describe '.disable_and_verify' do + it 'disables a feature flag' do + allow(described_class).to receive(:get) + .and_return(Struct.new(:code, :body).new(200, '[{ "name": "a-flag", "state": "off" }]')) + + expect(QA::Runtime::API::Request).to receive(:new) + .with(api_client, "/features/a-flag").and_return(request) + expect(described_class).to receive(:post) + .with(request.url, { value: false }).and_return(response_post) + expect(QA::Runtime::API::Request).to receive(:new) + .with(api_client, "/features").and_return(request) + + subject.disable_and_verify('a-flag') + end + end + describe '.enabled?' do it 'returns a feature flag state' do expect(QA::Runtime::API::Request) diff --git a/qa/spec/support/shared_examples/merge_with_code_owner_shared_examples.rb b/qa/spec/support/shared_examples/merge_with_code_owner_shared_examples.rb new file mode 100644 index 00000000000..feaeb78815d --- /dev/null +++ b/qa/spec/support/shared_examples/merge_with_code_owner_shared_examples.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true + +module QA + shared_examples 'code owner merge request' do + let(:branch_name) { 'new-branch' } + + it 'is approved and merged' do + # Require one approval from any eligible user on any branch + # This will confirm that this type of unrestricted approval is + # also satisfied when a code owner grants approval + Page::Project::Menu.perform(&:go_to_general_settings) + Page::Project::Settings::Main.perform do |main| + main.expand_merge_request_approvals_settings do |settings| + settings.set_default_number_of_approvals_required(1) + end + end + + Resource::Repository::Commit.fabricate_via_api! do |commit| + commit.project = project + commit.commit_message = 'Add CODEOWNERS' + commit.add_files( + [ + { + file_path: 'CODEOWNERS', + content: <<~CONTENT + README.md @#{codeowner} + CONTENT + } + ] + ) + end + + # Require approval from code owners on master + Resource::ProtectedBranch.fabricate! do |protected_branch| + protected_branch.project = project + protected_branch.branch_name = 'master' + protected_branch.new_branch = false + protected_branch.require_code_owner_approval = true + end + + # Push a change to the file with a CODEOWNERS rule + Resource::Repository::Push.fabricate! do |push| + push.repository_http_uri = project.repository_http_location.uri + push.branch_name = branch_name + push.file_name = 'README.md' + push.file_content = 'Updated' + end + + merge_request = Resource::MergeRequest.fabricate! do |merge_request| + merge_request.project = project + merge_request.target_new_branch = false + merge_request.source_branch = branch_name + merge_request.no_preparation = true + end + + Flow::Login.while_signed_in(as: approver) do + merge_request.visit! + + Page::MergeRequest::Show.perform do |merge_request| + expect(merge_request.approvals_required_from).to include('Code Owners') + expect(merge_request).not_to be_mergeable + + merge_request.click_approve + merge_request.merge! + + expect(merge_request).to be_merged + end + end + end + end +end |