From f9d20ae013a6adf637ad16b90158add1e1eea72d Mon Sep 17 00:00:00 2001 From: macbook pro Date: Sat, 7 Feb 2026 23:40:28 +0200 Subject: [PATCH 1/2] Fix typo in README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 33c85c6..d834712 100644 --- a/README.md +++ b/README.md @@ -63,3 +63,4 @@ npm start # Start server only (for manual testing) We are just updating to test the CI ## Update +## Again From 6410692862d0c294ce8bad1acfab0a7ce3ad6903 Mon Sep 17 00:00:00 2001 From: macbook pro Date: Sun, 8 Feb 2026 00:07:40 +0200 Subject: [PATCH 2/2] Added Register link on login page --- README.md | 22 +++++++- public/login.html | 1 + public/register.html | 104 +++++++++++++++++++++++++++++++++++++ server.js | 55 ++++++++++++++++++-- tests/logout.spec.js | 43 +++++++++++++++ tests/registration.spec.js | 88 +++++++++++++++++++++++++++++++ 6 files changed, 308 insertions(+), 5 deletions(-) create mode 100644 public/register.html create mode 100644 tests/logout.spec.js create mode 100644 tests/registration.spec.js diff --git a/README.md b/README.md index d834712..19e3a10 100644 --- a/README.md +++ b/README.md @@ -19,12 +19,15 @@ npm test ``` example-case/ -├── server.js # Express login server +├── server.js # Express server (login, logout, registration) ├── public/ │ ├── login.html # Login page +│ ├── register.html # Registration page │ └── dashboard.html # Post-login dashboard ├── tests/ -│ └── login.spec.js # Playwright E2E tests +│ ├── login.spec.js # Login E2E tests +│ ├── logout.spec.js # Logout E2E tests +│ └── registration.spec.js # Registration E2E tests ├── playwright.config.js # Playwright configuration ├── .github/workflows/ │ └── playwright.yml # GitHub Actions CI @@ -33,6 +36,7 @@ example-case/ ## Test Scenarios +### Login | Test | Flow | Expected | |------|------|----------| | Valid login | `testuser` / `Test123!` | Redirect to dashboard | @@ -40,6 +44,20 @@ example-case/ | Empty credentials | Submit empty form | Validation | | Locked account | `locked` / `Locked123!` | Account locked message | +### Logout +| Test | Flow | Expected | +|------|------|----------| +| Sign out | Click Sign out from dashboard | Redirect to login | +| Re-login | Logout then login again | Success | + +### Registration +| Test | Flow | Expected | +|------|------|----------| +| Valid registration | New username, email, password | Redirect to login | +| Duplicate username | Use existing username | Error message | +| Passwords mismatch | Different password/confirm | Error message | +| Short password | < 6 characters | Error message | + ## CI Pipeline The workflow runs on **pull requests** to `main` or `master`: diff --git a/public/login.html b/public/login.html index 7adc125..313fbd0 100644 --- a/public/login.html +++ b/public/login.html @@ -32,6 +32,7 @@

Login

+

Don't have an account? Register

