From e6171e25d60c38a344caa21b0d2b633deae4f836 Mon Sep 17 00:00:00 2001
From: DanielCliftonGuardian
<110032454+DanielCliftonGuardian@users.noreply.github.com>
Date: Thu, 28 May 2026 13:11:40 +0100
Subject: [PATCH 1/3] Remove deprecated ab test code
---
dotcom-rendering/package.json | 1 -
.../src/components/AdmiralScript.island.tsx | 4 +-
.../src/components/ArticlePage.tsx | 7 -
.../components/DirectoryPageNav.island.tsx | 4 +-
.../components/DirectoryPageNav.stories.tsx | 8 +-
.../EmailSignUpWrapper.island.test.tsx | 14 +-
.../components/EmailSignUpWrapper.island.tsx | 4 +-
.../components/EmailSignUpWrapper.stories.tsx | 6 +-
.../EnhanceAffiliateLinks.island.tsx | 4 +-
.../components/EnhanceAffiliateLinks.test.tsx | 6 +-
dotcom-rendering/src/components/FrontPage.tsx | 5 -
.../src/components/HostedContentPage.tsx | 7 -
.../src/components/Island.test.tsx | 9 +-
.../src/components/LiveBlogEpic.island.tsx | 4 +-
.../LiveblogGutterAskWrapper.island.tsx | 4 +-
.../src/components/Metrics.island.tsx | 32 ++---
.../components/MostViewedFooter.island.tsx | 6 -
.../MostViewedFooterData.island.tsx | 21 ---
.../src/components/SetABTests.island.tsx | 121 ++---------------
.../src/components/SlotBodyEnd.island.tsx | 4 +-
.../src/components/SportDataPageComponent.tsx | 7 -
.../components/StickyBottomBanner.island.tsx | 4 +-
dotcom-rendering/src/components/TagPage.tsx | 7 -
.../src/components/TopBarSupport.island.tsx | 4 +-
.../YoutubeBlockComponent.island.tsx | 4 +-
dotcom-rendering/src/experiments/ab-tests.ts | 8 --
.../experiments/lib/ab-participations.test.ts | 37 ------
.../src/experiments/lib/ab-participations.ts | 28 ----
...beta-ab-tests.test.ts => ab-tests.test.ts} | 125 ++++++++----------
.../lib/{beta-ab-tests.ts => ab-tests.ts} | 8 +-
.../src/experiments/tests/ab-test-test.ts | 38 ------
.../tests/no-auxia-sign-in-gate.ts | 32 -----
dotcom-rendering/src/experiments/utils.ts | 7 -
dotcom-rendering/src/layouts/AudioLayout.tsx | 4 +-
.../src/layouts/CommentLayout.tsx | 4 +-
dotcom-rendering/src/layouts/FrontLayout.tsx | 4 +-
.../src/layouts/GalleryLayout.tsx | 4 +-
dotcom-rendering/src/layouts/LiveLayout.tsx | 4 +-
.../src/layouts/PictureLayout.tsx | 4 +-
.../src/layouts/ShowcaseLayout.tsx | 4 +-
.../src/layouts/SportDataPageLayout.tsx | 4 +-
.../src/layouts/StandardLayout.tsx | 4 +-
.../src/layouts/TagPageLayout.tsx | 4 +-
.../src/lib/affiliateLinksUtils.ts | 2 +-
dotcom-rendering/src/lib/getABUrlHash.test.ts | 40 ------
dotcom-rendering/src/lib/getAbUrlHash.ts | 23 ----
dotcom-rendering/src/lib/useAB.ts | 28 +---
.../src/model/enhance-switches.ts | 33 -----
48 files changed, 146 insertions(+), 600 deletions(-)
delete mode 100644 dotcom-rendering/src/experiments/ab-tests.ts
delete mode 100644 dotcom-rendering/src/experiments/lib/ab-participations.test.ts
delete mode 100644 dotcom-rendering/src/experiments/lib/ab-participations.ts
rename dotcom-rendering/src/experiments/lib/{beta-ab-tests.test.ts => ab-tests.test.ts} (75%)
rename dotcom-rendering/src/experiments/lib/{beta-ab-tests.ts => ab-tests.ts} (93%)
delete mode 100644 dotcom-rendering/src/experiments/tests/ab-test-test.ts
delete mode 100644 dotcom-rendering/src/experiments/tests/no-auxia-sign-in-gate.ts
delete mode 100644 dotcom-rendering/src/experiments/utils.ts
delete mode 100644 dotcom-rendering/src/lib/getABUrlHash.test.ts
delete mode 100644 dotcom-rendering/src/lib/getAbUrlHash.ts
delete mode 100644 dotcom-rendering/src/model/enhance-switches.ts
diff --git a/dotcom-rendering/package.json b/dotcom-rendering/package.json
index a7a1ae8ddf7..6eb2c40aa47 100644
--- a/dotcom-rendering/package.json
+++ b/dotcom-rendering/package.json
@@ -27,7 +27,6 @@
"@emotion/cache": "11.14.0",
"@emotion/react": "11.14.0",
"@emotion/server": "11.11.0",
- "@guardian/ab-core": "8.0.0",
"@guardian/ab-testing-config": "workspace:ab-testing-config",
"@guardian/braze-components": "22.2.0",
"@guardian/bridget": "8.11.0",
diff --git a/dotcom-rendering/src/components/AdmiralScript.island.tsx b/dotcom-rendering/src/components/AdmiralScript.island.tsx
index 5ea1c936aa7..3f37973591e 100644
--- a/dotcom-rendering/src/components/AdmiralScript.island.tsx
+++ b/dotcom-rendering/src/components/AdmiralScript.island.tsx
@@ -4,7 +4,7 @@ import { cmp } from '@guardian/consent-manager';
import { getCookie, log } from '@guardian/libs';
import { useEffect } from 'react';
import { getOphan } from '../client/ophan/ophan';
-import { useBetaAB } from '../lib/useAB';
+import { useAB } from '../lib/useAB';
import { useConfig } from './ConfigContext';
const testName = 'growth-admiral-adblock-detect';
@@ -210,7 +210,7 @@ const setUpAdmiralEventLogger = (
export const AdmiralScript = () => {
const { renderingTarget } = useConfig();
- const abTests = useBetaAB();
+ const abTests = useAB();
const isInVariantDetectGroup =
abTests?.isUserInTestGroup(testName, 'variant-detect') ?? false;
const variantName = isInVariantDetectGroup
diff --git a/dotcom-rendering/src/components/ArticlePage.tsx b/dotcom-rendering/src/components/ArticlePage.tsx
index 597529fb2be..13983caa98c 100644
--- a/dotcom-rendering/src/components/ArticlePage.tsx
+++ b/dotcom-rendering/src/components/ArticlePage.tsx
@@ -4,7 +4,6 @@ import { DecideLayout } from '../layouts/DecideLayout';
import { buildAdTargeting } from '../lib/ad-targeting';
import { ArticleDesign } from '../lib/articleFormat';
import { rootStyles } from '../lib/rootStyles';
-import { filterABTestSwitches } from '../model/enhance-switches';
import type { NavType } from '../model/extract-nav';
import type { Article } from '../types/article';
import type { RenderingTarget } from '../types/renderingTarget';
@@ -132,12 +131,6 @@ export const ArticlePage = (props: WebProps | AppProps) => {
{
const isApps = renderingTarget === 'Apps';
- const ab = useBetaAB();
+ const ab = useAB();
const config = configs.find(
(cfg) =>
diff --git a/dotcom-rendering/src/components/DirectoryPageNav.stories.tsx b/dotcom-rendering/src/components/DirectoryPageNav.stories.tsx
index dbd4423b59e..0fe4ec1970d 100644
--- a/dotcom-rendering/src/components/DirectoryPageNav.stories.tsx
+++ b/dotcom-rendering/src/components/DirectoryPageNav.stories.tsx
@@ -1,17 +1,17 @@
import { allModes } from '../../.storybook/modes';
import preview from '../../.storybook/preview';
-import { BetaABTests } from '../experiments/lib/beta-ab-tests';
-import { setBetaABTests } from '../lib/useAB';
+import { ABTests } from '../experiments/lib/ab-tests';
+import { setABTests } from '../lib/useAB';
import { ConfigProvider } from './ConfigContext';
import { DirectoryPageNav } from './DirectoryPageNav.island';
-const mockAB = new BetaABTests({
+const mockAB = new ABTests({
isServer: true,
serverSideABTests: {
'webx-world-cup-2026-subnav': 'enable',
},
});
-setBetaABTests(mockAB);
+setABTests(mockAB);
const meta = preview.meta({
component: DirectoryPageNav,
diff --git a/dotcom-rendering/src/components/EmailSignUpWrapper.island.test.tsx b/dotcom-rendering/src/components/EmailSignUpWrapper.island.test.tsx
index 4042f55f4a5..edc7daa6bc4 100644
--- a/dotcom-rendering/src/components/EmailSignUpWrapper.island.test.tsx
+++ b/dotcom-rendering/src/components/EmailSignUpWrapper.island.test.tsx
@@ -5,7 +5,7 @@ import {
AB_TEST_NAME,
NEWSLETTER_SIGNUP_COMPONENT_ID,
} from '../lib/newsletterSignupTracking';
-import { useBetaAB } from '../lib/useAB';
+import { useAB } from '../lib/useAB';
import { useIsSignedIn } from '../lib/useAuthStatus';
import { useNewsletterSubscription } from '../lib/useNewsletterSubscription';
import { ConfigProvider } from './ConfigContext';
@@ -16,7 +16,7 @@ jest.mock('../client/ophan/ophan', () => ({
}));
jest.mock('../lib/useAB', () => ({
- useBetaAB: jest.fn(),
+ useAB: jest.fn(),
}));
jest.mock('../lib/useAuthStatus', () => ({
@@ -87,7 +87,7 @@ const renderWrapper = (props = {}, renderingTarget: 'Web' | 'Apps' = 'Web') =>
);
const mockAbTests = (isInVariant: boolean) => {
- (useBetaAB as jest.Mock).mockReturnValue({
+ (useAB as jest.Mock).mockReturnValue({
isUserInTestGroup: (_testName: string, group: string) =>
group === 'variant' ? isInVariant : !isInVariant,
});
@@ -97,7 +97,7 @@ describe('EmailSignUpWrapper', () => {
beforeEach(() => {
jest.resetAllMocks();
// Default: AB API not yet hydrated
- (useBetaAB as jest.Mock).mockReturnValue(undefined);
+ (useAB as jest.Mock).mockReturnValue(undefined);
(useIsSignedIn as jest.Mock).mockReturnValue(false);
(useNewsletterSubscription as jest.Mock).mockReturnValue(false);
});
@@ -137,7 +137,7 @@ describe('EmailSignUpWrapper', () => {
describe('flag on (showNewNewsletterSignupCard = true)', () => {
it('shows a placeholder when AB API has not hydrated yet', () => {
- // useBetaAB returns undefined before hydration — component shows
+ // useAB returns undefined before hydration — component shows
// Placeholder rather than committing to control or variant early.
// (default set in outer beforeEach, no override needed)
renderWrapper({ showNewNewsletterSignupCard: true });
@@ -187,7 +187,7 @@ describe('EmailSignUpWrapper', () => {
describe('Apps rendering target', () => {
it('renders the legacy EmailSignup without waiting for AB to resolve', () => {
- // useBetaAB remains undefined (AB framework never initialises on Apps)
+ // useAB remains undefined (AB framework never initialises on Apps)
// but the component should not block on it
renderWrapper({ showNewNewsletterSignupCard: true }, 'Apps');
expect(screen.getByTestId('email-signup')).toBeInTheDocument();
@@ -280,7 +280,7 @@ describe('EmailSignUpWrapper', () => {
});
it('does not fire a VIEW event while the AB client has not hydrated', () => {
- // useBetaAB returns undefined — default set in beforeEach
+ // useAB returns undefined — default set in beforeEach
renderWrapper({ showNewNewsletterSignupCard: true });
expect(submitComponentEvent).not.toHaveBeenCalled();
diff --git a/dotcom-rendering/src/components/EmailSignUpWrapper.island.tsx b/dotcom-rendering/src/components/EmailSignUpWrapper.island.tsx
index c5abb3c3c7e..6ae941da135 100644
--- a/dotcom-rendering/src/components/EmailSignUpWrapper.island.tsx
+++ b/dotcom-rendering/src/components/EmailSignUpWrapper.island.tsx
@@ -5,7 +5,7 @@ import {
NEWSLETTER_SIGNUP_COMPONENT_ID,
sendNewsletterSignupEvent,
} from '../lib/newsletterSignupTracking';
-import { useBetaAB } from '../lib/useAB';
+import { useAB } from '../lib/useAB';
import { useIsSignedIn } from '../lib/useAuthStatus';
import { useNewsletterSubscription } from '../lib/useNewsletterSubscription';
import { useConfig } from './ConfigContext';
@@ -78,7 +78,7 @@ export const EmailSignUpWrapper = ({
const abTestEnabled =
showNewNewsletterSignupCard && renderingTarget === 'Web';
- const abTests = useBetaAB();
+ const abTests = useAB();
const abResolved = abTests !== undefined;
const isVariant =
diff --git a/dotcom-rendering/src/components/EmailSignUpWrapper.stories.tsx b/dotcom-rendering/src/components/EmailSignUpWrapper.stories.tsx
index dc3849246eb..d862436a73a 100644
--- a/dotcom-rendering/src/components/EmailSignUpWrapper.stories.tsx
+++ b/dotcom-rendering/src/components/EmailSignUpWrapper.stories.tsx
@@ -2,14 +2,14 @@ import type { StoryObj } from '@storybook/react-webpack5';
import { mocked, within } from 'storybook/test';
import preview from '../../.storybook/preview';
import { lazyFetchEmailWithTimeout } from '../lib/fetchEmail';
-import { useBetaAB } from '../lib/useAB';
+import { useAB } from '../lib/useAB';
import { useIsSignedIn } from '../lib/useAuthStatus';
import { useNewsletterSubscription } from '../lib/useNewsletterSubscription';
import { EmailSignUpWrapper } from './EmailSignUpWrapper.island';
-/** Resolves `useBetaAB` as if the AB framework has hydrated, placing the user in control or variant. */
+/** Resolves `useAB` as if the AB framework has hydrated, placing the user in control or variant. */
const mockBetaAB = (isInVariant: boolean) => {
- mocked(useBetaAB).mockReturnValue({
+ mocked(useAB).mockReturnValue({
isUserInTestGroup: (_testName: string, group: string) =>
group === 'variant' ? isInVariant : !isInVariant,
isUserInTest: () => true,
diff --git a/dotcom-rendering/src/components/EnhanceAffiliateLinks.island.tsx b/dotcom-rendering/src/components/EnhanceAffiliateLinks.island.tsx
index da939f40cce..2ed24b574a5 100644
--- a/dotcom-rendering/src/components/EnhanceAffiliateLinks.island.tsx
+++ b/dotcom-rendering/src/components/EnhanceAffiliateLinks.island.tsx
@@ -1,7 +1,7 @@
import { useEffect } from 'react';
import { buildXcustParamForAffiliateLink } from '../lib/affiliateLinksUtils';
import { safeParseURL } from '../lib/parse';
-import { useBetaAB } from '../lib/useAB';
+import { useAB } from '../lib/useAB';
/**
* Add custom parameters to skimlink URLs:
@@ -44,7 +44,7 @@ const utmKeys = [
];
export const EnhanceAffiliateLinks = () => {
- const abTests = useBetaAB();
+ const abTests = useAB();
// Get users server & client-side AB test participations
const abTestParticipations = abTests?.getParticipations();
diff --git a/dotcom-rendering/src/components/EnhanceAffiliateLinks.test.tsx b/dotcom-rendering/src/components/EnhanceAffiliateLinks.test.tsx
index f61005f82e1..82a212c222c 100644
--- a/dotcom-rendering/src/components/EnhanceAffiliateLinks.test.tsx
+++ b/dotcom-rendering/src/components/EnhanceAffiliateLinks.test.tsx
@@ -1,11 +1,11 @@
import '@testing-library/jest-dom';
import { render } from '@testing-library/react';
-import { useBetaAB } from '../lib/useAB';
+import { useAB } from '../lib/useAB';
import { EnhanceAffiliateLinks } from './EnhanceAffiliateLinks.island';
// Mock the useAB module
jest.mock('../lib/useAB', () => ({
- useBetaAB: jest.fn(),
+ useAB: jest.fn(),
}));
describe('EnhanceAffiliateLinks', () => {
@@ -67,7 +67,7 @@ describe('EnhanceAffiliateLinks', () => {
Skimlink
`;
- (useBetaAB as jest.Mock).mockReturnValue({
+ (useAB as jest.Mock).mockReturnValue({
getParticipations: () => ({
test1: 'variantA',
test2: 'variantB',
diff --git a/dotcom-rendering/src/components/FrontPage.tsx b/dotcom-rendering/src/components/FrontPage.tsx
index 7d8eb839775..d35002e01a0 100644
--- a/dotcom-rendering/src/components/FrontPage.tsx
+++ b/dotcom-rendering/src/components/FrontPage.tsx
@@ -4,7 +4,6 @@ import { FrontLayout } from '../layouts/FrontLayout';
import { buildAdTargeting } from '../lib/ad-targeting';
import { ArticleDesign, ArticleDisplay, Pillar } from '../lib/articleFormat';
import { rootStyles } from '../lib/rootStyles';
-import { filterABTestSwitches } from '../model/enhance-switches';
import type { NavType } from '../model/extract-nav';
import type { Front } from '../types/front';
import { AdmiralScript } from './AdmiralScript.island';
@@ -81,10 +80,6 @@ export const FrontPage = ({ front, NAV }: Props) => {
diff --git a/dotcom-rendering/src/components/HostedContentPage.tsx b/dotcom-rendering/src/components/HostedContentPage.tsx
index 54c950dd89d..c9029d32598 100644
--- a/dotcom-rendering/src/components/HostedContentPage.tsx
+++ b/dotcom-rendering/src/components/HostedContentPage.tsx
@@ -5,7 +5,6 @@ import { HostedGalleryLayout } from '../layouts/HostedGalleryLayout';
import { HostedVideoLayout } from '../layouts/HostedVideoLayout';
import { ArticleDesign } from '../lib/articleFormat';
import { rootStyles } from '../lib/rootStyles';
-import { filterABTestSwitches } from '../model/enhance-switches';
import type { Article } from '../types/article';
import type { RenderingTarget } from '../types/renderingTarget';
import { AlreadyVisited } from './AlreadyVisited.island';
@@ -116,12 +115,6 @@ export const HostedContentPage = (props: WebProps | AppProps) => {
{
expect(() =>
renderToString(
-
- ,
+ ,
,
),
).not.toThrow();
diff --git a/dotcom-rendering/src/components/LiveBlogEpic.island.tsx b/dotcom-rendering/src/components/LiveBlogEpic.island.tsx
index c27af892d4b..708bbbf4035 100644
--- a/dotcom-rendering/src/components/LiveBlogEpic.island.tsx
+++ b/dotcom-rendering/src/components/LiveBlogEpic.island.tsx
@@ -17,7 +17,7 @@ import {
shouldHideSupportMessaging,
useHasOptedOutOfArticleCount,
} from '../lib/contributions';
-import { useBetaAB } from '../lib/useAB';
+import { useAB } from '../lib/useAB';
import { useIsSignedIn } from '../lib/useAuthStatus';
import { useCountryCode } from '../lib/useCountryCode';
import { usePageViewId } from '../lib/usePageViewId';
@@ -109,7 +109,7 @@ const usePayload = ({
const countryCode = useCountryCode('liveblog-epic');
const mvtId = useMvtId();
const isSignedIn = useIsSignedIn();
- const abTests = useBetaAB();
+ const abTests = useAB();
if (isSignedIn === 'Pending') {
return;
diff --git a/dotcom-rendering/src/components/LiveblogGutterAskWrapper.island.tsx b/dotcom-rendering/src/components/LiveblogGutterAskWrapper.island.tsx
index c3467cb5422..ddbf8b1454d 100644
--- a/dotcom-rendering/src/components/LiveblogGutterAskWrapper.island.tsx
+++ b/dotcom-rendering/src/components/LiveblogGutterAskWrapper.island.tsx
@@ -15,7 +15,7 @@ import type { Tracking } from '@guardian/support-dotcom-components/dist/shared/t
import { useEffect, useState } from 'react';
import { submitComponentEvent } from '../client/ophan/ophan';
import { shouldHideSupportMessaging } from '../lib/contributions';
-import { useBetaAB } from '../lib/useAB';
+import { useAB } from '../lib/useAB';
import { useIsSignedIn } from '../lib/useAuthStatus';
import { useCountryCode } from '../lib/useCountryCode';
import { usePageViewId } from '../lib/usePageViewId';
@@ -48,7 +48,7 @@ const LiveblogGutterAskBuilder = ({
const { renderingTarget } = useConfig();
const isSignedIn = useIsSignedIn();
- const abTests = useBetaAB();
+ const abTests = useAB();
// get gutter props
useEffect((): void => {
diff --git a/dotcom-rendering/src/components/Metrics.island.tsx b/dotcom-rendering/src/components/Metrics.island.tsx
index 59c49acd4a6..16ad6c9019e 100644
--- a/dotcom-rendering/src/components/Metrics.island.tsx
+++ b/dotcom-rendering/src/components/Metrics.island.tsx
@@ -1,4 +1,3 @@
-import type { ABTest, ABTestAPI } from '@guardian/ab-core';
import { activeABtests } from '@guardian/ab-testing-config';
import {
bypassCommercialMetricsSampling,
@@ -11,7 +10,7 @@ import {
} from '@guardian/core-web-vitals';
import { isUndefined } from '@guardian/libs';
import { useCallback, useEffect, useState } from 'react';
-import { useAB, useBetaAB } from '../lib/useAB';
+import { useAB } from '../lib/useAB';
import { useAdBlockInUse } from '../lib/useAdBlockInUse';
import { useBrowserId } from '../lib/useBrowserId';
import { useDetectAdBlock } from '../lib/useDetectAdBlock';
@@ -28,11 +27,6 @@ const sampling = 1 / 100;
/** defining this here allows to share this with other metrics */
const willRecordCoreWebVitals = Math.random() < sampling;
-// For these tests switch off sampling and collect metrics for 100% of views
-const clientSideTestsToForceMetrics: ABTest[] = [
- /* keep array multi-line */
-];
-
const shouldCollectMetricsForBetaTests = (userTestParticipations: string[]) => {
const userParticipationConfigs = activeABtests.filter((test) =>
userTestParticipations.includes(test.name),
@@ -73,8 +67,7 @@ const useDev = () => {
* (No visual story exists as this does not render anything)
*/
export const Metrics = ({ commercialMetricsEnabled, tests }: Props) => {
- const abTestApi = useAB()?.api;
- const betaABTest = useBetaAB();
+ const abTests = useAB();
const adBlockerInUse = useAdBlockInUse();
const detectedAdBlocker = useDetectAdBlock();
@@ -86,27 +79,24 @@ export const Metrics = ({ commercialMetricsEnabled, tests }: Props) => {
const userInServerSideTest = Object.keys(tests).length > 0;
- const userBetaParticipations = betaABTest?.getParticipations() ?? {};
+ const userBetaParticipations = abTests?.getParticipations() ?? {};
const collectBetaTestMetrics = shouldCollectMetricsForBetaTests(
Object.keys(userBetaParticipations),
);
const shouldBypassSampling = useCallback(
- (api: ABTestAPI) =>
+ () =>
willRecordCoreWebVitals ||
userInServerSideTest ||
- collectBetaTestMetrics ||
- clientSideTestsToForceMetrics.some((test) =>
- api.runnableTest(test),
- ),
+ collectBetaTestMetrics,
[userInServerSideTest, collectBetaTestMetrics],
);
useEffect(
function coreWebVitals() {
- if (isUndefined(abTestApi)) {
+ if (isUndefined(abTests)) {
return;
}
if (isUndefined(browserId)) {
@@ -119,7 +109,7 @@ export const Metrics = ({ commercialMetricsEnabled, tests }: Props) => {
return;
}
- const bypassSampling = shouldBypassSampling(abTestApi);
+ const bypassSampling = shouldBypassSampling();
/**
* We rely on `bypassSampling` rather than the built-in sampling,
@@ -139,7 +129,7 @@ export const Metrics = ({ commercialMetricsEnabled, tests }: Props) => {
void bypassCoreWebVitalsSampling('commercial');
}
},
- [abTestApi, browserId, isDev, pageViewId, shouldBypassSampling],
+ [abTests, browserId, isDev, pageViewId, shouldBypassSampling],
);
useEffect(
@@ -149,7 +139,7 @@ export const Metrics = ({ commercialMetricsEnabled, tests }: Props) => {
return;
}
- if (isUndefined(abTestApi)) {
+ if (isUndefined(abTests)) {
return;
}
if (isUndefined(adBlockerInUse)) {
@@ -165,7 +155,7 @@ export const Metrics = ({ commercialMetricsEnabled, tests }: Props) => {
return;
}
- const bypassSampling = shouldBypassSampling(abTestApi);
+ const bypassSampling = shouldBypassSampling();
// This is a new detection method we are trying, so we want to record it separately to `adBlockerInUse`
EventTimer.get().setProperty(
@@ -191,7 +181,7 @@ export const Metrics = ({ commercialMetricsEnabled, tests }: Props) => {
);
},
[
- abTestApi,
+ abTests,
adBlockerInUse,
detectedAdBlocker,
browserId,
diff --git a/dotcom-rendering/src/components/MostViewedFooter.island.tsx b/dotcom-rendering/src/components/MostViewedFooter.island.tsx
index c7fe9f77c00..da24eeea331 100644
--- a/dotcom-rendering/src/components/MostViewedFooter.island.tsx
+++ b/dotcom-rendering/src/components/MostViewedFooter.island.tsx
@@ -5,8 +5,6 @@ import { MostViewedFooterGrid } from './MostViewedFooterGrid';
type Props = {
tabs: TrailTabType[];
selectedColour?: string;
- abTestCypressDataAttr?: string;
- variantFromRunnable?: string;
sectionId?: string;
};
@@ -23,8 +21,6 @@ type Props = {
*/
export const MostViewedFooter = ({
tabs,
- abTestCypressDataAttr,
- variantFromRunnable,
sectionId,
selectedColour,
}: Props) => {
@@ -34,8 +30,6 @@ export const MostViewedFooter = ({
width: 100%;
`}
data-testid="mostviewed-footer"
- data-testid-ab-user-in-variant={abTestCypressDataAttr}
- data-testid-ab-runnable-test={variantFromRunnable}
data-link-name="most popular"
>
{
- // Example usage of AB Tests
- // Used in the Cypress tests as smoke test of the AB tests framework integration
- const ABTestAPI = useAB()?.api;
-
- let abTestCypressDataAttr = 'ab-test-not-in-test';
-
- if (ABTestAPI?.isUserInVariant('AbTestTest', 'control')) {
- abTestCypressDataAttr = 'ab-test-control';
- }
-
- if (ABTestAPI?.isUserInVariant('AbTestTest', 'variant')) {
- abTestCypressDataAttr = 'ab-test-variant';
- }
-
- const runnableTest = ABTestAPI?.runnableTest(abTestTest);
- const variantFromRunnable = runnableTest?.variantToRun.id ?? 'not-runnable';
-
const url = buildSectionUrl(ajaxUrl, edition, sectionId);
const { data, loading, error } = useApi<
MostViewedFooterPayloadType | FETrailTabType[]
@@ -90,8 +71,6 @@ export const MostViewedFooterData = ({
return (
diff --git a/dotcom-rendering/src/components/SetABTests.island.tsx b/dotcom-rendering/src/components/SetABTests.island.tsx
index 641c42c0afa..4d6e4995f20 100644
--- a/dotcom-rendering/src/components/SetABTests.island.tsx
+++ b/dotcom-rendering/src/components/SetABTests.island.tsx
@@ -1,65 +1,15 @@
-import type { CoreAPIConfig } from '@guardian/ab-core';
-import { AB } from '@guardian/ab-core';
-import { getCookie, isUndefined, log } from '@guardian/libs';
+import { log } from '@guardian/libs';
import { useEffect, useMemo, useState } from 'react';
import { getOphan } from '../client/ophan/ophan';
-import { tests } from '../experiments/ab-tests';
-import { runnableTestsToParticipations } from '../experiments/lib/ab-participations';
-import { BetaABTests } from '../experiments/lib/beta-ab-tests';
-import { getForcedParticipationsFromUrl } from '../lib/getAbUrlHash';
+import { ABTests } from '../experiments/lib/ab-tests';
import { isServer } from '../lib/isServer';
-import { setABTests, setBetaABTests } from '../lib/useAB';
-import type { ABTestSwitches } from '../model/enhance-switches';
-import type { ServerSideTests } from '../types/config';
+import { setABTests } from '../lib/useAB';
import { useConfig } from './ConfigContext';
type Props = {
- abTestSwitches: ABTestSwitches;
- forcedTestVariants?: CoreAPIConfig['forcedTestVariants'];
- isDev: boolean;
- pageIsSensitive: CoreAPIConfig['pageIsSensitive'];
- serverSideTests: ServerSideTests;
serverSideABTests: Record;
};
-const mvtMinValue = 1;
-const mvtMaxValue = 1_000_000;
-
-/** Parse a valid MVT ID between 1 and 1,000,000 or undefined if it fails */
-const parseMvtId = (id: string | null): number | undefined => {
- if (!id) {
- return;
- } // null or empty string
- const number = Number(id);
- if (Number.isNaN(number)) {
- return;
- }
- if (number < mvtMinValue) {
- return;
- }
- if (number > mvtMaxValue) {
- return;
- }
- return number;
-};
-
-const getMvtId = () =>
- parseMvtId(
- getCookie({
- name: 'GU_mvt_id',
- shouldMemoize: true,
- }),
- );
-
-/** Check if there is a local override */
-const getLocalMvtId = () =>
- parseMvtId(
- getCookie({
- name: 'GU_mvt_id_local',
- shouldMemoize: true,
- }),
- );
-
const errorReporter = (e: unknown) =>
window.guardian.modules.sentry.reportError(
e instanceof Error ? e : Error(String(e)),
@@ -77,14 +27,7 @@ const errorReporter = (e: unknown) =>
*
* Does not render **anything**.
*/
-export const SetABTests = ({
- isDev,
- pageIsSensitive,
- abTestSwitches,
- forcedTestVariants,
- serverSideTests,
- serverSideABTests,
-}: Props) => {
+export const SetABTests = ({ serverSideABTests }: Props) => {
const { renderingTarget } = useConfig();
const [ophan, setOphan] = useState>>();
@@ -99,8 +42,8 @@ export const SetABTests = ({
});
}, [renderingTarget]);
- const betaAb = useMemo(() => {
- const betaAB = new BetaABTests(
+ const ab = useMemo(() => {
+ const abTests = new ABTests(
isServer
? {
serverSideABTests,
@@ -108,8 +51,8 @@ export const SetABTests = ({
}
: { isServer: false },
);
- setBetaABTests(betaAB);
- return betaAB;
+ setABTests(abTests);
+ return abTests;
}, [serverSideABTests]);
useEffect(() => {
@@ -117,53 +60,11 @@ export const SetABTests = ({
return;
}
- const mvtId = isDev ? getLocalMvtId() ?? getMvtId() : getMvtId();
-
- if (isUndefined(mvtId)) {
- console.error('There is no MVT ID set, see SetABTests.island.tsx');
- }
-
- const allForcedTestVariants = {
- ...forcedTestVariants,
- ...getForcedParticipationsFromUrl(window.location.hash),
- };
-
- const ab = new AB({
- mvtId: mvtId ?? -1,
- mvtMaxValue,
- pageIsSensitive,
- abTestSwitches,
- arrayOfTestObjects: tests,
- forcedTestVariants: allForcedTestVariants,
- ophanRecord: ophan.record,
- serverSideTests,
- errorReporter,
- });
- const allRunnableTests = ab.allRunnableTests(tests);
- const participations = runnableTestsToParticipations(allRunnableTests);
-
- setABTests({
- api: ab,
- participations,
- });
-
- ab.trackABTests(allRunnableTests);
- ab.registerImpressionEvents(allRunnableTests);
- ab.registerCompleteEvents(allRunnableTests);
-
- betaAb.trackABTests(ophan.record, errorReporter);
+ ab.trackABTests(ophan.record, errorReporter);
log('dotcom', 'AB tests initialised');
- }, [
- abTestSwitches,
- forcedTestVariants,
- isDev,
- pageIsSensitive,
- ophan,
- serverSideTests,
- betaAb,
- ]);
+ }, [ab, ophan]);
- // we don’t render anything
+ // we don't render anything
return null;
};
diff --git a/dotcom-rendering/src/components/SlotBodyEnd.island.tsx b/dotcom-rendering/src/components/SlotBodyEnd.island.tsx
index d1bdfc9b08e..5015be74847 100644
--- a/dotcom-rendering/src/components/SlotBodyEnd.island.tsx
+++ b/dotcom-rendering/src/components/SlotBodyEnd.island.tsx
@@ -24,7 +24,7 @@ import type {
SlotConfig,
} from '../lib/messagePicker';
import { pickMessage } from '../lib/messagePicker';
-import { useBetaAB } from '../lib/useAB';
+import { useAB } from '../lib/useAB';
import { useIsSignedIn } from '../lib/useAuthStatus';
import { useBraze } from '../lib/useBraze';
import { useCountryCode } from '../lib/useCountryCode';
@@ -126,7 +126,7 @@ export const SlotBodyEnd = ({
const countryCode = useCountryCode('slot-body-end');
const isSignedIn = useIsSignedIn();
const ophanPageViewId = usePageViewId(renderingTarget);
- const abTests = useBetaAB();
+ const abTests = useAB();
const [pickMessageResult, setPickMessageResult] =
useState(null);
const [asyncArticleCount, setAsyncArticleCount] =
diff --git a/dotcom-rendering/src/components/SportDataPageComponent.tsx b/dotcom-rendering/src/components/SportDataPageComponent.tsx
index ce4656d2f75..09631c5fc3a 100644
--- a/dotcom-rendering/src/components/SportDataPageComponent.tsx
+++ b/dotcom-rendering/src/components/SportDataPageComponent.tsx
@@ -4,7 +4,6 @@ import { SportDataPageLayout } from '../layouts/SportDataPageLayout';
import { buildAdTargeting } from '../lib/ad-targeting';
import { ArticleDesign, ArticleDisplay, Pillar } from '../lib/articleFormat';
import { rootStyles } from '../lib/rootStyles';
-import { filterABTestSwitches } from '../model/enhance-switches';
import type { AppSportDataPage, WebSportDataPage } from '../sportDataPage';
import { AlreadyVisited } from './AlreadyVisited.island';
import { useConfig } from './ConfigContext';
@@ -76,12 +75,6 @@ export const SportDataPageComponent = (props: Props) => {
{
diff --git a/dotcom-rendering/src/components/TopBarSupport.island.tsx b/dotcom-rendering/src/components/TopBarSupport.island.tsx
index 6691dcf9612..e5a727243b9 100644
--- a/dotcom-rendering/src/components/TopBarSupport.island.tsx
+++ b/dotcom-rendering/src/components/TopBarSupport.island.tsx
@@ -22,7 +22,7 @@ import {
shouldHideSupportMessaging,
} from '../lib/contributions';
import { getHeader } from '../lib/sdcRequests';
-import { useBetaAB } from '../lib/useAB';
+import { useAB } from '../lib/useAB';
import { useIsSignedIn } from '../lib/useAuthStatus';
import { useCountryCode } from '../lib/useCountryCode';
import { usePageViewId } from '../lib/usePageViewId';
@@ -69,7 +69,7 @@ const ReaderRevenueLinksRemote = ({
const isSignedIn = useIsSignedIn();
const { renderingTarget } = useConfig();
- const abTests = useBetaAB();
+ const abTests = useAB();
useEffect((): void => {
if (isUndefined(countryCode) || isSignedIn === 'Pending') {
diff --git a/dotcom-rendering/src/components/YoutubeBlockComponent.island.tsx b/dotcom-rendering/src/components/YoutubeBlockComponent.island.tsx
index 76b1d4721c6..19dd76bdc3c 100644
--- a/dotcom-rendering/src/components/YoutubeBlockComponent.island.tsx
+++ b/dotcom-rendering/src/components/YoutubeBlockComponent.island.tsx
@@ -1,7 +1,7 @@
import type { ConsentState } from '@guardian/consent-manager';
import { useEffect, useState } from 'react';
import type { ArticleFormat } from '../lib/articleFormat';
-import { useBetaAB } from '../lib/useAB';
+import { useAB } from '../lib/useAB';
import { useAdTargeting } from '../lib/useAdTargeting';
import type { AdTargeting } from '../types/commercial';
import type { AspectRatio } from '../types/front';
@@ -103,7 +103,7 @@ export const YoutubeBlockComponent = ({
const adTargeting = useAdTargeting(duration);
const { renderingTarget } = useConfig();
- const abTests = useBetaAB();
+ const abTests = useAB();
const abTestParticipations = abTests?.getParticipations() ?? {};
/**
diff --git a/dotcom-rendering/src/experiments/ab-tests.ts b/dotcom-rendering/src/experiments/ab-tests.ts
deleted file mode 100644
index 1b5471e5cba..00000000000
--- a/dotcom-rendering/src/experiments/ab-tests.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import type { ABTest } from '@guardian/ab-core';
-import { abTestTest } from './tests/ab-test-test';
-import { noAuxiaSignInGate } from './tests/no-auxia-sign-in-gate';
-
-// keep in sync with ab-tests in frontend
-// https://github.com/guardian/frontend/tree/main/static/src/javascripts/projects/common/modules/experiments/ab-tests.ts
-
-export const tests: ABTest[] = [abTestTest, noAuxiaSignInGate];
diff --git a/dotcom-rendering/src/experiments/lib/ab-participations.test.ts b/dotcom-rendering/src/experiments/lib/ab-participations.test.ts
deleted file mode 100644
index 23b0e51838b..00000000000
--- a/dotcom-rendering/src/experiments/lib/ab-participations.test.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-import { isParticipations } from './ab-participations';
-
-describe('isParticipations validation', () => {
- test('Localstorage Participation data is validated correctly,', () => {
- const goodDatum: unknown = {
- foobar: { variant: 'foobar' },
- };
- const goodData: unknown = {
- foobar: { variant: 'foobar' },
- barfoo: { variant: 'barfoo' },
- };
-
- expect(isParticipations(goodDatum)).toBe(true);
- expect(isParticipations(goodData)).toBe(true);
- expect(
- isParticipations({
- foobar: { foobar: 'foobar' },
- }),
- ).toBe(false);
- expect(
- isParticipations({
- foobar: { variant: 1 },
- }),
- ).toBe(false);
- expect(
- isParticipations({
- foobar: 'abc',
- }),
- ).toBe(false);
- expect(
- isParticipations({
- 2: { foobar: 'foobar' },
- }),
- ).toBe(false);
- expect(isParticipations('anything')).toBe(false);
- });
-});
diff --git a/dotcom-rendering/src/experiments/lib/ab-participations.ts b/dotcom-rendering/src/experiments/lib/ab-participations.ts
deleted file mode 100644
index 536e0374a42..00000000000
--- a/dotcom-rendering/src/experiments/lib/ab-participations.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-import type { ABTest, Participations, Runnable } from '@guardian/ab-core';
-import { isObject, isString } from '@guardian/libs';
-
-const isParticipations = (
- participations: unknown,
-): participations is Participations =>
- isObject(participations) &&
- Object.values(participations).every(
- (participation) =>
- isObject(participation) && isString(participation.variant),
- );
-
-const runnableTestsToParticipations = (
- runnableTests: readonly Runnable[],
-): Participations =>
- runnableTests.reduce(
- (participations: Participations, { id: testId, variantToRun }) => ({
- ...participations,
- ...{
- [testId]: {
- variant: variantToRun.id,
- },
- },
- }),
- {},
- );
-
-export { isParticipations, runnableTestsToParticipations };
diff --git a/dotcom-rendering/src/experiments/lib/beta-ab-tests.test.ts b/dotcom-rendering/src/experiments/lib/ab-tests.test.ts
similarity index 75%
rename from dotcom-rendering/src/experiments/lib/beta-ab-tests.test.ts
rename to dotcom-rendering/src/experiments/lib/ab-tests.test.ts
index 796aa042942..9c8328f9d66 100644
--- a/dotcom-rendering/src/experiments/lib/beta-ab-tests.test.ts
+++ b/dotcom-rendering/src/experiments/lib/ab-tests.test.ts
@@ -1,4 +1,4 @@
-import { BetaABTests } from './beta-ab-tests';
+import { ABTests } from './ab-tests';
// Mock @guardian/libs
jest.mock('@guardian/libs', () => ({
@@ -12,8 +12,8 @@ jest.mock('../../client/abTesting', () => ({
getABTestParticipations: () => mockGetParticipations(),
}));
-describe('BetaABTests', () => {
- let betaABTests: BetaABTests;
+describe('ABTests', () => {
+ let abTests: ABTests;
let mockOphanRecord: jest.Mock;
let mockErrorReporter: jest.Mock;
@@ -33,12 +33,12 @@ describe('BetaABTests', () => {
mockGetParticipations.mockReturnValue(clientParticipations);
- betaABTests = new BetaABTests({
+ abTests = new ABTests({
isServer: false,
});
expect(mockGetParticipations).toHaveBeenCalled();
- expect(betaABTests.getParticipations()).toEqual(
+ expect(abTests.getParticipations()).toEqual(
clientParticipations,
);
});
@@ -53,11 +53,11 @@ describe('BetaABTests', () => {
serverTestB: 'serverVariantB',
};
mockGetParticipations.mockReturnValue(combinedParticipations);
- betaABTests = new BetaABTests({
+ abTests = new ABTests({
isServer: false,
});
- expect(betaABTests.getParticipations()).toEqual(
+ expect(abTests.getParticipations()).toEqual(
combinedParticipations,
);
});
@@ -72,15 +72,15 @@ describe('BetaABTests', () => {
serverTestB: 'serverVariantB',
};
mockGetParticipations.mockReturnValue(participations);
- betaABTests = new BetaABTests({
+ abTests = new ABTests({
isServer: false,
});
// Should work for both client and server tests
- expect(betaABTests.isUserInTest('clientTestA')).toBe(true);
- expect(betaABTests.isUserInTest('clientTestB')).toBe(true);
- expect(betaABTests.isUserInTest('serverTestA')).toBe(true);
- expect(betaABTests.isUserInTest('serverTestB')).toBe(true);
+ expect(abTests.isUserInTest('clientTestA')).toBe(true);
+ expect(abTests.isUserInTest('clientTestB')).toBe(true);
+ expect(abTests.isUserInTest('serverTestA')).toBe(true);
+ expect(abTests.isUserInTest('serverTestB')).toBe(true);
});
it('should return false when user is not in test (client-side)', () => {
@@ -89,12 +89,12 @@ describe('BetaABTests', () => {
testB: 'variantB',
};
mockGetParticipations.mockReturnValue(participations);
- betaABTests = new BetaABTests({
+ abTests = new ABTests({
isServer: false,
});
- expect(betaABTests.isUserInTest('testC')).toBe(false);
- expect(betaABTests.isUserInTest('nonExistentTest')).toBe(false);
+ expect(abTests.isUserInTest('testC')).toBe(false);
+ expect(abTests.isUserInTest('nonExistentTest')).toBe(false);
});
it('should return false when testId is undefined in client participations', () => {
@@ -102,11 +102,11 @@ describe('BetaABTests', () => {
testA: 'variantA',
};
mockGetParticipations.mockReturnValue(participations);
- betaABTests = new BetaABTests({
+ abTests = new ABTests({
isServer: false,
});
- expect(betaABTests.isUserInTest('testB')).toBe(false);
+ expect(abTests.isUserInTest('testB')).toBe(false);
});
});
@@ -120,34 +120,25 @@ describe('BetaABTests', () => {
serverTestB: 'control',
};
mockGetParticipations.mockReturnValue(participations);
- betaABTests = new BetaABTests({
+ abTests = new ABTests({
isServer: false,
});
// Should work for both client and server tests
expect(
- betaABTests.isUserInTestGroup(
- 'clientTestA',
- 'clientVariantA',
- ),
+ abTests.isUserInTestGroup('clientTestA', 'clientVariantA'),
).toBe(true);
expect(
- betaABTests.isUserInTestGroup(
- 'clientTestB',
- 'clientVariantB',
- ),
+ abTests.isUserInTestGroup('clientTestB', 'clientVariantB'),
).toBe(true);
expect(
- betaABTests.isUserInTestGroup('clientTestC', 'control'),
+ abTests.isUserInTestGroup('clientTestC', 'control'),
).toBe(true);
expect(
- betaABTests.isUserInTestGroup(
- 'serverTestA',
- 'serverVariantA',
- ),
+ abTests.isUserInTestGroup('serverTestA', 'serverVariantA'),
).toBe(true);
expect(
- betaABTests.isUserInTestGroup('serverTestB', 'control'),
+ abTests.isUserInTestGroup('serverTestB', 'control'),
).toBe(true);
});
@@ -158,17 +149,17 @@ describe('BetaABTests', () => {
testC: 'control',
};
mockGetParticipations.mockReturnValue(participations);
- betaABTests = new BetaABTests({
+ abTests = new ABTests({
isServer: false,
});
- expect(betaABTests.isUserInTestGroup('testA', 'variantB')).toBe(
+ expect(abTests.isUserInTestGroup('testA', 'variantB')).toBe(
false,
);
- expect(betaABTests.isUserInTestGroup('testB', 'control')).toBe(
+ expect(abTests.isUserInTestGroup('testB', 'control')).toBe(
false,
);
- expect(betaABTests.isUserInTestGroup('testC', 'variantA')).toBe(
+ expect(abTests.isUserInTestGroup('testC', 'variantA')).toBe(
false,
);
});
@@ -179,15 +170,12 @@ describe('BetaABTests', () => {
testB: 'variantB',
};
mockGetParticipations.mockReturnValue(participations);
- betaABTests = new BetaABTests({
+ abTests = new ABTests({
isServer: false,
});
expect(
- betaABTests.isUserInTestGroup(
- 'nonExistentTest',
- 'variantA',
- ),
+ abTests.isUserInTestGroup('nonExistentTest', 'variantA'),
).toBe(false);
});
});
@@ -201,7 +189,7 @@ describe('BetaABTests', () => {
serverTestB: 'serverVariantB',
};
- const serverInstance = new BetaABTests({
+ const serverInstance = new ABTests({
serverSideABTests,
isServer: true,
});
@@ -219,7 +207,7 @@ describe('BetaABTests', () => {
serverTestB: 'serverVariantB',
};
- const serverInstance = new BetaABTests({
+ const serverInstance = new ABTests({
serverSideABTests,
isServer: true,
});
@@ -233,7 +221,7 @@ describe('BetaABTests', () => {
serverTestA: 'serverVariantA',
};
- const serverInstance = new BetaABTests({
+ const serverInstance = new ABTests({
serverSideABTests,
isServer: true,
});
@@ -251,7 +239,7 @@ describe('BetaABTests', () => {
serverTestB: 'control',
};
- const serverInstance = new BetaABTests({
+ const serverInstance = new ABTests({
serverSideABTests,
isServer: true,
});
@@ -272,7 +260,7 @@ describe('BetaABTests', () => {
serverTestB: 'control',
};
- const serverInstance = new BetaABTests({
+ const serverInstance = new ABTests({
serverSideABTests,
isServer: true,
});
@@ -293,7 +281,7 @@ describe('BetaABTests', () => {
serverTestA: 'serverVariantA',
};
- const serverInstance = new BetaABTests({
+ const serverInstance = new ABTests({
serverSideABTests,
isServer: true,
});
@@ -314,11 +302,11 @@ describe('BetaABTests', () => {
testB: 'variantB',
};
mockGetParticipations.mockReturnValue(participations);
- betaABTests = new BetaABTests({ isServer: false });
+ abTests = new ABTests({ isServer: false });
});
it('should call ophanRecord with correct payload', () => {
- betaABTests.trackABTests(mockOphanRecord, mockErrorReporter);
+ abTests.trackABTests(mockOphanRecord, mockErrorReporter);
expect(mockOphanRecord).toHaveBeenCalledWith({
abTestRegister: {
@@ -337,9 +325,9 @@ describe('BetaABTests', () => {
it('should handle empty participations', () => {
mockGetParticipations.mockReturnValue({});
- betaABTests = new BetaABTests({ isServer: false });
+ abTests = new ABTests({ isServer: false });
- betaABTests.trackABTests(mockOphanRecord, mockErrorReporter);
+ abTests.trackABTests(mockOphanRecord, mockErrorReporter);
expect(mockOphanRecord).toHaveBeenCalledWith({
abTestRegister: {},
@@ -355,7 +343,7 @@ describe('BetaABTests', () => {
testB: 'variantB',
};
mockGetParticipations.mockReturnValue(participations);
- betaABTests = new BetaABTests({ isServer: false });
+ abTests = new ABTests({ isServer: false });
});
it('should handle errors and call errorReporter', () => {
@@ -365,7 +353,7 @@ describe('BetaABTests', () => {
throw new Error('Test error');
});
- betaABTests.trackABTests(mockOphanRecord, mockErrorReporter);
+ abTests.trackABTests(mockOphanRecord, mockErrorReporter);
expect(mockErrorReporter).toHaveBeenCalledWith(
new Error('Test error'),
@@ -386,9 +374,9 @@ describe('BetaABTests', () => {
},
};
mockGetParticipations.mockReturnValue(participationsWithError);
- betaABTests = new BetaABTests({ isServer: false });
+ abTests = new ABTests({ isServer: false });
- betaABTests.trackABTests(mockOphanRecord, mockErrorReporter);
+ abTests.trackABTests(mockOphanRecord, mockErrorReporter);
expect(mockErrorReporter).toHaveBeenCalledWith(
new Error('Test error'),
@@ -411,36 +399,33 @@ describe('BetaABTests', () => {
'ssr-experiment': 'control',
};
mockGetParticipations.mockReturnValue(participations);
- betaABTests = new BetaABTests({
+ abTests = new ABTests({
isServer: false,
});
// Test all methods together
- expect(betaABTests.getParticipations()).toEqual(participations);
+ expect(abTests.getParticipations()).toEqual(participations);
// Test client-side tests
- expect(betaABTests.isUserInTest('header-experiment')).toBe(true);
- expect(betaABTests.isUserInTest('non-existent-test')).toBe(false);
+ expect(abTests.isUserInTest('header-experiment')).toBe(true);
+ expect(abTests.isUserInTest('non-existent-test')).toBe(false);
expect(
- betaABTests.isUserInTestGroup('header-experiment', 'variant'),
+ abTests.isUserInTestGroup('header-experiment', 'variant'),
).toBe(true);
expect(
- betaABTests.isUserInTestGroup('header-experiment', 'control'),
+ abTests.isUserInTestGroup('header-experiment', 'control'),
).toBe(false);
// Test server-side tests that are available client-side
- expect(betaABTests.isUserInTest('server-side-test')).toBe(true);
+ expect(abTests.isUserInTest('server-side-test')).toBe(true);
expect(
- betaABTests.isUserInTestGroup(
- 'server-side-test',
- 'server-variant',
- ),
- ).toBe(true);
- expect(
- betaABTests.isUserInTestGroup('ssr-experiment', 'control'),
+ abTests.isUserInTestGroup('server-side-test', 'server-variant'),
).toBe(true);
+ expect(abTests.isUserInTestGroup('ssr-experiment', 'control')).toBe(
+ true,
+ );
- betaABTests.trackABTests(mockOphanRecord, mockErrorReporter);
+ abTests.trackABTests(mockOphanRecord, mockErrorReporter);
expect(mockOphanRecord).toHaveBeenCalledWith({
abTestRegister: {
diff --git a/dotcom-rendering/src/experiments/lib/beta-ab-tests.ts b/dotcom-rendering/src/experiments/lib/ab-tests.ts
similarity index 93%
rename from dotcom-rendering/src/experiments/lib/beta-ab-tests.ts
rename to dotcom-rendering/src/experiments/lib/ab-tests.ts
index d7369b8c231..e8154630036 100644
--- a/dotcom-rendering/src/experiments/lib/beta-ab-tests.ts
+++ b/dotcom-rendering/src/experiments/lib/ab-tests.ts
@@ -2,7 +2,7 @@ import { activeABtests } from '@guardian/ab-testing-config';
import { isUndefined } from '@guardian/libs';
import { getABTestParticipations } from '../../client/abTesting';
-export interface BetaABTestAPI {
+export interface ABTestAPI {
getParticipations: () => ABParticipations;
isUserInTest: (testId: string) => boolean;
isUserInTestGroup: (testId: string, groupId: string) => boolean;
@@ -28,7 +28,7 @@ type OphanRecordFunction = (send: Record) => void;
type ErrorReporter = (e: unknown) => void;
-type BetaABTestsConfig =
+type ABTestsConfig =
| {
isServer: true;
serverSideABTests: Record;
@@ -50,10 +50,10 @@ const makeABEvent = (variantName: string, complete: boolean): OphanABEvent => {
return event;
};
-export class BetaABTests implements BetaABTestAPI {
+export class ABTests implements ABTestAPI {
private participations: ABParticipations;
- constructor({ isServer, serverSideABTests }: BetaABTestsConfig) {
+ constructor({ isServer, serverSideABTests }: ABTestsConfig) {
if (isServer) {
this.participations = serverSideABTests;
} else {
diff --git a/dotcom-rendering/src/experiments/tests/ab-test-test.ts b/dotcom-rendering/src/experiments/tests/ab-test-test.ts
deleted file mode 100644
index 1f3864d9e84..00000000000
--- a/dotcom-rendering/src/experiments/tests/ab-test-test.ts
+++ /dev/null
@@ -1,38 +0,0 @@
-import type { ABTest } from '@guardian/ab-core';
-
-export const abTestTest: ABTest = {
- id: 'AbTestTest',
- start: '2020-05-20',
- expiry: '2023-12-01',
- author: 'gtrufitt',
- description: 'This Test',
- audience: 0.0001, // 0.01%
- audienceOffset: 0,
- successMeasure: 'It works',
- audienceCriteria: 'Everyone',
- idealOutcome: 'It works',
- showForSensitive: true,
- canRun: () => true,
- variants: [
- {
- id: 'control',
- test: (): void => {},
- impression: (impression: () => void): void => {
- impression();
- },
- success: (success: () => void): void => {
- success();
- },
- },
- {
- id: 'variant',
- test: (): void => {},
- impression: (impression: () => void): void => {
- impression();
- },
- success: (success: () => void): void => {
- success();
- },
- },
- ],
-};
diff --git a/dotcom-rendering/src/experiments/tests/no-auxia-sign-in-gate.ts b/dotcom-rendering/src/experiments/tests/no-auxia-sign-in-gate.ts
deleted file mode 100644
index 6ad633891d1..00000000000
--- a/dotcom-rendering/src/experiments/tests/no-auxia-sign-in-gate.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-import type { ABTest } from '@guardian/ab-core';
-
-/**
- * The requirement:
- * 1. keep a fixed 5% of the global audience excluded from Auxia (though they can still see gates)
- * 2. track participation of this 5% in the pageview table, regardless of whether they see gates
- *
- * The solution:
- * - On every article view, put 5% of the audience into a special NoAuxiaSignInGate AB test. This means the page tracks membership of the "test" through ophan. There are no variants in this test, it's just a way to track these browsers.
- * - The API call for the sign-in gate includes a flag indicating that the browser is in the NoAuxiaSignInGate test, and it excludes them from Auxia based on this.
- *
- * This enables us to query the pageview table for browsers in the NoAuxiaSignInGate group using the existing ab_test_array field.
- */
-export const noAuxiaSignInGate: ABTest = {
- id: 'NoAuxiaSignInGate',
- start: '2025-11-01',
- expiry: '2027-11-01',
- author: 'Growth Team',
- description:
- 'Defines a control group who should not have sign-in gate journeys handled by Auxia',
- audience: 0.05,
- audienceOffset: 0.95,
- audienceCriteria: 'All users',
- successMeasure: 'Control group for Auxia sign-in gate testing',
- canRun: () => true,
- variants: [
- {
- id: 'control',
- test: (): void => {},
- },
- ],
-};
diff --git a/dotcom-rendering/src/experiments/utils.ts b/dotcom-rendering/src/experiments/utils.ts
deleted file mode 100644
index f471a813c02..00000000000
--- a/dotcom-rendering/src/experiments/utils.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import { bypassCommercialMetricsSampling } from '@guardian/commercial-core';
-import { bypassCoreWebVitalsSampling } from '@guardian/core-web-vitals';
-
-export const bypassMetricsSampling = (): void => {
- void bypassCommercialMetricsSampling();
- void bypassCoreWebVitalsSampling();
-};
diff --git a/dotcom-rendering/src/layouts/AudioLayout.tsx b/dotcom-rendering/src/layouts/AudioLayout.tsx
index 15265ceb77a..1178a3c7f97 100644
--- a/dotcom-rendering/src/layouts/AudioLayout.tsx
+++ b/dotcom-rendering/src/layouts/AudioLayout.tsx
@@ -46,7 +46,7 @@ import { canRenderAds } from '../lib/canRenderAds';
import { getContributionsServiceUrl } from '../lib/contributions';
import { decideStoryPackageTrails } from '../lib/decideTrail';
import { parse } from '../lib/slot-machine-flags';
-import { useBetaAB } from '../lib/useAB';
+import { useAB } from '../lib/useAB';
import { worldCupTagId } from '../lib/worldCup2026';
import type { NavType } from '../model/extract-nav';
import { palette as themePalette } from '../palette';
@@ -167,7 +167,7 @@ export const AudioLayout = (props: WebProps | AppProps) => {
const isLabs = format.theme === ArticleSpecial.Labs;
- const ab = useBetaAB();
+ const ab = useAB();
const isWorldCup2026 =
article.tags.some((tag) => tag.id === worldCupTagId) &&
diff --git a/dotcom-rendering/src/layouts/CommentLayout.tsx b/dotcom-rendering/src/layouts/CommentLayout.tsx
index c3643b79e78..3721e99913c 100644
--- a/dotcom-rendering/src/layouts/CommentLayout.tsx
+++ b/dotcom-rendering/src/layouts/CommentLayout.tsx
@@ -44,7 +44,7 @@ import { canRenderAds } from '../lib/canRenderAds';
import { getContributionsServiceUrl } from '../lib/contributions';
import { decideStoryPackageTrails } from '../lib/decideTrail';
import { parse } from '../lib/slot-machine-flags';
-import { useBetaAB } from '../lib/useAB';
+import { useAB } from '../lib/useAB';
import { worldCupTagId } from '../lib/worldCup2026';
import type { NavType } from '../model/extract-nav';
import { palette as themePalette } from '../palette';
@@ -301,7 +301,7 @@ export const CommentLayout = (props: WebProps | AppsProps) => {
const contributionsServiceUrl = getContributionsServiceUrl(article);
- const ab = useBetaAB();
+ const ab = useAB();
const isWorldCup2026 =
article.tags.some((tag) => tag.id === worldCupTagId) &&
diff --git a/dotcom-rendering/src/layouts/FrontLayout.tsx b/dotcom-rendering/src/layouts/FrontLayout.tsx
index a8dd25bd139..8829801cacd 100644
--- a/dotcom-rendering/src/layouts/FrontLayout.tsx
+++ b/dotcom-rendering/src/layouts/FrontLayout.tsx
@@ -40,7 +40,7 @@ import {
} from '../lib/getFrontsAdPositions';
import { hideAge } from '../lib/hideAge';
import { ophanComponentId } from '../lib/ophan-helpers';
-import { useBetaAB } from '../lib/useAB';
+import { useAB } from '../lib/useAB';
import { worldCup2026PageIds } from '../lib/worldCup2026';
import type { NavType } from '../model/extract-nav';
import { palette as schemePalette } from '../palette';
@@ -124,7 +124,7 @@ export const FrontLayout = ({ front, NAV }: Props) => {
const hasPageSkin = renderAds && hasPageSkinConfig;
- const ab = useBetaAB();
+ const ab = useAB();
const isWorldCup2026 =
worldCup2026PageIds.includes(pageId) &&
diff --git a/dotcom-rendering/src/layouts/GalleryLayout.tsx b/dotcom-rendering/src/layouts/GalleryLayout.tsx
index 42b7bdb0a2a..3c020c66b55 100644
--- a/dotcom-rendering/src/layouts/GalleryLayout.tsx
+++ b/dotcom-rendering/src/layouts/GalleryLayout.tsx
@@ -46,7 +46,7 @@ import { canRenderAds } from '../lib/canRenderAds';
import { getContributionsServiceUrl } from '../lib/contributions';
import { decideMainMediaCaption } from '../lib/decide-caption';
import type { EditionId } from '../lib/edition';
-import { useBetaAB } from '../lib/useAB';
+import { useAB } from '../lib/useAB';
import { worldCupTagId } from '../lib/worldCup2026';
import type { NavType } from '../model/extract-nav';
import { palette } from '../palette';
@@ -108,7 +108,7 @@ export const GalleryLayout = (props: WebProps | AppProps) => {
const isLabs = format.theme === ArticleSpecial.Labs;
- const ab = useBetaAB();
+ const ab = useAB();
const isWorldCup2026 =
frontendData.tags.some((tag) => tag.id === worldCupTagId) &&
diff --git a/dotcom-rendering/src/layouts/LiveLayout.tsx b/dotcom-rendering/src/layouts/LiveLayout.tsx
index 353a5b570dd..2ff6fb5f7af 100644
--- a/dotcom-rendering/src/layouts/LiveLayout.tsx
+++ b/dotcom-rendering/src/layouts/LiveLayout.tsx
@@ -51,7 +51,7 @@ import { canRenderAds } from '../lib/canRenderAds';
import { getContributionsServiceUrl } from '../lib/contributions';
import { decideStoryPackageTrails } from '../lib/decideTrail';
import { getZIndex } from '../lib/getZIndex';
-import { useBetaAB } from '../lib/useAB';
+import { useAB } from '../lib/useAB';
import { worldCupTagId } from '../lib/worldCup2026';
import type { NavType } from '../model/extract-nav';
import { palette as themePalette } from '../palette';
@@ -308,7 +308,7 @@ export const LiveLayout = (props: WebProps | AppsProps) => {
const renderAds = canRenderAds(article);
- const ab = useBetaAB();
+ const ab = useAB();
const isWorldCup2026 =
article.tags.some((tag) => tag.id === worldCupTagId) &&
diff --git a/dotcom-rendering/src/layouts/PictureLayout.tsx b/dotcom-rendering/src/layouts/PictureLayout.tsx
index c495fd8789b..66a3a63a3f8 100644
--- a/dotcom-rendering/src/layouts/PictureLayout.tsx
+++ b/dotcom-rendering/src/layouts/PictureLayout.tsx
@@ -40,7 +40,7 @@ import { canRenderAds } from '../lib/canRenderAds';
import { getContributionsServiceUrl } from '../lib/contributions';
import { decideStoryPackageTrails } from '../lib/decideTrail';
import { decideLanguage, decideLanguageDirection } from '../lib/lang';
-import { useBetaAB } from '../lib/useAB';
+import { useAB } from '../lib/useAB';
import { worldCupTagId } from '../lib/worldCup2026';
import type { NavType } from '../model/extract-nav';
import { palette as themePalette } from '../palette';
@@ -276,7 +276,7 @@ export const PictureLayout = (props: WebProps | AppsProps) => {
const renderAds = canRenderAds(article);
- const ab = useBetaAB();
+ const ab = useAB();
const isWorldCup2026 =
article.tags.some((tag) => tag.id === worldCupTagId) &&
diff --git a/dotcom-rendering/src/layouts/ShowcaseLayout.tsx b/dotcom-rendering/src/layouts/ShowcaseLayout.tsx
index fd1ecbb0853..82a5f598d6c 100644
--- a/dotcom-rendering/src/layouts/ShowcaseLayout.tsx
+++ b/dotcom-rendering/src/layouts/ShowcaseLayout.tsx
@@ -51,7 +51,7 @@ import { getContributionsServiceUrl } from '../lib/contributions';
import { decideStoryPackageTrails } from '../lib/decideTrail';
import { decideLanguage, decideLanguageDirection } from '../lib/lang';
import { parse } from '../lib/slot-machine-flags';
-import { useBetaAB } from '../lib/useAB';
+import { useAB } from '../lib/useAB';
import { worldCupTagId } from '../lib/worldCup2026';
import type { NavType } from '../model/extract-nav';
import { palette as themePalette } from '../palette';
@@ -248,7 +248,7 @@ export const ShowcaseLayout = (props: WebProps | AppsProps) => {
const contributionsServiceUrl = getContributionsServiceUrl(article);
- const ab = useBetaAB();
+ const ab = useAB();
const isWorldCup2026 =
article.tags.some((tag) => tag.id === worldCupTagId) &&
diff --git a/dotcom-rendering/src/layouts/SportDataPageLayout.tsx b/dotcom-rendering/src/layouts/SportDataPageLayout.tsx
index 3314b8b0783..b4f5c79cdff 100644
--- a/dotcom-rendering/src/layouts/SportDataPageLayout.tsx
+++ b/dotcom-rendering/src/layouts/SportDataPageLayout.tsx
@@ -14,7 +14,7 @@ import { StickyBottomBanner } from '../components/StickyBottomBanner.island';
import { SubNav } from '../components/SubNav.island';
import { canRenderAds } from '../lib/canRenderAds';
import { getContributionsServiceUrl } from '../lib/contributions';
-import { useBetaAB } from '../lib/useAB';
+import { useAB } from '../lib/useAB';
import { worldCup2026PageIds } from '../lib/worldCup2026';
import { palette as themePalette } from '../palette';
import type {
@@ -99,7 +99,7 @@ export const SportDataPageLayout = (
const isApps = props.renderingTarget === 'Apps';
const pageFooter = sportData.pageFooter;
const renderAds = canRenderAds(sportData);
- const ab = useBetaAB();
+ const ab = useAB();
const contributionsServiceUrl = getContributionsServiceUrl(sportData);
diff --git a/dotcom-rendering/src/layouts/StandardLayout.tsx b/dotcom-rendering/src/layouts/StandardLayout.tsx
index 8c181465d57..47ebe01e09d 100644
--- a/dotcom-rendering/src/layouts/StandardLayout.tsx
+++ b/dotcom-rendering/src/layouts/StandardLayout.tsx
@@ -57,7 +57,7 @@ import { decideStoryPackageTrails } from '../lib/decideTrail';
import type { EditionId } from '../lib/edition';
import { safeParseURL } from '../lib/parse';
import { parse } from '../lib/slot-machine-flags';
-import { useBetaAB } from '../lib/useAB';
+import { useAB } from '../lib/useAB';
import { worldCupTagId } from '../lib/worldCup2026';
import type { NavType } from '../model/extract-nav';
import { palette as themePalette } from '../palette';
@@ -385,7 +385,7 @@ export const StandardLayout = (props: WebProps | AppProps) => {
const isLabs = format.theme === ArticleSpecial.Labs;
- const ab = useBetaAB();
+ const ab = useAB();
const isWorldCup2026 =
article.tags.some((tag) => tag.id === worldCupTagId) &&
diff --git a/dotcom-rendering/src/layouts/TagPageLayout.tsx b/dotcom-rendering/src/layouts/TagPageLayout.tsx
index 4a22b11253e..2fa6d37e165 100644
--- a/dotcom-rendering/src/layouts/TagPageLayout.tsx
+++ b/dotcom-rendering/src/layouts/TagPageLayout.tsx
@@ -25,7 +25,7 @@ import {
getTagPageBannerAdPositions,
getTagPageMobileAdPositions,
} from '../lib/getTagPageAdPositions';
-import { useBetaAB } from '../lib/useAB';
+import { useAB } from '../lib/useAB';
import { worldCup2026PageIds } from '../lib/worldCup2026';
import { enhanceTags } from '../model/enhanceTags';
import type { NavType } from '../model/extract-nav';
@@ -68,7 +68,7 @@ export const TagPageLayout = ({ tagPage, NAV }: Props) => {
const isAccessibilityPage =
tagPage.config.pageId === 'help/accessibility-help';
- const ab = useBetaAB();
+ const ab = useAB();
const isWorldCup2026 =
worldCup2026PageIds.includes(pageId) &&
diff --git a/dotcom-rendering/src/lib/affiliateLinksUtils.ts b/dotcom-rendering/src/lib/affiliateLinksUtils.ts
index 8129640c36c..bbe31e11f46 100644
--- a/dotcom-rendering/src/lib/affiliateLinksUtils.ts
+++ b/dotcom-rendering/src/lib/affiliateLinksUtils.ts
@@ -1,4 +1,4 @@
-import type { ABParticipations } from '../experiments/lib/beta-ab-tests';
+import type { ABParticipations } from '../experiments/lib/ab-tests';
export const SKIMLINK_REL = 'sponsored noreferrer noopener';
diff --git a/dotcom-rendering/src/lib/getABUrlHash.test.ts b/dotcom-rendering/src/lib/getABUrlHash.test.ts
deleted file mode 100644
index 5c3a2f67765..00000000000
--- a/dotcom-rendering/src/lib/getABUrlHash.test.ts
+++ /dev/null
@@ -1,40 +0,0 @@
-import { getForcedParticipationsFromUrl } from './getAbUrlHash';
-
-describe('getForcedParticipationsFromUrl', () => {
- it('Returns a single forced ab test variant', () => {
- expect(
- getForcedParticipationsFromUrl('#ab-inTest=variantId'),
- ).toStrictEqual({
- inTest: { variant: 'variantId' },
- });
- });
-
- it('Returns multiple forced ab test variants', () => {
- expect(
- getForcedParticipationsFromUrl(
- '#ab-inTest=variantId,inTest2=variantId2',
- ),
- ).toStrictEqual({
- inTest2: { variant: 'variantId2' },
- inTest: { variant: 'variantId' },
- });
-
- expect(
- getForcedParticipationsFromUrl(
- '#ab-inTest=variantId,inTest2=variantId2,anotherLongTestName=control',
- ),
- ).toStrictEqual({
- inTest2: { variant: 'variantId2' },
- inTest: { variant: 'variantId' },
- anotherLongTestName: { variant: 'control' },
- });
- });
-
- it('Returns empty if empty string', () => {
- expect(getForcedParticipationsFromUrl('')).toEqual({});
- });
-
- it('Returns empty if another hash', () => {
- expect(getForcedParticipationsFromUrl('#discussion-123')).toEqual({});
- });
-});
diff --git a/dotcom-rendering/src/lib/getAbUrlHash.ts b/dotcom-rendering/src/lib/getAbUrlHash.ts
deleted file mode 100644
index 5dfd6695962..00000000000
--- a/dotcom-rendering/src/lib/getAbUrlHash.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-import type { Participations } from '@guardian/ab-core';
-
-export const getForcedParticipationsFromUrl = (
- windowHash: string,
-): Participations => {
- if (windowHash.startsWith('#ab')) {
- const tokens = windowHash.replace('#ab-', '').split(',');
- return tokens.reduce((obj, token) => {
- const [testId, variantId] = token.split('=');
-
- if (testId && variantId) {
- return {
- ...obj,
- [testId]: { variant: variantId },
- };
- }
-
- return obj;
- }, {});
- }
-
- return {};
-};
diff --git a/dotcom-rendering/src/lib/useAB.ts b/dotcom-rendering/src/lib/useAB.ts
index b98b0dd8f74..494231e1175 100644
--- a/dotcom-rendering/src/lib/useAB.ts
+++ b/dotcom-rendering/src/lib/useAB.ts
@@ -1,19 +1,12 @@
-import type { ABTestAPI, Participations } from '@guardian/ab-core';
import { mutate } from 'swr';
import useSWRImmutable from 'swr/immutable';
-import type { BetaABTestAPI } from '../experiments/lib/beta-ab-tests';
-
-type ABTests = {
- api: ABTestAPI;
- participations: Participations;
-};
+import type { ABTestAPI } from '../experiments/lib/ab-tests';
/**
* A promise which never resolves, used to initialise the SWR hook.
* The actual value is set later via the `setABTests` function.
*/
-const apiPromise = new Promise(() => {});
-const betaAPIPromise = new Promise(() => {});
+const apiPromise = new Promise(() => {});
const key = 'ab-tests';
/**
@@ -21,23 +14,14 @@ const key = 'ab-tests';
* or undefined otherwise.
*
* Leverages an immutable SWR to satisfy all requests to the
- * AB Core. As soon as the tests are available, all instances of
+ * AB Tests. As soon as the tests are available, all instances of
* the useAB hook will render.
*/
-export const useAB = (): ABTests | undefined => {
+export const useAB = (): ABTestAPI | undefined => {
const { data } = useSWRImmutable(key, () => apiPromise);
return data;
};
-export const setABTests = ({ api, participations }: ABTests): void => {
- void mutate(key, { api, participations }, false);
-};
-
-export const useBetaAB = (): BetaABTestAPI | undefined => {
- const { data } = useSWRImmutable('beta-ab-tests', () => betaAPIPromise);
- return data;
-};
-
-export const setBetaABTests = (api: BetaABTestAPI): void => {
- void mutate('beta-ab-tests', api, false);
+export const setABTests = (api: ABTestAPI): void => {
+ void mutate(key, api, false);
};
diff --git a/dotcom-rendering/src/model/enhance-switches.ts b/dotcom-rendering/src/model/enhance-switches.ts
deleted file mode 100644
index 39850b0ae74..00000000000
--- a/dotcom-rendering/src/model/enhance-switches.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-import { isBoolean } from '@guardian/libs';
-import type { Switches } from '../types/config';
-
-type ABTestSwitches = { [key: `ab${string}`]: boolean };
-type ABTestSwitchesKey = keyof ABTestSwitches;
-type ABTestSwitchesValue = ABTestSwitches[ABTestSwitchesKey];
-
-const isABTestEntry = (
- entry: [string, boolean | undefined],
-): entry is [ABTestSwitchesKey, ABTestSwitchesValue] => {
- const [key, value] = entry;
- return key.startsWith('ab') && isBoolean(value);
-};
-
-/**
- * Switches that start with "ab" are used for AB tests.
- *
- * We strip those out of the list of switches to reduce
- * the amount of prop data that needs to be serialised
- * for the SetABTests Island.
- *
- * Ref: [`frontend`][] Switches & [`ab-testing`][] filter.
- *
- * [`frontend`]: https://github.com/guardian/frontend/blob/016e9e26/common/app/conf/switches/ABTestSwitches.scala
- * [`ab-testing`]: https://github.com/guardian/ab-testing/blob/b5de0e09/packages/ab-core/src/core.ts#L29-L30
- *
- * @param {Switches} switches
- * @returns {ABTestSwitches} an object containing only AB switches
- */
-const filterABTestSwitches = (switches: Switches): ABTestSwitches =>
- Object.fromEntries(Object.entries(switches).filter(isABTestEntry));
-
-export { ABTestSwitches, filterABTestSwitches };
From b4e50b118ee04304c63d131a061df560a030768e Mon Sep 17 00:00:00 2001
From: DanielCliftonGuardian
<110032454+DanielCliftonGuardian@users.noreply.github.com>
Date: Thu, 28 May 2026 13:12:36 +0100
Subject: [PATCH 2/3] Remove refs
---
pnpm-lock.yaml | 18 ------------------
1 file changed, 18 deletions(-)
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index b11ad9e64b3..77bf09b71d5 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -311,9 +311,6 @@ importers:
'@emotion/server':
specifier: 11.11.0
version: 11.11.0
- '@guardian/ab-core':
- specifier: 8.0.0
- version: 8.0.0(tslib@2.6.2)(typescript@5.9.3)
'@guardian/ab-testing-config':
specifier: workspace:ab-testing-config
version: link:../ab-testing/config
@@ -2406,15 +2403,6 @@ packages:
resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
- '@guardian/ab-core@8.0.0':
- resolution: {integrity: sha512-I6KV03kROJPnU7FdRbqkmEAzsTDMMK/bgnB7rbL/qht8+hrK9y52ySFSJF5WX0zPX/9MoMRyAgmf+wBWOeogBA==}
- peerDependencies:
- tslib: ^2.6.2
- typescript: ~5.5.2
- peerDependenciesMeta:
- typescript:
- optional: true
-
'@guardian/braze-components@22.2.0':
resolution: {integrity: sha512-uSkHd6mBVTAD+BrvJZNt+oSipYHQXBdVt9Pu/VTvkliXHzT8OUsep7ObIWM1lkf3znWbqLDhoXtwS5apX2AEWQ==}
engines: {node: ^18.15 || ^20.8}
@@ -11443,12 +11431,6 @@ snapshots:
'@eslint/core': 0.17.0
levn: 0.4.1
- '@guardian/ab-core@8.0.0(tslib@2.6.2)(typescript@5.9.3)':
- dependencies:
- tslib: 2.6.2
- optionalDependencies:
- typescript: 5.9.3
-
'@guardian/braze-components@22.2.0(@emotion/react@11.14.0(@types/react@18.3.1)(react@18.3.1))(@guardian/libs@32.0.0(@guardian/ophan-tracker-js@3.0.0)(tslib@2.6.2)(typescript@5.9.3))(@guardian/source@12.2.1(@emotion/react@11.14.0(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1)(tslib@2.6.2)(typescript@5.9.3))(react@18.3.1)':
dependencies:
'@emotion/react': 11.14.0(@types/react@18.3.1)(react@18.3.1)
From d291f4499c473551750cfea27554f53d6024728e Mon Sep 17 00:00:00 2001
From: DanielCliftonGuardian
<110032454+DanielCliftonGuardian@users.noreply.github.com>
Date: Thu, 28 May 2026 13:29:12 +0100
Subject: [PATCH 3/3] Remove deprecated ab test code
---
dotcom-rendering/.storybook/preview.ts | 16 +---
dotcom-rendering/README.md | 2 +-
.../docs/development/ab-testing-in-dcr.md | 2 -
.../development/legacy-ab-testing-in-dcr.md | 80 -------------------
4 files changed, 3 insertions(+), 97 deletions(-)
delete mode 100644 dotcom-rendering/docs/development/legacy-ab-testing-in-dcr.md
diff --git a/dotcom-rendering/.storybook/preview.ts b/dotcom-rendering/.storybook/preview.ts
index 03bd690454b..7761e00c4b1 100644
--- a/dotcom-rendering/.storybook/preview.ts
+++ b/dotcom-rendering/.storybook/preview.ts
@@ -1,4 +1,3 @@
-import { AB } from '@guardian/ab-core';
import { setCookie, storage } from '@guardian/libs';
import { resets } from '@guardian/source/foundations';
import { palette as sourcePalette } from '@guardian/source/foundations';
@@ -12,6 +11,7 @@ import { Lazy } from '../src/components/Lazy';
import { Picture } from '../src/components/Picture';
import { rawFontsCss } from '../src/lib/fonts-css';
import { mockFetch } from '../src/lib/mockRESTCalls';
+import { ABTests } from '../src/experiments/lib/ab-tests';
import { setABTests } from '../src/lib/useAB';
import { ConfigContextDecorator } from './decorators/configContextDecorator';
import {
@@ -42,19 +42,7 @@ global.fetch = mockFetch;
// Fix the date to prevent false negatives
MockDate.set('Sat Jan 1 2022 12:00:00 GMT+0000 (Greenwich Mean Time)');
-setABTests({
- api: new AB({
- mvtMaxValue: 1_000_000,
- mvtId: 1234,
- pageIsSensitive: false,
- abTestSwitches: {},
- arrayOfTestObjects: [],
- serverSideTests: {},
- ophanRecord: () => {},
- errorReporter: () => {},
- }),
- participations: {},
-});
+setABTests(new ABTests({ isServer: true, serverSideABTests: {} }));
// Add base css for the site
const css = `${rawFontsCss}${resets.resetCSS}`;
diff --git a/dotcom-rendering/README.md b/dotcom-rendering/README.md
index 07f6ff6f388..64e45480396 100644
--- a/dotcom-rendering/README.md
+++ b/dotcom-rendering/README.md
@@ -173,7 +173,7 @@ We recommend you update your workspace settings to automatically fix formatting
|
| [Chromatic](https://www.chromatic.com/) is a visual regression testing tool that tests our Storybook components at PR time. |
|
| [Jest](https://jestjs.io) is a unit testing tool. You will find Jest tests in the repo with `.test.` filenames. |
|
| [Playwright](https://playwright.dev/) is an integration testing tool that runs tests in the browser. You will find the Playwright tests in the [playwright directory](./playwright). |
-|
| The [A/B Testing library](https://github.com/guardian/csnx/tree/main/libs/@guardian/ab-core) is an internal NPM Module. There are [docs here](./docs/development/ab-testing-in-dcr.md). |
+|
| The A/B Testing framework is developed in-house. There are [docs here](./docs/development/ab-testing-in-dcr.md). |
|
| [Deno](https://deno.land/) is a JavaScript runtime that we've started incorporating into some of our Github Actions workflows. You will only need to install it if you are planning to run the workflow scripts locally. Some installation and troubleshooting instructions can be found in the [Deno scripts folder](../scripts/deno/README.md). |
## Concepts
diff --git a/dotcom-rendering/docs/development/ab-testing-in-dcr.md b/dotcom-rendering/docs/development/ab-testing-in-dcr.md
index b015aeee6a8..2226f53ae4c 100644
--- a/dotcom-rendering/docs/development/ab-testing-in-dcr.md
+++ b/dotcom-rendering/docs/development/ab-testing-in-dcr.md
@@ -2,8 +2,6 @@
This documentation covers the updated A/B test framework, developed by commercial-dev to support both client and server side A/B tests in DCR and launched in January 2026. If you're interested in how it works please visit the docs [here](https://github.com/guardian/dotcom-rendering/tree/main/ab-testing).
-Instructions for the legacy framework can still be found [here](./legacy-ab-testing-in-dcr.md).
-
## Creating a new A/B test
### 1. Configure your A/B test
diff --git a/dotcom-rendering/docs/development/legacy-ab-testing-in-dcr.md b/dotcom-rendering/docs/development/legacy-ab-testing-in-dcr.md
deleted file mode 100644
index bd54e5d8df5..00000000000
--- a/dotcom-rendering/docs/development/legacy-ab-testing-in-dcr.md
+++ /dev/null
@@ -1,80 +0,0 @@
-# AB Testing in DCR (Legacy)
-
-> [!NOTE]
-> The documentation below is for the **LEGACY** AB test framework. This framework will be deprecated. It is recommended that any AB tests introduced now use the new framework, documented [here](./ab-testing-in-dcr.md).
-
-This document explains how to set up A/B tests in Dotcom Rendering (DCR).
-
-## Client-side A/B tests
-
-> [!NOTE]
-> Setting up a client-side A/B test using the [A/B Testing Library](https://github.com/guardian/csnx/tree/main/libs/%40guardian/ab-core). The library docs explain the integration and the API.
-
-### Quick Start
-
-1. [Create a switch in Frontend](https://github.com/guardian/frontend/blob/main/common/app/conf/switches/ABTestSwitches.scala)
-1. Ensure that you [create an A/B test](https://github.com/guardian/frontend/tree/main/static/src/javascripts/projects/common/modules/experiments/tests) on _Frontend_ using the [A/B test API](https://github.com/guardian/csnx/tree/main/libs/%40guardian/ab-core#the-api).
-1. Add your test to [concurrent tests](https://github.com/guardian/frontend/blob/main/static/src/javascripts/projects/common/modules/experiments/ab-tests.ts) on _Frontend_.
-1. Copy the JS file into DCR (and update to TS types) in [experiments/tests](https://github.com/guardian/dotcom-rendering/blob/main/dotcom-rendering/src/experiments/ab-tests.ts)
-1. Add it to the test array in [src/experiments/ab-tests.ts](https://github.com/guardian/dotcom-rendering/blob/main/dotcom-rendering/src/experiments/ab-tests.ts)
-1. Force the A/B test (ignoring canRun of A/B test and variant) with the URL opt-in http://local...#ab-YourTest=yourVariant
-1. Set a GU_mvt_id or GU_mvt_id_local cookie with the MVT ID that matches the test Audience and Audience Offset ([Use this calculator](https://ab-tests.netlify.app/))
-1. Check the network tab for the Ophan request _abTestRegister_ has your test and variant
-
-### Gotchas
-
-- The ABTest Switch name must be hyphenated, lower case and must starts with `ab-`; for instance `ab-my-cool-ab-test`. The JavaScript/TypeScript ab-test ID must be in PascalCase; for instance `MyCoolAbTest`.
-- Assuming that your test has a variant whose id is `variant-1`, The url fragment for opt-in is, then, `#ab-MyCoolAbTest=variant-1`.
-- Your ABTest Switch has a sell by date and your abTest has an expiry date. Matching them up avoids confusion.
-
-### Use in Components
-
-```ts
-// Within the components
-import { useAB } from '../lib/useAB';
-
-// Example usage of AB Tests
-// Used in the e2e tests as smoke test of the AB tests framework integration
-const ABTestAPI = useAB()?.api;
-
-// We can check if a user is in a variant, returns a boolean
-// ABTestTest being an ab test that was passed in via the ab test array
-const abTestDataAttr =
- (ABTestAPI?.isUserInVariant('AbTestTest', 'control') &&
- 'ab-test-control') ||
- (ABTestAPI?.isUserInVariant('AbTestTest', 'variant') &&
- 'ab-test-variant') ||
- 'ab-test-not-in-test';
-
-// We can get the variant straight from a check for
-// whether the test is runnable
-const runnableTest = ABTestAPI?.runnableTest(abTestTest);
-const variantFromRunnable =
- (runnableTest && runnableTest.variantToRun.id) || 'not-runnable';
-
-
- AB Test
-
;
-```
-
-## Server-side A/B tests
-
-In order to set up a server-side test in DCR, follow steps 1-4 outlined in the `frontend` [documentation](https://github.com/guardian/frontend/blob/main/docs/03-dev-howtos/01-ab-testing.md#write-a-server-side-test).
-
-On the live website, Fastly automatically assigns users to buckets. You can force yourself into a test on your local machine by following these steps:
-
-1. Ensure you are running `frontend` locally and your server-side experiment is enabled in the dashboard.
-2. Use the Header Hacker extension to change the HTTP headers as described in the `frontend` documentation. Please note that this is the only way to opt-in locally. If testing in the CODE environment, use the `/opt/in/` link.
-
-You can verify that you have been correctly assigned to the variant by appending `.json?dcr` to the end of an article link (e.g. `http://localhost:9000/world/2021/jan/01/your-article.json?dcr`. This will return the document data in `JSON` format. Your A/B test will be within the `config` object in camel case, as follows:
-
-```json
-"abTests": {
- "yourAbTestVariant": "variant"
-}
-```
-
-You can access server-side `abTests` within DCR wherever the CAPI object is used (`CAPIArticle.config.abTests`).