e2e tests added. Core & Authentication

This commit is contained in:
AG
2025-12-08 10:18:42 +02:00
parent d284563301
commit 615c3a0cb7
16 changed files with 740 additions and 133 deletions

155
tests/core-auth.spec.ts Normal file
View File

@@ -0,0 +1,155 @@
import { test, expect } from './fixtures';
test.describe('I. Core & Authentication', () => {
test.beforeEach(async ({ page }) => {
// Console logs for debugging
page.on('console', msg => console.log('PAGE LOG:', msg.text()));
page.on('pageerror', exception => console.log(`PAGE ERROR: ${exception}`));
await page.goto('/');
});
// Helper to handle first login if needed
async function handleFirstLogin(page: any) {
// Wait for either Free Workout (already logged in/not first time)
// OR Change Password heading
// OR Error message
try {
const heading = page.getByRole('heading', { name: /Change Password/i });
const dashboard = page.getByText('Free Workout');
const loginButton = page.getByRole('button', { name: 'Login' });
// Race condition: wait for one of these to appear
// We use a small polling or just wait logic.
// Playwright doesn't have "race" for locators easily without Promise.race
// Simple approach: Check if Change Password appears within 5s
await expect(heading).toBeVisible({ timeout: 5000 });
// If we are here, Change Password is visible
console.log('Change Password screen detected. Handling...');
await page.getByLabel('New Password').fill('StrongNewPass123!');
await page.getByRole('button', { name: /Save|Change/i }).click();
// Now expect dashboard
await expect(dashboard).toBeVisible();
console.log('Password changed. Dashboard visible.');
} catch (e) {
// If Change Password didn't appear, maybe we are already at dashboard?
if (await page.getByText('Free Workout').isVisible()) {
console.log('Already at Dashboard.');
return;
}
// Check for login error
const error = page.locator('.text-error');
if (await error.isVisible()) {
console.log('Login Error detected:', await error.textContent());
throw new Error(`Login failed: ${await error.textContent()}`);
}
console.log('Failed to handle first login. Dumping page content...');
console.log(await page.content());
throw e;
}
}
// 1.1. A. Login - Successful Authentication
test('1.1 Login - Successful Authentication', async ({ page, createUniqueUser }) => {
const user = await createUniqueUser();
await page.getByLabel('Email').fill(user.email);
await page.getByLabel('Password').fill(user.password);
await page.getByRole('button', { name: 'Login' }).click();
await handleFirstLogin(page);
// Expect redirection to dashboard
await expect(page).not.toHaveURL(/\/login/);
await expect(page.getByText('Free Workout')).toBeVisible();
});
// 1.2. A. Login - Invalid Credentials
test('1.2 Login - Invalid Credentials', async ({ page }) => {
await page.getByLabel('Email').fill('invalid@user.com');
await page.getByLabel('Password').fill('wrongpassword');
await page.getByRole('button', { name: 'Login' }).click();
await expect(page.getByText('Invalid credentials')).toBeVisible();
await expect(page.getByRole('button', { name: 'Login' })).toBeVisible();
});
test('1.3 & 1.4 Login - First-Time Password Change', async ({ page, createUniqueUser }) => {
const user = await createUniqueUser();
await page.getByLabel('Email').fill(user.email);
await page.getByLabel('Password').fill(user.password);
await page.getByRole('button', { name: 'Login' }).click();
await expect(page.getByRole('heading', { name: /Change Password/i }).first()).toBeVisible({ timeout: 10000 });
// 1.4 Test short password
await page.getByLabel('New Password').fill('123');
await page.getByRole('button', { name: /Save|Change/i }).click();
await expect(page.getByText('Password too short')).toBeVisible();
// 1.3 Test successful change
await page.getByLabel('New Password').fill('StrongNewPass123!');
await page.getByRole('button', { name: /Save|Change/i }).click();
// Now we should be logged in
await expect(page.getByText('Free Workout')).toBeVisible();
});
// 1.5. A. Login - Language Selection (English)
test('1.5 Login - Language Selection (English)', async ({ page }) => {
await page.getByRole('combobox').selectOption('en');
await expect(page.getByLabel('Email')).toBeVisible();
await expect(page.getByRole('button', { name: 'Login' })).toBeVisible();
});
// 1.6. A. Login - Language Selection (Russian)
test('1.6 Login - Language Selection (Russian)', async ({ page }) => {
await page.getByRole('combobox').selectOption('ru');
await expect(page.getByRole('button', { name: 'Войти' })).toBeVisible();
});
// 1.7. B. Navigation - Desktop Navigation Rail
test('1.7 Navigation - Desktop Navigation Rail', async ({ page, createUniqueUser }) => {
const user = await createUniqueUser();
await page.getByLabel('Email').fill(user.email);
await page.getByLabel('Password').fill(user.password);
await page.getByRole('button', { name: 'Login' }).click();
await handleFirstLogin(page);
// Set viewport to desktop
await page.setViewportSize({ width: 1280, height: 720 });
await expect(page.getByRole('button', { name: 'Tracker' }).first()).toBeVisible();
await expect(page.getByRole('button', { name: 'Plans' }).first()).toBeVisible();
});
// 1.8. B. Navigation - Mobile Bottom Navigation Bar
test('1.8 Navigation - Mobile Bottom Navigation Bar', async ({ page, createUniqueUser }) => {
const user = await createUniqueUser();
await page.getByLabel('Email').fill(user.email);
await page.getByLabel('Password').fill(user.password);
await page.getByRole('button', { name: 'Login' }).click();
await handleFirstLogin(page);
// Set viewport to mobile
await page.setViewportSize({ width: 375, height: 667 });
await page.waitForTimeout(500); // Allow layout transition
// Verify visibility of mobile nav items
await expect(page.getByRole('button', { name: 'Tracker' }).last()).toBeVisible();
});
});

