Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12,598 changes: 12,598 additions & 0 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion playwright-tests/docs/test_details_home_page.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ Tests to check home page behaviour.
4. Verify redirect to external event platform in new tab
5. Verify external URL contains "meetup.com"

**Status:** Pending :x:
**Status:** Done :white_check_mark:

---

Expand Down
2 changes: 1 addition & 1 deletion playwright-tests/docs/test_plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ This is an overview of the playwright tests planned for this repository.
| ---------------------------------------------------------------------- | ------ |
| [HP-001: Join the Community](./test_details_home_page.md#hp-001) | ✅ |
| [HP-002: Explore Programmes](./test_details_home_page.md#hp-002) | ✅ |
| [HP-003: View Events](./test_details_home_page.md#hp-003) | |
| [HP-003: View Events](./test_details_home_page.md#hp-003) | |
| [HP-004: Learn About Volunteering](./test_details_home_page.md#hp-004) | ✅ |

**See:** [test_details_home_page.md](./test_details_home_page.md) for full test descriptions
Expand Down
104 changes: 104 additions & 0 deletions playwright-tests/pages/event-card.page.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import { expect, Locator, Page } from '@playwright/test';
import { BasePage } from '@pages/base.page';

export class EventCard extends BasePage {
readonly card: Locator;

constructor(page: Page, card: Locator) {
super(page);
this.card = card;
}

async verifyCardStructure(): Promise<void> {
await expect(this.card).toBeVisible();

// Event type label (e.g., "IN_PERSON", "ONLINE_MEETUP")
const eventType = this.card.getByTestId('event-card-type');
await expect(eventType).toHaveText(/^[A-Z_]+$/);

// Date format example: "Thu, May 30, 2024, 8:00 PM CEST - 9:30 PM CEST"
// Pattern: DayAbbr + content + Year(4 digits) + content + time
const eventDate = this.card.getByTestId('event-card-date');
await expect(eventDate).toHaveText(
/[A-Za-z]{3}.{0,20}\d{4}.{0,10}\d{1,2}:\d{2}/,
);

const eventTitle = this.card.getByTestId('event-card-title');
await expect(eventTitle).not.toBeEmpty();

const eventSpeaker = this.card.getByTestId('event-card-speaker');
await expect(eventSpeaker).toHaveText(/^Speaker:/);

const eventDescription = this.card.getByTestId('event-card-description');
await expect(eventDescription).not.toBeEmpty();

const eventImage = this.card.getByTestId('event-card-image');
await expect(eventImage).toBeVisible();

const eventCTA = this.card.getByTestId('event-card-cta');
await expect(eventCTA).not.toBeEmpty();
}

/**
* Clicks CTA button and waits for new page to open
* @returns The new page that was opened
*/
async clickCtaAndWaitForNewPage(): Promise<Page> {
const ctaButton = this.card.getByTestId('event-card-cta');
const [newPage] = await Promise.all([
this.page.context().waitForEvent('page'),
ctaButton.click(),
]);
await newPage.waitForLoadState();
return newPage;
}
}

// Represents the Events section container on the home page
export class EventsSection extends BasePage {
readonly section: Locator;

constructor(page: Page, section: Locator) {
super(page);
this.section = section;
}

/**
* Gets an EventCard instance by index
* @param index - Zero-based index of the event card
*/
getEventCard(index: number): EventCard {
const cards = this.section.getByTestId('event-card');
return new EventCard(this.page, cards.nth(index));
}

/**
* Gets an EventCard by matching title text
* @param titleText - The event title to search for
*/
getEventCardByTitle(titleText: string): EventCard {
const card = this.section
.getByTestId('event-card')
.filter({
has: this.page
.getByTestId('event-card-title')
.filter({ hasText: titleText }),
})
.first();
return new EventCard(this.page, card);
}

async verifySectionVisible(): Promise<void> {
const sectionTitle = this.section.getByTestId('events-section-title');
const viewAllLink = this.section.getByTestId('events-view-all-link');

await expect(this.section).toBeVisible();
await expect(sectionTitle).toBeVisible();
await expect(viewAllLink).toBeVisible();
}

async clickViewAllEventsLink(): Promise<void> {
const viewAllLink = this.section.getByTestId('events-view-all-link');
await viewAllLink.click();
}
}
9 changes: 9 additions & 0 deletions playwright-tests/pages/home.page.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Locator, Page } from '@playwright/test';

import { BasePage } from '@pages/base.page';
import { EventsSection } from '@pages/event-card.page';

export class HomePage extends BasePage {
readonly becomeMentorSectionTitle: Locator;
Expand Down Expand Up @@ -74,4 +75,12 @@ export class HomePage extends BasePage {

this.joinSlackButton = page.getByRole('link', { name: 'Join our Slack' });
}

// Gets the EventsSection page object for events-related interactions
get eventsSection(): EventsSection {
return new EventsSection(
this.page,
this.page.getByTestId('events-section'),
);
}
}
44 changes: 44 additions & 0 deletions playwright-tests/tests/home.page.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,50 @@ test.describe('Validate Home Page', () => {
await basePage.verifyPageContainsText('404 - Not found'); // not implemented yet
});

test('HP-003: Verify Events Card information and CTA link', async ({
page,
homePage,
}) => {
await test.step('Verify events section is visible', async () => {
await homePage.eventsSection.verifySectionVisible();
});

await test.step('Verify event card displays all required information', async () => {
const eventCard = homePage.eventsSection.getEventCard(0);
await eventCard.verifyCardStructure();
});

await test.step('Verify CTA button opens external link', async () => {
const eventCard = homePage.eventsSection.getEventCard(0);
const newPage = await eventCard.clickCtaAndWaitForNewPage();

const url = newPage.url();
const isValidDomain =
url.includes('github.com') || url.includes('meetup.com');
expect(isValidDomain).toBeTruthy();

await newPage.close();
await page.bringToFront();
});

await test.step('Verify "View all events" link navigates to events page', async () => {
await homePage.eventsSection.clickViewAllEventsLink();
await homePage.verifyURL('/events');
});
});

test('HP-004: Become Mentor section', async ({ homePage, basePage }) => {
await expect(homePage.becomeMentorSectionTitle).toBeVisible();
await expect(homePage.becomeMentorSectionDescription).toBeVisible();
await expect(homePage.joinAsMentorBtn).toBeVisible();
await basePage.clickElement(homePage.joinAsMentorBtn);

await basePage.verifyURL('/mentorship/mentor-registration');
await basePage.verifyPageContainsText(
'Welcome to the MentorRegistrationPage',
);
});

test('HP-004: Volunteer section', async ({ homePage, basePage }) => {
await basePage.clickElement(homePage.learnMoreVolunteerBtn);

Expand Down
10 changes: 9 additions & 1 deletion src/components/EventCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export const EventCard = ({
return (
<Grid
container
data-testid="event-card"
Comment thread
dricazenck marked this conversation as resolved.
sx={{
maxWidth: isMobile ? '362px' : '544px',
margin: '0 auto',
Expand Down Expand Up @@ -65,6 +66,7 @@ export const EventCard = ({
<Image
src={images[0].path}
alt={images[0].alt}
data-testid="event-card-image"
fill
style={{ objectFit: 'cover' }}
priority
Expand All @@ -74,6 +76,7 @@ export const EventCard = ({
<Image
src={images[0].path}
alt={images[0].alt}
data-testid="event-card-image"
width={134}
height={134}
style={{ objectFit: 'cover' }}
Expand All @@ -86,6 +89,7 @@ export const EventCard = ({
<Typography
variant="caption"
component="label"
data-testid="event-card-type"
sx={{
display: 'inline-block',
padding: '6px 12px',
Expand All @@ -111,6 +115,7 @@ export const EventCard = ({
<Typography
variant="body2"
fontSize={12}
data-testid="event-card-date"
fontWeight={500}
lineHeight={1.3}
>
Expand All @@ -119,6 +124,7 @@ export const EventCard = ({
</Box>
<Typography
variant="h5"
data-testid="event-card-title"
fontSize={24}
lineHeight={1.3}
fontWeight={400}
Expand All @@ -128,6 +134,7 @@ export const EventCard = ({
</Typography>
<Typography
variant="body2"
data-testid="event-card-speaker"
fontSize={14}
fontWeight={400}
lineHeight={1.4}
Expand All @@ -140,6 +147,7 @@ export const EventCard = ({
<Box>
<Typography
variant="body1"
data-testid="event-card-description"
fontSize={16}
fontWeight={400}
lineHeight={1.5}
Expand All @@ -150,7 +158,7 @@ export const EventCard = ({
</Box>
</Box>
<Box sx={{ marginTop: 'auto' }}>
<LinkButton href={link.uri} reversed>
<LinkButton href={link.uri} reversed data-testid="event-card-cta">
{link.label}{' '}
<LaunchIcon sx={{ paddingLeft: '5px', fontSize: '16px' }} />
</LinkButton>
Expand Down
8 changes: 6 additions & 2 deletions src/components/EventContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export const EventContainer = ({ title, link, items }: EventContainerProps) => {
const isMobile = useMediaQuery(theme.breakpoints.down(750));

return (
<Box sx={theme.custom.containerBox}>
<Box sx={theme.custom.containerBox} data-testid="events-section">
<Box sx={theme.custom.innerBox}>
<Box
sx={{
Expand All @@ -22,10 +22,13 @@ export const EventContainer = ({ title, link, items }: EventContainerProps) => {
width: '100%',
}}
>
<Typography variant="h3">{title}</Typography>
<Typography variant="h3" data-testid="events-section-title">
{title}
</Typography>
<Link
href={link.uri}
underline="none"
data-testid="events-view-all-link"
sx={{
display: 'flex',
alignItems: 'center',
Expand All @@ -50,6 +53,7 @@ export const EventContainer = ({ title, link, items }: EventContainerProps) => {
gradientColors="linear-gradient(90deg, #C7E7FF 0%, #FFDEA6 100%)"
/>
<Box
data-testid="events-cards-container"
sx={{
display: 'grid',
gap: isMobile ? '20px' : '40px',
Expand Down
4 changes: 4 additions & 0 deletions src/components/LinkButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ type LinkButtonProps = {
reversed?: boolean;
small?: boolean;
children: React.ReactNode;
'data-testid'?: string;
};

export const LinkButton = ({
href,
reversed,
small,
children,
'data-testid': dataTestId,
}: LinkButtonProps) => {
const isExternal = href.startsWith('https');

Expand All @@ -25,6 +27,7 @@ export const LinkButton = ({
target="_blank"
rel="noopener noreferrer"
variant="contained"
data-testid={dataTestId}
sx={{
backgroundColor: reversed ? '#fff' : 'primary.main',
color: reversed ? 'primary.main' : '#fff',
Expand All @@ -45,6 +48,7 @@ export const LinkButton = ({
<Button
component="a"
variant="contained"
data-testid={dataTestId}
sx={{
backgroundColor: reversed ? '#fff' : 'primary.main',
color: reversed ? 'primary.main' : '#fff',
Expand Down
Loading