diff --git a/app/controllers/projects/pipelines/tests_controller.rb b/app/controllers/projects/pipelines/tests_controller.rb
new file mode 100644
index 0000000000000000000000000000000000000000..6e4b5155a4ff41808f60d50c99c3428d8b35b1db
--- /dev/null
+++ b/app/controllers/projects/pipelines/tests_controller.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+module Projects
+  module Pipelines
+    class TestsController < Projects::ApplicationController
+      before_action :pipeline
+      before_action :authorize_read_pipeline!
+      before_action :authorize_read_build!
+      before_action :validate_feature_flag!
+
+      def summary
+        respond_to do |format|
+          format.json do
+            render json: TestReportSerializer
+              .new(project: project, current_user: @current_user)
+              .represent(pipeline.test_report_summary)
+          end
+        end
+      end
+
+      private
+
+      def validate_feature_flag!
+        render_404 unless Feature.enabled?(:build_report_summary, project)
+      end
+
+      def pipeline
+        project.all_pipelines.find(tests_params[:id])
+      end
+
+      def tests_params
+        params.permit(:id)
+      end
+    end
+  end
+end
diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb
index 0b6c0db211ec12763003930f85737c60b6ad143b..a8189a82c563695f3ac4b0655568b59379de1760 100644
--- a/app/controllers/projects/pipelines_controller.rb
+++ b/app/controllers/projects/pipelines_controller.rb
@@ -186,7 +186,7 @@ def test_report
       format.json do
         render json: TestReportSerializer
           .new(current_user: @current_user)
-          .represent(pipeline_test_report, project: project)
+          .represent(pipeline_test_report, project: project, details: true)
       end
     end
   end
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index 497e1a4d74a749c27d3c9715af3419ca7fb2dfc0..8e8fd774310d483ff76bb6234a14f83214801c43 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -80,6 +80,7 @@ class Pipeline < ApplicationRecord
     has_one :pipeline_config, class_name: 'Ci::PipelineConfig', inverse_of: :pipeline
 
     has_many :daily_build_group_report_results, class_name: 'Ci::DailyBuildGroupReportResult', foreign_key: :last_pipeline_id
+    has_many :latest_builds_report_results, through: :latest_builds, source: :report_results
 
     accepts_nested_attributes_for :variables, reject_if: :persisted?
 
@@ -802,6 +803,10 @@ def has_reports?(reports_scope)
       complete? && latest_report_builds(reports_scope).exists?
     end
 
+    def test_report_summary
+      Gitlab::Ci::Reports::TestReportSummary.new(latest_builds_report_results)
+    end
+
     def test_reports
       Gitlab::Ci::Reports::TestReports.new.tap do |test_reports|
         latest_report_builds(Ci::JobArtifact.test_reports).preload(:project).find_each do |build|
diff --git a/app/serializers/test_suite_entity.rb b/app/serializers/test_suite_entity.rb
index 53fa830718a2fd82cb0bd9fe8f3c67326f960a01..d04fd5f6a84b6a18ec3113c039b4903843b72afb 100644
--- a/app/serializers/test_suite_entity.rb
+++ b/app/serializers/test_suite_entity.rb
@@ -9,9 +9,11 @@ class TestSuiteEntity < Grape::Entity
   expose :failed_count
   expose :skipped_count
   expose :error_count
-  expose :suite_error
 
-  expose :test_cases, using: TestCaseEntity do |test_suite|
-    test_suite.suite_error ? [] : test_suite.test_cases.values.flat_map(&:values)
+  with_options if: -> (_, opts) { opts[:details] } do |test_suite|
+    expose :suite_error
+    expose :test_cases, using: TestCaseEntity do |test_suite|
+      test_suite.suite_error ? [] : test_suite.test_cases.values.flat_map(&:values)
+    end
   end
 end
diff --git a/config/routes/pipelines.rb b/config/routes/pipelines.rb
index cc3c3400526c0151017a7824e58bee76f532f589..c100526180eaabeaf3a096ec12a4f0f662418455 100644
--- a/config/routes/pipelines.rb
+++ b/config/routes/pipelines.rb
@@ -26,6 +26,12 @@
     resources :stages, only: [], param: :name do
       post :play_manual
     end
+
+    resources :tests, only: [], controller: 'pipelines/tests' do
+      collection do
+        get :summary
+      end
+    end
   end
 end
 