55
tests/fixtures.ts Normal file
View File

@@ -0,0 +1,55 @@
import { test as base, expect } from '@playwright/test';
import { request } from '@playwright/test';
// Define the type for our custom fixtures
type MyFixtures = {
createUniqueUser: () => Promise<{ email: string, password: string, id: string }>;
};
// Extend the base test with our custom fixture
export const test = base.extend<MyFixtures>({
createUniqueUser: async ({ }, use) => {
// We use a new API context for setup to avoid polluting request history,
// although setup requests are usually separate anyway.
const apiContext = await request.newContext({
baseURL: 'http://localhost:3001' // Direct access to backend
});
// Setup: Helper function to create a user
const createUser = async () => {
const uniqueId = Math.random().toString(36).substring(7);
const email = `test.user.${uniqueId}@example.com`;
const password = 'StrongPassword123!';
const response = await apiContext.post('/api/auth/register', {
data: {
email,
password
}
});
const body = await response.json();
// If registration fails because we hit a collision (unlikely) or other error, fail the test
if (!response.ok()) {
console.error(`REGISTRATION FAILED: ${response.status()} ${response.statusText()}`);
console.error(`RESPONSE BODY: ${JSON.stringify(body, null, 2)}`);
throw new Error(`Failed to register user: ${JSON.stringify(body)}`);
}
return { email, password, id: body.user.id };
};
// Use the fixture
await use(createUser);
// Cleanup: In a real "test:full" env with ephemeral db, cleanup might not be needed.
// But if we want to be clean, we can delete the user.
// Requires admin privileges usually, or specific delete-me endpoint.
// Given the requirements "delete own account" exists (5.6), we could theoretically login and delete.
// For now we skip auto-cleanup to keep it simple, assuming test DB is reset periodically.
},
});
export { expect };

7
tests/seed.spec.ts Normal file
View File

@@ -0,0 +1,7 @@
import { test, expect } from '@playwright/test';
test.describe('Test group', () => {
test('seed', async ({ page }) => {
// generate code here.
});
});