feat(client): add 25 dollar tier ab test (#62014)

This commit is contained in:
Ahmad Abdolsaheb
2025-09-11 13:04:01 +03:00
committed by GitHub
parent 09dc696c29
commit ded6b598d7
4 changed files with 109 additions and 30 deletions

View File

@@ -30,6 +30,32 @@
"name": "tests the conversion rate of the new design comparing to the old one"
}
]
},
"replace-20-with-25": {
"defaultValue": false,
"rules": [
{
"coverage": 1,
"hashAttribute": "id",
"seed": "replace-20-with-25",
"hashVersion": 2,
"variations": [false, true],
"weights": [0.5, 0.5],
"key": "replace-20-with-25",
"meta": [
{
"key": "0",
"name": "Control"
},
{
"key": "1",
"name": "Variation 1"
}
],
"phase": "0",
"name": "stg replace 20 with 25"
}
]
},
"show-modal-randomly": {
"defaultValue": false,

View File

@@ -14,7 +14,9 @@ import { useTranslation } from 'react-i18next';
import {
PaymentContext,
subscriptionAmounts,
subscriptionAmountsB,
defaultTierAmount,
defaultTierAmountB,
type DonationAmount
} from '../../../../shared/config/donation-settings'; // You can further extract these into separate components and import them
import callGA from '../../analytics/call-ga';
@@ -42,6 +44,10 @@ function SelectionTabs({
isAnimationEnabled?: boolean;
}) {
const { t } = useTranslation();
const replace20With25 = useFeature('replace-20-with-25').on;
const activeSubscriptionAmounts = replace20With25
? subscriptionAmountsB
: subscriptionAmounts;
const switchTab = (value: string): void => {
setDonationAmount(Number(value) as DonationAmount);
};
@@ -81,7 +87,7 @@ function SelectionTabs({
onValueChange={switchTab}
>
<TabsList className='nav-lists'>
{subscriptionAmounts.map(value => (
{activeSubscriptionAmounts.map(value => (
<TabsTrigger
key={value}
value={value.toString()}
@@ -92,7 +98,7 @@ function SelectionTabs({
))}
</TabsList>
<Spacer size='xs' />
{subscriptionAmounts.map(value => {
{activeSubscriptionAmounts.map(value => {
const usd = formattedAmountLabel(donationAmount);
const hours = convertToTimeContributed(donationAmount);
const donationDescription = t('donate.your-donation-2', {
@@ -169,7 +175,10 @@ const MultiTierDonationForm: React.FC<MultiTierDonationFormProps> = ({
paymentContext,
isAnimationEnabled
}) => {
const [donationAmount, setDonationAmount] = useState(defaultTierAmount);
const replace20With25 = useFeature('replace-20-with-25').on;
const [donationAmount, setDonationAmount] = useState(
replace20With25 ? defaultTierAmountB : defaultTierAmount
);
const [showDonateForm, setShowDonateForm] = useState(false);

View File

@@ -1,5 +1,6 @@
import { test, expect } from '@playwright/test';
import translations from '../client/i18n/locales/english/translations.json';
import { addGrowthbookCookie } from './utils/add-growthbook-cookie';
const pageElements = {
mainHeading: 'main-head',
@@ -16,10 +17,14 @@ const donationStringReplacements = {
};
const donationFormStrings = {
conformTwentyDollar: translations.donate['confirm-monthly'].replace(
confirmTwentyDollar: translations.donate['confirm-monthly'].replace(
donationStringReplacements.usdPlaceHolder,
'20'
),
confirmTwentyFiveDollar: translations.donate['confirm-monthly'].replace(
donationStringReplacements.usdPlaceHolder,
'25'
),
confirmFiveDollars: translations.donate['confirm-monthly'].replace(
donationStringReplacements.usdPlaceHolder,
'5'
@@ -27,6 +32,9 @@ const donationFormStrings = {
twentyDollarsLearningContribution: translations.donate['your-donation-2']
.replace(donationStringReplacements.usdPlaceHolder, '20')
.replace(donationStringReplacements.hoursPlaceHolder, '1,000'),
twentyFiveDollarsLearningContribution: translations.donate['your-donation-2']
.replace(donationStringReplacements.usdPlaceHolder, '25')
.replace(donationStringReplacements.hoursPlaceHolder, '1,250'),
fiveDollarsLearningContribution: translations.donate['your-donation-2']
.replace(donationStringReplacements.usdPlaceHolder, '5')
.replace(donationStringReplacements.hoursPlaceHolder, '250'),
@@ -213,31 +221,6 @@ function donatePageTests() {
await faq12.click();
});
test('should select $20 tier by default', async ({ page }) => {
await expect(
page.getByText(donationFormStrings.conformTwentyDollar)
).toBeVisible();
const tabs = await page.$$('[role="tab"]');
expect(tabs.length).toBe(4);
for (const tab of tabs) {
const tabText = await tab.innerText();
expect(['$5', '$10', '$20', '$40']).toContain(tabText);
if (tabText === '$20') {
const isActive = await tab.getAttribute('data-state');
expect(isActive).toBe('active');
} else {
const isActive = await tab.getAttribute('data-state');
expect(isActive).not.toBe('active');
}
}
await expect(
page.getByText(donationFormStrings.twentyDollarsLearningContribution)
).toBeVisible();
});
test('should make $5 tier selectable', async ({ page }) => {
await page.click('[role="tab"]:has-text("$5")');
@@ -281,6 +264,33 @@ function donatePageTests() {
});
}
interface DefaultTierTestConfig {
defaultTier: string;
tiers: string[];
confirmationText: string;
contributionText: string;
}
function donatePageDefault(config: DefaultTierTestConfig) {
const { defaultTier, tiers, confirmationText, contributionText } = config;
test(`should select ${defaultTier} tier by default`, async ({ page }) => {
await expect(page.getByText(confirmationText)).toBeVisible();
const tabs = await page.$$('[role="tab"]');
expect(tabs.length).toBe(tiers.length);
for (const tab of tabs) {
const tabText = await tab.innerText();
expect(tiers).toContain(tabText);
const isActive = await tab.getAttribute('data-state');
if (tabText === defaultTier) {
expect(isActive).toBe('active');
} else {
expect(isActive).not.toBe('active');
}
}
await expect(page.getByText(contributionText)).toBeVisible();
});
}
test.describe('Authenticated User', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/donate');
@@ -288,6 +298,19 @@ test.describe('Authenticated User', () => {
donatePageTests();
});
test.describe('Authenticated User Page Defaults - Variation A', () => {
test.beforeEach(async ({ context, page }) => {
await addGrowthbookCookie({ context, variation: 'A243' });
await page.goto('/donate');
});
donatePageDefault({
defaultTier: '$20',
tiers: ['$5', '$10', '$20', '$40'],
confirmationText: donationFormStrings.confirmTwentyDollar,
contributionText: donationFormStrings.twentyDollarsLearningContribution
});
});
test.describe('Unauthenticated User', () => {
test.use({ storageState: { cookies: [], origins: [] } });
test.beforeEach(async ({ page }) => {
@@ -295,3 +318,17 @@ test.describe('Unauthenticated User', () => {
});
donatePageTests();
});
test.describe('Unauthenticated User Page Default - Variation B', () => {
test.use({ storageState: { cookies: [], origins: [] } });
test.beforeEach(async ({ context, page }) => {
await addGrowthbookCookie({ context, variation: 'B145' });
await page.goto('/donate');
});
donatePageDefault({
defaultTier: '$25',
tiers: ['$5', '$10', '$25', '$40'],
confirmationText: donationFormStrings.confirmTwentyFiveDollar,
contributionText: donationFormStrings.twentyFiveDollarsLearningContribution
});
});

View File

@@ -1,6 +1,6 @@
// Configuration for client side
export type DonationAmount = 500 | 1000 | 2000 | 4000;
export type DonationAmount = 500 | 1000 | 2000 | 2500 | 4000;
export type DonationDuration = 'one-time' | 'month';
export interface DonationConfig {
donationAmount: DonationAmount;
@@ -8,6 +8,7 @@ export interface DonationConfig {
}
export const subscriptionAmounts: DonationAmount[] = [500, 1000, 2000, 4000];
export const subscriptionAmountsB: DonationAmount[] = [500, 1000, 2500, 4000];
export const defaultDonation: DonationConfig = {
donationAmount: 500,
@@ -15,6 +16,7 @@ export const defaultDonation: DonationConfig = {
};
export const defaultTierAmount: DonationAmount = 2000;
export const defaultTierAmountB: DonationAmount = 2500;
export const onetimeSKUConfig = {
production: [
@@ -53,6 +55,7 @@ export const paypalConfigTypes = {
500: { planId: 'P-6B636789V3105190KMTJFH7A' },
1000: { planId: 'P-53P76823N8780520DMVTWF3I' },
2000: { planId: 'P-8HY47434FB9663500MVTWFOA' },
2500: { planId: 'P-1E758922LA293854BNC3SK3A' },
3000: { planId: 'P-1KY930839N8045117L6E4BKY' },
4000: { planId: 'P-0MH28916302828423MVTWEBI' },
5000: { planId: 'P-0WR49877YD949401BL6E4CTA' }
@@ -63,6 +66,7 @@ export const paypalConfigTypes = {
500: { planId: 'P-37N14480BW163382FLZYPVMA' },
1000: { planId: 'P-28B62039J8092810UL6E3FXA' },
2000: { planId: 'P-7HR706961M9170433L6HI5VI' },
2500: { planId: 'P-2BK29709FB733490FNC3RPGQ' },
3000: { planId: 'P-35V33574BU596924JL6HI6XY' },
4000: { planId: 'P-45M45060289267734L6HJSXA' },
5000: { planId: 'P-0MD70861FY4172444L6HJTUQ' }
@@ -90,6 +94,7 @@ export const paypalConfigurator = (
500: { planId: string };
1000: { planId: string };
2000: { planId: string };
2500: { planId: string };
3000: { planId: string };
4000: { planId: string };
5000: { planId: string };
@@ -130,6 +135,7 @@ const stripeProductIds = {
500: 'prod_Cc9bIxB2NvjpLy',
1000: 'prod_BuiSxWk7jGSFlJ',
2000: 'prod_IElpZVK7kOn6Fe',
2500: 'prod_JCakZSxh12ZaDF',
4000: 'prod_IElq1foW39g3Cx'
}
},
@@ -138,6 +144,7 @@ const stripeProductIds = {
500: 'prod_GD1GGbJsqQaupl',
1000: 'prod_GD1IzNEXfSCGgy',
2000: 'prod_IEkNp8M03xvsuB',
2500: 'prod_T12UtcRPvzzVN1',
4000: 'prod_IEkPebxS63mVbs'
}
}