diff --git a/lib/api/pipelines.rb b/lib/api/pipelines.rb
index c09bca26a41450ddae11a7eef115930abdc52c6f..8cac6ad6bed54059baf9cdfdfc8371150f639608 100644
--- a/lib/api/pipelines.rb
+++ b/lib/api/pipelines.rb
@@ -120,7 +120,7 @@ class Pipelines < Grape::API
 
         authorize! :read_build, pipeline
 
-        present pipeline.test_reports, with: TestReportEntity
+        present pipeline.test_reports, with: TestReportEntity, details: true
       end
 
       desc 'Deletes a pipeline' do
diff --git a/lib/gitlab/ci/reports/test_report_summary.rb b/lib/gitlab/ci/reports/test_report_summary.rb
new file mode 100644
index 0000000000000000000000000000000000000000..85b83b790e7a856eb53e0d4ff5b526b87c1c461a
--- /dev/null
+++ b/lib/gitlab/ci/reports/test_report_summary.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+module Gitlab
+  module Ci
+    module Reports
+      class TestReportSummary
+        attr_reader :all_results
+
+        def initialize(all_results)
+          @all_results = all_results
+        end
+
+        def total
+          TestSuiteSummary.new(all_results)
+        end
+
+        def total_time
+          total.total_time
+        end
+
+        def total_count
+          total.total_count
+        end
+
+        def success_count
+          total.success_count
+        end
+
+        def failed_count
+          total.failed_count
+        end
+
+        def skipped_count
+          total.skipped_count
+        end
+
+        def error_count
+          total.error_count
+        end
+
+        def test_suites
+          all_results
+            .group_by(&:tests_name)
+            .transform_values { |results| TestSuiteSummary.new(results) }
+        end
+      end
+    end
+  end
+end
diff --git a/lib/gitlab/ci/reports/test_suite_summary.rb b/lib/gitlab/ci/reports/test_suite_summary.rb
new file mode 100644
index 0000000000000000000000000000000000000000..707b443a113e7c83eeadccdfaea697f0999256c2
--- /dev/null
+++ b/lib/gitlab/ci/reports/test_suite_summary.rb
@@ -0,0 +1,45 @@
+# frozen_string_literal: true
+
+module Gitlab
+  module Ci
+    module Reports
+      class TestSuiteSummary
+        attr_reader :results
+
+        def initialize(results)
+          @results = results
+        end
+
+        def name
+          @name ||= results.first.tests_name
+        end
+
+        # rubocop: disable CodeReuse/ActiveRecord
+        def total_time
+          @total_time ||= results.sum(&:tests_duration)
+        end
+
+        def success_count
+          @success_count ||= results.sum(&:tests_success)
+        end
+
+        def failed_count
+          @failed_count ||= results.sum(&:tests_failed)
+        end
+
+        def skipped_count
+          @skipped_count ||= results.sum(&:tests_skipped)
+        end
+
+        def error_count
+          @error_count ||= results.sum(&:tests_errored)
+        end
+
+        def total_count
+          @total_count ||= [success_count, failed_count, skipped_count, error_count].sum
+        end
+        # rubocop: disable CodeReuse/ActiveRecord
+      end
+    end
+  end
+end
diff --git a/spec/controllers/projects/pipelines/tests_controller_spec.rb b/spec/controllers/projects/pipelines/tests_controller_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..78d29e9825e6bc20cacf1bbab7e8d6f7666b2f8f
--- /dev/null
+++ b/spec/controllers/projects/pipelines/tests_controller_spec.rb
@@ -0,0 +1,58 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Projects::Pipelines::TestsController do
+  let(:user) { create(:user) }
+  let(:project) { create(:project, :public, :repository) }
+  let(:pipeline) { create(:ci_pipeline, project: project) }
+
+  before do
+    sign_in(user)
+  end
+
+  describe 'GET #summary.json' do
+    context 'when pipeline has build report results' do
+      let(:pipeline) { create(:ci_pipeline, :with_report_results, project: project) }
+
+      it 'renders test report summary data' do
+        get_tests_summary_json
+
+        expect(response).to have_gitlab_http_status(:ok)
+        expect(json_response['total_count']).to eq(2)
+      end
+    end
+
+    context 'when pipeline does not have build report results' do
+      it 'renders test report summary data' do
+        get_tests_summary_json
+
+        expect(response).to have_gitlab_http_status(:ok)
+        expect(json_response['total_count']).to eq(0)
+      end
+    end
+
+    context 'when feature is disabled' do
+      before do
+        stub_feature_flags(build_report_summary: false)
+      end
+
+      it 'returns 404' do
+        get_tests_summary_json
+
+        expect(response).to have_gitlab_http_status(:not_found)
+        expect(response.body).to be_empty
+      end
+    end
+  end
+
+  def get_tests_summary_json
+    get :summary,
+      params: {
+        namespace_id: project.namespace,
+        project_id: project,
+        id: pipeline.id
+      },
+      format: :json
+  end
+end
diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb
index 9403967aa0a2b5ffa5444be8e1a068f79323fd8c..76a38f152454ee6fe5956754c58d3fb1cdcb4849 100644
--- a/spec/factories/ci/builds.rb
+++ b/spec/factories/ci/builds.rb
@@ -302,6 +302,12 @@
       end
     end
 
