Skip to content

Commit 97cecbd

Browse files
committed
feat(e2e): enhance end-to-end tests with prayers and meetings pages
- Updated Playwright configuration for improved parallel execution and dynamic base URL. - Added new test data for prayers and meetings to support E2E coverage. - Introduced PrayersPage and MeetingsPage classes for better page object management. - Removed outdated placeholder tests for prayers and meetings, focusing on new implementations. - Updated utility functions to reset test data, including seeding prayers and meetings.
1 parent 13034d1 commit 97cecbd

9 files changed

Lines changed: 266 additions & 63 deletions

File tree

e2e/fixtures/test-data.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,38 @@ export const TEST_TASKS = {
6464
},
6565
} as const;
6666

67+
/**
68+
* Test prayers seeded for Program > Prayers E2E coverage.
69+
*/
70+
export const TEST_PRAYERS = {
71+
step: {
72+
id: '66666666-aaaa-aaaa-aaaa-aaaaaaaaaaaa',
73+
title: 'Step Three Prayer',
74+
content: 'Guide me to turn my will and my life over to your care.',
75+
category: 'step' as const,
76+
stepNumber: 3,
77+
sortOrder: 10,
78+
},
79+
common: {
80+
id: '77777777-aaaa-aaaa-aaaa-aaaaaaaaaaaa',
81+
title: 'Serenity Prayer',
82+
content: 'Grant me the serenity to accept the things I cannot change.',
83+
category: 'common' as const,
84+
sortOrder: 20,
85+
},
86+
} as const;
87+
88+
/**
89+
* Test meetings seeded for Program > Meetings E2E coverage.
90+
*/
91+
export const TEST_MEETINGS = {
92+
today: {
93+
id: '88888888-aaaa-aaaa-aaaa-aaaaaaaaaaaa',
94+
name: 'E2E Morning Meeting',
95+
location: 'Online',
96+
},
97+
} as const;
98+
6799
/**
68100
* Create a unique signup email address for end-to-end tests.
69101
*

e2e/pages/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ export { EditSavingsSheet } from './savings.page';
1717
export {
1818
ProgramPage,
1919
PlaceholderPage,
20+
PrayersPage,
21+
MeetingsPage,
2022
createDailyReadingsPage,
21-
createPrayersPage,
2223
createLiteraturePage,
23-
createMeetingsPage,
2424
} from './program.page';

e2e/pages/program.page.ts

Lines changed: 86 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import { Page, expect, Locator } from '@playwright/test';
22
import { BasePage } from './base.page';
33

4+
/**
5+
* Page object for the Program tab navigation.
6+
*/
47
export class ProgramPage extends BasePage {
58
readonly tabBar: Locator;
69
readonly stepsTab: Locator;
@@ -56,6 +59,9 @@ export class ProgramPage extends BasePage {
5659
}
5760
}
5861

62+
/**
63+
* Page object for Program placeholder screens.
64+
*/
5965
export class PlaceholderPage extends BasePage {
6066
readonly screen: Locator;
6167
readonly title: Locator;
@@ -85,7 +91,85 @@ export class PlaceholderPage extends BasePage {
8591
}
8692
}
8793

