Implemented role-based access control, Updated e2e authentication tests and Playwright setup.

This commit is contained in:
2025-10-15 21:56:11 +02:00
parent 94214125fd
commit ef88d2925e
23 changed files with 308 additions and 41 deletions

187
tests/e2e/Readme.md Normal file
View File

@@ -0,0 +1,187 @@
# README - End-to-End Authentication Testing Guide
This README provides details on how to set up, configure, and run the end-to-end (e2e) authentication tests for the **My Managing Tools** application. The tests are implemented using `pytest` with `playwright` for browser automation.
## Purpose
The e2e tests verify that the authentication system works as expected, including login functionality, session validation, and database isolation for testing purposes. These tests ensure the login page behaves properly under different scenarios such as unauthenticated access, form validation, and successful user login.
---
## Table of Contents
1. [Setup Instructions](#setup-instructions)
- [Application Setup](#application-setup)
- [Database Setup](#database-setup)
2. [Installing Dependencies](#installing-dependencies)
3. [Running Tests](#running-tests)
4. [Debugging and Logs](#debugging-and-logs)
5. [Test Scenarios](#test-scenarios)
6. [Troubleshooting](#troubleshooting)
---
## Setup Instructions
Before running the tests, make sure the environment is set up correctly for both the application and the test database.
### Application Setup
1. **Install Required Dependencies** (Python version 3.9 or higher is recommended):
```bash
pip install -r requirements.txt
```
2. **Run the Application**:
Navigate to the `src` directory and start the application:
```bash
cd src
python main.py
```
Alternatively, use Docker:
```bash
docker-compose up -d
```
Once the server starts, ensure the app is accessible at the base URL defined in `playwright_config.py`. By default, it should be `http://localhost:5002`.
### Database Setup
The e2e tests are designed to interact with an **isolated test database**. During setup, a temporary SQLite database will be created for each test session.
1. **Test Database Initialization**:
The database is initialized dynamically during the test execution using a custom temporary path. The `test_database` fixture creates a database with the following characteristics:
- **Temporary Directory**: Ensures no conflicts with the production database.
- **Admin User Auto-Creation**: Adds an admin account if the environment variables `ADMIN_EMAIL` and `ADMIN_PASSWORD` are set.
2. **Environment Variables**:
Ensure the following environment variables are defined for the test database:
```env
DB_PATH="test_mmt_tools.db"
ADMIN_EMAIL="test.admin@test.com"
ADMIN_PASSWORD="TestAdmin123"
```
3. **Configuration**:
The `test_database.py` script handles the database isolation and cleanup after each test session. It ensures that no state is leaked between tests.
---
## Installing Dependencies
Install the required packages for the tests:
1. **Playwright and Pytest**:
Install `pytest-playwright` and playwright dependencies:
```bash
pip install pytest-playwright
playwright install
playwright install-deps # For Linux systems
```
2. **Project Dependencies**:
Use the provided `requirements.txt` to install other necessary packages:
```bash
pip install -r requirements.txt
```
3. **Verify Installation**:
Confirm Playwright is installed and functional:
```bash
playwright --version
```
---
## Running Tests
To execute the end-to-end authentication tests, run the following commands from the project root.
1. **Run All Tests**:
Execute all e2e tests:
```bash
pytest -m "e2e"
```
2. **Run Specific Test**:
Use the test path and `-k` argument to target specific tests:
```bash
pytest tests/test_e2e_authentication.py -k "test_unauthenticated_user_redirected_to_login"
```
3. **Record Test Results**:
Save test results (HTML report):
```bash
pytest --html=test-results/report.html --self-contained-html
```
4. **Parallel Execution**:
If desired, run tests in parallel:
```bash
pytest -n auto
```
---
## Debugging and Logs
1. **Console Logs**:
Console errors during page navigation are captured using the `test_page_loads_without_errors` test. Check the pytest output for details.
2. **Debugging Tools**:
- Videos and HAR traces are recorded in the `test-results` directory.
- Use the `page.pause()` method inside a test for interactive debugging with Playwright Inspector.
3. **Screenshots and Debug HTML**:
If a test fails, screenshots and full HTML are saved for post-mortem debugging:
- `debug_after_login.png`: Screenshot post-login.
- `debug_page.html`: Captured HTML structure of the page.
4. **Server Logs**:
When using the `app_server` fixture, server `stdout` and `stderr` are recorded during the test execution.
---
## Test Scenarios
The main authentication tests cover the following scenarios:
1. **Unauthenticated User Redirect**:
Verify that unauthenticated users accessing protected resources are redirected to the login page.
2. **Login Page Elements**:
Validate that key login page elements are visible, such as email/password fields and the submit button.
3. **Successful Login**:
Ensure users can log in successfully with valid credentials and are redirected to the home page.
4. **Test Database Isolation**:
Confirm that the test environment uses an isolated temporary database and does not affect production data.
5. **Page Load Without Errors**:
Ensure that the login page loads without console or HTTP errors.
For a full list of test cases, see `tests/test_e2e_authentication.py`.
---
## Troubleshooting
- **Server Not Starting**:
If the server fails to start during testing, confirm the `DB_PATH`, `BASE_URL`, and all dependencies are correctly configured in the `.env` file.
- **Playwright Issues**:
Ensure Playwright dependencies (`playwright install`) are installed and functional. Missing browsers or outdated Playwright versions can cause tests to fail.
- **Database Error**:
Check that the `tools.db` file or the temporary test database exists and is accessible. Ensure the test database environment variables are set correctly during tests.
---
## Additional Notes
- The app server is started with test-specific configurations (`app_server` fixture) that ensure end-to-end isolation.
- Tests requiring authentication use an admin user defined in the `test_users` fixture:
- Email: `test.admin@test.com`
- Password: `TestAdmin123`
- Ensure no sensitive data or configurations are hardcoded in the test scripts.

0
tests/e2e/__init__.py Normal file
View File

View File

@@ -0,0 +1,255 @@
# tests/test_e2e_authentication.py
import pytest
from playwright.sync_api import Page, expect
from playwright_config import BASE_URL
class TestAuthentication:
"""Tests for authentication and login functionality"""
@pytest.mark.e2e
@pytest.mark.smoke
def test_unauthenticated_user_redirected_to_login(self, app_server, page: Page):
"""Test that when not logged in, the default page is the login page"""
# Navigate to the root URL
page.goto(BASE_URL)
# Wait for the page to fully load
page.wait_for_load_state("networkidle")
# Check that we're on the login page
# Option 1: Check URL contains login-related path
expect(page).to_have_url(f"{BASE_URL}/authlogin/login")
# Option 2: Check for login form elements
# Look for typical login form elements
login_indicators = [
"input[type='email']",
"input[type='password']",
"input[name*='username']",
"input[name*='email']",
"input[name*='password']",
"button[type='submit']",
"form"
]
# At least one login indicator should be present
login_form_found = False
for selector in login_indicators:
if page.locator(selector).count() > 0:
login_form_found = True
break
assert login_form_found, "No login form elements found on the page"
# Option 3: Check for login-related text content
page_content = page.content().lower()
login_keywords = ["login", "sign in", "authenticate", "username", "password", "email"]
has_login_content = any(keyword in page_content for keyword in login_keywords)
assert has_login_content, "Page does not contain login-related content"
# Option 4: Ensure we're not on a protected page
# Check that we don't see protected content like "dashboard", "logout", "profile", "settings"]
protected_content = ["dashboard", "logout", "profile", "settings"]
page_text = page.locator("body").inner_text().lower()
for protected_word in protected_content:
assert protected_word not in page_text, f"Found protected content '{protected_word}' when not logged in"
@pytest.mark.e2e
@pytest.mark.regression
def test_login_page_has_required_elements(self, app_server, page: Page):
"""Test that the login page contains all required elements"""
page.goto(BASE_URL)
page.wait_for_load_state("networkidle")
# Check for essential login form elements
expect(page.locator("form")).to_be_visible()
# Check for input fields (at least email/username and password)
username_input = page.locator("input[type='email'], input[name*='username'], input[name*='email']")
expect(username_input.first).to_be_visible()
password_input = page.locator("input[type='password'], input[name*='password']")
expect(password_input.first).to_be_visible()
# Check for submit button
submit_button = page.locator("button[type='submit'], input[type='submit']")
expect(submit_button.first).to_be_visible()
def test_page_loads_without_errors(self, app_server, page: Page):
"""Test that the login page loads without console errors"""
console_errors = []
def handle_console(msg):
if msg.type == "error":
console_errors.append(msg.text)
page.on("console", handle_console)
page.goto(BASE_URL)
page.wait_for_load_state("networkidle")
# Check for console errors
assert len(console_errors) == 0, f"Console errors found: {console_errors}"
# Check that the page actually loaded (not a 404 or 500 error)
expect(page.locator("body")).to_be_visible()
# Check that the page has a title
expect(page).to_have_title("My Managing Tools")
# New test to validate database isolation
@pytest.mark.e2e
@pytest.mark.smoke
def test_test_database_isolation(self, app_server, test_database, test_users):
"""Test that application uses isolated test database"""
import os
# Verify test environment variables are configured
assert os.environ.get("DB_PATH") == test_database
assert os.environ.get("ADMIN_EMAIL") == test_users["admin"]["email"]
assert os.environ.get("ADMIN_PASSWORD") == test_users["admin"]["password"]
# Verify temporary database file exists
assert os.path.exists(test_database), f"Test database file not found: {test_database}"
# Verify path contains 'test_mmt_' to confirm isolation
assert "test_mmt_" in test_database, "Database path should contain test prefix"
print(f"✅ Test database isolation confirmed: {test_database}")
# =============================================================================
# PRIORITY 1 TESTS - CRITICAL AUTHENTICATION FLOWS
# =============================================================================
@pytest.mark.e2e
@pytest.mark.priority1
@pytest.mark.smoke
def test_successful_login_with_valid_credentials(self, app_server, test_users, page: Page):
"""
Priority 1 Test: Validate complete successful login flow with valid credentials
This test ensures that:
1. User can access the login page
2. User can enter valid credentials
3. Form submission works correctly
4. User is redirected to home page after successful authentication
5. User session is properly established
"""
admin_user = test_users["admin"]
# Step 1: Navigate to login page
page.goto(BASE_URL)
page.wait_for_load_state("networkidle")
# Verify we're on the login page
expect(page).to_have_url(f"{BASE_URL}/authlogin/login")
# Step 2: Locate form elements
email_input = page.locator("input[type='email'], input[name='email']")
password_input = page.locator("input[type='password'], input[name='password']")
submit_button = page.locator("button[type='submit']")
# Verify all required elements are present and visible
expect(email_input).to_be_visible()
expect(password_input).to_be_visible()
expect(submit_button).to_be_visible()
# Step 3: Fill in valid credentials
email_input.fill(admin_user["email"])
password_input.fill(admin_user["password"])
# Verify credentials were entered correctly
expect(email_input).to_have_value(admin_user["email"])
expect(password_input).to_have_value(admin_user["password"])
# Step 4: Submit the login form
# Use click with wait for navigation to handle the redirect
with page.expect_navigation(wait_until="networkidle"):
submit_button.click()
# DEBUGGING BLOCK - Add this
print(f"🔍 Current URL: {page.url}")
print(f"🔍 Page title: {page.title()}")
# Take screenshot
page.screenshot(path="debug_after_login.png")
# Save full HTML for inspection
with open("debug_page.html", "w", encoding="utf-8") as f:
f.write(page.content())
# Check specific content that's causing the failure
page_content = page.content().lower()
login_keywords = ["sign in", "login", "authenticate"]
print("🔍 Checking for login keywords:")
for keyword in login_keywords:
if keyword in page_content:
print(f" ❌ Found '{keyword}' in page content")
# Find exactly where this keyword appears
occurrences = page.get_by_text(keyword, exact=False)
count = occurrences.count()
print(f" Found in {count} elements:")
for i in range(min(count, 3)): # Show first 3 occurrences
element = occurrences.nth(i)
try:
print(f" - Element {i}: '{element.inner_text()[:50]}...' (visible: {element.is_visible()})")
except:
print(f" - Element {i}: Could not get text")
else:
print(f"'{keyword}' NOT found")
# Your original assertion (will still fail, but now you have debug info)
has_login_content = any(keyword in page_content for keyword in login_keywords)
assert not has_login_content, "Login content still visible after successful authentication"
page.pause()
# Step 5: Verify successful authentication and redirect
# Should be redirected to the home page
expect(page).to_have_url(BASE_URL + "/")
# Step 6: Verify we're actually authenticated (not redirected back to login)
# The page should load without being redirected back to login
page.wait_for_load_state("networkidle")
current_url = page.url
assert not current_url.endswith("/authlogin/login"), f"User was redirected back to login page: {current_url}"
# Step 7: Verify authenticated content is present
# Check that we don't see login-related content anymore
page_content = page.content().lower()
login_keywords = ["sign in", "login", "authenticate"]
# Should not see login form elements on authenticated page
has_login_content = any(keyword in page_content for keyword in login_keywords)
assert not has_login_content, "Login content still visible after successful authentication"
# Step 8: Verify no error messages are displayed
# Look for common error message containers
error_selectors = [
".error",
".alert-error",
".bg-error",
"[class*='error']",
".text-red",
"[class*='text-red']"
]
for error_selector in error_selectors:
error_elements = page.locator(error_selector)
if error_elements.count() > 0:
# If error elements exist, they should not be visible or should be empty
for i in range(error_elements.count()):
element = error_elements.nth(i)
if element.is_visible():
element_text = element.inner_text().strip()
assert not element_text, f"Error message found after successful login: {element_text}"
# Step 9: Verify the page has expected title
expect(page).to_have_title("My Managing Tools")
print(f"✅ Successful login test completed - User {admin_user['email']} authenticated successfully")