mirror of
https://github.com/freeCodeCamp/freeCodeCamp.git
synced 2026-05-28 18:26:54 +00:00
AB testing skill based landing page top (#60753)
Co-authored-by: Mrugesh Mohapatra <1884376+raisedadead@users.noreply.github.com>
This commit is contained in:
@@ -5,17 +5,17 @@
|
||||
"aa-test-in-component": {
|
||||
"defaultValue": false
|
||||
},
|
||||
"landing-page-redesign": {
|
||||
"landing-top-skill-focused": {
|
||||
"defaultValue": false,
|
||||
"rules": [
|
||||
{
|
||||
"coverage": 1,
|
||||
"hashAttribute": "id",
|
||||
"seed": "landing-page-redesign",
|
||||
"seed": "landing-top-skill-focused",
|
||||
"hashVersion": 2,
|
||||
"variations": [false, true],
|
||||
"weights": [0.5, 0.5],
|
||||
"key": "landing-page-redesign",
|
||||
"key": "landing-top-skill-focused",
|
||||
"meta": [
|
||||
{
|
||||
"key": "0",
|
||||
|
||||
@@ -114,12 +114,12 @@
|
||||
},
|
||||
"landing": {
|
||||
"big-heading-1": "Learn to code — for free.",
|
||||
"big-heading-1-b": "Learn to code.",
|
||||
"big-heading-2": "Build projects.",
|
||||
"big-heading-1-b": "Build Your Skills for Free.",
|
||||
"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 <strong>100,000</strong> freeCodeCamp.org graduates have gotten <strong>jobs</strong> at tech companies including:",
|
||||
"advance-career": "Advance your career by learning in-demand skills in Programming, DevOps, Cybersecurity, AI Engineering, and English for Developers.",
|
||||
"h2-heading": "More than <strong>100,000</strong> freeCodeCamp.org graduates have gotten <strong>jobs</strong> at tech companies including:",
|
||||
"graduates-work": "More than <strong>100,000</strong> freeCodeCamp graduates work in companies such as",
|
||||
"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.",
|
||||
|
||||
@@ -14,79 +14,69 @@ import {
|
||||
import BigCallToAction from './big-call-to-action';
|
||||
import CampersImage from './campers-image';
|
||||
|
||||
const LogoRow = (): JSX.Element => {
|
||||
function LandingTop(): 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'
|
||||
>
|
||||
<Trans>landing.h2-heading-b</Trans>
|
||||
</p>
|
||||
<div
|
||||
className='logo-row'
|
||||
data-playwright-test-label='brand-logo-container'
|
||||
>
|
||||
<AppleLogo />
|
||||
<GoogleLogo />
|
||||
<MicrosoftLogo />
|
||||
{showChineseLogos ? (
|
||||
<>
|
||||
<TencentLogo />
|
||||
<AlibabaLogo />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<SpotifyLogo />
|
||||
<AmazonLogo />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
function LandingTop(): JSX.Element {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Container fluid={true} className='gradient-container'>
|
||||
<Container className='landing-top landing-top-b'>
|
||||
<Spacer size='m' />
|
||||
<Row>
|
||||
<Col lg={8} lgOffset={2} sm={10} smOffset={1} xs={12}>
|
||||
<Container
|
||||
fluid={true}
|
||||
className='landing-top lading-top-c gradient-container'
|
||||
>
|
||||
<Container>
|
||||
<Row className='landing-top-two-column'>
|
||||
<Spacer size='m' />
|
||||
<Col className='landing-top-left'>
|
||||
<h1
|
||||
id='content-start'
|
||||
className='mega-heading'
|
||||
className='ultra-heading'
|
||||
data-test-label='landing-header'
|
||||
data-playwright-test-label='landing-big-heading-1'
|
||||
data-playwright-test-label='big-heading-1-b'
|
||||
>
|
||||
{t('landing.big-heading-1')}
|
||||
{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 data-playwright-test-label='advance-career'>
|
||||
{t('landing.advance-career')}
|
||||
</p>
|
||||
<p
|
||||
className='mega-heading'
|
||||
data-playwright-test-label='landing-big-heading-3'
|
||||
>
|
||||
{t('landing.big-heading-3')}
|
||||
</p>
|
||||
<LogoRow />
|
||||
<Spacer size='m' />
|
||||
|
||||
<BigCallToAction />
|
||||
<Spacer size='m' />
|
||||
</Col>
|
||||
<Col className='landing-top-right'>
|
||||
<CampersImage />
|
||||
<Spacer size='m' />
|
||||
</Col>
|
||||
</Row>
|
||||
</Container>
|
||||
<Container>
|
||||
<Row>
|
||||
<Col lg={8} lgOffset={2} sm={10} smOffset={1} xs={12}>
|
||||
<CampersImage />
|
||||
<Col sm={10} smOffset={1} xs={12} className='brands-container'>
|
||||
<Spacer size='l' />
|
||||
<p data-playwright-test-label='graduates-work'>
|
||||
<Trans>landing.graduates-work</Trans>
|
||||
</p>
|
||||
<Spacer size='s' />
|
||||
<div
|
||||
className='logo-row'
|
||||
data-playwright-test-label='brand-logo-container'
|
||||
>
|
||||
<AppleLogo />
|
||||
<GoogleLogo />
|
||||
<MicrosoftLogo />
|
||||
{showChineseLogos ? (
|
||||
<>
|
||||
<TencentLogo />
|
||||
<AlibabaLogo />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<SpotifyLogo />
|
||||
<AmazonLogo />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<Spacer size='m' />
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
import { Container, Col, Row, Spacer } from '@freecodecamp/ui';
|
||||
|
||||
import { clientLocale } from '../../../../config/env.json';
|
||||
import {
|
||||
AmazonLogo,
|
||||
@@ -15,64 +14,80 @@ import {
|
||||
import BigCallToAction from './big-call-to-action';
|
||||
import CampersImage from './campers-image';
|
||||
|
||||
function LandingTop(): JSX.Element {
|
||||
const { t } = useTranslation();
|
||||
const LogoRow = (): JSX.Element => {
|
||||
const showChineseLogos = ['chinese', 'chinese-tradition'].includes(
|
||||
clientLocale
|
||||
);
|
||||
|
||||
return (
|
||||
<Container className='landing-top landing-top-a'>
|
||||
<Row>
|
||||
<>
|
||||
<p className='logo-row-title' data-playwright-test-label='h2-heading'>
|
||||
<Trans>landing.h2-heading</Trans>
|
||||
</p>
|
||||
<div
|
||||
className='logo-row'
|
||||
data-playwright-test-label='brand-logo-container'
|
||||
>
|
||||
<AppleLogo />
|
||||
<GoogleLogo />
|
||||
<MicrosoftLogo />
|
||||
{showChineseLogos ? (
|
||||
<>
|
||||
<TencentLogo />
|
||||
<AlibabaLogo />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<SpotifyLogo />
|
||||
<AmazonLogo />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
function LandingTop(): JSX.Element {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Container fluid={true} className='gradient-container'>
|
||||
<Container className='landing-top'>
|
||||
<Spacer size='m' />
|
||||
<Col lg={8} lgOffset={2} sm={10} smOffset={1} xs={12}>
|
||||
<h1
|
||||
id='content-start'
|
||||
className='big-heading'
|
||||
data-test-label='landing-header'
|
||||
data-playwright-test-label='landing-big-heading-1'
|
||||
>
|
||||
{t('landing.big-heading-1')}
|
||||
</h1>
|
||||
<p
|
||||
className='big-heading'
|
||||
data-playwright-test-label='landing-big-heading-2'
|
||||
>
|
||||
{t('landing.big-heading-2')}
|
||||
</p>
|
||||
<p
|
||||
className='big-heading'
|
||||
data-playwright-test-label='landing-big-heading-3'
|
||||
>
|
||||
{t('landing.big-heading-3')}
|
||||
</p>
|
||||
<p data-playwright-test-label='landing-h2-heading'>
|
||||
{t('landing.h2-heading')}
|
||||
</p>
|
||||
<div
|
||||
className='logo-row'
|
||||
data-playwright-test-label='brand-logo-container'
|
||||
>
|
||||
<AppleLogo />
|
||||
<GoogleLogo />
|
||||
<MicrosoftLogo />
|
||||
{showChineseLogos ? (
|
||||
<>
|
||||
<TencentLogo />
|
||||
<AlibabaLogo />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<SpotifyLogo />
|
||||
<AmazonLogo />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<Spacer size='m' />
|
||||
<BigCallToAction />
|
||||
<CampersImage />
|
||||
<Spacer size='m' />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row>
|
||||
<Col lg={8} lgOffset={2} sm={10} smOffset={1} xs={12}>
|
||||
<h1
|
||||
id='content-start'
|
||||
className='mega-heading'
|
||||
data-test-label='landing-header'
|
||||
data-playwright-test-label='landing-big-heading-1'
|
||||
>
|
||||
{t('landing.big-heading-1')}
|
||||
</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>
|
||||
<LogoRow />
|
||||
<Spacer size='m' />
|
||||
<BigCallToAction />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row>
|
||||
<Col lg={8} lgOffset={2} sm={10} smOffset={1} xs={12}>
|
||||
<CampersImage />
|
||||
<Spacer size='m' />
|
||||
</Col>
|
||||
</Row>
|
||||
</Container>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Spacer } from '@freecodecamp/ui';
|
||||
import Media from 'react-responsive';
|
||||
import landingPageb from '../../../assets/images/landing/landing-page-b.svg';
|
||||
import { LazyImage } 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='xl' />
|
||||
</Media>
|
||||
);
|
||||
}
|
||||
|
||||
UIImages.displayName = 'UIImages';
|
||||
export default UIImages;
|
||||
@@ -98,7 +98,6 @@ figcaption.caption {
|
||||
|
||||
.landing-top p {
|
||||
font-size: 1.2rem;
|
||||
font-weight: bold;
|
||||
margin: 0.6rem 0;
|
||||
}
|
||||
|
||||
@@ -218,33 +217,33 @@ figcaption.caption {
|
||||
}
|
||||
|
||||
/* AB testing styles */
|
||||
.landing-page-b .mega-heading {
|
||||
.landing-top .mega-heading {
|
||||
font-size: 2.2rem;
|
||||
margin: 0px 0px 2rem;
|
||||
font-weight: 700;
|
||||
line-height: 2rem;
|
||||
}
|
||||
|
||||
.landing-page-b .landing-top .btn-cta-big {
|
||||
.landing-top .btn-cta-big {
|
||||
max-width: fit-content;
|
||||
padding: 12px 20px;
|
||||
}
|
||||
|
||||
@media (min-width: 500px) {
|
||||
.landing-page-b .mega-heading {
|
||||
.landing-top .mega-heading {
|
||||
font-size: 3rem;
|
||||
line-height: 2.5rem;
|
||||
}
|
||||
.landing-page-b .landing-top .btn-cta-big {
|
||||
.landing-top .btn-cta-big {
|
||||
padding: 12px 40px;
|
||||
}
|
||||
}
|
||||
|
||||
.landing-page-b .logo-row-title {
|
||||
.logo-row-title {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.landing-page-b {
|
||||
.landing-page {
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
@@ -297,3 +296,62 @@ figure.ui-images img {
|
||||
.benefits-container p {
|
||||
color: var(--gray-15);
|
||||
}
|
||||
|
||||
/* AB testing styles */
|
||||
.landing-top-two-column {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.landing-top-two-column p {
|
||||
font-size: 1.3rem;
|
||||
font-weight: normal;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
@media (min-width: 1200px) {
|
||||
.landing-top-two-column {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.landing-top-two-column .landing-top-left {
|
||||
flex: 0 0 50%;
|
||||
max-width: 50%;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.landing-top-two-column .landing-top-right {
|
||||
flex: 0 1 auto;
|
||||
min-width: 0;
|
||||
overflow: visible;
|
||||
}
|
||||
}
|
||||
|
||||
.landing-top .landing-top-two-column figure,
|
||||
.landing-top .landing-top-two-column figure img {
|
||||
margin-left: -100px;
|
||||
margin-top: 0px;
|
||||
height: 550px;
|
||||
width: 850px;
|
||||
max-width: none;
|
||||
-webkit-mask-image: linear-gradient(to right, transparent 0%, black 20%);
|
||||
mask-image: linear-gradient(to right, transparent 0%, black 20%);
|
||||
}
|
||||
|
||||
.landing-top .ultra-heading {
|
||||
font-size: 4rem;
|
||||
}
|
||||
|
||||
.landing-top-right figcaption {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.landing-top .brands-container p {
|
||||
color: var(--quaternary-color);
|
||||
font-weight: normal;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@@ -3,9 +3,8 @@ import { useTranslation } from 'react-i18next';
|
||||
import { useGrowthBook } from '@growthbook/growthbook-react';
|
||||
import SEO from '../components/seo';
|
||||
import { Loader } from '../components/helpers';
|
||||
import LandingTop from '../components/landing/components/landing-top';
|
||||
import LandingTopB from '../components/landing/components/landing-top-b';
|
||||
import AsSeenIn from '../components/landing/components/as-seen-in';
|
||||
import LandingTop from '../components/landing/components/landing-top';
|
||||
import Testimonials from '../components/landing/components/testimonials';
|
||||
import Certifications from '../components/landing/components/certifications';
|
||||
import Faq from '../components/landing/components/faq';
|
||||
@@ -14,19 +13,12 @@ import '../components/landing/landing.css';
|
||||
|
||||
type LandingProps = {
|
||||
showLandingPageRedesign: boolean;
|
||||
showBenefitsSection: boolean;
|
||||
};
|
||||
|
||||
const Landing = ({
|
||||
showLandingPageRedesign,
|
||||
showBenefitsSection
|
||||
}: LandingProps) => (
|
||||
<main
|
||||
className={`landing-page ${showLandingPageRedesign ? 'landing-page-b' : ''}`}
|
||||
>
|
||||
const Landing = ({ showLandingPageRedesign }: LandingProps) => (
|
||||
<main className={`landing-page`}>
|
||||
{showLandingPageRedesign ? <LandingTopB /> : <LandingTop />}
|
||||
{showBenefitsSection ? <Benefits /> : <AsSeenIn />}
|
||||
|
||||
<Benefits />
|
||||
<Testimonials />
|
||||
<Certifications />
|
||||
<Faq />
|
||||
@@ -38,21 +30,14 @@ function IndexPage(): JSX.Element {
|
||||
const growthbook = useGrowthBook();
|
||||
if (growthbook && growthbook.ready) {
|
||||
const showLandingPageRedesign = growthbook.getFeatureValue(
|
||||
'landing-page-redesign',
|
||||
false
|
||||
);
|
||||
const showBenefitsSection = growthbook.getFeatureValue(
|
||||
'show-benefits',
|
||||
'landing-top-skill-focused',
|
||||
false
|
||||
);
|
||||
growthbook.getFeatureValue('landing-aa-test', false);
|
||||
return (
|
||||
<>
|
||||
<SEO title={t('metaTags:title')} />
|
||||
<Landing
|
||||
showLandingPageRedesign={showLandingPageRedesign}
|
||||
showBenefitsSection={showBenefitsSection}
|
||||
/>
|
||||
<Landing showLandingPageRedesign={showLandingPageRedesign} />
|
||||
</>
|
||||
);
|
||||
} else {
|
||||
|
||||
+50
-52
@@ -51,70 +51,32 @@ test.describe('Landing Top - Variation B', () => {
|
||||
await goToLandingPage(page);
|
||||
});
|
||||
|
||||
test('The supporting copy renders correctly', async ({ page }) => {
|
||||
const landingH2Heading = page.getByTestId('landing-h2-heading-b');
|
||||
await expect(landingH2Heading).toHaveText(
|
||||
translations.landing['h2-heading-b'].replace(/<\/?strong>/g, '')
|
||||
test('Main heading copy renders correctly', async ({ page }) => {
|
||||
const bigHeading = page.getByTestId('big-heading-1-b');
|
||||
await expect(bigHeading).toHaveText(
|
||||
translations.landing['big-heading-1-b']
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Second section - Variation B', () => {
|
||||
test('The component Why learn with freeCodeCamp renders correctly', async ({
|
||||
context,
|
||||
page
|
||||
}) => {
|
||||
await addGrowthbookCookie({ context, variation: 'C' });
|
||||
await goToLandingPage(page);
|
||||
const h2Element = page.locator(
|
||||
`h2:has-text("${translations.landing.benefits['heading']}")`
|
||||
test('Supporting copy renders correctly', async ({ page }) => {
|
||||
const bigHeading = page.getByTestId('advance-career');
|
||||
await expect(bigHeading).toHaveText(translations.landing['advance-career']);
|
||||
});
|
||||
|
||||
test('Logo row copy renders correctly', async ({ page }) => {
|
||||
const landingH2Heading = page.getByTestId('graduates-work');
|
||||
await expect(landingH2Heading).toHaveText(
|
||||
translations.landing['graduates-work'].replace(/<\/?strong>/g, '')
|
||||
);
|
||||
|
||||
await expect(h2Element).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Landing Top - Variation A', () => {
|
||||
test.beforeEach(async ({ context, page }) => {
|
||||
await addGrowthbookCookie({ context, variation: 'A' });
|
||||
await addGrowthbookCookie({ context, variation: 'newA' });
|
||||
await goToLandingPage(page);
|
||||
});
|
||||
|
||||
test('The supporting copy renders correctly', async ({ page }) => {
|
||||
const landingH2Heading = page.getByTestId('landing-h2-heading');
|
||||
await expect(landingH2Heading).toHaveText(
|
||||
translations.landing['h2-heading']
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Second section - Variation A', () => {
|
||||
test('The component As Seen renders correctly', async ({ context, page }) => {
|
||||
await addGrowthbookCookie({ context, variation: 'E' });
|
||||
await goToLandingPage(page);
|
||||
const h2Element = page.locator(
|
||||
`h2:has-text("${translations.landing['as-seen-in']}")`
|
||||
);
|
||||
|
||||
await expect(h2Element).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Landing Page', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await goToLandingPage(page);
|
||||
});
|
||||
|
||||
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('The headline renders correctly', async ({ page }) => {
|
||||
const landingHeading1 = page.getByTestId('landing-big-heading-1');
|
||||
await expect(landingHeading1).toHaveText(
|
||||
@@ -132,6 +94,42 @@ test.describe('Landing Page', () => {
|
||||
);
|
||||
});
|
||||
|
||||
test('Logo row copy renders correctly', async ({ page }) => {
|
||||
const landingH2Heading = page.getByTestId('h2-heading');
|
||||
await expect(landingH2Heading).toHaveText(
|
||||
translations.landing['h2-heading'].replace(/<\/?strong>/g, '')
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Landing Page', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await goToLandingPage(page);
|
||||
});
|
||||
|
||||
test('The component Why learn with freeCodeCamp renders correctly', async ({
|
||||
context,
|
||||
page
|
||||
}) => {
|
||||
await addGrowthbookCookie({ context, variation: 'C' });
|
||||
await goToLandingPage(page);
|
||||
const h2Element = page.locator(
|
||||
`h2:has-text("${translations.landing.benefits['heading']}")`
|
||||
);
|
||||
|
||||
await expect(h2Element).toBeVisible();
|
||||
});
|
||||
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user