94+
/**
95+
* Page object for the Program > Prayers tab.
96+
*/
97+
export class PrayersPage extends BasePage {
98+
readonly prayersList: Locator;
99+
readonly filterAll: Locator;
100+
readonly filterFavorites: Locator;
101+
readonly filterStep: Locator;
102+
readonly filterCommon: Locator;
103+
readonly prayerCards: Locator;
104+
105+
constructor(page: Page) {
106+
super(page);
107+
this.prayersList = page.getByTestId('prayers-list');
108+
this.filterAll = page.getByTestId('filter-all');
109+
this.filterFavorites = page.getByTestId('filter-favorites');
110+
this.filterStep = page.getByTestId('filter-step');
111+
this.filterCommon = page.getByTestId('filter-common');
112+
this.prayerCards = page.getByTestId(/^prayer-card-/);
113+
}
114+
115+
async goto(): Promise<void> {
116+
await this.page.goto('/program/prayers');
117+
await this.waitForPageLoad();
118+
}
119+
120+
async expectOnPrayersPage(): Promise<void> {
121+
await expect(this.prayersList).toBeVisible();
122+
}
123+
124+
async selectFilter(filter: 'all' | 'favorites' | 'step' | 'common'): Promise<void> {
125+
await this.page.getByTestId(`filter-${filter}`).click();
126+
}
127+
128+
getPrayerCard(prayerId: string): Locator {
129+
return this.page.getByTestId(`prayer-card-${prayerId}`);
130+
}
131+
132+
getFavoriteButton(prayerId: string): Locator {
133+
return this.page.getByTestId(`prayer-favorite-${prayerId}`);
134+
}
135+
}
136+
137+
/**
138+
* Page object for the Program > Meetings tab.
139+
*/
140+
export class MeetingsPage extends BasePage {
141+
readonly meetingItems: Locator;
142+
143+
constructor(page: Page) {
144+
super(page);
145+
this.meetingItems = page.getByTestId(/^meeting-item-/);
146+
}
147+
148+
async goto(): Promise<void> {
149+
await this.page.goto('/program/meetings');
150+
await this.waitForPageLoad();
151+
}
152+
153+
async expectOnMeetingsPage(): Promise<void> {
154+
const today = new Date().getDate();
155+
await expect(this.getCalendarDay(today)).toBeVisible();
156+
}
157+
158+
getCalendarDay(day: number): Locator {
159+
return this.page.getByTestId(`calendar-day-${day}`);
160+
}
161+
162+
getMeetingItem(meetingId: string): Locator {
163+
return this.page.getByTestId(`meeting-item-${meetingId}`);
164+
}
165+
}
166+
167+
/**
168+
* Factory for Daily Readings placeholder page object.
169+
*/
88170
export const createDailyReadingsPage = (page: Page) => new PlaceholderPage(page, 'daily-readings');
89-
export const createPrayersPage = (page: Page) => new PlaceholderPage(page, 'prayers');
171+
172+
/**
173+
* Factory for Literature placeholder page object.
174+
*/
90175
export const createLiteraturePage = (page: Page) => new PlaceholderPage(page, 'literature');
91-
export const createMeetingsPage = (page: Page) => new PlaceholderPage(page, 'meetings');

e2e/tests/program/meetings.spec.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { test, expect } from '@playwright/test';
2+
import { MeetingsPage } from '../../pages';
3+
import { TEST_MEETINGS } from '../../fixtures/test-data';
4+
5+
test.describe('Program Meetings', () => {
6+
let meetingsPage: MeetingsPage;
7+
8+
test.beforeEach(async ({ page }) => {
9+
meetingsPage = new MeetingsPage(page);
10+
await meetingsPage.goto();
11+
});
12+
13+
test('should display calendar and seeded meeting', async ({ page }) => {
14+
await meetingsPage.expectOnMeetingsPage();
15+
await expect(meetingsPage.getMeetingItem(TEST_MEETINGS.today.id)).toBeVisible();
16+
await expect(page.getByText(TEST_MEETINGS.today.name)).toBeVisible();
17+
});
18+
19+
test('should open day detail sheet for today', async ({ page }) => {
20+
const today = new Date().getDate();
21+
await meetingsPage.getCalendarDay(today).click();
22+
await expect(page.getByText('Log Meeting')).toBeVisible();
23+
});
24+
});

e2e/tests/program/placeholder.spec.ts

Lines changed: 0 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,7 @@ import { test, expect } from '@playwright/test';
22
import {
33
ProgramPage,
44
createDailyReadingsPage,
5-
createPrayersPage,
65
createLiteraturePage,
7-
createMeetingsPage,
86
} from '../../pages/program.page';
97

108
test.describe('Program Placeholder Screens', () => {
@@ -39,30 +37,6 @@ test.describe('Program Placeholder Screens', () => {
3937
});
4038
});
4139