+    trait :report_results do
+      after(:build) do |build|
+        build.report_results << build(:ci_build_report_result)
+      end
+    end
+
     trait :test_reports do
       after(:build) do |build|
         build.job_artifacts << create(:ci_job_artifact, :junit, job: build)
diff --git a/spec/factories/ci/pipelines.rb b/spec/factories/ci/pipelines.rb
index 85cdeaca12c3c3459b97f29934c8d407a8e8ea25..3a1bad8d2857af5b4fc89195c4382baa4746a612 100644
--- a/spec/factories/ci/pipelines.rb
+++ b/spec/factories/ci/pipelines.rb
@@ -65,6 +65,14 @@
         add_attribute(:protected) { true }
       end
 
+      trait :with_report_results do
+        status { :success }
+
+        after(:build) do |pipeline, evaluator|
+          pipeline.builds << build(:ci_build, :report_results, pipeline: pipeline, project: pipeline.project)
+        end
+      end
+
       trait :with_test_reports do
         status { :success }
 
diff --git a/spec/lib/gitlab/ci/reports/test_report_summary_spec.rb b/spec/lib/gitlab/ci/reports/test_report_summary_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..34ca5c764d294a02f04100e2e15a4b8d8dd6e770
--- /dev/null
+++ b/spec/lib/gitlab/ci/reports/test_report_summary_spec.rb
@@ -0,0 +1,90 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Ci::Reports::TestReportSummary do
+  let(:build_report_result_1) { build(:ci_build_report_result) }
+  let(:build_report_result_2) { build(:ci_build_report_result, :with_junit_success) }
+  let(:test_report_summary) { described_class.new([build_report_result_1, build_report_result_2]) }
+
+  describe '#total' do
+    subject { test_report_summary.total }
+
+    context 'when test report summary has several build report results' do
+      it 'returns test suite summary object' do
+        expect(subject).to be_a_kind_of(Gitlab::Ci::Reports::TestSuiteSummary)
+      end
+    end
+  end
+
+  describe '#total_time' do
+    subject { test_report_summary.total_time }
+
+    context 'when test report summary has several build report results' do
+      it 'returns the total' do
+        expect(subject).to eq(0.84)
+      end
+    end
+  end
+
+  describe '#total_count' do
+    subject { test_report_summary.total_count }
+
+    context 'when test report summary has several build report results' do
+      it 'returns the total count' do
+        expect(subject).to eq(4)
+      end
+    end
+  end
+
+  describe '#success_count' do
+    subject { test_report_summary.success_count }
+
+    context 'when test suite summary has several build report results' do
+      it 'returns the total success' do
+        expect(subject).to eq(2)
+      end
+    end
+  end
+
+  describe '#failed_count' do
+    subject { test_report_summary.failed_count }
+
+    context 'when test suite summary has several build report results' do
+      it 'returns the total failed' do
+        expect(subject).to eq(0)
+      end
+    end
+  end
+
+  describe '#error_count' do
+    subject { test_report_summary.error_count }
+
+    context 'when test suite summary has several build report results' do
+      it 'returns the total errored' do
+        expect(subject).to eq(2)
+      end
+    end
+  end
+
+  describe '#skipped_count' do
+    subject { test_report_summary.skipped_count }
+
+    context 'when test suite summary has several build report results' do
+      it 'returns the total skipped' do
+        expect(subject).to eq(0)
+      end
+    end
+  end
+
+  describe '#test_suites' do
+    subject { test_report_summary.test_suites }
+
+    context 'when test report summary has several build report results' do
+      it 'returns test suites grouped by name' do
+        expect(subject.keys).to eq(["rspec"])
+        expect(subject.keys.size).to eq(1)
+      end
+    end
+  end
+end
diff --git a/spec/lib/gitlab/ci/reports/test_suite_summary_spec.rb b/spec/lib/gitlab/ci/reports/test_suite_summary_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..5bbef62b43de57e28b4d5c1a92ba1015e7dcf637
--- /dev/null
+++ b/spec/lib/gitlab/ci/reports/test_suite_summary_spec.rb
@@ -0,0 +1,79 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Ci::Reports::TestSuiteSummary do
+  let(:build_report_result_1) { build(:ci_build_report_result) }
+  let(:build_report_result_2) { build(:ci_build_report_result, :with_junit_success) }
+  let(:test_suite_summary) { described_class.new([build_report_result_1, build_report_result_2]) }
+
+  describe '#name' do
+    subject { test_suite_summary.name }
+
+    context 'when test suite summary has several build report results' do
+      it 'returns the suite name' do
+        expect(subject).to eq("rspec")
+      end
+    end
+  end
+
+  describe '#total_time' do
+    subject { test_suite_summary.total_time }
+
+    context 'when test suite summary has several build report results' do
+      it 'returns the total time' do
+        expect(subject).to eq(0.84)
+      end
+    end
+  end
+
+  describe '#success_count' do
+    subject { test_suite_summary.success_count }
+
+    context 'when test suite summary has several build report results' do
+      it 'returns the total success' do
+        expect(subject).to eq(2)
+      end
+    end
+  end
+
+  describe '#failed_count' do
+    subject { test_suite_summary.failed_count }
+
+    context 'when test suite summary has several build report results' do
+      it 'returns the total failed' do
+        expect(subject).to eq(0)
+      end
+    end
+  end
+
+  describe '#error_count' do
+    subject { test_suite_summary.error_count }
+
+    context 'when test suite summary has several build report results' do
+      it 'returns the total errored' do
+        expect(subject).to eq(2)
+      end
+    end
+  end
+
+  describe '#skipped_count' do
+    subject { test_suite_summary.skipped_count }
+
+    context 'when test suite summary has several build report results' do
+      it 'returns the total skipped' do
+        expect(subject).to eq(0)
+      end
+    end
+  end
+
+  describe '#total_count' do
+    subject { test_suite_summary.total_count }
+
+    context 'when test suite summary has several build report results' do
+      it 'returns the total count' do
+        expect(subject).to eq(4)
+      end
+    end
+  end
+end
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index da543180c9c29b9922c274a4e9685d86d187e442..b5562a84db50c90953fb1057d7c908352b7b1d3c 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -226,6 +226,7 @@ ci_pipelines:
 - daily_build_group_report_results
 - latest_builds
 - daily_report_results
