mirror of
https://github.com/freeCodeCamp/freeCodeCamp.git
synced 2026-04-30 16:01:14 -04:00
feat: AB test initial donation modal interval (#56078)
This commit is contained in:
@@ -30,6 +30,37 @@
|
||||
"name": "tests the conversion rate of the new design comparing to the old one"
|
||||
}
|
||||
]
|
||||
},
|
||||
"show-modal-randomly": {
|
||||
"defaultValue": false,
|
||||
"rules": [
|
||||
{
|
||||
"coverage": 1,
|
||||
"hashAttribute": "id",
|
||||
"seed": "show-modal-randomly",
|
||||
"hashVersion": 2,
|
||||
"variations": [
|
||||
false,
|
||||
true
|
||||
],
|
||||
"weights": [
|
||||
0.5,
|
||||
0.5
|
||||
],
|
||||
"key": "show-modal-randomly",
|
||||
"meta": [
|
||||
{
|
||||
"key": "0",
|
||||
"name": "Control"
|
||||
},
|
||||
{
|
||||
"key": "1",
|
||||
"name": "Variation 1"
|
||||
}
|
||||
],
|
||||
"phase": "0",
|
||||
"name": "stg show modal randomly"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -5,49 +5,49 @@ import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import {
|
||||
isSignedInSelector,
|
||||
showMultipleProgressModalsSelector,
|
||||
isRandomCompletionThresholdSelector,
|
||||
userIdSelector,
|
||||
userFetchStateSelector
|
||||
} from '../../redux/selectors';
|
||||
import { setShowMultipleProgressModals } from '../../redux/actions';
|
||||
import { setIsRandomCompletionThreshold } from '../../redux/actions';
|
||||
import { UserFetchState } from '../../redux/prop-types';
|
||||
import callGA from '../../analytics/call-ga';
|
||||
|
||||
const mapStateToProps = createSelector(
|
||||
isSignedInSelector,
|
||||
showMultipleProgressModalsSelector,
|
||||
isRandomCompletionThresholdSelector,
|
||||
userIdSelector,
|
||||
userFetchStateSelector,
|
||||
(
|
||||
isSignedIn: boolean,
|
||||
showMultipleProgressModals: boolean,
|
||||
isRandomCompletionThreshold: boolean,
|
||||
userId: string,
|
||||
userFetchState: UserFetchState
|
||||
) => ({
|
||||
isSignedIn,
|
||||
showMultipleProgressModals,
|
||||
isRandomCompletionThreshold,
|
||||
userId,
|
||||
userFetchState
|
||||
})
|
||||
);
|
||||
|
||||
type StateProps = ReturnType<typeof mapStateToProps>;
|
||||
type DispatchProps = { setShowMultipleProgressModals: (arg: boolean) => void };
|
||||
type DispatchProps = { setIsRandomCompletionThreshold: (arg: boolean) => void };
|
||||
|
||||
interface GrowthBookReduxConnector extends StateProps, DispatchProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
const mapDispatchToProps = {
|
||||
setShowMultipleProgressModals
|
||||
setIsRandomCompletionThreshold
|
||||
};
|
||||
|
||||
const GrowthBookReduxConnector = ({
|
||||
children,
|
||||
isSignedIn,
|
||||
showMultipleProgressModals,
|
||||
isRandomCompletionThreshold,
|
||||
userId,
|
||||
setShowMultipleProgressModals,
|
||||
setIsRandomCompletionThreshold,
|
||||
userFetchState
|
||||
}: GrowthBookReduxConnector) => {
|
||||
// Send user id to GA
|
||||
@@ -60,23 +60,17 @@ const GrowthBookReduxConnector = ({
|
||||
}
|
||||
}, [userFetchState, userId, isSignedIn]);
|
||||
|
||||
const displayProgressModalMultipleTimes = useFeature(
|
||||
'display_progress_modal_multiple_times'
|
||||
).on;
|
||||
const showModalsRandomly = useFeature('show-modal-randomly').on;
|
||||
useFeature('aa-test');
|
||||
useEffect(() => {
|
||||
if (
|
||||
isSignedIn &&
|
||||
displayProgressModalMultipleTimes &&
|
||||
!showMultipleProgressModals
|
||||
) {
|
||||
setShowMultipleProgressModals(true);
|
||||
if (isSignedIn && showModalsRandomly && !isRandomCompletionThreshold) {
|
||||
setIsRandomCompletionThreshold(true);
|
||||
}
|
||||
}, [
|
||||
isSignedIn,
|
||||
showMultipleProgressModals,
|
||||
displayProgressModalMultipleTimes,
|
||||
setShowMultipleProgressModals
|
||||
isRandomCompletionThreshold,
|
||||
showModalsRandomly,
|
||||
setIsRandomCompletionThreshold
|
||||
]);
|
||||
return <>{children}</>;
|
||||
};
|
||||
|
||||
@@ -9,7 +9,7 @@ export const actionTypes = createTypes(
|
||||
'allowBlockDonationRequests',
|
||||
'setRenderStartTime',
|
||||
'preventBlockDonationRequests',
|
||||
'setShowMultipleProgressModals',
|
||||
'setIsRandomCompletionThreshold',
|
||||
'openDonationModal',
|
||||
'closeDonationModal',
|
||||
'openSignoutModal',
|
||||
|
||||
@@ -17,9 +17,8 @@ export const openDonationModal = createAction(actionTypes.openDonationModal);
|
||||
export const preventBlockDonationRequests = createAction(
|
||||
actionTypes.preventBlockDonationRequests
|
||||
);
|
||||
|
||||
export const setShowMultipleProgressModals = createAction(
|
||||
actionTypes.setShowMultipleProgressModals
|
||||
export const setIsRandomCompletionThreshold = createAction(
|
||||
actionTypes.setIsRandomCompletionThreshold
|
||||
);
|
||||
export const updateDonationFormState = createAction(
|
||||
actionTypes.updateDonationFormState
|
||||
|
||||
@@ -50,7 +50,7 @@ export const defaultDonationFormState = {
|
||||
|
||||
const initialState = {
|
||||
appUsername: '',
|
||||
showMultipleProgressModals: false,
|
||||
isRandomCompletionThreshold: false,
|
||||
recentlyClaimedBlock: null,
|
||||
currentChallengeId: store.get(CURRENT_CHALLENGE_KEY),
|
||||
examInProgress: false,
|
||||
@@ -273,9 +273,9 @@ export const reducer = handleActions(
|
||||
...state,
|
||||
recentlyClaimedBlock: null
|
||||
}),
|
||||
[actionTypes.setShowMultipleProgressModals]: (state, { payload }) => ({
|
||||
[actionTypes.setIsRandomCompletionThreshold]: (state, { payload }) => ({
|
||||
...state,
|
||||
showMultipleProgressModals: payload
|
||||
isRandomCompletionThreshold: payload
|
||||
}),
|
||||
[actionTypes.resetUserData]: state => ({
|
||||
...state,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Certification } from '../../../shared/config/certification-settings';
|
||||
import { randomBetween } from '../utils/random-between';
|
||||
import { getSessionChallengeData } from '../utils/session-storage';
|
||||
import { ns as MainApp } from './action-types';
|
||||
|
||||
@@ -12,8 +13,8 @@ export const partiallyCompletedChallengesSelector = state =>
|
||||
export const currentChallengeIdSelector = state =>
|
||||
state[MainApp].currentChallengeId;
|
||||
export const completionCountSelector = state => state[MainApp].completionCount;
|
||||
export const showMultipleProgressModalsSelector = state =>
|
||||
state[MainApp].showMultipleProgressModals;
|
||||
export const isRandomCompletionThresholdSelector = state =>
|
||||
state[MainApp].isRandomCompletionThreshold;
|
||||
export const isDonatingSelector = state => userSelector(state).isDonating;
|
||||
export const isOnlineSelector = state => state[MainApp].isOnline;
|
||||
export const isServerOnlineSelector = state => state[MainApp].isServerOnline;
|
||||
@@ -36,6 +37,8 @@ export const shouldRequestDonationSelector = state => {
|
||||
const completedChallengeCount = completedChallengesSelector(state).length;
|
||||
const isDonating = isDonatingSelector(state);
|
||||
const recentlyClaimedBlock = recentlyClaimedBlockSelector(state);
|
||||
const isRandomCompletionThreshold =
|
||||
isRandomCompletionThresholdSelector(state);
|
||||
|
||||
// don't request donation if already donating
|
||||
if (isDonating) return false;
|
||||
@@ -58,9 +61,16 @@ export const shouldRequestDonationSelector = state => {
|
||||
// not before the 11th challenge has mounted)
|
||||
if (completedChallengeCount < 10) return false;
|
||||
|
||||
// this will mean we have completed 3 or more challenges this browser session
|
||||
// and enough challenges overall to not be new
|
||||
return sessionChallengeData.currentCount >= 3;
|
||||
/*
|
||||
Show modal if user has completed 10 challanged in total
|
||||
and 3 or more in this session.
|
||||
The isRandomCompletionThreshold flag is used to AB test interval randomness
|
||||
*/
|
||||
if (isRandomCompletionThreshold) {
|
||||
return sessionChallengeData.currentCount >= randomBetween(3, 7);
|
||||
} else {
|
||||
return sessionChallengeData.currentCount >= 3;
|
||||
}
|
||||
};
|
||||
|
||||
export const userTokenSelector = state => {
|
||||
|
||||
3
client/src/utils/random-between.ts
Normal file
3
client/src/utils/random-between.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
// returns a random number between x and y
|
||||
export const randomBetween = (x: number, y: number): number =>
|
||||
Math.floor(Math.random() * (y - x + 1)) + x;
|
||||
@@ -1,5 +1,6 @@
|
||||
import { execSync } from 'child_process';
|
||||
import { test, expect, type Page } from '@playwright/test';
|
||||
import { addGrowthbookCookie } from './utils/add-growthbook-cookie';
|
||||
|
||||
import { clearEditor, focusEditor } from './utils/editor';
|
||||
|
||||
@@ -34,122 +35,74 @@ const completeFrontEndCert = async (page: Page) => {
|
||||
}
|
||||
};
|
||||
|
||||
const completeThreeChallenges = async ({
|
||||
page,
|
||||
browserName,
|
||||
isMobile
|
||||
}: {
|
||||
page: Page;
|
||||
browserName: string;
|
||||
isMobile: boolean;
|
||||
}) => {
|
||||
await page.goto(
|
||||
'/learn/javascript-algorithms-and-data-structures/basic-javascript/comment-your-javascript-code'
|
||||
);
|
||||
|
||||
const challenges = [
|
||||
{
|
||||
url: '/learn/javascript-algorithms-and-data-structures/basic-javascript/comment-your-javascript-code',
|
||||
solution: `// some comment\n/* some comment */`
|
||||
},
|
||||
{
|
||||
url: '/learn/javascript-algorithms-and-data-structures/basic-javascript/declare-javascript-variables',
|
||||
solution: 'var myName;'
|
||||
},
|
||||
{
|
||||
url: '/learn/javascript-algorithms-and-data-structures/basic-javascript/storing-values-with-the-assignment-operator',
|
||||
solution: `// Setup\nvar a;\n\n// Only change code below this line\na = 7;`
|
||||
}
|
||||
];
|
||||
|
||||
for (const challenge of challenges) {
|
||||
await page.waitForURL(challenge.url);
|
||||
|
||||
await focusEditor({ page, isMobile });
|
||||
await clearEditor({ page, browserName });
|
||||
|
||||
await page.evaluate(
|
||||
async contents => await navigator.clipboard.writeText(contents),
|
||||
challenge.solution
|
||||
);
|
||||
await page.keyboard.press('ControlOrMeta+V');
|
||||
|
||||
await page.getByRole('button', { name: 'Run' }).click();
|
||||
await expect(page.getByRole('dialog')).toBeVisible(); // completion dialog
|
||||
await page.getByRole('button', { name: 'Submit' }).click();
|
||||
const challenges = [
|
||||
{
|
||||
url: '/learn/javascript-algorithms-and-data-structures/basic-javascript/comment-your-javascript-code',
|
||||
solution: `// some comment\n/* some comment */`
|
||||
},
|
||||
{
|
||||
url: '/learn/javascript-algorithms-and-data-structures/basic-javascript/declare-javascript-variables',
|
||||
solution: 'var myName;'
|
||||
},
|
||||
{
|
||||
url: '/learn/javascript-algorithms-and-data-structures/basic-javascript/storing-values-with-the-assignment-operator',
|
||||
solution: `// Setup\nvar a;\n\n// Only change code below this line\na = 7;`
|
||||
},
|
||||
{
|
||||
url: '/learn/javascript-algorithms-and-data-structures/basic-javascript/assigning-the-value-of-one-variable-to-another',
|
||||
solution: `// Setup\nvar a;\na = 7;\nvar b;\n\n// Only change code below this line\nb = a;`
|
||||
},
|
||||
{
|
||||
url: '/learn/javascript-algorithms-and-data-structures/basic-javascript/initializing-variables-with-the-assignment-operator',
|
||||
solution: 'var a = 9;'
|
||||
},
|
||||
{
|
||||
url: '/learn/javascript-algorithms-and-data-structures/basic-javascript/declare-string-variables',
|
||||
solution: `var myFirstName = 'foo';\nvar myLastName = 'bar';`
|
||||
},
|
||||
{
|
||||
url: '/learn/javascript-algorithms-and-data-structures/basic-javascript/understanding-uninitialized-variables',
|
||||
solution: `// Only change code below this line\nvar a = 5;\nvar b = 10;\nvar c = 'I am a';\n// Only change code above this line\n\na = a + 1;\nb = b + 5;\nc = c + " String!";`
|
||||
},
|
||||
{
|
||||
url: '/learn/javascript-algorithms-and-data-structures/basic-javascript/understanding-case-sensitivity-in-variables',
|
||||
solution: `// Variable declarations\nvar studlyCapVar;\nvar properCamelCase;\nvar titleCaseOver;\n\n// Variable assignments\nstudlyCapVar = 10;\nproperCamelCase = "A String";\ntitleCaseOver = 9000;`
|
||||
},
|
||||
{
|
||||
url: '/learn/javascript-algorithms-and-data-structures/basic-javascript/explore-differences-between-the-var-and-let-keywords',
|
||||
solution: `let catName = "Oliver";\nlet catSound = "Meow!";`
|
||||
},
|
||||
{
|
||||
url: '/learn/javascript-algorithms-and-data-structures/basic-javascript/declare-a-read-only-variable-with-the-const-keyword',
|
||||
solution: `const FCC = "freeCodeCamp";\n// Change this line\nlet fact = "is cool!";\n// Change this line\nfact = "is awesome!";\nconsole.log(FCC, fact);\n// Change this line`
|
||||
}
|
||||
};
|
||||
];
|
||||
|
||||
const completeTenChallenges = async ({
|
||||
const completeChallenges = async ({
|
||||
page,
|
||||
browserName,
|
||||
isMobile
|
||||
isMobile,
|
||||
number
|
||||
}: {
|
||||
page: Page;
|
||||
browserName: string;
|
||||
isMobile: boolean;
|
||||
number: number;
|
||||
}) => {
|
||||
await page.goto(
|
||||
'/learn/javascript-algorithms-and-data-structures/basic-javascript/comment-your-javascript-code'
|
||||
);
|
||||
|
||||
const challenges = [
|
||||
{
|
||||
url: '/learn/javascript-algorithms-and-data-structures/basic-javascript/comment-your-javascript-code',
|
||||
solution: `// some comment\n/* some comment */`
|
||||
},
|
||||
{
|
||||
url: '/learn/javascript-algorithms-and-data-structures/basic-javascript/declare-javascript-variables',
|
||||
solution: 'var myName;'
|
||||
},
|
||||
{
|
||||
url: '/learn/javascript-algorithms-and-data-structures/basic-javascript/storing-values-with-the-assignment-operator',
|
||||
solution: `// Setup\nvar a;\n\n// Only change code below this line\na = 7;`
|
||||
},
|
||||
{
|
||||
url: '/learn/javascript-algorithms-and-data-structures/basic-javascript/assigning-the-value-of-one-variable-to-another',
|
||||
solution: `// Setup\nvar a;\na = 7;\nvar b;\n\n// Only change code below this line\nb = a;`
|
||||
},
|
||||
{
|
||||
url: '/learn/javascript-algorithms-and-data-structures/basic-javascript/initializing-variables-with-the-assignment-operator',
|
||||
solution: 'var a = 9;'
|
||||
},
|
||||
{
|
||||
url: '/learn/javascript-algorithms-and-data-structures/basic-javascript/declare-string-variables',
|
||||
solution: `var myFirstName = 'foo';\nvar myLastName = 'bar';`
|
||||
},
|
||||
{
|
||||
url: '/learn/javascript-algorithms-and-data-structures/basic-javascript/understanding-uninitialized-variables',
|
||||
solution: `// Only change code below this line\nvar a = 5;\nvar b = 10;\nvar c = 'I am a';\n// Only change code above this line\n\na = a + 1;\nb = b + 5;\nc = c + " String!";`
|
||||
},
|
||||
{
|
||||
url: '/learn/javascript-algorithms-and-data-structures/basic-javascript/understanding-case-sensitivity-in-variables',
|
||||
solution: `// Variable declarations\nvar studlyCapVar;\nvar properCamelCase;\nvar titleCaseOver;\n\n// Variable assignments\nstudlyCapVar = 10;\nproperCamelCase = "A String";\ntitleCaseOver = 9000;`
|
||||
},
|
||||
{
|
||||
url: '/learn/javascript-algorithms-and-data-structures/basic-javascript/explore-differences-between-the-var-and-let-keywords',
|
||||
solution: `let catName = "Oliver";\nlet catSound = "Meow!";`
|
||||
},
|
||||
{
|
||||
url: '/learn/javascript-algorithms-and-data-structures/basic-javascript/declare-a-read-only-variable-with-the-const-keyword',
|
||||
solution: `const FCC = "freeCodeCamp";\n// Change this line\nlet fact = "is cool!";\n// Change this line\nfact = "is awesome!";\nconsole.log(FCC, fact);\n// Change this line`
|
||||
}
|
||||
];
|
||||
|
||||
for (const challenge of challenges) {
|
||||
await page.goto(challenges[0].url);
|
||||
for (const challenge of challenges.slice(0, number)) {
|
||||
await page.waitForURL(challenge.url);
|
||||
|
||||
await focusEditor({ page, isMobile });
|
||||
await clearEditor({ page, browserName });
|
||||
|
||||
await page.evaluate(
|
||||
async contents => await navigator.clipboard.writeText(contents),
|
||||
challenge.solution
|
||||
);
|
||||
await page.keyboard.press('ControlOrMeta+V');
|
||||
|
||||
await page.getByRole('button', { name: 'Run' }).click();
|
||||
await expect(page.getByRole('dialog')).toBeVisible(); // completion dialog
|
||||
await expect(
|
||||
page.getByRole('dialog').filter({ hasText: 'Basic Javascript' })
|
||||
).toBeVisible(); // completion dialog
|
||||
await page.getByRole('button', { name: 'Submit' }).click();
|
||||
}
|
||||
};
|
||||
@@ -160,6 +113,10 @@ test.skip(
|
||||
);
|
||||
|
||||
test.describe('Donation modal display', () => {
|
||||
test.beforeEach(async ({ context }) => {
|
||||
await addGrowthbookCookie({ context, variation: 'A' });
|
||||
});
|
||||
|
||||
test.use({ storageState: 'playwright/.auth/certified-user.json' });
|
||||
|
||||
test('should display the content correctly and disable close when the animation is not complete', async ({
|
||||
@@ -171,7 +128,7 @@ test.describe('Donation modal display', () => {
|
||||
await context.grantPermissions(['clipboard-read', 'clipboard-write']);
|
||||
test.setTimeout(40000);
|
||||
|
||||
await completeThreeChallenges({ page, browserName, isMobile });
|
||||
await completeChallenges({ page, browserName, isMobile, number: 3 });
|
||||
|
||||
const donationModal = page
|
||||
.getByRole('dialog')
|
||||
@@ -241,6 +198,9 @@ test.describe('Donation modal display', () => {
|
||||
|
||||
test.describe('Donation modal appearance logic - New user', () => {
|
||||
test.use({ storageState: 'playwright/.auth/development-user.json' });
|
||||
test.beforeEach(async ({ context }) => {
|
||||
await addGrowthbookCookie({ context, variation: 'B' });
|
||||
});
|
||||
|
||||
test.beforeEach(() => {
|
||||
execSync('node ./tools/scripts/seed/seed-demo-user');
|
||||
@@ -259,7 +219,7 @@ test.describe('Donation modal appearance logic - New user', () => {
|
||||
await context.grantPermissions(['clipboard-read', 'clipboard-write']);
|
||||
|
||||
// Development user doesn't have any completed challenges, we are completing the first 3.
|
||||
await completeThreeChallenges({ page, browserName, isMobile });
|
||||
await completeChallenges({ page, browserName, isMobile, number: 3 });
|
||||
|
||||
const donationModal = page
|
||||
.getByRole('dialog')
|
||||
@@ -276,7 +236,7 @@ test.describe('Donation modal appearance logic - New user', () => {
|
||||
await context.grantPermissions(['clipboard-read', 'clipboard-write']);
|
||||
test.setTimeout(50000);
|
||||
|
||||
await completeTenChallenges({ page, isMobile, browserName });
|
||||
await completeChallenges({ page, isMobile, browserName, number: 10 });
|
||||
|
||||
const donationModal = page
|
||||
.getByRole('dialog')
|
||||
@@ -333,6 +293,9 @@ test.describe('Donation modal appearance logic - New user', () => {
|
||||
|
||||
test.describe('Donation modal appearance logic - Certified user', () => {
|
||||
test.use({ storageState: 'playwright/.auth/certified-user.json' });
|
||||
test.beforeEach(async ({ context }) => {
|
||||
await addGrowthbookCookie({ context, variation: 'A' });
|
||||
});
|
||||
|
||||
test('should appear if the user has completed 3 challenges and has more than 10 completed challenges in total', async ({
|
||||
page,
|
||||
@@ -344,7 +307,7 @@ test.describe('Donation modal appearance logic - Certified user', () => {
|
||||
test.setTimeout(40000);
|
||||
|
||||
// Certified user already has more than 10 completed challenges, we are just completing 3 more.
|
||||
await completeThreeChallenges({ page, browserName, isMobile });
|
||||
await completeChallenges({ page, isMobile, browserName, number: 3 });
|
||||
|
||||
const donationModal = page
|
||||
.getByRole('dialog')
|
||||
@@ -390,7 +353,7 @@ test.describe('Donation modal appearance logic - Donor user', () => {
|
||||
}) => {
|
||||
await context.grantPermissions(['clipboard-read', 'clipboard-write']);
|
||||
|
||||
await completeThreeChallenges({ page, browserName, isMobile });
|
||||
await completeChallenges({ page, browserName, isMobile, number: 3 });
|
||||
|
||||
const donationModal = page
|
||||
.getByRole('dialog')
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { BrowserContext, expect, Page, test } from '@playwright/test';
|
||||
import { expect, Page, test } from '@playwright/test';
|
||||
import intro from '../client/i18n/locales/english/intro.json';
|
||||
import translations from '../client/i18n/locales/english/translations.json';
|
||||
import { SuperBlocks } from '../shared/config/curriculum';
|
||||
import { addGrowthbookCookie } from './utils/add-growthbook-cookie';
|
||||
|
||||
const landingPageElements = {
|
||||
heading: 'landing-header',
|
||||
@@ -37,25 +38,13 @@ const superBlocks = [
|
||||
intro[SuperBlocks.PythonForEverybody].title
|
||||
];
|
||||
|
||||
async function addDefaultCookies(context: BrowserContext, variation: string) {
|
||||
await context.addCookies([
|
||||
{
|
||||
name: 'gbuuid',
|
||||
value: variation,
|
||||
domain: 'localhost',
|
||||
path: '/',
|
||||
expires: Math.floor(Date.now() / 1000) + 400 * 24 * 60 * 60 // 400 days from now
|
||||
}
|
||||
]);
|
||||
}
|
||||
|
||||
async function goToLandingPage(page: Page) {
|
||||
await page.goto('/');
|
||||
}
|
||||
|
||||
test.describe('Landing Page - Variation B', () => {
|
||||
test.beforeEach(async ({ context, page }) => {
|
||||
await addDefaultCookies(context, 'B');
|
||||
await addGrowthbookCookie({ context, variation: 'B' });
|
||||
await goToLandingPage(page);
|
||||
});
|
||||
|
||||
@@ -132,7 +121,7 @@ test.describe('Landing Page - Variation B', () => {
|
||||
|
||||
test.describe('Landing Page - Variation A', () => {
|
||||
test.beforeEach(async ({ context, page }) => {
|
||||
await addDefaultCookies(context, 'A');
|
||||
await addGrowthbookCookie({ context, variation: 'A' });
|
||||
await goToLandingPage(page);
|
||||
});
|
||||
|
||||
|
||||
19
e2e/utils/add-growthbook-cookie.ts
Normal file
19
e2e/utils/add-growthbook-cookie.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { BrowserContext } from '@playwright/test';
|
||||
|
||||
export async function addGrowthbookCookie({
|
||||
context,
|
||||
variation
|
||||
}: {
|
||||
context: BrowserContext;
|
||||
variation: string;
|
||||
}) {
|
||||
await context.addCookies([
|
||||
{
|
||||
name: 'gbuuid',
|
||||
value: variation,
|
||||
domain: 'localhost',
|
||||
path: '/',
|
||||
expires: Math.floor(Date.now() / 1000) + 400 * 24 * 60 * 60 // 400 days from now
|
||||
}
|
||||
]);
|
||||
}
|
||||
Reference in New Issue
Block a user