+ + diff --git a/server.js b/server.js index fcf15ea..11f37e2 100644 --- a/server.js +++ b/server.js @@ -8,10 +8,15 @@ app.use(express.urlencoded({ extended: true })); app.use(express.json()); app.use(express.static('public')); -// Demo credentials (for testing only - never use in production!) +// Demo credentials and in-memory users (for testing only - never use in production!) const VALID_USER = { username: 'testuser', password: 'Test123!' }; const LOCKED_USER = { username: 'locked', password: 'Locked123!' }; +const users = new Map([ + [VALID_USER.username, { ...VALID_USER, email: 'test@example.com' }], + [LOCKED_USER.username, { ...LOCKED_USER, email: 'locked@example.com' }], +]); + app.get('/', (req, res) => { res.redirect('/login'); }); @@ -30,14 +35,15 @@ app.post('/login', (req, res) => { }); } - if (username === LOCKED_USER.username) { + if (username.trim() === LOCKED_USER.username) { return res.status(423).json({ success: false, message: 'Account is locked. Please contact support.' }); } - if (username === VALID_USER.username && password === VALID_USER.password) { + const user = users.get(username.trim()); + if (user && user.password === password) { return res.json({ success: true, message: 'Login successful', @@ -55,6 +61,49 @@ app.get('/dashboard', (req, res) => { res.sendFile(path.join(__dirname, 'public', 'dashboard.html')); }); +app.get('/register', (req, res) => { + res.sendFile(path.join(__dirname, 'public', 'register.html')); +}); + +app.post('/register', (req, res) => { + const { username, email, password } = req.body; + + if (!username || !email || !password) { + return res.status(400).json({ + success: false, + message: 'Username, email and password are required' + }); + } + + if (username.trim().length === 0 || email.trim().length === 0) { + return res.status(400).json({ + success: false, + message: 'Username and email cannot be empty' + }); + } + + if (password.length < 6) { + return res.status(400).json({ + success: false, + message: 'Password must be at least 6 characters' + }); + } + + if (users.has(username.trim())) { + return res.status(409).json({ + success: false, + message: 'Username already exists' + }); + } + + users.set(username.trim(), { username: username.trim(), email: email.trim(), password }); + return res.status(201).json({ + success: true, + message: 'Registration successful. You can now sign in.', + redirectUrl: '/login' + }); +}); + app.listen(PORT, () => { console.log(`Server running at http://localhost:${PORT}`); }); diff --git a/tests/logout.spec.js b/tests/logout.spec.js new file mode 100644 index 0000000..515c9c5 --- /dev/null +++ b/tests/logout.spec.js @@ -0,0 +1,43 @@ +// @ts-check +const { test, expect } = require('@playwright/test'); + +const VALID_CREDENTIALS = { username: 'testuser', password: 'Test123!' }; + +test.describe('Logout - E2E Tests', () => { + test.beforeEach(async ({ page }) => { + await page.goto('/login'); + await page.getByLabel('Username').fill(VALID_CREDENTIALS.username); + await page.getByLabel('Password').fill(VALID_CREDENTIALS.password); + await page.getByRole('button', { name: 'Sign In' }).click(); + await expect(page).toHaveURL(/\/dashboard/); + }); + + test('should redirect to login page when clicking Sign out', async ({ page }) => { + await page.getByRole('link', { name: 'Sign out' }).click(); + + await expect(page).toHaveURL(/\/login/); + await expect(page.getByRole('heading', { name: 'Login' })).toBeVisible(); + await expect(page.getByRole('button', { name: 'Sign In' })).toBeVisible(); + }); + + test('should display login form after logout', async ({ page }) => { + await page.getByRole('link', { name: 'Sign out' }).click(); + + await expect(page.getByLabel('Username')).toBeVisible(); + await expect(page.getByLabel('Password')).toBeVisible(); + await expect(page.getByLabel('Username')).toHaveValue(''); + await expect(page.getByLabel('Password')).toHaveValue(''); + }); + + test('should allow login again after logout', async ({ page }) => { + await page.getByRole('link', { name: 'Sign out' }).click(); + await expect(page).toHaveURL(/\/login/); + + await page.getByLabel('Username').fill(VALID_CREDENTIALS.username); + await page.getByLabel('Password').fill(VALID_CREDENTIALS.password); + await page.getByRole('button', { name: 'Sign In' }).click(); + + await expect(page).toHaveURL(/\/dashboard/); + await expect(page.getByRole('heading', { name: 'Dashboard' })).toBeVisible(); + }); +}); diff --git a/tests/registration.spec.js b/tests/registration.spec.js new file mode 100644 index 0000000..45d90d1 --- /dev/null +++ b/tests/registration.spec.js @@ -0,0 +1,88 @@ +// @ts-check +const { test, expect } = require('@playwright/test'); + +const VALID_CREDENTIALS = { username: 'testuser', password: 'Test123!' }; + +test.describe('Registration - E2E Tests', () => { + test.beforeEach(async ({ page }) => { + await page.goto('/register'); + }); + + test('should display registration form with required fields', async ({ page }) => { + await expect(page.getByRole('heading', { name: 'Register' })).toBeVisible(); + await expect(page.getByLabel('Username')).toBeVisible(); + await expect(page.getByLabel('Email')).toBeVisible(); + await expect(page.getByLabel('Password')).toBeVisible(); + await expect(page.getByLabel('Confirm Password')).toBeVisible(); + await expect(page.getByRole('button', { name: 'Create Account' })).toBeVisible(); + }); + + test('valid registration flow - should redirect to login on success', async ({ page }) => { + const uniqueUser = `newuser_${Date.now()}`; + await page.getByLabel('Username').fill(uniqueUser); + await page.getByLabel('Email').fill(`${uniqueUser}@example.com`); + await page.getByLabel('Password').fill('Password123!'); + await page.getByLabel('Confirm Password').fill('Password123!'); + await page.getByRole('button', { name: 'Create Account' }).click(); + + await expect(page).toHaveURL(/\/login/); + await expect(page.getByRole('heading', { name: 'Login' })).toBeVisible(); + }); + + test('valid registration flow - new user can login after registration', async ({ page }) => { + const uniqueUser = `reguser_${Date.now()}`; + await page.getByLabel('Username').fill(uniqueUser); + await page.getByLabel('Email').fill(`${uniqueUser}@example.com`); + await page.getByLabel('Password').fill('Password123!'); + await page.getByLabel('Confirm Password').fill('Password123!'); + await page.getByRole('button', { name: 'Create Account' }).click(); + + await expect(page).toHaveURL(/\/login/); + + await page.getByLabel('Username').fill(uniqueUser); + await page.getByLabel('Password').fill('Password123!'); + await page.getByRole('button', { name: 'Sign In' }).click(); + + await expect(page).toHaveURL(/\/dashboard/); + await expect(page.getByRole('heading', { name: 'Dashboard' })).toBeVisible(); + }); + + test('invalid registration flow - should show error for duplicate username', async ({ page }) => { + await page.getByLabel('Username').fill(VALID_CREDENTIALS.username); + await page.getByLabel('Email').fill('new@example.com'); + await page.getByLabel('Password').fill('Password123!'); + await page.getByLabel('Confirm Password').fill('Password123!'); + await page.getByRole('button', { name: 'Create Account' }).click(); + + await expect(page).toHaveURL(/\/register/); + await expect(page.getByRole('alert')).toContainText('Username already exists'); + }); + + test('invalid registration flow - should show error when passwords do not match', async ({ page }) => { + await page.getByLabel('Username').fill('newuser'); + await page.getByLabel('Email').fill('new@example.com'); + await page.getByLabel('Password').fill('Password123!'); + await page.getByLabel('Confirm Password').fill('Password456!'); + await page.getByRole('button', { name: 'Create Account' }).click(); + + await expect(page).toHaveURL(/\/register/); + await expect(page.getByRole('alert')).toContainText('Passwords do not match'); + }); + + test('invalid registration flow - should show error for short password', async ({ page }) => { + await page.getByLabel('Username').fill('newuser'); + await page.getByLabel('Email').fill('new@example.com'); + await page.getByLabel('Password').fill('12345'); + await page.getByLabel('Confirm Password').fill('12345'); + await page.getByRole('button', { name: 'Create Account' }).click(); + + await expect(page).toHaveURL(/\/register/); + await expect(page.getByRole('alert')).toContainText('at least 6 characters'); + }); + + test('should have link to login page', async ({ page }) => { + await expect(page.getByRole('link', { name: 'Sign in' })).toBeVisible(); + await page.getByRole('link', { name: 'Sign in' }).click(); + await expect(page).toHaveURL(/\/login/); + }); +});