diff --git a/client/config/growthbook-features-default.json b/client/config/growthbook-features-default.json index 0af2d8d8bc4..048953fe1d6 100644 --- a/client/config/growthbook-features-default.json +++ b/client/config/growthbook-features-default.json @@ -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, diff --git a/client/src/components/Donation/multi-tier-donation-form.tsx b/client/src/components/Donation/multi-tier-donation-form.tsx index 2fdf4fdcb3a..6c39db4ba63 100644 --- a/client/src/components/Donation/multi-tier-donation-form.tsx +++ b/client/src/components/Donation/multi-tier-donation-form.tsx @@ -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} > - {subscriptionAmounts.map(value => ( + {activeSubscriptionAmounts.map(value => ( - {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 = ({ 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); diff --git a/e2e/donate-page-default.spec.ts b/e2e/donate-page-default.spec.ts index bc24b37c271..1e731e46755 100644 --- a/e2e/donate-page-default.spec.ts +++ b/e2e/donate-page-default.spec.ts @@ -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 + }); +}); diff --git a/shared/config/donation-settings.ts b/shared/config/donation-settings.ts index 768f3cec2f9..f5877e178ed 100644 --- a/shared/config/donation-settings.ts +++ b/shared/config/donation-settings.ts @@ -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' } }