+- latest_builds_report_results
 ci_refs:
 - project
 - ci_pipelines
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index 782a4206c369cea8fac8dc0dc948facb47b265cc..5769e371478d0c5ba380108d4f7a22a5c19c13c4 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -2891,6 +2891,39 @@ def create_build(name, stage_idx)
     end
   end
 
+  describe '#test_report_summary' do
+    subject { pipeline.test_report_summary }
+
+    context 'when pipeline has multiple builds with report results' do
+      let(:pipeline) { create(:ci_pipeline, :success, project: project) }
+
+      before do
+        create(:ci_build, :success, :report_results, name: 'rspec', pipeline: pipeline, project: project)
+        create(:ci_build, :success, :report_results, name: 'java', pipeline: pipeline, project: project)
+      end
+
+      it 'returns test report summary with collected data', :aggregate_failures do
+        expect(subject.total_time).to be(0.84)
+        expect(subject.total_count).to be(4)
+        expect(subject.success_count).to be(0)
+        expect(subject.failed_count).to be(0)
+        expect(subject.error_count).to be(4)
+        expect(subject.skipped_count).to be(0)
+      end
+    end
+
+    context 'when pipeline does not have any builds with report results' do
+      it 'returns empty test report sumary', :aggregate_failures do
+        expect(subject.total_time).to be(0)
+        expect(subject.total_count).to be(0)
+        expect(subject.success_count).to be(0)
+        expect(subject.failed_count).to be(0)
+        expect(subject.error_count).to be(0)
+        expect(subject.skipped_count).to be(0)
+      end
+    end
+  end
+
   describe '#test_reports' do
     subject { pipeline.test_reports }
 
