diff --git a/app/assets/javascripts/pipeline_editor/components/header/validation_segment.vue b/app/assets/javascripts/pipeline_editor/components/header/validation_segment.vue
index 280ad2951a7b83cad557c7016d0c50a4b2067d5c..23f1592cac159c8355b5dd75da6c2dd86325b148 100644
--- a/app/assets/javascripts/pipeline_editor/components/header/validation_segment.vue
+++ b/app/assets/javascripts/pipeline_editor/components/header/validation_segment.vue
@@ -111,7 +111,10 @@ export default {
 
     <span v-else class="gl-display-inline-flex gl-white-space-nowrap gl-max-w-full">
       <tooltip-on-truncate :title="message" class="gl-text-truncate">
-        <gl-icon :name="icon" /> <span data-testid="validationMsg">{{ message }}</span>
+        <gl-icon :name="icon" />
+        <span data-qa-selector="validation_message_content" data-testid="validationMsg">
+          {{ message }}
+        </span>
       </tooltip-on-truncate>
       <span v-if="!isEmpty" class="gl-flex-shrink-0 gl-pl-2">
         <gl-link data-testid="learnMoreLink" :href="helpPath">
diff --git a/app/assets/javascripts/pipeline_editor/components/pipeline_editor_tabs.vue b/app/assets/javascripts/pipeline_editor/components/pipeline_editor_tabs.vue
index a418668933b1c6639b2d0140538d6838845505f6..c75b1d4bb1130dd9af612b024ff8a25f395c4b7d 100644
--- a/app/assets/javascripts/pipeline_editor/components/pipeline_editor_tabs.vue
+++ b/app/assets/javascripts/pipeline_editor/components/pipeline_editor_tabs.vue
@@ -146,6 +146,7 @@ export default {
 <template>
   <gl-tabs
     class="file-editor gl-mb-3"
+    data-qa-selector="file_editor_container"
     :query-param-name="$options.query.TAB_QUERY_PARAM"
     sync-active-tab-with-query-params
   >
diff --git a/app/assets/javascripts/pipelines/components/pipeline_graph/pipeline_graph.vue b/app/assets/javascripts/pipelines/components/pipeline_graph/pipeline_graph.vue
index 64210576b2942b76e8b6df55437ee14495962de6..8daf85e2b2e89cf7f9a08e631b4e8e13fc1011d8 100644
--- a/app/assets/javascripts/pipelines/components/pipeline_graph/pipeline_graph.vue
+++ b/app/assets/javascripts/pipelines/components/pipeline_graph/pipeline_graph.vue
@@ -132,6 +132,7 @@ export default {
       :ref="$options.CONTAINER_REF"
       class="gl-bg-gray-10 gl-overflow-auto"
       data-testid="graph-container"
+      data-qa-selector="pipeline_graph_container"
     >
       <links-layer
         :pipeline-data="pipelineStages"
@@ -147,7 +148,10 @@ export default {
           :key="`${stage.name}-${index}`"
           class="gl-flex-direction-column"
         >
-          <div class="gl-display-flex gl-align-items-center gl-w-full gl-px-9 gl-py-4 gl-mb-5">
+          <div
+            class="gl-display-flex gl-align-items-center gl-w-full gl-px-9 gl-py-4 gl-mb-5"
+            data-qa-selector="stage_container"
+          >
             <stage-name :stage-name="stage.name" />
           </div>
           <div :class="$options.jobWrapperClasses">
@@ -158,6 +162,7 @@ export default {
               :pipeline-id="$options.PIPELINE_ID"
               :is-hovered="highlightedJob === group.name"
               :is-faded-out="isFadedOut(group.name)"
+              data-qa-selector="job_container"
               @on-mouse-enter="setHoveredJob"
               @on-mouse-leave="removeHoveredJob"
             />
diff --git a/qa/qa/page/project/pipeline_editor/show.rb b/qa/qa/page/project/pipeline_editor/show.rb
index c620eaac111079920d8e06b25a3d09e622dc1c97..8289039d4c5a4952eae086a600b40f420c1e41d1 100644
--- a/qa/qa/page/project/pipeline_editor/show.rb
+++ b/qa/qa/page/project/pipeline_editor/show.rb
@@ -32,6 +32,19 @@ class Show < QA::Page::Base
             element :commit_changes_button
           end
 
+          view 'app/assets/javascripts/pipeline_editor/components/header/validation_segment.vue' do
+            element :validation_message_content
+          end
+
+          view 'app/assets/javascripts/pipelines/components/pipeline_graph/pipeline_graph.vue' do
+            element :stage_container
+            element :job_container
+          end
+
+          view 'app/assets/javascripts/pipeline_editor/components/pipeline_editor_tabs.vue' do
+            element :file_editor_container
+          end
+
           def initialize
             super
 
@@ -80,8 +93,48 @@ def pipeline_id
             find_element(:pipeline_id_content).text.delete!('#').to_i
           end
 
+          def ci_syntax_validate_message
+            find_element(:validation_message_content).text
+          end
+
+          def go_to_visualize_tab
+            go_to_tab('Visualize')
+          end
+
+          def go_to_lint_tab
+            go_to_tab('Lint')
+          end
+
+          def go_to_view_merged_yaml_tab
+            go_to_tab('View merged YAML')
+          end
+
+          def has_source_editor?
+            has_element?(:source_editor_container)
+          end
+
+          def has_stage?(name)
+            all_elements(:stage_container, minimum: 1).any? { |item| item.text.match(/#{name}/i) }
+          end
+
+          def has_job?(name)
+            all_elements(:job_container, minimum: 1).any? { |item| item.text.match(/#{name}/i) }
+          end
+
+          def tab_alert_message
+            within_element(:file_editor_container) do
+              find('.gl-alert-body').text
+            end
+          end
+
           private
 
+          def go_to_tab(name)
+            within_element(:file_editor_container) do
+              find('.nav-item', text: name).click
+            end
+          end
+
           # If the page thinks user has never opened pipeline editor before
           # It will expand pipeline editor sidebar by default
           # Collapse the sidebar if it is expanded
diff --git a/qa/qa/specs/features/browser_ui/4_verify/pipeline/pipeline_editor_lint_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/pipeline/pipeline_editor_lint_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..8f3284662d710329ba077a27e6129edced51e620
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/4_verify/pipeline/pipeline_editor_lint_spec.rb
@@ -0,0 +1,95 @@
+# frozen_string_literal: true
+
+module QA
+  RSpec.describe 'Verify' do
+    describe 'Pipeline editor' do
+      let(:project) do
+        Resource::Project.fabricate_via_api! do |project|
+          project.name = 'pipeline-editor-project'
+        end
+      end
+
+      let!(:commit) do
+        Resource::Repository::Commit.fabricate_via_api! do |commit|
+          commit.project = project
+          commit.commit_message = 'Add .gitlab-ci.yml'
+          commit.add_files(
+            [
+              {
+                file_path: '.gitlab-ci.yml',
+                content: <<~YAML
+                  stages:
+                    - stage1
+                    - stage2
+
+                  job1:
+                    stage: stage1
+                    script: echo 'Done.'
+
+                  job2:
+                    stage: stage2
+                    script: echo 'Done.'
+                YAML
+              }
+            ]
+          )
+        end
+      end
+
+      before do
+        Flow::Login.sign_in
+        project.visit!
+        Page::Project::Menu.perform(&:go_to_pipeline_editor)
+      end
+
+      after do
+        project&.remove_via_api!
+      end
+
+      context 'when CI has valid syntax' do
+        it 'shows valid validations', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/349128' do
+          Page::Project::PipelineEditor::Show.perform do |show|
+            aggregate_failures do
+              expect(show.ci_syntax_validate_message).to have_content('CI configuration is valid')
+
+              show.go_to_visualize_tab
+              { stage1: 'job1', stage2: 'job2' }.each_pair do |stage, job|
+                expect(show).to have_stage(stage), "Pipeline graph does not have stage #{stage}."
+                expect(show).to have_job(job), "Pipeline graph does not have job #{job}."
+              end
+
+              show.go_to_lint_tab
+              expect(show.tab_alert_message).to have_content('Syntax is correct')
+
+              show.go_to_view_merged_yaml_tab
+              expect(show).to have_source_editor
+            end
+          end
+        end
+      end
+
+      context 'when CI has invalid syntax' do
+        it 'shows invalid validations', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/349129' do
+          invalid_msg = 'syntax is invalid'
+
+          Page::Project::PipelineEditor::Show.perform do |show|
+            show.write_to_editor(SecureRandom.hex(10))
+
+            aggregate_failures do
+              show.go_to_visualize_tab
+              expect(show.tab_alert_message).to have_content(invalid_msg)
+
+              show.go_to_lint_tab
+              expect(show.tab_alert_message).to have_content('Syntax is incorrect')
+
+              show.go_to_view_merged_yaml_tab
+              expect(show.tab_alert_message).to have_content(invalid_msg)
+
+              expect(show.ci_syntax_validate_message).to have_content('CI configuration is invalid')
+            end
+          end
+        end
+      end
+    end
+  end
+end