mirror of
https://github.com/freeCodeCamp/freeCodeCamp.git
synced 2026-02-17 16:00:32 -05:00
feat: add AB test landing page top (#55659)
Co-authored-by: Naomi the Technomancer <accounts+github@nhcarrigan.com> Co-authored-by: Huyen Nguyen <25715018+huyenltnguyen@users.noreply.github.com> Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com>
This commit is contained in:
35
client/config/growthbook-features-default.json
Normal file
35
client/config/growthbook-features-default.json
Normal file
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"aa-test": {
|
||||
"defaultValue": false
|
||||
},
|
||||
"aa-test-in-component": {
|
||||
"defaultValue": false
|
||||
},
|
||||
"landing-page-redesign": {
|
||||
"defaultValue": false,
|
||||
"rules": [
|
||||
{
|
||||
"coverage": 1,
|
||||
"hashAttribute": "id",
|
||||
"seed": "landing-page-redesign",
|
||||
"hashVersion": 2,
|
||||
"variations": [false, true],
|
||||
"weights": [0.5, 0.5],
|
||||
"key": "landing-page-redesign",
|
||||
"meta": [
|
||||
{
|
||||
"key": "0",
|
||||
"name": "Control"
|
||||
},
|
||||
{
|
||||
"key": "1",
|
||||
"name": "Variation 1"
|
||||
}
|
||||
],
|
||||
"phase": "0",
|
||||
"name": "tests the conversion rate of the new design comparing to the old one"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"buttons": {
|
||||
"logged-in-cta-btn": "Get started (it's free)",
|
||||
"get-started": "Get Started",
|
||||
"logged-out-cta-btn": "Sign in to save your progress (it's free)",
|
||||
"view-curriculum": "View the Curriculum",
|
||||
"first-lesson": "Go to the first lesson",
|
||||
@@ -106,11 +107,15 @@
|
||||
},
|
||||
"landing": {
|
||||
"big-heading-1": "Learn to code — for free.",
|
||||
"big-heading-1-b": "Learn to code.",
|
||||
"big-heading-2": "Build projects.",
|
||||
"big-heading-3": "Earn certifications.",
|
||||
"big-heading-4": "All for free.",
|
||||
"h2-heading": "Since 2014, more than 40,000 freeCodeCamp.org graduates have gotten jobs at tech companies including:",
|
||||
"h2-heading-b": "More than 100,000 freeCodeCamp.org graduates have gotten jobs at tech companies including:",
|
||||
"hero-img-description": "freeCodeCamp students at a local study group in South Korea.",
|
||||
"hero-img-alt": "A group of people, including a White man, a Black woman, and an Asian woman, gathered around a laptop.",
|
||||
"hero-img-uis": "A group of screenshots showing the freeCodeCamp editor interface on both a mobile and desktop device and a certification.",
|
||||
"as-seen-in": "As seen in:",
|
||||
"testimonials": {
|
||||
"heading": "Here is what our alumni say about freeCodeCamp:",
|
||||
|
||||
987
client/src/assets/images/landing/landing-page-b.svg
Normal file
987
client/src/assets/images/landing/landing-page-b.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 1.3 MiB |
@@ -699,6 +699,30 @@ a.patreon-button:hover {
|
||||
);
|
||||
}
|
||||
|
||||
.light-palette .gradient-foreground {
|
||||
background-image: linear-gradient(
|
||||
-10deg,
|
||||
rgb(0, 51, 133) 35%,
|
||||
rgba(237, 202, 216, 0) 75%,
|
||||
rgb(150, 15, 46) 100%
|
||||
),
|
||||
radial-gradient(circle, rgb(157, 1, 69) 0%, rgb(0, 89, 189) 100%);
|
||||
color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
.dark-palette .gradient-foreground {
|
||||
background-image: linear-gradient(
|
||||
-10deg,
|
||||
rgb(223 243 255) 35%,
|
||||
rgba(237, 202, 216, 0) 75%,
|
||||
rgb(255 215 224) 100%
|
||||
),
|
||||
radial-gradient(circle, rgb(255 139 189) 0%, rgb(187 219 255) 100%);
|
||||
color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
.supporters-background {
|
||||
background-repeat: repeat;
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
userFetchStateSelector
|
||||
} from '../../redux/selectors';
|
||||
import envData from '../../../config/env.json';
|
||||
import defaultGrowthBookFeatures from '../../../config/growthbook-features-default.json';
|
||||
import { User, UserFetchState } from '../../redux/prop-types';
|
||||
import { getUUID } from '../../utils/growthbook-cookie';
|
||||
import callGA from '../../analytics/call-ga';
|
||||
@@ -77,17 +78,20 @@ const GrowthBookWrapper = ({
|
||||
|
||||
useEffect(() => {
|
||||
async function setGrowthBookFeatures() {
|
||||
if (!growthbookUri) return;
|
||||
|
||||
try {
|
||||
const res = await fetch(growthbookUri);
|
||||
const data = (await res.json()) as {
|
||||
features: Record<string, FeatureDefinition>;
|
||||
};
|
||||
growthbook.setFeatures(data.features);
|
||||
} catch (e) {
|
||||
// TODO: report to sentry when it's enabled
|
||||
console.error(e);
|
||||
if (!growthbookUri) {
|
||||
// Defaults are added to facilitate testing, and avoid passing the related env
|
||||
growthbook.setFeatures(defaultGrowthBookFeatures);
|
||||
} else {
|
||||
try {
|
||||
const res = await fetch(growthbookUri);
|
||||
const data = (await res.json()) as {
|
||||
features: Record<string, FeatureDefinition>;
|
||||
};
|
||||
growthbook.setFeatures(data.features);
|
||||
} catch (e) {
|
||||
// TODO: report to sentry when it's enabled
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
110
client/src/components/landing/components/landing-top-b.tsx
Normal file
110
client/src/components/landing/components/landing-top-b.tsx
Normal file
@@ -0,0 +1,110 @@
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Container, Col, Row } from '@freecodecamp/ui';
|
||||
import { clientLocale } from '../../../../config/env.json';
|
||||
import {
|
||||
AmazonLogo,
|
||||
AppleLogo,
|
||||
MicrosoftLogo,
|
||||
SpotifyLogo,
|
||||
GoogleLogo,
|
||||
TencentLogo,
|
||||
AlibabaLogo
|
||||
} from '../../../assets/images/components';
|
||||
import { Spacer } from '../../helpers';
|
||||
import BigCallToAction from './big-call-to-action';
|
||||
import UIImages from './ui-images';
|
||||
|
||||
const LogoRow = (): JSX.Element => {
|
||||
const { t } = useTranslation();
|
||||
const showChineseLogos = ['chinese', 'chinese-tradition'].includes(
|
||||
clientLocale
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<p
|
||||
className='logo-row-title'
|
||||
data-playwright-test-label='landing-h2-heading-b'
|
||||
>
|
||||
{t('landing.h2-heading-b')}
|
||||
</p>
|
||||
<Spacer size='small' />
|
||||
<ul
|
||||
className='logo-row'
|
||||
data-playwright-test-label='brand-logo-container'
|
||||
>
|
||||
<AppleLogo />
|
||||
<GoogleLogo />
|
||||
<MicrosoftLogo />
|
||||
{showChineseLogos ? (
|
||||
<>
|
||||
<TencentLogo />
|
||||
<AlibabaLogo />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<SpotifyLogo />
|
||||
<AmazonLogo />
|
||||
</>
|
||||
)}
|
||||
</ul>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
function LandingTop(): JSX.Element {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Container fluid={true} className='gradient-container'>
|
||||
<Container className='landing-top'>
|
||||
<Row>
|
||||
<Spacer size='medium' />
|
||||
<Col lg={6} sm={12} xs={12}>
|
||||
<h1
|
||||
id='content-start'
|
||||
className='mega-heading'
|
||||
data-test-label='landing-header'
|
||||
>
|
||||
{t('landing.big-heading-1-b')}
|
||||
</h1>
|
||||
<p
|
||||
className='mega-heading'
|
||||
data-playwright-test-label='landing-big-heading-2'
|
||||
>
|
||||
{t('landing.big-heading-2')}
|
||||
</p>
|
||||
<p
|
||||
className='mega-heading'
|
||||
data-playwright-test-label='landing-big-heading-3'
|
||||
>
|
||||
{t('landing.big-heading-3')}
|
||||
</p>
|
||||
<p
|
||||
className='mega-heading gradient-foreground'
|
||||
data-playwright-test-label='landing-big-heading-4'
|
||||
>
|
||||
{t('landing.big-heading-4')}
|
||||
</p>
|
||||
|
||||
<Spacer size='medium' />
|
||||
<BigCallToAction text={t('buttons.get-started')} />
|
||||
</Col>
|
||||
<Col lg={6}>
|
||||
<UIImages />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row>
|
||||
<Spacer size='large' />
|
||||
<Col xs={12}>
|
||||
<LogoRow />
|
||||
</Col>
|
||||
</Row>
|
||||
</Container>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
LandingTop.displayName = 'LandingTop';
|
||||
export default LandingTop;
|
||||
27
client/src/components/landing/components/ui-images.tsx
Normal file
27
client/src/components/landing/components/ui-images.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
// eslint-disable-next-line filenames-simple/naming-convention
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import Media from 'react-responsive';
|
||||
import landingPageb from '../../../assets/images/landing/landing-page-b.svg';
|
||||
import { LazyImage, Spacer } from '../../helpers';
|
||||
|
||||
const LARGE_SCREEN_SIZE = 1200;
|
||||
|
||||
function UIImages(): JSX.Element {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Media minWidth={LARGE_SCREEN_SIZE}>
|
||||
<figure
|
||||
className='ui-images'
|
||||
data-playwright-test-label='landing-page-figure'
|
||||
>
|
||||
<LazyImage alt={t('landing.hero-img-uis')} src={landingPageb} />
|
||||
</figure>
|
||||
<Spacer size='exLarge' />
|
||||
</Media>
|
||||
);
|
||||
}
|
||||
|
||||
UIImages.displayName = 'UIImages';
|
||||
export default UIImages;
|
||||
@@ -1,30 +1,58 @@
|
||||
import React, { ReactElement } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { useGrowthBook } from '@growthbook/growthbook-react';
|
||||
import SEO from '../seo';
|
||||
import { Loader } from '../helpers';
|
||||
import AsSeenIn from './components/as-seen-in';
|
||||
import Certifications from './components/certifications';
|
||||
import LandingTop from './components/landing-top';
|
||||
import LandingTopB from './components/landing-top-b';
|
||||
import Testimonials from './components/testimonials';
|
||||
import Faq from './components/faq';
|
||||
|
||||
import './landing.css';
|
||||
|
||||
const LandingA = () => (
|
||||
<main className='landing-page'>
|
||||
<LandingTop />
|
||||
<AsSeenIn />
|
||||
<Testimonials />
|
||||
<Certifications />
|
||||
<Faq />
|
||||
</main>
|
||||
);
|
||||
|
||||
const LandingB = () => (
|
||||
<main className='landing-page landing-page-b'>
|
||||
<LandingTopB />
|
||||
<Testimonials />
|
||||
<Certifications />
|
||||
<Faq />
|
||||
</main>
|
||||
);
|
||||
|
||||
function Landing(): ReactElement {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<>
|
||||
<SEO title={t('metaTags:title')} />
|
||||
<main className='landing-page'>
|
||||
<LandingTop />
|
||||
<AsSeenIn />
|
||||
<Testimonials />
|
||||
<Certifications />
|
||||
<Faq />
|
||||
</main>
|
||||
</>
|
||||
);
|
||||
const growthbook = useGrowthBook();
|
||||
if (growthbook && growthbook.ready) {
|
||||
const showLandingPageRedesign = growthbook.getFeatureValue(
|
||||
'landing-page-redesign',
|
||||
false
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<SEO title={t('metaTags:title')} />
|
||||
{showLandingPageRedesign === true ? <LandingB /> : <LandingA />}
|
||||
</>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<>
|
||||
<SEO title={t('metaTags:title')} />
|
||||
<Loader fullScreen={true} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Landing.displayName = 'Landing';
|
||||
|
||||
@@ -214,3 +214,54 @@ figcaption.caption {
|
||||
padding: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
/* AB testing styles */
|
||||
.landing-page-b .mega-heading {
|
||||
font-size: 2rem;
|
||||
margin: 0px 0px 1rem;
|
||||
font-weight: 700;
|
||||
line-height: 3rem;
|
||||
}
|
||||
|
||||
@media (min-width: 500px) {
|
||||
.landing-page-b .mega-heading {
|
||||
font-size: 3rem;
|
||||
}
|
||||
}
|
||||
|
||||
.landing-page-b .landing-top .btn-cta-big {
|
||||
margin: 0px;
|
||||
min-width: 300px;
|
||||
max-width: fit-content;
|
||||
background-image: none;
|
||||
color: var(--primary-background) !important;
|
||||
background-color: var(--primary-color);
|
||||
border-color: var(--primary-color);
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.landing-page-b .landing-top .btn-cta-big:hover,
|
||||
.landing-page-b .landing-top .btn-cta-big:focus {
|
||||
background-color: var(--quaternary-color) !important;
|
||||
}
|
||||
|
||||
.landing-page-b .logo-row-title {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.landing-page-b {
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
figure.ui-images {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
height: auto;
|
||||
width: 750px;
|
||||
top: 3vw;
|
||||
}
|
||||
|
||||
figure.ui-images img {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { expect, test, type Page } from '@playwright/test';
|
||||
import { BrowserContext, 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';
|
||||
@@ -37,136 +37,236 @@ const superBlocks = [
|
||||
intro[SuperBlocks.PythonForEverybody].title
|
||||
];
|
||||
|
||||
let page: Page;
|
||||
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
|
||||
}
|
||||
]);
|
||||
}
|
||||
|
||||
test.beforeAll(async ({ browser }) => {
|
||||
page = await browser.newPage();
|
||||
async function goToLandingPage(page: Page) {
|
||||
await page.goto('/');
|
||||
});
|
||||
}
|
||||
|
||||
test('The component Landing-top renders correctly', async () => {
|
||||
const landingHeading1 = page.getByTestId('landing-big-heading-1');
|
||||
await expect(landingHeading1).toHaveText(
|
||||
translations.landing['big-heading-1']
|
||||
);
|
||||
|
||||
const landingHeading2 = page.getByTestId('landing-big-heading-2');
|
||||
await expect(landingHeading2).toHaveText(
|
||||
translations.landing['big-heading-2']
|
||||
);
|
||||
|
||||
const landingHeading3 = page.getByTestId('landing-big-heading-3');
|
||||
await expect(landingHeading3).toHaveText(
|
||||
translations.landing['big-heading-3']
|
||||
);
|
||||
|
||||
const landingH2Heading = page.getByTestId('landing-h2-heading');
|
||||
await expect(landingH2Heading).toHaveText(translations.landing['h2-heading']);
|
||||
});
|
||||
|
||||
test('Has 5 brand logos', async () => {
|
||||
const logos = page.getByTestId('brand-logo-container').locator('svg');
|
||||
await expect(logos).toHaveCount(5);
|
||||
for (const logo of await logos.all()) {
|
||||
await expect(logo).toBeVisible();
|
||||
}
|
||||
});
|
||||
|
||||
test('The landing-top & testimonial sections should contain call-to-action, and have a descriptive text content', async () => {
|
||||
const ctas = await page
|
||||
.getByRole('link', {
|
||||
name: translations.buttons['logged-in-cta-btn']
|
||||
})
|
||||
.all();
|
||||
|
||||
expect(ctas).toHaveLength(4);
|
||||
|
||||
for (const cta of ctas) {
|
||||
await expect(cta).toBeVisible();
|
||||
}
|
||||
});
|
||||
|
||||
test("The landing-top should contain a descriptive text explaining the camper's image", async ({
|
||||
isMobile
|
||||
}) => {
|
||||
const campersImage = page.getByAltText(translations.landing['hero-img-alt']);
|
||||
const captionText = page.getByText(
|
||||
translations.landing['hero-img-description']
|
||||
);
|
||||
|
||||
if (isMobile) {
|
||||
await expect(campersImage).toBeHidden();
|
||||
await expect(captionText).toBeHidden();
|
||||
} else {
|
||||
await expect(campersImage).toBeVisible();
|
||||
await expect(captionText).toBeVisible();
|
||||
}
|
||||
});
|
||||
|
||||
test('The campers landing page figure is visible on desktop and hidden on mobile view', async ({
|
||||
isMobile
|
||||
}) => {
|
||||
const landingPageImage = page.getByTestId('landing-page-figure');
|
||||
if (isMobile) {
|
||||
await expect(landingPageImage).toBeHidden();
|
||||
} else {
|
||||
await expect(landingPageImage).toBeVisible();
|
||||
}
|
||||
});
|
||||
|
||||
test('The as seen in container is visible with featured logos', async () => {
|
||||
const asSeenInContainer = page.getByTestId('landing-as-seen-in-text');
|
||||
await expect(asSeenInContainer).toHaveText(
|
||||
translations.landing['as-seen-in']
|
||||
);
|
||||
const featuredLogos = page.getByTestId('landing-as-seen-in-container-logos');
|
||||
await expect(featuredLogos).toBeVisible();
|
||||
});
|
||||
|
||||
test('Testimonial section has a header', async () => {
|
||||
const testimonialsHeader = page.getByTestId('testimonials-section-header');
|
||||
await expect(testimonialsHeader).toHaveText(
|
||||
translations.landing.testimonials['heading']
|
||||
);
|
||||
});
|
||||
|
||||
test('Testimonial endorser people have images, occupation, location and testimony visible', async () => {
|
||||
const cards = page.getByTestId('testimonial-card');
|
||||
await expect(cards).toHaveCount(3);
|
||||
for (const card of await cards.all()) {
|
||||
await expect(card).toBeVisible();
|
||||
await expect(
|
||||
card.getByTestId('testimonials-endorser-image-container')
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
card.getByTestId('testimonials-endorser-location')
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
card.getByTestId('testimonials-endorser-occupation')
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
card.getByTestId('testimonials-endorser-testimony')
|
||||
).toBeVisible();
|
||||
}
|
||||
});
|
||||
|
||||
test('Has links to all curriculum', async () => {
|
||||
const curriculumBtns = page.getByTestId(landingPageElements.curriculumBtns);
|
||||
await expect(curriculumBtns).toHaveCount(21);
|
||||
for (let index = 0; index < superBlocks.length; index++) {
|
||||
const btn = curriculumBtns.nth(index);
|
||||
await expect(btn).toContainText(superBlocks[index]);
|
||||
}
|
||||
});
|
||||
|
||||
test('Has FAQ section', async () => {
|
||||
const faqs = page.getByTestId(landingPageElements.faq);
|
||||
await expect(faqs).toHaveCount(9);
|
||||
});
|
||||
|
||||
test("Has CTA Get Started It's free buttons", async () => {
|
||||
const ctaButtons = page.getByRole('link', {
|
||||
name: "Get started (it's free)"
|
||||
test.describe('Landing Page - Variation B', () => {
|
||||
test.beforeEach(async ({ context, page }) => {
|
||||
await addDefaultCookies(context, 'B');
|
||||
await goToLandingPage(page);
|
||||
});
|
||||
|
||||
test('The component Landing-top renders correctly', async ({ page }) => {
|
||||
await expect(
|
||||
page
|
||||
.getByRole('heading', { level: 1 })
|
||||
.filter({ hasText: `${translations.landing['big-heading-1-b']}` })
|
||||
).toBeVisible();
|
||||
|
||||
const landingHeading2 = page.getByTestId('landing-big-heading-2');
|
||||
await expect(landingHeading2).toHaveText(
|
||||
translations.landing['big-heading-2']
|
||||
);
|
||||
|
||||
const landingHeading3 = page.getByTestId('landing-big-heading-3');
|
||||
await expect(landingHeading3).toHaveText(
|
||||
translations.landing['big-heading-3']
|
||||
);
|
||||
|
||||
const landingHeading4 = page.getByTestId('landing-big-heading-4');
|
||||
await expect(landingHeading4).toHaveText(
|
||||
translations.landing['big-heading-4']
|
||||
);
|
||||
|
||||
const landingH2Heading = page.getByTestId('landing-h2-heading-b');
|
||||
await expect(landingH2Heading).toHaveText(
|
||||
translations.landing['h2-heading-b']
|
||||
);
|
||||
});
|
||||
|
||||
test('CTA buttons should render correctly', async ({ page }) => {
|
||||
const mainCta = page.getByRole('link', {
|
||||
name: translations.buttons['get-started'],
|
||||
exact: true
|
||||
});
|
||||
await expect(mainCta).toHaveCount(1);
|
||||
for (const cta of await mainCta.all()) {
|
||||
await expect(cta).toBeVisible();
|
||||
}
|
||||
|
||||
const ctas = page.getByRole('link', {
|
||||
name: translations.buttons['logged-in-cta-btn'],
|
||||
exact: true
|
||||
});
|
||||
await expect(ctas).toHaveCount(3);
|
||||
for (const cta of await ctas.all()) {
|
||||
await expect(cta).toBeVisible();
|
||||
}
|
||||
});
|
||||
|
||||
test('Hero image should have a descriptive alt', async ({
|
||||
isMobile,
|
||||
page
|
||||
}) => {
|
||||
const campersImage = page.getByAltText(
|
||||
translations.landing['hero-img-uis']
|
||||
);
|
||||
|
||||
if (isMobile) {
|
||||
await expect(campersImage).toBeHidden();
|
||||
} else {
|
||||
await expect(campersImage).toBeVisible();
|
||||
}
|
||||
});
|
||||
|
||||
test('The as seen in container with featured logos should not exist', async ({
|
||||
page
|
||||
}) => {
|
||||
const asSeenInContainer = page.getByTestId('landing-as-seen-in-text');
|
||||
await expect(asSeenInContainer).toHaveCount(0);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Landing Page - Variation A', () => {
|
||||
test.beforeEach(async ({ context, page }) => {
|
||||
await addDefaultCookies(context, 'A');
|
||||
await goToLandingPage(page);
|
||||
});
|
||||
|
||||
test('The component Landing-top renders correctly', async ({ page }) => {
|
||||
const landingHeading1 = page.getByTestId('landing-big-heading-1');
|
||||
await expect(landingHeading1).toHaveText(
|
||||
translations.landing['big-heading-1']
|
||||
);
|
||||
|
||||
const landingHeading2 = page.getByTestId('landing-big-heading-2');
|
||||
await expect(landingHeading2).toHaveText(
|
||||
translations.landing['big-heading-2']
|
||||
);
|
||||
|
||||
const landingHeading3 = page.getByTestId('landing-big-heading-3');
|
||||
await expect(landingHeading3).toHaveText(
|
||||
translations.landing['big-heading-3']
|
||||
);
|
||||
|
||||
const landingH2Heading = page.getByTestId('landing-h2-heading');
|
||||
await expect(landingH2Heading).toHaveText(
|
||||
translations.landing['h2-heading']
|
||||
);
|
||||
});
|
||||
|
||||
test('Call to action buttons should render correctly', async ({ page }) => {
|
||||
const ctas = page.getByRole('link', {
|
||||
name: translations.buttons['logged-in-cta-btn']
|
||||
});
|
||||
await expect(ctas).toHaveCount(4);
|
||||
for (const cta of await ctas.all()) {
|
||||
await expect(cta).toBeVisible();
|
||||
}
|
||||
});
|
||||
|
||||
test('Hero image should have an alt and a description', async ({
|
||||
isMobile,
|
||||
page
|
||||
}) => {
|
||||
const campersImage = page.getByAltText(
|
||||
translations.landing['hero-img-alt']
|
||||
);
|
||||
const captionText = page.getByText(
|
||||
translations.landing['hero-img-description']
|
||||
);
|
||||
|
||||
if (isMobile) {
|
||||
await expect(campersImage).toBeHidden();
|
||||
await expect(captionText).toBeHidden();
|
||||
} else {
|
||||
await expect(campersImage).toBeVisible();
|
||||
await expect(captionText).toBeVisible();
|
||||
}
|
||||
});
|
||||
|
||||
test('The as seen in container is visible with featured logos', async ({
|
||||
page
|
||||
}) => {
|
||||
const asSeenInContainer = page.getByTestId('landing-as-seen-in-text');
|
||||
await expect(asSeenInContainer).toHaveText(
|
||||
translations.landing['as-seen-in']
|
||||
);
|
||||
const featuredLogos = page.getByTestId(
|
||||
'landing-as-seen-in-container-logos'
|
||||
);
|
||||
await expect(featuredLogos).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Landing Page - common', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await goToLandingPage(page);
|
||||
});
|
||||
|
||||
test('Has 5 brand logos', async ({ page }) => {
|
||||
const logos = page.getByTestId('brand-logo-container').locator('svg');
|
||||
await expect(logos).toHaveCount(5);
|
||||
for (const logo of await logos.all()) {
|
||||
await expect(logo).toBeVisible();
|
||||
}
|
||||
});
|
||||
|
||||
test('The campers landing page figure is visible on desktop and hidden on mobile view', async ({
|
||||
page,
|
||||
isMobile
|
||||
}) => {
|
||||
const landingPageImage = page.getByTestId('landing-page-figure');
|
||||
if (isMobile) {
|
||||
await expect(landingPageImage).toBeHidden();
|
||||
} else {
|
||||
await expect(landingPageImage).toBeVisible();
|
||||
}
|
||||
});
|
||||
|
||||
test('Testimonial section has a header', async ({ page }) => {
|
||||
const testimonialsHeader = page.getByTestId('testimonials-section-header');
|
||||
await expect(testimonialsHeader).toHaveText(
|
||||
translations.landing.testimonials['heading']
|
||||
);
|
||||
});
|
||||
|
||||
test('Testimonial endorser people have images, occupation, location and testimony visible', async ({
|
||||
page
|
||||
}) => {
|
||||
const cards = page.getByTestId('testimonial-card');
|
||||
await expect(cards).toHaveCount(3);
|
||||
for (const card of await cards.all()) {
|
||||
await expect(card).toBeVisible();
|
||||
await expect(
|
||||
card.getByTestId('testimonials-endorser-image-container')
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
card.getByTestId('testimonials-endorser-location')
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
card.getByTestId('testimonials-endorser-occupation')
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
card.getByTestId('testimonials-endorser-testimony')
|
||||
).toBeVisible();
|
||||
}
|
||||
});
|
||||
|
||||
test('Has links to all curriculum', async ({ page }) => {
|
||||
const curriculumBtns = page.getByTestId(landingPageElements.curriculumBtns);
|
||||
await expect(curriculumBtns).toHaveCount(21);
|
||||
for (let index = 0; index < superBlocks.length; index++) {
|
||||
const btn = curriculumBtns.nth(index);
|
||||
await expect(btn).toContainText(superBlocks[index]);
|
||||
}
|
||||
});
|
||||
|
||||
test('Has FAQ section', async ({ page }) => {
|
||||
const faqs = page.getByTestId(landingPageElements.faq);
|
||||
await expect(faqs).toHaveCount(9);
|
||||
});
|
||||
await expect(ctaButtons).toHaveCount(4);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user