42-
test.describe('Prayers', () => {
43-
test('should display prayers placeholder screen', async ({ page }) => {
44-
await programPage.navigateToPrayers();
45-
46-
const prayersPage = createPrayersPage(page);
47-
await prayersPage.expectOnPlaceholderScreen();
48-
});
49-
50-
test('should show correct title', async ({ page }) => {
51-
await programPage.navigateToPrayers();
52-
53-
const prayersPage = createPrayersPage(page);
54-
const title = await prayersPage.getTitle();
55-
expect(title).toBe('Prayers');
56-
});
57-
58-
test('should show coming soon subtitle', async ({ page }) => {
59-
await programPage.navigateToPrayers();
60-
61-
const prayersPage = createPrayersPage(page);
62-
await prayersPage.expectComingSoon();
63-
});
64-
});
65-
6640
test.describe('Literature', () => {
6741
test('should display literature placeholder screen', async ({ page }) => {
6842
await programPage.navigateToLiterature();
@@ -87,36 +61,10 @@ test.describe('Program Placeholder Screens', () => {
8761
});
8862
});
8963

90-
test.describe('Meetings', () => {
91-
test('should display meetings placeholder screen', async ({ page }) => {
92-
await programPage.navigateToMeetings();
93-
94-
const meetingsPage = createMeetingsPage(page);
95-
await meetingsPage.expectOnPlaceholderScreen();
96-
});
97-
98-
test('should show correct title', async ({ page }) => {
99-
await programPage.navigateToMeetings();
100-
101-
const meetingsPage = createMeetingsPage(page);
102-
const title = await meetingsPage.getTitle();
103-
expect(title).toBe('Meetings');
104-
});
105-
106-
test('should show coming soon subtitle', async ({ page }) => {
107-
await programPage.navigateToMeetings();
108-
109-
const meetingsPage = createMeetingsPage(page);
110-
await meetingsPage.expectComingSoon();
111-
});
112-
});
113-
11464
test('all placeholder screens should show coming soon message', async ({ page }) => {
11565
const screens = [
11666
{ navigate: () => programPage.navigateToDaily(), factory: createDailyReadingsPage },
117-
{ navigate: () => programPage.navigateToPrayers(), factory: createPrayersPage },
11867
{ navigate: () => programPage.navigateToLiterature(), factory: createLiteraturePage },
119-
{ navigate: () => programPage.navigateToMeetings(), factory: createMeetingsPage },
12068
];
12169

12270
for (const { navigate, factory } of screens) {

e2e/tests/program/prayers.spec.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { test, expect } from '@playwright/test';
2+
import { PrayersPage } from '../../pages';
3+
import { TEST_PRAYERS } from '../../fixtures/test-data';
4+
5+
test.describe('Program Prayers', () => {
6+
let prayersPage: PrayersPage;
7+
8+
test.beforeEach(async ({ page }) => {
9+
prayersPage = new PrayersPage(page);
10+
await prayersPage.goto();
11+
});
12+
13+
test('should display filters and list', async () => {
14+
await prayersPage.expectOnPrayersPage();
15+
await expect(prayersPage.filterAll).toBeVisible();
16+
await expect(prayersPage.filterFavorites).toBeVisible();
17+
await expect(prayersPage.filterStep).toBeVisible();
18+
await expect(prayersPage.filterCommon).toBeVisible();
19+
});
20+
21+
test('should render seeded prayers', async () => {
22+
await expect(prayersPage.getPrayerCard(TEST_PRAYERS.step.id)).toBeVisible();
23+
await expect(prayersPage.getPrayerCard(TEST_PRAYERS.common.id)).toBeVisible();
24+
});
25+
26+
test('should show empty state for favorites filter', async ({ page }) => {
27+
await prayersPage.selectFilter('favorites');
28+
await expect(page.getByText('No favorite prayers yet', { exact: false })).toBeVisible();
29+
});
30+
31+
test('should filter to step prayers', async () => {
32+
await prayersPage.selectFilter('step');
33+
await expect(prayersPage.getPrayerCard(TEST_PRAYERS.step.id)).toBeVisible();
34+
await expect(prayersPage.getPrayerCard(TEST_PRAYERS.common.id)).toHaveCount(0);
35+
});
36+
});

e2e/utils/supabase-helpers.ts

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
import { createClient, SupabaseClient } from '@supabase/supabase-js';
2-
import { TEST_USERS, TEST_TASKS, TEST_INVITE_CODES } from '../fixtures/test-data';
2+
import {
3+
TEST_USERS,
4+
TEST_TASKS,
5+
TEST_INVITE_CODES,
6+
TEST_PRAYERS,
7+
TEST_MEETINGS,
8+
} from '../fixtures/test-data';
39

410
// Lazy-initialized client to avoid errors when listing tests without env vars
511
let adminClient: SupabaseClient | null = null;
@@ -45,12 +51,17 @@ export async function resetTestData(): Promise<void> {
4551
const client = getAdminClient();
4652
const now = new Date().toISOString();
4753
const inviteExpiry = new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString();
54+
const recentMeetingTime = new Date(Date.now() - 60 * 60 * 1000).toISOString();
4855

4956
// Reset task completions for test users (IDs from test-data.ts fixtures)
5057
const testUserIds = [TEST_USERS.primary.id, TEST_USERS.sponsor.id, TEST_USERS.sponsee.id];
5158

5259
await client.from('task_completions').delete().in('user_id', testUserIds);
5360

61+
await client.from('user_prayer_favorites').delete().eq('user_id', TEST_USERS.primary.id);
62+
await client.from('user_meeting_milestones').delete().eq('user_id', TEST_USERS.primary.id);
63+
await client.from('user_meetings').delete().eq('user_id', TEST_USERS.primary.id);
64+
5465
// Reset onboarding user profile
5566
await client.from('profiles').delete().eq('email', TEST_USERS.onboarding.email);
5667

@@ -103,6 +114,35 @@ export async function resetTestData(): Promise<void> {
103114
throw new Error(`Failed to seed relationships: ${relationshipsError.message}`);
104115
}
105116

117+
const { error: prayersError } = await client.from('prayers').upsert(
118+
[
119+
{
120+
id: TEST_PRAYERS.step.id,
121+
title: TEST_PRAYERS.step.title,
122+
content: TEST_PRAYERS.step.content,
123+
category: TEST_PRAYERS.step.category,
124+
step_number: TEST_PRAYERS.step.stepNumber,
125+
sort_order: TEST_PRAYERS.step.sortOrder,
126+
created_at: now,
127+
updated_at: now,
128+
},
129+
{
130+
id: TEST_PRAYERS.common.id,
131+
title: TEST_PRAYERS.common.title,
132+
content: TEST_PRAYERS.common.content,
133+
category: TEST_PRAYERS.common.category,
134+
sort_order: TEST_PRAYERS.common.sortOrder,
135+
created_at: now,
136+
updated_at: now,
137+
},
138+
],
139+
{ onConflict: 'id' }
140+
);
141+
142+
if (prayersError) {
143+
throw new Error(`Failed to seed prayers: ${prayersError.message}`);
144+
}
145+
106146
const { error: tasksError } = await client.from('tasks').upsert([
107147
{
108148
id: TEST_TASKS.meditation.id,
@@ -140,6 +180,26 @@ export async function resetTestData(): Promise<void> {
140180
throw new Error(`Failed to seed tasks: ${tasksError.message}`);
141181
}
142182

183+
const { error: meetingsError } = await client.from('user_meetings').upsert(
184+
[
185+
{
186+
id: TEST_MEETINGS.today.id,
187+
user_id: TEST_USERS.primary.id,
188+
meeting_name: TEST_MEETINGS.today.name,
189+
meeting_type: 'other',
190+
location: TEST_MEETINGS.today.location,
191+
attended_at: recentMeetingTime,
192+
created_at: now,
193+
updated_at: now,
194+
},
195+
],
196+
{ onConflict: 'id' }
197+
);
198+
199+
if (meetingsError) {
200+
throw new Error(`Failed to seed meetings: ${meetingsError.message}`);
201+
}
202+
143203
const { error: inviteError } = await client.from('invite_codes').upsert(
144204
{
145205
code: TEST_INVITE_CODES.sponsor,

0 commit comments

Comments
 (0)