Initial commit
This commit is contained in:
46
tests/e2e/tests/admin.spec.ts
Normal file
46
tests/e2e/tests/admin.spec.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { loginAsAdmin } from './helpers';
|
||||
|
||||
test.describe('Admin portal', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await loginAsAdmin(page);
|
||||
});
|
||||
|
||||
test('admin user sees the Admin portal button in the header', async ({ page }) => {
|
||||
await expect(page.getByRole('button', { name: 'Admin portal' })).toBeVisible();
|
||||
});
|
||||
|
||||
test('navigates to the admin portal', async ({ page }) => {
|
||||
await page.getByRole('button', { name: 'Admin portal' }).click();
|
||||
await page.waitForURL('/admin');
|
||||
|
||||
await expect(page.getByRole('heading', { name: 'Admin Portal' })).toBeVisible();
|
||||
await expect(page.getByText('Manage user accounts')).toBeVisible();
|
||||
});
|
||||
|
||||
test('admin portal lists the admin account under Approved accounts', async ({ page }) => {
|
||||
await page.goto('/admin');
|
||||
|
||||
await expect(page.getByText('Approved accounts')).toBeVisible();
|
||||
// Target the name paragraph inside the admin's list item
|
||||
const adminRow = page.locator('li').filter({ hasText: 'admin@randall.local' });
|
||||
await expect(adminRow.getByRole('paragraph').filter({ hasText: /^Admin$/ })).toBeVisible();
|
||||
});
|
||||
|
||||
test('admin account has the Admin badge and no Make admin button', async ({ page }) => {
|
||||
await page.goto('/admin');
|
||||
|
||||
const adminRow = page.locator('li').filter({ hasText: 'admin@randall.local' });
|
||||
// The badge is a <span> with class bg-amber-100
|
||||
await expect(adminRow.locator('span').filter({ hasText: /^Admin$/ })).toBeVisible();
|
||||
await expect(adminRow.getByRole('button', { name: 'Make admin' })).not.toBeVisible();
|
||||
});
|
||||
|
||||
test('Back to planner link returns to the planner', async ({ page }) => {
|
||||
await page.goto('/admin');
|
||||
await page.getByRole('button', { name: '← Back to planner' }).click();
|
||||
|
||||
await page.waitForURL('/');
|
||||
await expect(page.getByText('Reserve your workspace up to 2 weeks ahead')).toBeVisible();
|
||||
});
|
||||
});
|
||||
63
tests/e2e/tests/auth.spec.ts
Normal file
63
tests/e2e/tests/auth.spec.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { ADMIN_EMAIL, ADMIN_PASSWORD } from './helpers';
|
||||
|
||||
test.describe('Authentication', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/');
|
||||
});
|
||||
|
||||
test('shows the sign-in form on initial load', async ({ page }) => {
|
||||
await expect(page.getByRole('heading', { name: 'Office Planner' })).toBeVisible();
|
||||
await expect(page.getByPlaceholder('jane@company.com')).toBeVisible();
|
||||
await expect(page.getByPlaceholder('••••••••')).toBeVisible();
|
||||
await expect(page.locator('button[type="submit"]')).toHaveText('Sign in');
|
||||
});
|
||||
|
||||
test('signs in with valid credentials and reaches the planner', async ({ page }) => {
|
||||
await page.getByPlaceholder('jane@company.com').fill(ADMIN_EMAIL);
|
||||
await page.getByPlaceholder('••••••••').fill(ADMIN_PASSWORD);
|
||||
await page.locator('button[type="submit"]').click();
|
||||
|
||||
await expect(page.getByText('Reserve your workspace up to 2 weeks ahead')).toBeVisible();
|
||||
});
|
||||
|
||||
test('shows an error for an unknown email', async ({ page }) => {
|
||||
await page.getByPlaceholder('jane@company.com').fill('nobody@example.com');
|
||||
await page.getByPlaceholder('••••••••').fill('anything');
|
||||
await page.locator('button[type="submit"]').click();
|
||||
|
||||
await expect(page.getByText(/invalid email or password/i)).toBeVisible();
|
||||
});
|
||||
|
||||
test('shows an error for a wrong password', async ({ page }) => {
|
||||
await page.getByPlaceholder('jane@company.com').fill(ADMIN_EMAIL);
|
||||
await page.getByPlaceholder('••••••••').fill('wrongpassword');
|
||||
await page.locator('button[type="submit"]').click();
|
||||
|
||||
await expect(page.getByText(/invalid email or password/i)).toBeVisible();
|
||||
});
|
||||
|
||||
test('registers a new account and shows pending approval message', async ({ page }) => {
|
||||
// Switch to register mode
|
||||
await page.locator('button').filter({ hasText: 'Create account' }).first().click();
|
||||
|
||||
await page.getByPlaceholder('Jane Smith').fill('Test User');
|
||||
await page.getByPlaceholder('jane@company.com').fill(`testuser+${Date.now()}@example.com`);
|
||||
await page.getByPlaceholder('••••••••').fill('Test@1234');
|
||||
await page.locator('button[type="submit"]').click();
|
||||
|
||||
await expect(page.getByText('Account pending approval')).toBeVisible();
|
||||
await expect(page.getByText(/administrator will review/i)).toBeVisible();
|
||||
});
|
||||
|
||||
test('signs out and returns to the sign-in form', async ({ page }) => {
|
||||
await page.getByPlaceholder('jane@company.com').fill(ADMIN_EMAIL);
|
||||
await page.getByPlaceholder('••••••••').fill(ADMIN_PASSWORD);
|
||||
await page.locator('button[type="submit"]').click();
|
||||
await page.waitForURL('/');
|
||||
|
||||
await page.getByRole('button', { name: 'Sign out' }).click();
|
||||
|
||||
await expect(page.locator('button[type="submit"]')).toHaveText('Sign in');
|
||||
});
|
||||
});
|
||||
25
tests/e2e/tests/helpers.ts
Normal file
25
tests/e2e/tests/helpers.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { type Page } from '@playwright/test';
|
||||
|
||||
export const ADMIN_EMAIL = 'admin@randall.local';
|
||||
export const ADMIN_PASSWORD = 'Admin@123';
|
||||
export const ADMIN_NAME = 'Admin';
|
||||
|
||||
export async function loginAs(page: Page, email: string, password: string) {
|
||||
await page.goto('/');
|
||||
await page.getByPlaceholder('jane@company.com').fill(email);
|
||||
await page.getByPlaceholder('••••••••').fill(password);
|
||||
await page.locator('button[type="submit"]').click();
|
||||
await page.waitForURL('/');
|
||||
await page.getByText('Reserve your workspace up to 2 weeks ahead').waitFor();
|
||||
}
|
||||
|
||||
export async function loginAsAdmin(page: Page) {
|
||||
await loginAs(page, ADMIN_EMAIL, ADMIN_PASSWORD);
|
||||
}
|
||||
|
||||
/** Returns today's date offset by `days` in yyyy-MM-dd format */
|
||||
export function offsetDate(days: number): string {
|
||||
const d = new Date();
|
||||
d.setDate(d.getDate() + days);
|
||||
return d.toISOString().split('T')[0];
|
||||
}
|
||||
100
tests/e2e/tests/planner.spec.ts
Normal file
100
tests/e2e/tests/planner.spec.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { loginAsAdmin, offsetDate } from './helpers';
|
||||
|
||||
test.describe('Planner', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await loginAsAdmin(page);
|
||||
});
|
||||
|
||||
test('displays both pods with 8 desks each', async ({ page }) => {
|
||||
// Wait for floor plan to load — at least one free desk must be visible
|
||||
await expect(page.getByRole('button', { name: 'D1 Free' })).toBeVisible();
|
||||
|
||||
// All 16 desk buttons contain a desk label (D1–D16) in their text content
|
||||
const allDesks = page.getByRole('button').filter({ hasText: /D\d+/ });
|
||||
await expect(allDesks).toHaveCount(16);
|
||||
});
|
||||
|
||||
test('date picker is constrained to today and 14 days ahead', async ({ page }) => {
|
||||
const input = page.locator('input[type="date"]');
|
||||
const today = offsetDate(0);
|
||||
const max = offsetDate(14);
|
||||
|
||||
await expect(input).toHaveAttribute('min', today);
|
||||
await expect(input).toHaveAttribute('max', max);
|
||||
});
|
||||
|
||||
test('previous-day button is disabled when on today', async ({ page }) => {
|
||||
await expect(page.getByRole('button', { name: '←' })).toBeDisabled();
|
||||
});
|
||||
|
||||
test('next-day button is disabled when on the maximum date', async ({ page }) => {
|
||||
const input = page.locator('input[type="date"]');
|
||||
await input.fill(offsetDate(14));
|
||||
await input.dispatchEvent('change');
|
||||
|
||||
await expect(page.getByRole('button', { name: '→' })).toBeDisabled();
|
||||
});
|
||||
|
||||
test('reserves a desk and shows it as Mine', async ({ page }) => {
|
||||
const targetDate = offsetDate(7);
|
||||
const input = page.locator('input[type="date"]');
|
||||
await input.fill(targetDate);
|
||||
await input.dispatchEvent('change');
|
||||
|
||||
await page.getByRole('button', { name: 'D1 Free' }).click();
|
||||
await expect(page.getByRole('heading', { name: 'Reserve desk' })).toBeVisible();
|
||||
await page.getByRole('button', { name: 'Confirm' }).click();
|
||||
|
||||
await expect(page.getByRole('button', { name: /^D1\s+Mine/ })).toBeVisible();
|
||||
|
||||
// Clean up
|
||||
await page.getByRole('button', { name: /^D1\s+Mine/ }).click();
|
||||
await page.getByRole('button', { name: 'Cancel reservation' }).click();
|
||||
});
|
||||
|
||||
test('reserved desk appears in My reservations', async ({ page }) => {
|
||||
const targetDate = offsetDate(8);
|
||||
const input = page.locator('input[type="date"]');
|
||||
await input.fill(targetDate);
|
||||
await input.dispatchEvent('change');
|
||||
|
||||
await page.getByRole('button', { name: 'D2 Free' }).click();
|
||||
await page.getByRole('button', { name: 'Confirm' }).click();
|
||||
|
||||
await expect(page.getByRole('heading', { name: 'My reservations' })).toBeVisible();
|
||||
await expect(page.locator('section').filter({ hasText: 'My reservations' }).getByText('D2')).toBeVisible();
|
||||
|
||||
// Clean up
|
||||
await page.getByRole('button', { name: /^D2\s+Mine/ }).click();
|
||||
await page.getByRole('button', { name: 'Cancel reservation' }).click();
|
||||
});
|
||||
|
||||
test('cancels a reservation and desk returns to Free', async ({ page }) => {
|
||||
const targetDate = offsetDate(9);
|
||||
const input = page.locator('input[type="date"]');
|
||||
await input.fill(targetDate);
|
||||
await input.dispatchEvent('change');
|
||||
|
||||
// Reserve
|
||||
await page.getByRole('button', { name: 'D3 Free' }).click();
|
||||
await page.getByRole('button', { name: 'Confirm' }).click();
|
||||
await expect(page.getByRole('button', { name: /^D3\s+Mine/ })).toBeVisible();
|
||||
|
||||
// Cancel
|
||||
await page.getByRole('button', { name: /^D3\s+Mine/ }).click();
|
||||
await expect(page.getByRole('heading', { name: 'Cancel reservation' })).toBeVisible();
|
||||
await page.getByRole('button', { name: 'Cancel reservation' }).click();
|
||||
|
||||
await expect(page.getByRole('button', { name: 'D3 Free' })).toBeVisible();
|
||||
});
|
||||
|
||||
test('dismisses the reservation modal when clicking Cancel', async ({ page }) => {
|
||||
await page.getByRole('button', { name: 'D4 Free' }).click();
|
||||
await expect(page.getByRole('heading', { name: 'Reserve desk' })).toBeVisible();
|
||||
|
||||
// Scope to the modal overlay to avoid matching Cancel buttons in My reservations
|
||||
await page.locator('.fixed.inset-0').getByRole('button', { name: 'Cancel' }).click();
|
||||
await expect(page.getByRole('heading', { name: 'Reserve desk' })).not.toBeVisible();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user