diff --git a/app/assets/javascripts/jobs/store/actions.js b/app/assets/javascripts/jobs/store/actions.js
index 6da8f8b455eca8fc27e66cd40f35d2ce3830c72c..e76a3693db9d77a6d76cd20c93de0ea9534e644f 100644
--- a/app/assets/javascripts/jobs/store/actions.js
+++ b/app/assets/javascripts/jobs/store/actions.js
@@ -2,7 +2,7 @@ import Visibility from 'visibilityjs';
 import * as types from './mutation_types';
 import axios from '~/lib/utils/axios_utils';
 import Poll from '~/lib/utils/poll';
-import { setFaviconOverlay, resetFavicon } from '~/lib/utils/common_utils';
+import { setFaviconOverlay, resetFavicon } from '~/lib/utils/favicon';
 import { deprecatedCreateFlash as flash } from '~/flash';
 import { __ } from '~/locale';
 import {
diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js
index 1c8cb4238280c3976b9e91062d7b2d6dbda652cc..128ef5b335e6fa3f11d4ce6855f236b8093ef646 100644
--- a/app/assets/javascripts/lib/utils/common_utils.js
+++ b/app/assets/javascripts/lib/utils/common_utils.js
@@ -6,7 +6,6 @@ import { GlBreakpointInstance as breakpointInstance } from '@gitlab/ui/dist/util
 import $ from 'jquery';
 import { isFunction, defer } from 'lodash';
 import Cookies from 'js-cookie';
-import axios from './axios_utils';
 import { getLocationHash } from './url_utility';
 import { convertToCamelCase, convertToSnakeCase } from './text_utility';
 import { isObject } from './type_utility';
@@ -548,92 +547,6 @@ export const backOff = (fn, timeout = 60000) => {
   });
 };
 