diff --git a/spec/serializers/test_suite_entity_spec.rb b/spec/serializers/test_suite_entity_spec.rb
index bd88d23501316658e3a883be163b47e17b176e93..83d3086ea6b43cc95aa35ac0a41427d6e865c29e 100644
--- a/spec/serializers/test_suite_entity_spec.rb
+++ b/spec/serializers/test_suite_entity_spec.rb
@@ -2,36 +2,46 @@
 
 require 'spec_helper'
 
-describe TestSuiteEntity do
-  let(:pipeline)   { create(:ci_pipeline, :with_test_reports) }
+RSpec.describe TestSuiteEntity do
+  let(:pipeline) { create(:ci_pipeline, :with_test_reports) }
   let(:test_suite) { pipeline.test_reports.test_suites.each_value.first }
-  let(:entity)     { described_class.new(test_suite) }
+  let(:user) { create(:user) }
+  let(:request) { double('request', current_user: user) }
 
-  describe '#as_json' do
-    subject(:as_json) { entity.as_json }
+  subject { described_class.new(test_suite, request: request).as_json }
+
+  context 'when details option is not present' do
+    it 'does not expose suite error and test cases', :aggregate_failures do
+      expect(subject).not_to include(:test_cases)
+      expect(subject).not_to include(:suite_error)
+    end
+  end
+
+  context 'when details option is present' do
+    subject { described_class.new(test_suite, request: request, details: true).as_json }
 
     it 'contains the suite name' do
-      expect(as_json[:name]).to be_present
+      expect(subject[:name]).to be_present
     end
 
     it 'contains the total time' do
-      expect(as_json[:total_time]).to be_present
+      expect(subject[:total_time]).to be_present
     end
 
     it 'contains the counts' do
-      expect(as_json[:total_count]).to eq(4)
-      expect(as_json[:success_count]).to eq(2)
-      expect(as_json[:failed_count]).to eq(2)
-      expect(as_json[:skipped_count]).to eq(0)
-      expect(as_json[:error_count]).to eq(0)
+      expect(subject[:total_count]).to eq(4)
+      expect(subject[:success_count]).to eq(2)
+      expect(subject[:failed_count]).to eq(2)
+      expect(subject[:skipped_count]).to eq(0)
+      expect(subject[:error_count]).to eq(0)
     end
 
     it 'contains the test cases' do
-      expect(as_json[:test_cases].count).to eq(4)
+      expect(subject[:test_cases].count).to eq(4)
     end
 
     it 'contains an empty error message' do
-      expect(as_json[:suite_error]).to be_nil
+      expect(subject[:suite_error]).to be_nil
     end
 
     context 'with a suite error' do
@@ -40,27 +50,27 @@
       end
 
       it 'contains the suite name' do
-        expect(as_json[:name]).to be_present
+        expect(subject[:name]).to be_present
       end
 
       it 'contains the total time' do
-        expect(as_json[:total_time]).to be_present
+        expect(subject[:total_time]).to be_present
       end
 
       it 'returns all the counts as 0' do
-        expect(as_json[:total_count]).to eq(0)
-        expect(as_json[:success_count]).to eq(0)
-        expect(as_json[:failed_count]).to eq(0)
-        expect(as_json[:skipped_count]).to eq(0)
-        expect(as_json[:error_count]).to eq(0)
+        expect(subject[:total_count]).to eq(0)
+        expect(subject[:success_count]).to eq(0)
+        expect(subject[:failed_count]).to eq(0)
+        expect(subject[:skipped_count]).to eq(0)
+        expect(subject[:error_count]).to eq(0)
       end
 
       it 'returns no test cases' do
-        expect(as_json[:test_cases]).to be_empty
+        expect(subject[:test_cases]).to be_empty
       end
 
       it 'returns a suite error' do
-        expect(as_json[:suite_error]).to eq('a really bad error')
+        expect(subject[:suite_error]).to eq('a really bad error')
       end
     end
   end