Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
c3bda75
Add Playwright E2E test suite with global auth and split serving tests
dnplkndll Mar 6, 2026
8ca7759
fix(test): resolve flaky settings tests on clean DB
dnplkndll Mar 6, 2026
8e74c90
fix(tests): update nav selector from #primaryNavButton to #site-header
dnplkndll Mar 7, 2026
8434355
fix(tests): remove nav element dependency in auth helper
dnplkndll Mar 7, 2026
d0907aa
fix(test): wait for church selection dialog instead of email input hide
dnplkndll Mar 7, 2026
d636661
fix(test): detect auth state by racing navButton vs emailInput
dnplkndll Mar 7, 2026
7c51f02
fix(test): use 20s timeouts and confirm navButton ready before returning
dnplkndll Mar 7, 2026
7a00b9a
fix(test): increase CI test timeout to 60s (login takes 20s+navButton…
dnplkndll Mar 7, 2026
c806691
fix(test): reduce emailInput wait to 5s, navButton to 30s — cuts per-…
dnplkndll Mar 7, 2026
132e7ac
debug: add instrumentation to failing settings tests
dnplkndll Mar 8, 2026
11d9c52
fix: Mobile Settings tests navigate via primary nav, remove debug
dnplkndll Mar 8, 2026
cf57dff
fix: use data-testid selector for Mobile nav item
dnplkndll Mar 9, 2026
fb26bf1
chore: add CI diagnostics for Mobile Settings debugging
dnplkndll Mar 9, 2026
39b51cf
chore: remove CI diagnostic instrumentation from settings tests
dnplkndll Mar 9, 2026
4b8b31f
fix: increase actionTimeout and fix dashboard test navigation
dnplkndll Mar 9, 2026
1f8ee6c
fix: target correct form in edit/cancel tests to avoid seed data coll…
dnplkndll Mar 9, 2026
a887a89
fix: target test form in delete test to avoid deleting seed data form
dnplkndll Mar 9, 2026
f27caed
fix: stabilize 6 flaky settings E2E tests
dnplkndll Mar 12, 2026
06810f5
fix: un-skip tests, add mobile nav test IDs
dnplkndll Mar 13, 2026
ec60f0c
fix(tests): eliminate race conditions in settings E2E tests
dnplkndll Mar 13, 2026
655d441
fix(tests): increase cleanup loop limit to handle accumulated test data
dnplkndll Mar 13, 2026
b78132c
fix(tests): replace brittle selectors and fixed timeouts with proper …
dnplkndll Mar 14, 2026
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -119,3 +119,4 @@ dist

# TernJS port file
.tern-port
.auth-state.json
45 changes: 28 additions & 17 deletions playwright.config.ts
Original file line number Diff line number Diff line change
@@ -1,41 +1,52 @@
import { defineConfig, devices } from '@playwright/test';
import path from 'path';
import { fileURLToPath } from 'url';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const STORAGE_STATE_PATH = path.join(__dirname, 'tests', '.auth-state.json');

export default defineConfig({
testDir: './tests',
testMatch: /.*\.spec\.ts/,
fullyParallel: false,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 1,
workers: 4,
reporter: 'html',
timeout: 30 * 1000,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 2 : undefined,
reporter: process.env.CI ? 'list' : 'html',
timeout: 60 * 1000,
expect: { timeout: 5 * 1000 },

globalSetup: './tests/global-setup.ts',

use: {
baseURL: 'https://demo.b1.church',
baseURL: process.env.BASE_URL || 'https://demo.b1.church',
storageState: STORAGE_STATE_PATH,
trace: 'on-first-retry',
screenshot: 'only-on-failure',
video: 'retain-on-failure',
actionTimeout: 5 * 1000,
navigationTimeout: 10 * 1000,
actionTimeout: 15 * 1000,
navigationTimeout: 30 * 1000,
},

projects: [
// Settings must run first — it renames the church, which website tests depend on
{
name: 'settings',
use: {
...devices['Desktop Chrome'],
headless: true,
},
testMatch: /settings\.spec\.ts/,
},
// All other tests run in parallel after settings completes
{
name: 'chromium',
use: {
dependencies: ['settings'],
use: {
...devices['Desktop Chrome'],
headless: true,
args: [
'--no-sandbox',
'--disable-setuid-sandbox',
'--disable-dev-shm-usage',
'--disable-extensions',
'--disable-gpu',
'--disable-web-security',
'--disable-features=VizDisplayCompositor'
]
},
testIgnore: /settings\.spec\.ts/,
},
],
});
16 changes: 8 additions & 8 deletions src/components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,10 +97,10 @@ export const Header: React.FC = () => {
"/donations/funds": "nav-item-funds",
"/serving/songs": "nav-item-songs",
"/profile": "nav-item-profile",
"/profile/devices": "nav-item-devices"
// Temporarily hidden
// "/sermons": "nav-item-sermons",
// "/calendars": "nav-item-calendars",
"/profile/devices": "nav-item-devices",
"/mobile": "nav-item-mobile",
"/site/pages": "nav-item-website",
"/sermons": "nav-item-sermons",
};

// Find all navigation links
Expand Down Expand Up @@ -129,10 +129,10 @@ export const Header: React.FC = () => {
funds: "nav-item-funds",
songs: "nav-item-songs",
profile: "nav-item-profile",
devices: "nav-item-devices"
// Temporarily hidden
// sermons: "nav-item-sermons",
// calendars: "nav-item-calendars",
devices: "nav-item-devices",
mobile: "nav-item-mobile",
website: "nav-item-website",
sermons: "nav-item-sermons",
};

for (const [key, testId] of Object.entries(textToTestId)) {
Expand Down
62 changes: 25 additions & 37 deletions tests/attendance.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,12 @@ test.describe('Attendance Management', () => {
await menuBtn.click();
const peopleHomeBtn = page.locator('[data-testid="nav-item-people"]');
await peopleHomeBtn.click();
await page.waitForTimeout(5000);
await expect(page).toHaveURL(/\/people/);
const attHomeBtn = page.locator('[id="secondaryMenu"]').getByText('Attendance');
await attHomeBtn.click();
await page.waitForTimeout(2000);
await expect(page).toHaveURL(/\/attendance/);
});

/* test('should load attendance page', async ({ page }) => {
const attendanceHeader = page.locator('h4').getByText('Attendance');
await attendanceHeader.click();
}); */

test.describe('Setup', () => {

test('should add campus', async ({ page }) => {
Expand All @@ -31,9 +24,8 @@ test.describe('Attendance Management', () => {
await campusName.fill('Octavian Test Campus');
const saveBtn = page.locator('button').getByText('Save');
await saveBtn.click();
await page.waitForTimeout(500);
const verifiedName = page.locator('button').getByText('Octavian Test Campus');
await expect(verifiedName).toHaveCount(1);
await expect(verifiedName).toHaveCount(1, { timeout: 10000 });
});

test('should cancel adding campus', async ({ page }) => {
Expand All @@ -43,7 +35,7 @@ test.describe('Attendance Management', () => {
await expect(campusName).toHaveCount(1);
const cancelBtn = page.locator('button').getByText('Cancel');
await cancelBtn.click();
await expect(campusName).toHaveCount(0);
await expect(campusName).toHaveCount(0, { timeout: 10000 });
});

test('should edit campus', async ({ page }) => {
Expand All @@ -53,9 +45,8 @@ test.describe('Attendance Management', () => {
await campusName.fill('Octavius Test Campus');
const saveBtn = page.locator('button').getByText('Save');
await saveBtn.click();
await page.waitForTimeout(500);
const verifiedName = page.locator('button').getByText('Octavius Test Campus');
await expect(verifiedName).toHaveCount(1);
await expect(verifiedName).toHaveCount(1, { timeout: 10000 });
});

test('should cancel editing campus', async ({ page }) => {
Expand All @@ -65,22 +56,23 @@ test.describe('Attendance Management', () => {
await expect(campusName).toHaveCount(1);
const cancelBtn = page.locator('button').getByText('Cancel');
await cancelBtn.click();
await expect(campusName).toHaveCount(0);
await expect(campusName).toHaveCount(0, { timeout: 10000 });
});

test('should add service', async ({ page }) => {
const addServBtn = page.locator('button').getByText('Add Service').nth(3);
const addServBtn = page.locator('button').getByText('Add Service').last();
await addServBtn.click();
const campusSelect = page.locator('div[role="combobox"]');
await campusSelect.click();
const selCampus = page.locator('li').getByText('Octavius Test Campus');
await expect(selCampus).toBeVisible({ timeout: 10000 });
await selCampus.click();
const servName = page.locator('input[id="name"]');
await servName.fill('Octavian Test Service');
const saveBtn = page.locator('button').getByText('Save');
await saveBtn.click();
const verifiedServ = page.locator('button').getByText('Octavian Test Service');
await expect(verifiedServ).toHaveCount(1);
await expect(verifiedServ).toHaveCount(1, { timeout: 10000 });
});

test('should edit service', async ({ page }) => {
Expand All @@ -89,25 +81,24 @@ test.describe('Attendance Management', () => {
const campusSelect = page.locator('div[role="combobox"]');
await campusSelect.click();
const selCampus = page.locator('li').getByText('Octavius Test Campus');
await expect(selCampus).toBeVisible({ timeout: 10000 });
await selCampus.click();
const servName = page.locator('input[id="name"]');
await servName.fill('Octavius Test Service');
const saveBtn = page.locator('button').getByText('Save');
await saveBtn.click();
await page.waitForTimeout(500);
const verifiedServ = page.locator('button').getByText('Octavius Test Service');
await expect(verifiedServ).toHaveCount(1);
await expect(verifiedServ).toHaveCount(1, { timeout: 10000 });
});

test('should cancel editing service', async ({ page }) => {
const serv = page.locator('button').getByText('Sunday Evening Service');
await serv.click();
const campusSelect = page.locator('div[role="combobox"]');
await expect(campusSelect).toHaveCount(1);
await expect(campusSelect).toHaveCount(1, { timeout: 10000 });
const cancelBtn = page.locator('button').getByText('Cancel');
await cancelBtn.click();
await page.waitForTimeout(500);
await expect(campusSelect).toHaveCount(0);
await expect(campusSelect).toHaveCount(0, { timeout: 10000 });
});

test('should add service time', async ({ page }) => {
Expand All @@ -116,13 +107,14 @@ test.describe('Attendance Management', () => {
const servSelect = page.locator('div[role="combobox"]');
await servSelect.click();
const selServ = page.locator('li').getByText('Octavius Test Service');
await expect(selServ).toBeVisible({ timeout: 10000 });
await selServ.click();
const timeName = page.locator('input[id="name"]');
await timeName.fill('Octavian Test Time');
const saveBtn = page.locator('button').getByText('Save');
await saveBtn.click();
const verifiedTime = page.locator('button').getByText('Octavian Test Time');
await expect(verifiedTime).toHaveCount(1);
await expect(verifiedTime).toHaveCount(1, { timeout: 10000 });
});

test('should edit service time', async ({ page }) => {
Expand All @@ -131,25 +123,24 @@ test.describe('Attendance Management', () => {
const servSelect = page.locator('div[role="combobox"]');
await servSelect.click();
const selServ = page.locator('li').getByText('Octavius Test Service');
await expect(selServ).toBeVisible({ timeout: 10000 });
await selServ.click();
const timeName = page.locator('input[id="name"]');
await timeName.fill('Octavius Test Time');
const saveBtn = page.locator('button').getByText('Save');
await saveBtn.click();
await page.waitForTimeout(500);
const verifiedTime = page.locator('button').getByText('Octavius Test Time');
await expect(verifiedTime).toHaveCount(1);
await expect(verifiedTime).toHaveCount(1, { timeout: 10000 });
});

test('should cancel editing service time', async ({ page }) => {
const serv = page.locator('button').getByText('6:00 PM Service');
await serv.click();
const servSelect = page.locator('div[role="combobox"]');
await expect(servSelect).toHaveCount(1);
await expect(servSelect).toHaveCount(1, { timeout: 10000 });
const cancelBtn = page.locator('button').getByText('Cancel');
await cancelBtn.click();
await page.waitForTimeout(500);
await expect(servSelect).toHaveCount(0);
await expect(servSelect).toHaveCount(0, { timeout: 10000 });
});

test('should delete service time', async ({ page }) => {
Expand All @@ -163,8 +154,7 @@ test.describe('Attendance Management', () => {
await time.click();
const deleteBtn = page.locator('button').getByText('Delete');
await deleteBtn.click();
await page.waitForTimeout(500);
await expect(time).toHaveCount(0);
await expect(time).toHaveCount(0, { timeout: 10000 });
});

test('should delete service', async ({ page }) => {
Expand All @@ -178,8 +168,7 @@ test.describe('Attendance Management', () => {
await serv.click();
const deleteBtn = page.locator('button').getByText('Delete');
await deleteBtn.click();
await page.waitForTimeout(500);
await expect(serv).toHaveCount(0);
await expect(serv).toHaveCount(0, { timeout: 10000 });
});

test('should delete campus', async ({ page }) => {
Expand All @@ -193,7 +182,7 @@ test.describe('Attendance Management', () => {
await originName.click();
const deleteBtn = page.locator('button').getByText('Delete');
await deleteBtn.click();
await expect(originName).toHaveCount(0);
await expect(originName).toHaveCount(0, { timeout: 10000 });
});

test('should view group from attendance homepage', async ({ page }) => {
Expand All @@ -212,9 +201,9 @@ test.describe('Attendance Management', () => {
test('should filter attendance trends', async ({ page }) => {
const trendTab = page.locator('button[role="tab"]').getByText('Attendance Trend');
await trendTab.click();
await page.waitForTimeout(500);

const campusName = page.locator('[id="mui-component-select-campusId"]');
await expect(campusName).toBeVisible({ timeout: 10000 });
await campusName.click();
const campusSel = page.locator('li').getByText('Main Campus');
await campusSel.click();
Expand All @@ -232,19 +221,18 @@ test.describe('Attendance Management', () => {
await groupSel.click();
const runBtn = page.locator('button').getByText('Run Report');
await runBtn.click();
await page.waitForTimeout(500);

const resultsTableRows = page.locator('[id="reportsBox"] table tr');
expect(resultsTableRows).toHaveCount(36);
await expect(resultsTableRows).toHaveCount(36, { timeout: 10000 });
});

test('UPDATE should display group attendance', async ({ page }) => {
// completed as I can, correcting reports display info is up to father. Data does not load in.
const trendTab = page.locator('button[role="tab"]').getByText('Group Attendance');
await trendTab.click();
await page.waitForTimeout(500);

const campusName = page.locator('[id="mui-component-select-campusId"]');
await expect(campusName).toBeVisible({ timeout: 10000 });
await campusName.click();
const campusSel = page.locator('li').getByText('Main Campus');
await campusSel.click();
Expand All @@ -256,10 +244,10 @@ test.describe('Attendance Management', () => {
await weekBox.fill('2024-03-03');
const runBtn = page.locator('button').getByText('Run Report');
await runBtn.click();
await page.waitForTimeout(500);
const report = page.locator('td').getByText('10:30 AM Service');
await expect(report).toBeVisible({ timeout: 10000 });
await report.click();
});
});

});
});
Loading