diff --git a/ee/app/assets/javascripts/security_orchestration/components/policy_editor/editor_layout.vue b/ee/app/assets/javascripts/security_orchestration/components/policy_editor/editor_layout.vue
index c85099800bd795afdd28d8f90047569f35d4c57b..57ac96edd1ae6dbaee93a6d345da081a424899ba 100644
--- a/ee/app/assets/javascripts/security_orchestration/components/policy_editor/editor_layout.vue
+++ b/ee/app/assets/javascripts/security_orchestration/components/policy_editor/editor_layout.vue
@@ -253,6 +253,7 @@ export default {
             >
               <gl-form-input
                 id="policyName"
+                data-testid="policy-name-text"
                 :disabled="hasParsingError"
                 :state="hasValidName || !showValidation"
                 :value="policy.name"
@@ -268,6 +269,7 @@ export default {
             >
               <gl-form-textarea
                 id="policyDescription"
+                data-testid="policy-description-text"
                 :disabled="hasParsingError"
                 :value="policy.description"
                 no-resize
diff --git a/ee/app/assets/javascripts/security_orchestration/components/policy_editor/pipeline_execution/action/code_block_file_path.vue b/ee/app/assets/javascripts/security_orchestration/components/policy_editor/pipeline_execution/action/code_block_file_path.vue
index c378ac7db3f89a298357f986354d86bb9d7caba5..b056f9facc7ed4eb699f0aadadee203d25ddccf5 100644
--- a/ee/app/assets/javascripts/security_orchestration/components/policy_editor/pipeline_execution/action/code_block_file_path.vue
+++ b/ee/app/assets/javascripts/security_orchestration/components/policy_editor/pipeline_execution/action/code_block_file_path.vue
@@ -193,6 +193,7 @@ export default {
         <template #projectSelector>
           <group-projects-dropdown
             class="gl-max-w-20"
+            data-testid="pipeline-execution-project-dropdown"
             :group-full-path="groupProjectsPath"
             :selected="selectedProjectId"
             :multiple="false"
@@ -235,6 +236,7 @@ export default {
           <gl-form-group
             class="gl-mb-0 gl-w-full"
             label-sr-only
+            data-testid="ci-file-path-text"
             :label="$options.i18n.formGroupLabel"
             :optional="false"
             :invalid-feedback="filePathValidationError"
diff --git a/ee/app/assets/javascripts/security_orchestration/components/policy_editor/pipeline_execution/action/code_block_strategy_selector.vue b/ee/app/assets/javascripts/security_orchestration/components/policy_editor/pipeline_execution/action/code_block_strategy_selector.vue
index 59e97730c463e9563a37bab8fddcf7b01794b496..b5d32a9d510ec7198203134ea40376a33d996c59 100644
--- a/ee/app/assets/javascripts/security_orchestration/components/policy_editor/pipeline_execution/action/code_block_strategy_selector.vue
+++ b/ee/app/assets/javascripts/security_orchestration/components/policy_editor/pipeline_execution/action/code_block_strategy_selector.vue
@@ -32,6 +32,7 @@ export default {
 <template>
   <gl-collapsible-listbox
     label-for="file-path"
+    data-testid="strategy-selector-dropdown"
     :items="$options.CUSTOM_STRATEGY_OPTIONS_LISTBOX_ITEMS"
     :toggle-text="toggleText"
     :selected="strategy"
diff --git a/qa/qa/ee/page/group/policies/security_policies.rb b/qa/qa/ee/page/group/policies/security_policies.rb
new file mode 100644
index 0000000000000000000000000000000000000000..d75d17af170b207ba5f87a17b31b342baa68a9ad
--- /dev/null
+++ b/qa/qa/ee/page/group/policies/security_policies.rb
@@ -0,0 +1,76 @@
+# frozen_string_literal: true
+
+module QA
+  module EE
+    module Page
+      module Group
+        module Policies
+          class SecurityPolicies < QA::Page::Base
+            view 'ee/app/assets/javascripts/security_orchestration/components/policies/list_header.vue' do
+              element 'new-policy-button'
+            end
+
+            view 'ee/app/assets/javascripts/security_orchestration/components/policy_editor/pipeline_execution/action' \
+              '/code_block_file_path.vue' do
+              element 'pipeline-execution-project-dropdown'
+              element 'ci-file-path-text'
+            end
+
+            view 'ee/app/assets/javascripts/security_orchestration/components/policy_editor/editor_layout.vue' do
+              element 'policy-name-text'
+              element 'policy-description-text'
+              element 'save-policy'
+            end
+
+            def click_new_policy
+              click_element('new-policy-button')
+              wait_for_requests
+            end
+
+            def click_pipeline_execution_policy
+              click_element('select-policy-pipeline_execution_policy')
+              wait_for_requests
+            end
+
+            def set_policy_name(policy_name)
+              fill_element('policy-name-text', policy_name)
+            end
+
+            def select_strategy(override = false)
+              click_element('strategy-selector-dropdown')
+
+              if override
+                click_element('listbox-item-override_project_ci')
+              else
+                click_element('listbox-item-inject_ci')
+              end
+            end
+
+            def set_ci_file_path(file_path)
+              within_element('ci-file-path-text') do
+                find('[id="file-path"]').set(file_path)
+              end
+            end
+
+            def set_policy_description(description)
+              fill_element('policy-description-text', description)
+            end
+
+            def select_project(project_id)
+              click_element('pipeline-execution-project-dropdown')
+              find_element(project_listbox_item(project_id)).click
+            end
+
+            def save_policy
+              click_element('save-policy')
+            end
+
+            def project_listbox_item(project_id)
+              "listbox-item-gid://gitlab/Project/#{project_id}"
+            end
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/qa/qa/specs/features/ee/browser_ui/10_govern/group_pipeline_execution_policy_spec.rb b/qa/qa/specs/features/ee/browser_ui/10_govern/group_pipeline_execution_policy_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..b53a456ca0d1bbd77d797b041ecbd82a1668899a
--- /dev/null
+++ b/qa/qa/specs/features/ee/browser_ui/10_govern/group_pipeline_execution_policy_spec.rb
@@ -0,0 +1,193 @@
+# frozen_string_literal: true
+
+module QA
+  RSpec.describe 'Govern', :runner, product_group: :security_policies do
+    describe 'Group Pipeline Execution Policy' do
+      let(:executor) { "qa-runner-#{Faker::Alphanumeric.alphanumeric(number: 8)}" }
+      let(:group) { create(:group) }
+      let(:policy_name) { 'Greyhound' }
+      let(:template_project) do
+        create(:project, name: 'template-project-pipeline-exec-policy',
+          group: group, initialize_with_readme: true)
+      end
+
+      let(:template_project_ci_path) { "policy-ci-#{Faker::Alphanumeric.alphanumeric(number: 4)}.yml" }
+
+      let(:project) do
+        create(:project, name: 'project-test-pipeline-exec-policy', group: group, initialize_with_readme: true)
+      end
+
+      let(:merge_request) do
+        create(:merge_request,
+          project: project,
+          description: Faker::Lorem.sentence,
+          target_new_branch: false,
+          file_name: Faker::File.unique.file_name,
+          file_content: Faker::Lorem.sentence)
+      end
+
+      let!(:runner) { create(:group_runner, group: group, name: executor, tags: [executor]) }
+
+      let!(:pipeline_exec_policy_ci_file) do
+        create(:commit, project: template_project, commit_message: 'Add policy-ci.yml', actions: [
+          { action: 'create', file_path: template_project_ci_path, content: pipeline_execution_policy_yaml }
+        ])
+      end
+
+      let!(:project_ci_file) do
+        create(:commit, project: project, commit_message: 'Add project .gitlab-ci.yml', actions: [
+          { action: 'create', file_path: '.gitlab-ci.yml', content: project_ci_yaml }
+        ])
+      end
+
+      let(:expected_job_log) { "This job is due to pipeline execution policy for #{group.path}" }
+
+      before do
+        Flow::Login.sign_in
+      end
+
+      after do
+        runner.remove_via_api!
+      end
+
+      it 'executes jobs as per inject strategy',
+        testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/499398' do
+        create_pipeline_execution_policy
+        merge_pipeline_execution_policy
+
+        merge_request.visit!
+        Flow::Pipeline.wait_for_pipeline_creation_via_api(project: project)
+        Flow::Pipeline.wait_for_latest_pipeline_to_have_status(project: project, status: 'success')
+
+        Flow::Pipeline.visit_latest_pipeline
+        aggregate_failures do
+          Page::Project::Pipeline::Show.perform do |pipeline|
+            expect(pipeline).to have_job('pipeline_exec_policy_build_job')
+            expect(pipeline).to have_job('pipeline_exec_policy_pre_job')
+            expect(pipeline).to have_job('pipeline_exec_policy_post_job')
+            expect(pipeline).to have_job('project_job')
+          end
+        end
+
+        project.visit_job('pipeline_exec_policy_build_job')
+
+        verify_expected_job_log(job_name: 'pipeline_exec_policy_build_job', expected_text: expected_job_log)
+
+        project.visit_job('pipeline_exec_policy_pre_job')
+
+        verify_expected_job_log(job_name: 'pipeline_exec_policy_pre_job', expected_text: expected_job_log)
+
+        project.visit_job('pipeline_exec_policy_post_job')
+
+        verify_expected_job_log(job_name: 'pipeline_exec_policy_post_job', expected_text: expected_job_log)
+
+        project.visit_job('project_job')
+
+        verify_expected_job_log(job_name: 'project_job', expected_text: 'This is the project job. Colour grey')
+      end
+
+      it 'executes jobs as per override strategy',
+        testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/499418' do
+        create_pipeline_execution_policy(override: true)
+        merge_pipeline_execution_policy
+
+        merge_request.visit!
+        Flow::Pipeline.wait_for_pipeline_creation_via_api(project: project)
+        Flow::Pipeline.wait_for_latest_pipeline_to_have_status(project: project, status: 'success')
+
+        Flow::Pipeline.visit_latest_pipeline
+        aggregate_failures do
+          Page::Project::Pipeline::Show.perform do |pipeline|
+            expect(pipeline).to have_job('pipeline_exec_policy_build_job')
+            expect(pipeline).to have_job('pipeline_exec_policy_pre_job')
+            expect(pipeline).to have_job('pipeline_exec_policy_post_job')
+            expect(pipeline).not_to have_job('project_job')
+          end
+        end
+
+        project.visit_job('pipeline_exec_policy_build_job')
+
+        verify_expected_job_log(job_name: 'pipeline_exec_policy_build_job', expected_text: expected_job_log)
+
+        project.visit_job('pipeline_exec_policy_pre_job')
+
+        verify_expected_job_log(job_name: 'pipeline_exec_policy_pre_job', expected_text: expected_job_log)
+
+        project.visit_job('pipeline_exec_policy_post_job')
+
+        verify_expected_job_log(job_name: 'pipeline_exec_policy_post_job', expected_text: expected_job_log)
+      end
+
+      private
+
+      def create_pipeline_execution_policy(override: false)
+        group.visit!
+        Page::Group::Menu.perform(&:go_to_policies)
+
+        EE::Page::Group::Policies::SecurityPolicies.perform do |new_policy_page|
+          new_policy_page.click_new_policy
+          new_policy_page.click_pipeline_execution_policy
+          new_policy_page.set_policy_name(policy_name)
+          new_policy_page.set_policy_description('To test pipeline execution policy')
+          new_policy_page.select_strategy(override) if override
+          new_policy_page.select_project(template_project.id)
+          new_policy_page.set_ci_file_path(template_project_ci_path)
+          new_policy_page.save_policy
+        end
+      end
+
+      def merge_pipeline_execution_policy
+        Support::Waiter.wait_until(message: 'Wait for policy MR page', max_duration: 80) do
+          Page::MergeRequest::Show.perform(&:has_merge_button?)
+        end
+
+        Support::Retrier.retry_on_exception(sleep_interval: 2, message: "Retrying policy merge") do
+          Page::MergeRequest::Show.perform(&:merge!)
+        end
+      end
+
+      def verify_expected_job_log(job_name:, expected_text:)
+        project.visit_job(job_name)
+        Page::Project::Job::Show.perform do |show|
+          expect(show.output).to have_content(expected_text),
+            "Didn't find '#{expected_text}' within #{job_name}'s log:\n#{show.output}."
+        end
+      end
+
+      def pipeline_execution_policy_yaml
+        <<~YAML
+          default:
+            tags: ["#{executor}"]
+
+          pipeline_exec_policy_build_job:
+            stage: build
+            script:
+              - echo "#{expected_job_log}"
+
+          pipeline_exec_policy_pre_job:
+            stage: .pipeline-policy-pre
+            script:
+              - echo "#{expected_job_log}"
+
+          pipeline_exec_policy_post_job:
+            stage: .pipeline-policy-post
+            script:
+              - echo "#{expected_job_log}"
+        YAML
+      end
+
+      def project_ci_yaml
+        <<~YAML
+          stages:
+            - build
+
+          project_job:
+            stage: build
+            tags: ["#{executor}"]
+            script:
+              - echo "This is the project job. Colour grey"
+        YAML
+      end
+    end
+  end
+end