-export const createOverlayIcon = (iconPath, overlayPath) => {
-  const faviconImage = document.createElement('img');
-
-  return new Promise((resolve) => {
-    faviconImage.onload = () => {
-      const size = 32;
-
-      const canvas = document.createElement('canvas');
-      canvas.width = size;
-      canvas.height = size;
-
-      const context = canvas.getContext('2d');
-      context.clearRect(0, 0, size, size);
-      context.drawImage(
-        faviconImage,
-        0,
-        0,
-        faviconImage.width,
-        faviconImage.height,
-        0,
-        0,
-        size,
-        size,
-      );
-
-      const overlayImage = document.createElement('img');
-      overlayImage.onload = () => {
-        context.drawImage(
-          overlayImage,
-          0,
-          0,
-          overlayImage.width,
-          overlayImage.height,
-          0,
-          0,
-          size,
-          size,
-        );
-
-        const faviconWithOverlayUrl = canvas.toDataURL();
-
-        resolve(faviconWithOverlayUrl);
-      };
-      overlayImage.src = overlayPath;
-    };
-    faviconImage.src = iconPath;
-  });
-};
-
-export const setFaviconOverlay = (overlayPath) => {
-  const faviconEl = document.getElementById('favicon');
-
-  if (!faviconEl) {
-    return null;
-  }
-
-  const iconPath = faviconEl.getAttribute('data-original-href');
-
-  return createOverlayIcon(iconPath, overlayPath).then((faviconWithOverlayUrl) =>
-    faviconEl.setAttribute('href', faviconWithOverlayUrl),
-  );
-};
-
-export const resetFavicon = () => {
-  const faviconEl = document.getElementById('favicon');
-
-  if (faviconEl) {
-    const originalFavicon = faviconEl.getAttribute('data-original-href');
-    faviconEl.setAttribute('href', originalFavicon);
-  }
-};
-
-export const setCiStatusFavicon = (pageUrl) =>
-  axios
-    .get(pageUrl)
-    .then(({ data }) => {
-      if (data && data.favicon) {
-        return setFaviconOverlay(data.favicon);
-      }
-      return resetFavicon();
-    })
-    .catch((error) => {
-      resetFavicon();
-      throw error;
-    });
-
 export const spriteIcon = (icon, className = '') => {
   const classAttribute = className.length > 0 ? `class="${className}"` : '';
 
diff --git a/app/assets/javascripts/lib/utils/favicon.js b/app/assets/javascripts/lib/utils/favicon.js
new file mode 100644
index 0000000000000000000000000000000000000000..47596a76306e5a20168879b6d8c36471fcd7d6e3
--- /dev/null
+++ b/app/assets/javascripts/lib/utils/favicon.js
@@ -0,0 +1,30 @@
+import { FaviconOverlayManager } from '@gitlab/favicon-overlay';
+import { memoize } from 'lodash';
+
+// FaviconOverlayManager is a glorious singleton/static class. Let's start to encapsulate that with this helper.
+const getDefaultFaviconManager = memoize(async () => {
+  await FaviconOverlayManager.initialize({ faviconSelector: '#favicon' });
+
+  return FaviconOverlayManager;
+});
+
+export const setFaviconOverlay = async (path) => {
+  const manager = await getDefaultFaviconManager();
+
+  manager.setFaviconOverlay(path);
+};
+
+export const resetFavicon = async () => {
+  const manager = await getDefaultFaviconManager();
+
+  manager.resetFaviconOverlay();
+};
+
+/**
+ * Clears the cached memoization of the default manager.
+ *
+ * This is needed for determinism in tests.
+ */
+export const clearMemoizeCache = () => {
+  getDefaultFaviconManager.cache.clear();
+};
diff --git a/app/assets/javascripts/lib/utils/favicon_ci.js b/app/assets/javascripts/lib/utils/favicon_ci.js
new file mode 100644
index 0000000000000000000000000000000000000000..613e2620e02a245b75b5079a6dcd520ba531b94b
--- /dev/null
+++ b/app/assets/javascripts/lib/utils/favicon_ci.js
@@ -0,0 +1,16 @@
+import axios from './axios_utils';
+import { setFaviconOverlay, resetFavicon } from './favicon';
+
+export const setCiStatusFavicon = (pageUrl) =>
+  axios
+    .get(pageUrl)
+    .then(({ data }) => {
+      if (data && data.favicon) {
+        return setFaviconOverlay(data.favicon);
+      }
+      return resetFavicon();
+    })
+    .catch((error) => {
+      resetFavicon();
+      throw error;
+    });
diff --git a/app/assets/javascripts/pipelines.js b/app/assets/javascripts/pipelines.js
index 07abe714367e1270187d8683a313c8c89b993948..3b4e8d0e019ef065dc1115892cf47f101cc4487b 100644
--- a/app/assets/javascripts/pipelines.js
+++ b/app/assets/javascripts/pipelines.js
@@ -1,5 +1,5 @@
 import LinkedTabs from './lib/utils/bootstrap_linked_tabs';
-import { setCiStatusFavicon } from './lib/utils/common_utils';
+import { setCiStatusFavicon } from './lib/utils/favicon_ci';
 
 export default class Pipelines {
   constructor(options = {}) {
diff --git a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
index a620e155ff55ff41ca707aa2deed69a89616d943..cebbc7f2466bd923ee1468c8506df19051f53584 100644
--- a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
@@ -43,7 +43,7 @@ import SourceBranchRemovalStatus from './components/source_branch_removal_status
 import TerraformPlan from './components/terraform/mr_widget_terraform_container.vue';
 import GroupedCodequalityReportsApp from '../reports/codequality_report/grouped_codequality_reports_app.vue';
 import GroupedTestReportsApp from '../reports/components/grouped_test_reports_app.vue';
-import { setFaviconOverlay } from '../lib/utils/common_utils';
+import { setFaviconOverlay } from '../lib/utils/favicon';
 import GroupedAccessibilityReportsApp from '../reports/accessibility_report/grouped_accessibility_reports_app.vue';
 import getStateQuery from './queries/get_state.query.graphql';
 
diff --git a/jest.config.base.js b/jest.config.base.js
index 3ac6aa9091c82c735d55068e5efaf721a08bdc9a..6ad63dcf15fd4528349dda4b8007b29df92d951b 100644
--- a/jest.config.base.js
+++ b/jest.config.base.js
@@ -91,7 +91,7 @@ module.exports = (path) => {
       '^.+\\.(md|zip|png)$': 'jest-raw-loader',
     },
     transformIgnorePatterns: [
-      'node_modules/(?!(@gitlab/ui|bootstrap-vue|three|monaco-editor|monaco-yaml|fast-mersenne-twister)/)',
+      'node_modules/(?!(@gitlab/ui|@gitlab/favicon-overlay|bootstrap-vue|three|monaco-editor|monaco-yaml|fast-mersenne-twister)/)',
     ],
     timers: 'fake',
     testEnvironment: '<rootDir>/spec/frontend/environment.js',
diff --git a/package.json b/package.json
index 737d9071b529f59e1b4dcb4af7608623c1219f6c..e9385625b37f0ae0d729f1ab9c3c72505ab5987b 100644
--- a/package.json
+++ b/package.json
@@ -42,6 +42,7 @@
     "@babel/plugin-syntax-import-meta": "^7.10.1",
     "@babel/preset-env": "^7.10.1",
     "@gitlab/at.js": "1.5.5",
+    "@gitlab/favicon-overlay": "2.0.0",
     "@gitlab/svgs": "1.178.0",
     "@gitlab/tributejs": "1.0.0",
     "@gitlab/ui": "25.4.0",
diff --git a/spec/frontend/lib/utils/common_utils_spec.js b/spec/frontend/lib/utils/common_utils_spec.js
index 84aa9c32288bbb2186a1d0a3aa2e7a5192553572..90222f0f718e05342e904b930261593908829d58 100644
--- a/spec/frontend/lib/utils/common_utils_spec.js
+++ b/spec/frontend/lib/utils/common_utils_spec.js
@@ -514,27 +514,6 @@ describe('common_utils', () => {
     });
   });
 
-  describe('resetFavicon', () => {
-    beforeEach(() => {
-      const favicon = document.createElement('link');
-      favicon.setAttribute('id', 'favicon');
-      favicon.setAttribute('data-original-href', 'default/favicon');
-      document.body.appendChild(favicon);
-    });
-
-    afterEach(() => {
-      document.body.removeChild(document.getElementById('favicon'));
-    });
-
-    it('should reset page favicon to the default icon', () => {
-      const favicon = document.getElementById('favicon');
-      favicon.setAttribute('href', 'new/favicon');
-      commonUtils.resetFavicon();
-
-      expect(document.getElementById('favicon').getAttribute('href')).toEqual('default/favicon');
-    });
-  });
-
   describe('spriteIcon', () => {
     let beforeGon;
 
diff --git a/spec/frontend/lib/utils/favicon_ci_spec.js b/spec/frontend/lib/utils/favicon_ci_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..e35b008b862ffe47735c04630d827f3d75a3f93c
--- /dev/null
+++ b/spec/frontend/lib/utils/favicon_ci_spec.js
@@ -0,0 +1,50 @@
+import MockAdapter from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
+import { setFaviconOverlay, resetFavicon } from '~/lib/utils/favicon';
+import { setCiStatusFavicon } from '~/lib/utils/favicon_ci';
+
+jest.mock('~/lib/utils/favicon');
+
+const TEST_URL = '/test/pipelinable/1';
+const TEST_FAVICON = '/favicon.test.ico';
+
+describe('~/lib/utils/favicon_ci', () => {
+  let mock;
+
+  beforeEach(() => {
+    mock = new MockAdapter(axios);
+  });
+
+  afterEach(() => {
+    mock.restore();
+    mock = null;
+  });
+
+  describe('setCiStatusFavicon', () => {
+    it.each`
+      response                     | setFaviconOverlayCalls | resetFaviconCalls
+      ${{}}                        | ${[]}                  | ${[[]]}
+      ${{ favicon: TEST_FAVICON }} | ${[[TEST_FAVICON]]}    | ${[]}
+    `(
+      'with response=$response',
+      async ({ response, setFaviconOverlayCalls, resetFaviconCalls }) => {
+        mock.onGet(TEST_URL).replyOnce(200, response);
+
+        expect(setFaviconOverlay).not.toHaveBeenCalled();
+        expect(resetFavicon).not.toHaveBeenCalled();
+
+        await setCiStatusFavicon(TEST_URL);
+
+        expect(setFaviconOverlay.mock.calls).toEqual(setFaviconOverlayCalls);
+        expect(resetFavicon.mock.calls).toEqual(resetFaviconCalls);
+      },
+    );
+
+    it('with error', async () => {
+      mock.onGet(TEST_URL).replyOnce(500);
+
+      await expect(setCiStatusFavicon(TEST_URL)).rejects.toEqual(expect.any(Error));
+      expect(resetFavicon).toHaveBeenCalled();
+    });
+  });
+});
diff --git a/spec/frontend/lib/utils/favicon_spec.js b/spec/frontend/lib/utils/favicon_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..1b986432b8a4ab6a6e06b24144f62b14641e25d7
--- /dev/null
+++ b/spec/frontend/lib/utils/favicon_spec.js
@@ -0,0 +1,39 @@
+import { FaviconOverlayManager } from '@gitlab/favicon-overlay';
+import * as faviconUtils from '~/lib/utils/favicon';
+
+jest.mock('@gitlab/favicon-overlay');
+
+describe('~/lib/utils/favicon', () => {
+  afterEach(() => {
+    faviconUtils.clearMemoizeCache();
+  });
+
+  describe.each`
+    fnName                 | managerFn                                    | args
+    ${'setFaviconOverlay'} | ${FaviconOverlayManager.setFaviconOverlay}   | ${['test']}
+    ${'resetFavicon'}      | ${FaviconOverlayManager.resetFaviconOverlay} | ${[]}
+  `('$fnName', ({ fnName, managerFn, args }) => {
+    const call = () => faviconUtils[fnName](...args);
+
+    it('initializes only once when called', async () => {
+      expect(FaviconOverlayManager.initialize).not.toHaveBeenCalled();
+
+      // Call twice so we can make sure initialize is only called once
+      await call();
+      await call();
+
+      expect(FaviconOverlayManager.initialize).toHaveBeenCalledWith({
+        faviconSelector: '#favicon',
+      });
+      expect(FaviconOverlayManager.initialize).toHaveBeenCalledTimes(1);
+    });
+
+    it('passes call to manager', async () => {
+      expect(managerFn).not.toHaveBeenCalled();
+
+      await call();
+
+      expect(managerFn).toHaveBeenCalledWith(...args);
+    });
+  });
+});
diff --git a/spec/frontend/lib/utils/mock_data.js b/spec/frontend/lib/utils/mock_data.js
index c466b0cd1ed319ea040d12752673dbfed4ec77ef..df1f79529e732f39e93772a0e6736094c2451401 100644
--- a/spec/frontend/lib/utils/mock_data.js
+++ b/spec/frontend/lib/utils/mock_data.js
@@ -3,6 +3,3 @@ export const faviconDataUrl =
 
 export const overlayDataUrl =
   'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAA85JREFUWAntVllIVGEUPv/9b46O41KplYN7PeRkti8TjQlhCUGh3MmeQugpIsGKAi2soIcIooiohxYKK2daqDAlIpIiWwxtQaJcaHE0d5tMrbn37z9XRqfR0TvVW56Hudf//uec72zfEWBCJjIwkYGJDPzvGSD/KgExN3Oi2Q+2DJgSDYQEMwItVGH1iZGmJw/Si1y+/PwVAMYYib22MYc/8hVQFgKDEfYoId0KYzagAQebsos/ewMZoeB9wdffcTYpQSaCTWHKoqSQaDk7zkIt0+aCUR8BelEHrf3dUNv9AcqbnsHtT5UKB/hTASh0SLYjnjb/CIDRJi0XiFAaJOpCD8zLpdb4NB66b1OfelthX815dtdRRfiti2aAXLvVLiMQ6olGyztGDkSo4JGGXk8/QFdGpYzpHG2GBQTDhtgVhPEaVbbVpvI6GJz22rv4TcAfrYI1x7Rj5MWWAppomKFVVb2302SFzUkZHAbkG+0b1+Gh77yNYjrmqnWTrLBLRxdvBWv8qlFujH/kYjJYyvLkj71t78zAUvzMAMnHhpN4zf9UREJhd8omyssxu1IgazQDwDnHUcNuH6vhPIE1fmuBzHt74Hn7W89jWGtcAjoaIDOFrdcMYJBkgOCoaRF0Lj0oglddDbCj6tRvKjphEpgjkzEQs2YAKsNxMzjn3nKurhzK+Ly7xe28ua8TwgMMcHJZnvvT0BPtEEKM4tDJ+C8GvIIk4ylINIXVZ0EUKJxYuh3mhCeokbudl6TtVc88dfBdLwbyaWB6zQCYQJpBYSrDGQxBQ/ZWRM2B+VNmQnVnHWx7elyNuL2/R336co7KyJR8CL9oLgEuFlREevWUkEl6uGwpVEG4FBm0OEf9N10NMgPlvWYAuNVwsWDKvcUNYsHUWTCZ13ysyFEXe6TO6aC8CUr9IiK+A05TQrc8yjwmxARHeeMAPlfQJw+AQRwu0YhL/GDXi9NwufG+S8dYkuYMqIb4SsWthotlNMOUCOM6r+G9cqXxPmd1dqrBav/o1zJy2l5/NUjJA/VORwYuFnOUaTQcPs9wMqwV++Xv8oADxKAcZ8nLPr8AoGW+xR6HSqYk3GodAz2QNj0V+Gr26dT9ASNH5239Pf0gktVNWZca8ZvfAFBprWS6hSu1pqt++Y0PD+WIwDAhIWQGtzvSHDbcodfFUFB9hg1Gjs5LXqIdFL+acFBl+FddqYwdxsWC3I70OvgfUaA65zhq2O2c8VxYcyIGFTVlXegYtvCXANCQZJMobjVcLMjtSK/IcEgyOOe8Ve5w7ryKDefp2P3+C/5ohv8HZmVLAAAAAElFTkSuQmCC';
-
-export const faviconWithOverlayDataUrl =
-  'data:image/png;base64,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';
diff --git a/spec/frontend/test_setup.js b/spec/frontend/test_setup.js
index 39ee8a3822ac2020c52cc5871413dca1887a9e38..1c786b32f90f88c795a0a0443121805d48c019f2 100644
--- a/spec/frontend/test_setup.js
+++ b/spec/frontend/test_setup.js
@@ -13,6 +13,9 @@ import './helpers/dom_shims';
 import './helpers/jquery';
 import '~/commons/bootstrap';
 
+// This module has some fairly decent visual test coverage in it's own repository.
+jest.mock('@gitlab/favicon-overlay');
+
 process.on('unhandledRejection', global.promiseRejectionHandler);
 
 setupManualMocks();
diff --git a/spec/frontend/vue_mr_widget/mr_widget_options_spec.js b/spec/frontend/vue_mr_widget/mr_widget_options_spec.js
index b40833851176e75973821af024d6d88d4354cb26..0344ac0ab685bfa222b643fc4f2e8fdbbf1d124c 100644
--- a/spec/frontend/vue_mr_widget/mr_widget_options_spec.js
+++ b/spec/frontend/vue_mr_widget/mr_widget_options_spec.js
@@ -7,6 +7,7 @@ import mrWidgetOptions from '~/vue_merge_request_widget/mr_widget_options.vue';
 import eventHub from '~/vue_merge_request_widget/event_hub';
 import notify from '~/lib/utils/notify';
 import SmartInterval from '~/smart_interval';
+import { setFaviconOverlay } from '~/lib/utils/favicon';
 import { stateKey } from '~/vue_merge_request_widget/stores/state_maps';
 import mockData from './mock_data';
 import { faviconDataUrl, overlayDataUrl } from '../lib/utils/mock_data';
@@ -14,6 +15,8 @@ import { SUCCESS } from '~/vue_merge_request_widget/components/deployment/consta
 
 jest.mock('~/smart_interval');
 
+jest.mock('~/lib/utils/favicon');
+
 const returnPromise = (data) =>
   new Promise((resolve) => {
     resolve({
@@ -421,21 +424,12 @@ describe('mrWidgetOptions', () => {
           document.body.removeChild(document.getElementById('favicon'));
         });
 
-        it('should call setFavicon method', (done) => {
+        it('should call setFavicon method', async () => {
           vm.mr.ciStatusFaviconPath = overlayDataUrl;
-          vm.setFaviconHelper()
-            .then(() => {
-              /*
-            It would be better if we'd could mock commonUtils.setFaviconURL
-            with a spy and test that it was called. We are doing the following
-            tests as a proxy to show that the function has been called
-            */
-              expect(faviconElement.getAttribute('href')).not.toEqual(null);
-              expect(faviconElement.getAttribute('href')).not.toEqual(overlayDataUrl);
-              expect(faviconElement.getAttribute('href')).not.toEqual(faviconDataUrl);
-            })
-            .then(done)
-            .catch(done.fail);
+
+          await vm.setFaviconHelper();
+
+          expect(setFaviconOverlay).toHaveBeenCalledWith(overlayDataUrl);
         });
 
         it('should not call setFavicon when there is no ciStatusFaviconPath', (done) => {
diff --git a/spec/javascripts/lib/utils/browser_spec.js b/spec/javascripts/lib/utils/browser_spec.js
index d219ccfacaa97a4046dbef95857eb5ed85a35820..f41fa2503b13fe1f26f699896cb5f44a686c5495 100644
--- a/spec/javascripts/lib/utils/browser_spec.js
+++ b/spec/javascripts/lib/utils/browser_spec.js
@@ -5,30 +5,8 @@
  * https://gitlab.com/groups/gitlab-org/-/epics/895#what-if-theres-a-karma-spec-which-is-simply-unmovable-to-jest-ie-it-is-dependent-on-a-running-browser-environment
  */
 
-import MockAdapter from 'axios-mock-adapter';
 import { GlBreakpointInstance as breakpointInstance } from '@gitlab/ui/dist/utils';
-import axios from '~/lib/utils/axios_utils';
 import * as commonUtils from '~/lib/utils/common_utils';
-import { faviconDataUrl, overlayDataUrl, faviconWithOverlayDataUrl } from './mock_data';
-
-const PIXEL_TOLERANCE = 0.2;
-
-/**
- * Loads a data URL as the src of an
- * {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/Image|Image}
- * and resolves to that Image once loaded.
- *
- * @param url
- * @returns {Promise}
- */
-const urlToImage = (url) =>
-  new Promise((resolve) => {
-    const img = new Image();
-    img.onload = function () {
-      resolve(img);
-    };
-    img.src = url;
-  });
 
 describe('common_utils browser specific specs', () => {
   describe('contentTop', () => {
@@ -63,90 +41,6 @@ describe('common_utils browser specific specs', () => {
     });
   });
 
-  describe('createOverlayIcon', () => {
-    it('should return the favicon with the overlay', (done) => {
-      commonUtils
-        .createOverlayIcon(faviconDataUrl, overlayDataUrl)
-        .then((url) => Promise.all([urlToImage(url), urlToImage(faviconWithOverlayDataUrl)]))
-        .then(([actual, expected]) => {
-          expect(actual).toImageDiffEqual(expected, PIXEL_TOLERANCE);
-          done();
-        })
-        .catch(done.fail);
-    });
-  });
-
-  describe('setFaviconOverlay', () => {
-    beforeEach(() => {
-      const favicon = document.createElement('link');
-      favicon.setAttribute('id', 'favicon');
-      favicon.setAttribute('data-original-href', faviconDataUrl);
-      document.body.appendChild(favicon);
-    });
-
-    afterEach(() => {
-      document.body.removeChild(document.getElementById('favicon'));
-    });
-
-    it('should set page favicon to provided favicon overlay', (done) => {
-      commonUtils
-        .setFaviconOverlay(overlayDataUrl)
-        .then(() => document.getElementById('favicon').getAttribute('href'))
-        .then((url) => Promise.all([urlToImage(url), urlToImage(faviconWithOverlayDataUrl)]))
-        .then(([actual, expected]) => {
-          expect(actual).toImageDiffEqual(expected, PIXEL_TOLERANCE);
-          done();
-        })
-        .catch(done.fail);
-    });
-  });
-
-  describe('setCiStatusFavicon', () => {
-    const BUILD_URL = `${gl.TEST_HOST}/frontend-fixtures/builds-project/-/jobs/1/status.json`;
-    let mock;
-
-    beforeEach(() => {
-      const favicon = document.createElement('link');
-      favicon.setAttribute('id', 'favicon');
-      favicon.setAttribute('href', 'null');
-      favicon.setAttribute('data-original-href', faviconDataUrl);
-      document.body.appendChild(favicon);
-      mock = new MockAdapter(axios);
-    });
-
-    afterEach(() => {
-      mock.restore();
-      document.body.removeChild(document.getElementById('favicon'));
-    });
-
-    it('should reset favicon in case of error', (done) => {
-      mock.onGet(BUILD_URL).replyOnce(500);
-
-      commonUtils.setCiStatusFavicon(BUILD_URL).catch(() => {
-        const favicon = document.getElementById('favicon');
-
-        expect(favicon.getAttribute('href')).toEqual(faviconDataUrl);
-        done();
-      });
-    });
-
-    it('should set page favicon to CI status favicon based on provided status', (done) => {
-      mock.onGet(BUILD_URL).reply(200, {
-        favicon: overlayDataUrl,
-      });
-
-      commonUtils
-        .setCiStatusFavicon(BUILD_URL)
-        .then(() => document.getElementById('favicon').getAttribute('href'))
-        .then((url) => Promise.all([urlToImage(url), urlToImage(faviconWithOverlayDataUrl)]))
-        .then(([actual, expected]) => {
-          expect(actual).toImageDiffEqual(expected, PIXEL_TOLERANCE);
-          done();
-        })
-        .catch(done.fail);
-    });
-  });
-
   describe('isInViewport', () => {
     let el;
 
diff --git a/yarn.lock b/yarn.lock
index 2d0b2dbd089cc8f06b805c89d7c0183ef20ec05f..4b981c4280e4bafc47a951c675ec027e9eb282cf 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -861,6 +861,11 @@
     eslint-plugin-vue "^6.2.1"
     vue-eslint-parser "^7.0.0"
 
+"@gitlab/favicon-overlay@2.0.0":
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/@gitlab/favicon-overlay/-/favicon-overlay-2.0.0.tgz#2f32d0b6a4d5b8ac44e2927083d9ab478a78c984"
+  integrity sha512-GNcORxXJ98LVGzOT9dDYKfbheqH6lNgPDD72lyXRnQIH7CjgGyos8i17aSBPq1f4s3zF3PyedFiAR4YEZbva2Q==
+
 "@gitlab/svgs@1.178.0":
   version "1.178.0"
   resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.178.0.tgz#069edb8abb4c7137d48f527592476655f066538b"