Add authentication tests
This commit is contained in:
parent
f1b3c1c8ac
commit
938869c53b
27 changed files with 4936 additions and 600 deletions
67
backend/app/Http/Controllers/API/TestSetupController.php
Normal file
67
backend/app/Http/Controllers/API/TestSetupController.php
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\API;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Hash;
|
||||||
|
|
||||||
|
class TestSetupController extends Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Create test user for E2E tests (development only)
|
||||||
|
*/
|
||||||
|
public function createTestUser(Request $request)
|
||||||
|
{
|
||||||
|
// Only allow in development/testing environments
|
||||||
|
if (app()->environment('production')) {
|
||||||
|
return response()->json(['error' => 'Not available in production'], 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
$validated = $request->validate([
|
||||||
|
'email' => 'required|email',
|
||||||
|
'password' => 'required|min:8',
|
||||||
|
'name' => 'required|string'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$user = User::firstOrCreate(
|
||||||
|
['email' => $validated['email']],
|
||||||
|
[
|
||||||
|
'name' => $validated['name'],
|
||||||
|
'password' => Hash::make($validated['password']),
|
||||||
|
'email_verified_at' => now(),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'success' => true,
|
||||||
|
'message' => $user->wasRecentlyCreated ? 'Test user created' : 'Test user already exists',
|
||||||
|
'data' => [
|
||||||
|
'id' => $user->id,
|
||||||
|
'email' => $user->email,
|
||||||
|
'name' => $user->name
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clean up test data
|
||||||
|
*/
|
||||||
|
public function cleanup(Request $request)
|
||||||
|
{
|
||||||
|
if (app()->environment('production')) {
|
||||||
|
return response()->json(['error' => 'Not available in production'], 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete test users (those with specific test email patterns)
|
||||||
|
$deleted = User::where('email', 'LIKE', 'test%@example.com')
|
||||||
|
->orWhere('email', 'LIKE', 'test.user.%@example.com')
|
||||||
|
->delete();
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'success' => true,
|
||||||
|
'message' => "Deleted $deleted test users"
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
36
backend/database/seeders/TestUserSeeder.php
Normal file
36
backend/database/seeders/TestUserSeeder.php
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Database\Seeders;
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Database\Seeder;
|
||||||
|
use Illuminate\Support\Facades\Hash;
|
||||||
|
|
||||||
|
class TestUserSeeder extends Seeder
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the database seeds for E2E testing.
|
||||||
|
*/
|
||||||
|
public function run(): void
|
||||||
|
{
|
||||||
|
// Create a standard test user for login tests
|
||||||
|
User::firstOrCreate(
|
||||||
|
['email' => 'test@example.com'],
|
||||||
|
[
|
||||||
|
'name' => 'Test User',
|
||||||
|
'password' => Hash::make('password123'),
|
||||||
|
'email_verified_at' => now(),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create additional test users if needed
|
||||||
|
User::firstOrCreate(
|
||||||
|
['email' => 'admin@example.com'],
|
||||||
|
[
|
||||||
|
'name' => 'Admin User',
|
||||||
|
'password' => Hash::make('admin123'),
|
||||||
|
'email_verified_at' => now(),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -72,35 +72,6 @@ services:
|
||||||
networks:
|
networks:
|
||||||
- trip-planner-network
|
- trip-planner-network
|
||||||
|
|
||||||
selenium-hub:
|
|
||||||
image: selenium/hub:latest
|
|
||||||
container_name: trip-planner-selenium-hub
|
|
||||||
ports:
|
|
||||||
- "4442:4442"
|
|
||||||
- "4443:4443"
|
|
||||||
- "4444:4444"
|
|
||||||
environment:
|
|
||||||
- GRID_MAX_SESSION=4
|
|
||||||
- GRID_BROWSER_TIMEOUT=300
|
|
||||||
- GRID_TIMEOUT=300
|
|
||||||
networks:
|
|
||||||
- trip-planner-network
|
|
||||||
|
|
||||||
selenium-chrome:
|
|
||||||
image: selenium/node-chrome:latest
|
|
||||||
container_name: trip-planner-selenium-chrome
|
|
||||||
environment:
|
|
||||||
- HUB_HOST=selenium-hub
|
|
||||||
- HUB_PORT=4444
|
|
||||||
- SE_EVENT_BUS_HOST=selenium-hub
|
|
||||||
- SE_EVENT_BUS_PUBLISH_PORT=4442
|
|
||||||
- SE_EVENT_BUS_SUBSCRIBE_PORT=4443
|
|
||||||
- NODE_MAX_INSTANCES=4
|
|
||||||
- NODE_MAX_SESSION=4
|
|
||||||
depends_on:
|
|
||||||
- selenium-hub
|
|
||||||
networks:
|
|
||||||
- trip-planner-network
|
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
trip-planner-network:
|
trip-planner-network:
|
||||||
|
|
|
||||||
19
tests/.env.example
Normal file
19
tests/.env.example
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
# Test Environment Configuration
|
||||||
|
|
||||||
|
# Frontend URL (default: http://localhost:5173)
|
||||||
|
BASE_URL=http://localhost:5173
|
||||||
|
|
||||||
|
# Selenium Hub URL (default: http://localhost:4444)
|
||||||
|
# Use http://localhost:4444 for Docker Selenium
|
||||||
|
SELENIUM_HUB=http://localhost:4444
|
||||||
|
|
||||||
|
# Test Mode
|
||||||
|
# Set to "true" to run tests in headless mode
|
||||||
|
HEADLESS=false
|
||||||
|
|
||||||
|
# Test User Credentials (for existing user tests)
|
||||||
|
TEST_USER_EMAIL=test@example.com
|
||||||
|
TEST_USER_PASSWORD=password123
|
||||||
|
|
||||||
|
# Test Timeout (in milliseconds)
|
||||||
|
TEST_TIMEOUT=30000
|
||||||
9
tests/.gitignore
vendored
9
tests/.gitignore
vendored
|
|
@ -1,2 +1,7 @@
|
||||||
/node_modules
|
node_modules/
|
||||||
/package-lock.json
|
screenshots/
|
||||||
|
*.log
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
coverage/
|
||||||
|
test-results/
|
||||||
|
|
|
||||||
|
|
@ -70,23 +70,26 @@ npm test screenshot.test.js
|
||||||
|
|
||||||
```
|
```
|
||||||
tests/
|
tests/
|
||||||
├── config/
|
├── specs/
|
||||||
│ ├── jest.setup.js # Docker Selenium configuration
|
│ ├── auth/
|
||||||
│ ├── jest.setup.local.js # Local Chrome configuration
|
│ │ └── auth-clean.test.js # Clean authentication tests
|
||||||
│ └── test-utils.js # Helper utilities
|
│ └── integration/
|
||||||
├── e2e/
|
│ └── full-auth-flow.test.js # Full user journey test
|
||||||
│ ├── auth.test.js # Main authentication test suite
|
├── support/
|
||||||
│ ├── debug.test.js # Simple connection test
|
│ ├── config/
|
||||||
│ ├── visual-auth.test.js # Slow visual test for debugging
|
│ │ ├── jest.setup.js # Docker Selenium configuration
|
||||||
│ └── screenshot.test.js # Takes screenshots for debugging
|
│ │ ├── jest.setup.local.js # Local Chrome configuration
|
||||||
├── fixtures/
|
│ │ └── test-utils.js # Helper utilities
|
||||||
│ └── users.json # Test user data
|
│ ├── pages/
|
||||||
├── pages/
|
│ │ ├── BasePage.js # Base page object
|
||||||
│ ├── BasePage.js # Base page object
|
│ │ ├── LoginPage.js # Login page object
|
||||||
│ ├── LoginPage.js # Login page object
|
│ │ ├── RegistrationPage.js # Registration page object
|
||||||
│ ├── RegistrationPage.js # Registration page object
|
│ │ └── DashboardPage.js # Dashboard page object
|
||||||
│ └── DashboardPage.js # Dashboard page object
|
│ ├── fixtures/
|
||||||
└── screenshots/ # Screenshot output directory
|
│ │ └── users.json # Test user data
|
||||||
|
│ └── helpers/
|
||||||
|
│ └── test-data.js # Test data management
|
||||||
|
└── screenshots/ # Screenshot output directory
|
||||||
```
|
```
|
||||||
|
|
||||||
## Environment Variables
|
## Environment Variables
|
||||||
|
|
|
||||||
|
|
@ -1,225 +0,0 @@
|
||||||
const RegistrationPage = require('../pages/RegistrationPage');
|
|
||||||
const LoginPage = require('../pages/LoginPage');
|
|
||||||
const DashboardPage = require('../pages/DashboardPage');
|
|
||||||
const users = require('../fixtures/users.json');
|
|
||||||
|
|
||||||
describe('Authentication E2E Tests', () => {
|
|
||||||
let driver;
|
|
||||||
let registrationPage;
|
|
||||||
let loginPage;
|
|
||||||
let dashboardPage;
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
|
||||||
driver = await global.createDriver();
|
|
||||||
registrationPage = new RegistrationPage(driver);
|
|
||||||
loginPage = new LoginPage(driver);
|
|
||||||
dashboardPage = new DashboardPage(driver);
|
|
||||||
});
|
|
||||||
|
|
||||||
afterAll(async () => {
|
|
||||||
await global.quitDriver(driver);
|
|
||||||
});
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
// Navigate to home page before each test to ensure clean state
|
|
||||||
await driver.get(global.testConfig.baseUrl);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('User Registration', () => {
|
|
||||||
test('should successfully register a new user', async () => {
|
|
||||||
const testUser = users.registrationTestUser;
|
|
||||||
|
|
||||||
await registrationPage.navigateToRegistration();
|
|
||||||
|
|
||||||
expect(await registrationPage.isRegistrationFormDisplayed()).toBe(true);
|
|
||||||
expect(await registrationPage.getRegistrationHeading()).toBe('Register');
|
|
||||||
|
|
||||||
await registrationPage.register(
|
|
||||||
testUser.name,
|
|
||||||
testUser.email,
|
|
||||||
testUser.password
|
|
||||||
);
|
|
||||||
|
|
||||||
await registrationPage.waitForSuccessfulRegistration();
|
|
||||||
|
|
||||||
const successMessage = await registrationPage.getSuccessMessage();
|
|
||||||
expect(successMessage).toContain('Registration successful');
|
|
||||||
expect(await registrationPage.isFormCleared()).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should show validation errors for invalid registration data', async () => {
|
|
||||||
const invalidUser = users.invalidUsers.shortPassword;
|
|
||||||
|
|
||||||
await registrationPage.navigateToRegistration();
|
|
||||||
await registrationPage.register(
|
|
||||||
invalidUser.name,
|
|
||||||
invalidUser.email,
|
|
||||||
invalidUser.password
|
|
||||||
);
|
|
||||||
|
|
||||||
await registrationPage.waitForRegistrationError();
|
|
||||||
|
|
||||||
// Check that form shows validation errors
|
|
||||||
const hasErrors = await registrationPage.hasPasswordFieldError() ||
|
|
||||||
await registrationPage.getPasswordErrorMessage() !== null ||
|
|
||||||
await registrationPage.getGeneralErrorMessage() !== null;
|
|
||||||
|
|
||||||
expect(hasErrors).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should show error for password mismatch', async () => {
|
|
||||||
const mismatchUser = users.invalidUsers.passwordMismatch;
|
|
||||||
|
|
||||||
await registrationPage.navigateToRegistration();
|
|
||||||
await registrationPage.register(
|
|
||||||
mismatchUser.name,
|
|
||||||
mismatchUser.email,
|
|
||||||
mismatchUser.password,
|
|
||||||
mismatchUser.passwordConfirmation
|
|
||||||
);
|
|
||||||
|
|
||||||
await registrationPage.waitForRegistrationError();
|
|
||||||
|
|
||||||
const hasPasswordConfirmationError = await registrationPage.hasPasswordConfirmationFieldError() ||
|
|
||||||
await registrationPage.getPasswordConfirmationErrorMessage() !== null;
|
|
||||||
|
|
||||||
expect(hasPasswordConfirmationError).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should show error for empty required fields', async () => {
|
|
||||||
await registrationPage.navigateToRegistration();
|
|
||||||
await registrationPage.clickSubmit();
|
|
||||||
|
|
||||||
// The form should prevent submission with empty required fields
|
|
||||||
// or show validation errors
|
|
||||||
const submitButtonText = await registrationPage.getSubmitButtonText();
|
|
||||||
expect(submitButtonText).toBe('Register'); // Button text shouldn't change to "Registering..."
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should show error for invalid email format', async () => {
|
|
||||||
const invalidEmailUser = users.invalidUsers.invalidEmail;
|
|
||||||
|
|
||||||
await registrationPage.navigateToRegistration();
|
|
||||||
await registrationPage.register(
|
|
||||||
invalidEmailUser.name,
|
|
||||||
invalidEmailUser.email,
|
|
||||||
invalidEmailUser.password
|
|
||||||
);
|
|
||||||
|
|
||||||
// The HTML5 email validation should prevent form submission
|
|
||||||
// or backend should return validation error
|
|
||||||
const currentUrl = await registrationPage.getCurrentUrl();
|
|
||||||
expect(currentUrl).toContain('/register');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('User Login', () => {
|
|
||||||
test('should successfully login with valid credentials', async () => {
|
|
||||||
const validCredentials = users.loginTestCases.validCredentials;
|
|
||||||
|
|
||||||
await loginPage.navigateToLogin();
|
|
||||||
|
|
||||||
expect(await loginPage.isLoginFormDisplayed()).toBe(true);
|
|
||||||
expect(await loginPage.getLoginHeading()).toBe('Login');
|
|
||||||
|
|
||||||
await loginPage.login(
|
|
||||||
validCredentials.email,
|
|
||||||
validCredentials.password
|
|
||||||
);
|
|
||||||
|
|
||||||
await loginPage.waitForSuccessfulLogin();
|
|
||||||
|
|
||||||
// Should see dashboard after successful login
|
|
||||||
const dashboardVisible = await dashboardPage.isDashboardDisplayed();
|
|
||||||
expect(dashboardVisible).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should show error for invalid credentials', async () => {
|
|
||||||
const invalidCredentials = users.loginTestCases.invalidCredentials;
|
|
||||||
|
|
||||||
await loginPage.navigateToLogin();
|
|
||||||
await loginPage.login(
|
|
||||||
invalidCredentials.email,
|
|
||||||
invalidCredentials.password
|
|
||||||
);
|
|
||||||
|
|
||||||
await loginPage.waitForLoginError();
|
|
||||||
|
|
||||||
const generalError = await loginPage.getGeneralErrorMessage();
|
|
||||||
expect(generalError).toContain('Invalid email or password');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should show validation errors for empty fields', async () => {
|
|
||||||
await loginPage.navigateToLogin();
|
|
||||||
await loginPage.clickSubmit();
|
|
||||||
|
|
||||||
// The form should prevent submission with empty required fields
|
|
||||||
const submitButtonText = await loginPage.getSubmitButtonText();
|
|
||||||
expect(submitButtonText).toBe('Login'); // Button text shouldn't change to "Logging in..."
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should show error for invalid email format', async () => {
|
|
||||||
const invalidEmailCredentials = users.loginTestCases.invalidEmailFormat;
|
|
||||||
|
|
||||||
await loginPage.navigateToLogin();
|
|
||||||
await loginPage.login(
|
|
||||||
invalidEmailCredentials.email,
|
|
||||||
invalidEmailCredentials.password
|
|
||||||
);
|
|
||||||
|
|
||||||
// The HTML5 email validation should prevent form submission
|
|
||||||
// or backend should return validation error
|
|
||||||
const currentUrl = await loginPage.getCurrentUrl();
|
|
||||||
expect(currentUrl).toContain('/login');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should disable submit button while login is in progress', async () => {
|
|
||||||
const validCredentials = users.loginTestCases.validCredentials;
|
|
||||||
|
|
||||||
await loginPage.navigateToLogin();
|
|
||||||
await loginPage.enterEmail(validCredentials.email);
|
|
||||||
await loginPage.enterPassword(validCredentials.password);
|
|
||||||
|
|
||||||
// Click submit and immediately check if button is disabled
|
|
||||||
await loginPage.clickSubmit();
|
|
||||||
|
|
||||||
// Check if button text changes to "Logging in..." (indicating loading state)
|
|
||||||
const submitButtonText = await loginPage.getSubmitButtonText();
|
|
||||||
const isButtonDisabled = await loginPage.isSubmitButtonDisabled();
|
|
||||||
|
|
||||||
expect(submitButtonText === 'Logging in...' || isButtonDisabled).toBe(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Authentication Flow Integration', () => {
|
|
||||||
test('should allow registration followed by immediate login', async () => {
|
|
||||||
// Use a unique email for this test to avoid conflicts
|
|
||||||
const timestamp = Date.now();
|
|
||||||
const testUser = {
|
|
||||||
name: 'Integration Test User',
|
|
||||||
email: `integration.test.${timestamp}@example.com`,
|
|
||||||
password: 'password123'
|
|
||||||
};
|
|
||||||
|
|
||||||
// Register new user
|
|
||||||
await registrationPage.navigateToRegistration();
|
|
||||||
await registrationPage.register(
|
|
||||||
testUser.name,
|
|
||||||
testUser.email,
|
|
||||||
testUser.password
|
|
||||||
);
|
|
||||||
|
|
||||||
await registrationPage.waitForSuccessfulRegistration();
|
|
||||||
|
|
||||||
// Navigate to login and use the same credentials
|
|
||||||
await loginPage.navigateToLogin();
|
|
||||||
await loginPage.login(testUser.email, testUser.password);
|
|
||||||
|
|
||||||
await loginPage.waitForSuccessfulLogin();
|
|
||||||
|
|
||||||
// Should be successfully logged in
|
|
||||||
const currentUrl = await loginPage.getCurrentUrl();
|
|
||||||
expect(currentUrl).not.toContain('/login');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
describe('Debug Test', () => {
|
|
||||||
let driver;
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
|
||||||
console.log('Creating driver...');
|
|
||||||
driver = await global.createDriver();
|
|
||||||
console.log('Driver created successfully');
|
|
||||||
});
|
|
||||||
|
|
||||||
afterAll(async () => {
|
|
||||||
console.log('Quitting driver...');
|
|
||||||
await global.quitDriver(driver);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should connect to frontend', async () => {
|
|
||||||
console.log('Attempting to navigate to:', global.testConfig.baseUrl);
|
|
||||||
|
|
||||||
try {
|
|
||||||
await driver.get(global.testConfig.baseUrl);
|
|
||||||
console.log('Navigation successful');
|
|
||||||
|
|
||||||
const title = await driver.getTitle();
|
|
||||||
console.log('Page title:', title);
|
|
||||||
|
|
||||||
const currentUrl = await driver.getCurrentUrl();
|
|
||||||
console.log('Current URL:', currentUrl);
|
|
||||||
|
|
||||||
expect(title).toBeDefined();
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error during navigation:', error.message);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}, 60000); // 60 second timeout
|
|
||||||
});
|
|
||||||
|
|
@ -1,61 +0,0 @@
|
||||||
const fs = require('fs');
|
|
||||||
const path = require('path');
|
|
||||||
|
|
||||||
describe('Screenshot Debug Test', () => {
|
|
||||||
let driver;
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
|
||||||
driver = await global.createDriver();
|
|
||||||
|
|
||||||
// Create screenshots directory if it doesn't exist
|
|
||||||
const screenshotsDir = path.join(__dirname, '../screenshots');
|
|
||||||
if (!fs.existsSync(screenshotsDir)) {
|
|
||||||
fs.mkdirSync(screenshotsDir);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
afterAll(async () => {
|
|
||||||
await global.quitDriver(driver);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('take screenshot of homepage', async () => {
|
|
||||||
console.log('Navigating to:', global.testConfig.baseUrl);
|
|
||||||
await driver.get(global.testConfig.baseUrl);
|
|
||||||
|
|
||||||
// Wait for page to load
|
|
||||||
await driver.sleep(3000);
|
|
||||||
|
|
||||||
// Take screenshot
|
|
||||||
const screenshot = await driver.takeScreenshot();
|
|
||||||
const screenshotPath = path.join(__dirname, '../screenshots/homepage.png');
|
|
||||||
fs.writeFileSync(screenshotPath, screenshot, 'base64');
|
|
||||||
console.log('Screenshot saved to:', screenshotPath);
|
|
||||||
|
|
||||||
// Get page info
|
|
||||||
const title = await driver.getTitle();
|
|
||||||
const url = await driver.getCurrentUrl();
|
|
||||||
console.log('Page title:', title);
|
|
||||||
console.log('Current URL:', url);
|
|
||||||
|
|
||||||
// Try to find and screenshot the auth container
|
|
||||||
try {
|
|
||||||
const authContainer = await driver.findElement({ className: 'auth-container' });
|
|
||||||
console.log('Found auth container');
|
|
||||||
|
|
||||||
// Click register button and take screenshot
|
|
||||||
const registerButton = await driver.findElement({ css: '.auth-toggle button:last-child' });
|
|
||||||
await registerButton.click();
|
|
||||||
await driver.sleep(1000);
|
|
||||||
|
|
||||||
const screenshot2 = await driver.takeScreenshot();
|
|
||||||
const screenshotPath2 = path.join(__dirname, '../screenshots/register-form.png');
|
|
||||||
fs.writeFileSync(screenshotPath2, screenshot2, 'base64');
|
|
||||||
console.log('Register form screenshot saved to:', screenshotPath2);
|
|
||||||
|
|
||||||
} catch (e) {
|
|
||||||
console.log('Auth container not found:', e.message);
|
|
||||||
}
|
|
||||||
|
|
||||||
expect(true).toBe(true);
|
|
||||||
}, 60000);
|
|
||||||
});
|
|
||||||
|
|
@ -1,77 +0,0 @@
|
||||||
const { By, until } = require('selenium-webdriver');
|
|
||||||
|
|
||||||
describe('Simple Authentication Test', () => {
|
|
||||||
let driver;
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
|
||||||
driver = await global.createDriver();
|
|
||||||
});
|
|
||||||
|
|
||||||
afterAll(async () => {
|
|
||||||
await global.quitDriver(driver);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should display auth forms', async () => {
|
|
||||||
console.log('Navigating to:', global.testConfig.baseUrl);
|
|
||||||
await driver.get(global.testConfig.baseUrl);
|
|
||||||
|
|
||||||
// Wait for page to load
|
|
||||||
await driver.sleep(2000);
|
|
||||||
|
|
||||||
// Log page source to see what's actually there
|
|
||||||
const pageSource = await driver.getPageSource();
|
|
||||||
console.log('Page contains auth-container?', pageSource.includes('auth-container'));
|
|
||||||
console.log('Page contains login-form?', pageSource.includes('login-form'));
|
|
||||||
console.log('Page contains registration-form?', pageSource.includes('registration-form'));
|
|
||||||
|
|
||||||
// Try to find auth container
|
|
||||||
try {
|
|
||||||
const authContainer = await driver.findElement(By.className('auth-container'));
|
|
||||||
console.log('Found auth-container');
|
|
||||||
|
|
||||||
// Check for toggle buttons
|
|
||||||
const toggleButtons = await driver.findElements(By.css('.auth-toggle button'));
|
|
||||||
console.log('Found toggle buttons:', toggleButtons.length);
|
|
||||||
|
|
||||||
// Check which form is visible
|
|
||||||
try {
|
|
||||||
const loginForm = await driver.findElement(By.className('login-form'));
|
|
||||||
const isLoginVisible = await loginForm.isDisplayed();
|
|
||||||
console.log('Login form visible:', isLoginVisible);
|
|
||||||
} catch (e) {
|
|
||||||
console.log('Login form not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const regForm = await driver.findElement(By.className('registration-form'));
|
|
||||||
const isRegVisible = await regForm.isDisplayed();
|
|
||||||
console.log('Registration form visible:', isRegVisible);
|
|
||||||
} catch (e) {
|
|
||||||
console.log('Registration form not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.log('Auth container not found');
|
|
||||||
|
|
||||||
// Check if we're seeing the dashboard instead
|
|
||||||
try {
|
|
||||||
const dashboard = await driver.findElement(By.className('dashboard'));
|
|
||||||
console.log('Dashboard found - user might already be logged in');
|
|
||||||
} catch (e) {
|
|
||||||
console.log('Dashboard not found either');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Log the actual page title and URL
|
|
||||||
const title = await driver.getTitle();
|
|
||||||
const url = await driver.getCurrentUrl();
|
|
||||||
console.log('Page title:', title);
|
|
||||||
console.log('Current URL:', url);
|
|
||||||
|
|
||||||
// Get first 500 chars of body text
|
|
||||||
const bodyText = await driver.findElement(By.tagName('body')).getText();
|
|
||||||
console.log('Page text (first 500 chars):', bodyText.substring(0, 500));
|
|
||||||
}
|
|
||||||
|
|
||||||
expect(true).toBe(true); // Just to make test pass while debugging
|
|
||||||
}, 60000);
|
|
||||||
});
|
|
||||||
|
|
@ -1,136 +0,0 @@
|
||||||
const { By, until } = require('selenium-webdriver');
|
|
||||||
|
|
||||||
describe('Visual Authentication Test', () => {
|
|
||||||
let driver;
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
|
||||||
console.log('Starting Chrome browser...');
|
|
||||||
driver = await global.createDriver();
|
|
||||||
console.log('Browser started!');
|
|
||||||
});
|
|
||||||
|
|
||||||
afterAll(async () => {
|
|
||||||
console.log('Test complete - keeping browser open for 5 seconds...');
|
|
||||||
await driver.sleep(5000);
|
|
||||||
await global.quitDriver(driver);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should test registration and login visually', async () => {
|
|
||||||
console.log('Navigating to app...');
|
|
||||||
await driver.get(global.testConfig.baseUrl);
|
|
||||||
|
|
||||||
// Wait for page load
|
|
||||||
console.log('Waiting for page to load...');
|
|
||||||
await driver.sleep(2000);
|
|
||||||
|
|
||||||
// Check if auth container exists
|
|
||||||
try {
|
|
||||||
const authContainer = await driver.findElement(By.className('auth-container'));
|
|
||||||
console.log('✓ Found auth container');
|
|
||||||
|
|
||||||
// Click Register tab
|
|
||||||
console.log('Clicking Register tab...');
|
|
||||||
const registerButton = await driver.findElement(By.css('.auth-toggle button:last-child'));
|
|
||||||
await registerButton.click();
|
|
||||||
await driver.sleep(1000);
|
|
||||||
|
|
||||||
// Fill registration form
|
|
||||||
console.log('Filling registration form...');
|
|
||||||
const nameInput = await driver.findElement(By.id('name'));
|
|
||||||
await nameInput.sendKeys('Test User');
|
|
||||||
await driver.sleep(500);
|
|
||||||
|
|
||||||
const emailInput = await driver.findElement(By.id('email'));
|
|
||||||
await emailInput.sendKeys('test' + Date.now() + '@example.com');
|
|
||||||
await driver.sleep(500);
|
|
||||||
|
|
||||||
const passwordInput = await driver.findElement(By.id('password'));
|
|
||||||
await passwordInput.sendKeys('password123');
|
|
||||||
await driver.sleep(500);
|
|
||||||
|
|
||||||
const confirmPasswordInput = await driver.findElement(By.id('password_confirmation'));
|
|
||||||
await confirmPasswordInput.sendKeys('password123');
|
|
||||||
await driver.sleep(1000);
|
|
||||||
|
|
||||||
console.log('Submitting registration form...');
|
|
||||||
const submitButton = await driver.findElement(By.css('button[type="submit"]'));
|
|
||||||
await submitButton.click();
|
|
||||||
|
|
||||||
// Wait to see the result
|
|
||||||
console.log('Waiting for registration response...');
|
|
||||||
await driver.sleep(3000);
|
|
||||||
|
|
||||||
// Check for success message
|
|
||||||
try {
|
|
||||||
const successMessage = await driver.findElement(By.className('alert-success'));
|
|
||||||
const text = await successMessage.getText();
|
|
||||||
console.log('✓ Registration successful:', text);
|
|
||||||
} catch (e) {
|
|
||||||
console.log('No success message found');
|
|
||||||
|
|
||||||
// Check for errors
|
|
||||||
try {
|
|
||||||
const errorMessage = await driver.findElement(By.className('alert-error'));
|
|
||||||
const errorText = await errorMessage.getText();
|
|
||||||
console.log('✗ Error:', errorText);
|
|
||||||
} catch (e2) {
|
|
||||||
console.log('No error message found either');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now test login
|
|
||||||
console.log('\nSwitching to Login form...');
|
|
||||||
const loginButton = await driver.findElement(By.css('.auth-toggle button:first-child'));
|
|
||||||
await loginButton.click();
|
|
||||||
await driver.sleep(1000);
|
|
||||||
|
|
||||||
console.log('Filling login form...');
|
|
||||||
const loginEmail = await driver.findElement(By.css('.login-form #email'));
|
|
||||||
await loginEmail.clear();
|
|
||||||
await loginEmail.sendKeys('test@example.com');
|
|
||||||
await driver.sleep(500);
|
|
||||||
|
|
||||||
const loginPassword = await driver.findElement(By.css('.login-form #password'));
|
|
||||||
await loginPassword.clear();
|
|
||||||
await loginPassword.sendKeys('password123');
|
|
||||||
await driver.sleep(1000);
|
|
||||||
|
|
||||||
console.log('Submitting login form...');
|
|
||||||
const loginSubmit = await driver.findElement(By.css('.login-form button[type="submit"]'));
|
|
||||||
await loginSubmit.click();
|
|
||||||
|
|
||||||
console.log('Waiting for login response...');
|
|
||||||
await driver.sleep(3000);
|
|
||||||
|
|
||||||
// Check if we reached dashboard
|
|
||||||
try {
|
|
||||||
const dashboard = await driver.findElement(By.className('dashboard'));
|
|
||||||
console.log('✓ Successfully logged in - Dashboard visible');
|
|
||||||
} catch (e) {
|
|
||||||
console.log('Dashboard not found - checking for login errors...');
|
|
||||||
|
|
||||||
try {
|
|
||||||
const errorMessage = await driver.findElement(By.className('alert-error'));
|
|
||||||
const errorText = await errorMessage.getText();
|
|
||||||
console.log('✗ Login error:', errorText);
|
|
||||||
} catch (e2) {
|
|
||||||
console.log('Still on login form');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.log('✗ Auth container not found');
|
|
||||||
console.log('Error:', error.message);
|
|
||||||
|
|
||||||
// Check if already logged in
|
|
||||||
try {
|
|
||||||
const dashboard = await driver.findElement(By.className('dashboard'));
|
|
||||||
console.log('User appears to be already logged in (dashboard visible)');
|
|
||||||
} catch (e) {
|
|
||||||
console.log('Dashboard not found either - page structure might be different');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
expect(true).toBe(true);
|
|
||||||
}, 120000);
|
|
||||||
});
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"testEnvironment": "node",
|
"testEnvironment": "node",
|
||||||
"testMatch": ["**/e2e/**/*.test.js"],
|
"testMatch": ["**/specs/**/*.test.js"],
|
||||||
"setupFilesAfterEnv": ["<rootDir>/config/jest.setup.local.js"],
|
"setupFilesAfterEnv": ["<rootDir>/support/config/jest.setup.local.js"],
|
||||||
"testTimeout": 30000
|
"testTimeout": 30000
|
||||||
}
|
}
|
||||||
4074
tests/package-lock.json
generated
Normal file
4074
tests/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -4,20 +4,20 @@
|
||||||
"description": "End-to-end tests for Trip Planner application",
|
"description": "End-to-end tests for Trip Planner application",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "jest",
|
"test": "jest --config jest-local.json",
|
||||||
"test:headless": "HEADLESS=true jest",
|
"test:docker": "jest",
|
||||||
"test:watch": "jest --watch",
|
"test:headless": "HEADLESS=true jest --config jest-local.json",
|
||||||
"test:debug": "HEADLESS=false jest --detectOpenHandles",
|
"test:watch": "jest --config jest-local.json --watch",
|
||||||
"test:auth": "jest e2e/auth.test.js",
|
"test:auth": "jest --config jest-local.json specs/auth",
|
||||||
"test:debug-simple": "HEADLESS=false jest e2e/debug.test.js --verbose",
|
"test:integration": "jest --config jest-local.json specs/integration",
|
||||||
"test:local": "jest --config jest-local.json",
|
"test:full-auth": "jest --config jest-local.json specs/integration/full-auth-flow.test.js"
|
||||||
"test:local:debug": "HEADLESS=false jest --config jest-local.json e2e/debug.test.js --verbose"
|
|
||||||
},
|
},
|
||||||
"keywords": ["selenium", "e2e", "testing"],
|
"keywords": ["selenium", "e2e", "testing"],
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"selenium-webdriver": "^4.24.0"
|
"selenium-webdriver": "^4.24.0",
|
||||||
|
"axios": "^1.6.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"jest": "^29.7.0",
|
"jest": "^29.7.0",
|
||||||
|
|
@ -25,14 +25,14 @@
|
||||||
},
|
},
|
||||||
"jest": {
|
"jest": {
|
||||||
"testEnvironment": "node",
|
"testEnvironment": "node",
|
||||||
"testMatch": ["**/e2e/**/*.test.js"],
|
"testMatch": ["**/specs/**/*.test.js"],
|
||||||
"setupFilesAfterEnv": ["<rootDir>/config/jest.setup.js"],
|
"setupFilesAfterEnv": ["<rootDir>/support/config/jest.setup.js"],
|
||||||
"testTimeout": 30000
|
"testTimeout": 30000
|
||||||
},
|
},
|
||||||
"jest-local": {
|
"jest-local": {
|
||||||
"testEnvironment": "node",
|
"testEnvironment": "node",
|
||||||
"testMatch": ["**/e2e/**/*.test.js"],
|
"testMatch": ["**/specs/**/*.test.js"],
|
||||||
"setupFilesAfterEnv": ["<rootDir>/config/jest.setup.local.js"],
|
"setupFilesAfterEnv": ["<rootDir>/support/config/jest.setup.local.js"],
|
||||||
"testTimeout": 30000
|
"testTimeout": 30000
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
65
tests/run-clean.sh
Executable file
65
tests/run-clean.sh
Executable file
|
|
@ -0,0 +1,65 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Simple Test Runner - Clean Output
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Colors
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
RED='\033[0;31m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
NC='\033[0m'
|
||||||
|
|
||||||
|
echo -e "${YELLOW}🧪 Running Trip Planner E2E Tests${NC}"
|
||||||
|
|
||||||
|
# Check if npm packages are installed
|
||||||
|
if [ ! -d "node_modules" ]; then
|
||||||
|
echo -e "${YELLOW}📦 Installing dependencies...${NC}"
|
||||||
|
npm install > /dev/null 2>&1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if frontend is accessible
|
||||||
|
if ! curl -s -o /dev/null http://localhost:5173; then
|
||||||
|
echo -e "${RED}❌ Frontend not accessible at http://localhost:5173${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "${GREEN}✅ Environment ready${NC}"
|
||||||
|
|
||||||
|
# Default to running only clean tests
|
||||||
|
TEST_FILE="specs/auth/auth-clean.test.js"
|
||||||
|
|
||||||
|
# Parse simple arguments
|
||||||
|
case "${1:-}" in
|
||||||
|
--full)
|
||||||
|
TEST_FILE="specs/integration/full-auth-flow.test.js"
|
||||||
|
echo -e "${YELLOW}🔄 Running full authentication flow test...${NC}"
|
||||||
|
;;
|
||||||
|
--integration)
|
||||||
|
TEST_FILE="specs/integration"
|
||||||
|
echo -e "${YELLOW}🔗 Running integration tests...${NC}"
|
||||||
|
;;
|
||||||
|
--auth)
|
||||||
|
TEST_FILE="specs/auth"
|
||||||
|
echo -e "${YELLOW}🔑 Running authentication tests...${NC}"
|
||||||
|
;;
|
||||||
|
--all)
|
||||||
|
TEST_FILE=""
|
||||||
|
echo -e "${YELLOW}🌟 Running all tests...${NC}"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo -e "${YELLOW}🧹 Running clean authentication tests...${NC}"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# Create screenshots directory
|
||||||
|
mkdir -p screenshots
|
||||||
|
|
||||||
|
# Run tests with timing logs visible
|
||||||
|
if [ -n "$TEST_FILE" ]; then
|
||||||
|
HEADLESS=false npm test -- "$TEST_FILE" --verbose=false
|
||||||
|
else
|
||||||
|
HEADLESS=false npm test --verbose=false
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "${GREEN}✅ Tests completed!${NC}"
|
||||||
111
tests/run-tests.sh
Executable file
111
tests/run-tests.sh
Executable file
|
|
@ -0,0 +1,111 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Test Runner Script for Trip Planner E2E Tests
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Colors for output
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
# Default values
|
||||||
|
TEST_TYPE="local"
|
||||||
|
TEST_FILE=""
|
||||||
|
HEADLESS="false"
|
||||||
|
|
||||||
|
# Parse arguments
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case $1 in
|
||||||
|
--docker)
|
||||||
|
TEST_TYPE="docker"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--headless)
|
||||||
|
HEADLESS="true"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--auth)
|
||||||
|
TEST_FILE="e2e/auth.test.js"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--full-auth)
|
||||||
|
TEST_FILE="e2e/full-auth-flow.test.js"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--help)
|
||||||
|
echo "Usage: ./run-tests.sh [options]"
|
||||||
|
echo ""
|
||||||
|
echo "Options:"
|
||||||
|
echo " --docker Run tests using Docker Selenium (requires Docker setup)"
|
||||||
|
echo " --headless Run tests in headless mode"
|
||||||
|
echo " --auth Run auth.test.js only"
|
||||||
|
echo " --full-auth Run full-auth-flow.test.js only"
|
||||||
|
echo " --help Show this help message"
|
||||||
|
echo ""
|
||||||
|
echo "Examples:"
|
||||||
|
echo " ./run-tests.sh # Run all tests with local Chrome"
|
||||||
|
echo " ./run-tests.sh --docker # Run all tests with Docker Selenium"
|
||||||
|
echo " ./run-tests.sh --full-auth # Run full auth test only"
|
||||||
|
echo " ./run-tests.sh --headless # Run locally in headless mode"
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo -e "${RED}Unknown option: $1${NC}"
|
||||||
|
echo "Use --help for usage information"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# Check if npm packages are installed
|
||||||
|
if [ ! -d "node_modules" ]; then
|
||||||
|
echo -e "${YELLOW}Installing npm packages...${NC}"
|
||||||
|
npm install
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if Docker services are running (for Docker mode)
|
||||||
|
if [ "$TEST_TYPE" = "docker" ]; then
|
||||||
|
echo -e "${YELLOW}Checking Docker services...${NC}"
|
||||||
|
|
||||||
|
# Check if selenium-hub is running
|
||||||
|
if ! podman ps | grep -q "selenium-hub"; then
|
||||||
|
echo -e "${RED}Selenium Hub is not running!${NC}"
|
||||||
|
echo "Please start Docker services with: podman-compose -f ../docker-compose.dev.yml up -d"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if frontend is accessible
|
||||||
|
if ! curl -s -o /dev/null -w "%{http_code}" http://localhost:5173 | grep -q "200"; then
|
||||||
|
echo -e "${RED}Frontend is not accessible at http://localhost:5173${NC}"
|
||||||
|
echo "Please start the frontend service"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create screenshots directory if it doesn't exist
|
||||||
|
mkdir -p screenshots
|
||||||
|
|
||||||
|
# Run tests
|
||||||
|
echo -e "${GREEN}Running tests...${NC}"
|
||||||
|
echo "Mode: $TEST_TYPE"
|
||||||
|
echo "Headless: $HEADLESS"
|
||||||
|
|
||||||
|
if [ "$TEST_TYPE" = "local" ]; then
|
||||||
|
# Local Chrome execution
|
||||||
|
if [ -n "$TEST_FILE" ]; then
|
||||||
|
HEADLESS=$HEADLESS npm test -- $TEST_FILE
|
||||||
|
else
|
||||||
|
HEADLESS=$HEADLESS npm test
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# Docker Selenium execution
|
||||||
|
if [ -n "$TEST_FILE" ]; then
|
||||||
|
HEADLESS=$HEADLESS npm run test:docker -- $TEST_FILE
|
||||||
|
else
|
||||||
|
HEADLESS=$HEADLESS npm run test:docker
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "${GREEN}Tests completed!${NC}"
|
||||||
179
tests/specs/auth/auth-clean.test.js
Normal file
179
tests/specs/auth/auth-clean.test.js
Normal file
|
|
@ -0,0 +1,179 @@
|
||||||
|
const { By, until } = require('selenium-webdriver');
|
||||||
|
const RegistrationPage = require('../../support/pages/RegistrationPage');
|
||||||
|
const LoginPage = require('../../support/pages/LoginPage');
|
||||||
|
const DashboardPage = require('../../support/pages/DashboardPage');
|
||||||
|
|
||||||
|
describe('Authentication Tests (Clean)', () => {
|
||||||
|
let driver;
|
||||||
|
let registrationPage;
|
||||||
|
let loginPage;
|
||||||
|
let dashboardPage;
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
driver = await global.createDriver();
|
||||||
|
registrationPage = new RegistrationPage(driver);
|
||||||
|
loginPage = new LoginPage(driver);
|
||||||
|
dashboardPage = new DashboardPage(driver);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await global.quitDriver(driver);
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
// Ultra-fast cleanup: clear storage and navigate in parallel
|
||||||
|
await Promise.all([
|
||||||
|
driver.manage().deleteAllCookies().catch(() => {}),
|
||||||
|
driver.executeScript('try { localStorage.clear(); sessionStorage.clear(); } catch(e) {}')
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Navigate fresh to home page
|
||||||
|
await driver.get(global.testConfig.baseUrl);
|
||||||
|
await driver.sleep(200); // Minimal wait time
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should successfully register a new user', async () => {
|
||||||
|
const timestamp = Date.now();
|
||||||
|
const testUser = {
|
||||||
|
name: `Test User ${timestamp}`,
|
||||||
|
email: `test.${timestamp}@example.com`,
|
||||||
|
password: 'password123'
|
||||||
|
};
|
||||||
|
|
||||||
|
await registrationPage.navigateToRegistration();
|
||||||
|
await registrationPage.register(
|
||||||
|
testUser.name,
|
||||||
|
testUser.email,
|
||||||
|
testUser.password
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check for success (either message or auto-login to dashboard)
|
||||||
|
await driver.sleep(800);
|
||||||
|
const hasSuccess = await registrationPage.getSuccessMessage() !== null;
|
||||||
|
const hasDashboard = await dashboardPage.isDashboardDisplayed();
|
||||||
|
|
||||||
|
expect(hasSuccess || hasDashboard).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should successfully login with valid credentials', async () => {
|
||||||
|
// First register a user
|
||||||
|
const timestamp = Date.now();
|
||||||
|
const testUser = {
|
||||||
|
name: `Login Test ${timestamp}`,
|
||||||
|
email: `login.${timestamp}@example.com`,
|
||||||
|
password: 'password123'
|
||||||
|
};
|
||||||
|
|
||||||
|
await registrationPage.navigateToRegistration();
|
||||||
|
await registrationPage.register(
|
||||||
|
testUser.name,
|
||||||
|
testUser.email,
|
||||||
|
testUser.password
|
||||||
|
);
|
||||||
|
|
||||||
|
// Wait for response
|
||||||
|
await driver.sleep(1500);
|
||||||
|
|
||||||
|
// Force logout and clean state
|
||||||
|
await driver.manage().deleteAllCookies();
|
||||||
|
await driver.executeScript('localStorage.clear(); sessionStorage.clear();');
|
||||||
|
await driver.get(global.testConfig.baseUrl);
|
||||||
|
await driver.sleep(1000);
|
||||||
|
|
||||||
|
// Verify we're back at auth page
|
||||||
|
const authContainer = await driver.findElement({ className: 'auth-container' });
|
||||||
|
expect(await authContainer.isDisplayed()).toBe(true);
|
||||||
|
|
||||||
|
// Now test login
|
||||||
|
await loginPage.navigateToLogin();
|
||||||
|
await loginPage.login(testUser.email, testUser.password);
|
||||||
|
|
||||||
|
await driver.sleep(1500);
|
||||||
|
const isDashboardVisible = await dashboardPage.isDashboardDisplayed();
|
||||||
|
expect(isDashboardVisible).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should show error for invalid login credentials', async () => {
|
||||||
|
await loginPage.navigateToLogin();
|
||||||
|
await loginPage.login('invalid@example.com', 'wrongpassword');
|
||||||
|
|
||||||
|
await driver.sleep(1000);
|
||||||
|
const errorMessage = await loginPage.getGeneralErrorMessage();
|
||||||
|
expect(errorMessage).toBeTruthy();
|
||||||
|
expect(errorMessage.toLowerCase()).toContain('invalid');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should successfully logout after login', async () => {
|
||||||
|
// First register and login a user
|
||||||
|
const timestamp = Date.now();
|
||||||
|
const testUser = {
|
||||||
|
name: `Logout Test ${timestamp}`,
|
||||||
|
email: `logout.${timestamp}@example.com`,
|
||||||
|
password: 'password123'
|
||||||
|
};
|
||||||
|
|
||||||
|
await registrationPage.navigateToRegistration();
|
||||||
|
await registrationPage.register(
|
||||||
|
testUser.name,
|
||||||
|
testUser.email,
|
||||||
|
testUser.password
|
||||||
|
);
|
||||||
|
|
||||||
|
// Wait for auto-login after registration
|
||||||
|
await driver.sleep(1500);
|
||||||
|
|
||||||
|
// Verify we're logged in (dashboard visible)
|
||||||
|
const isDashboardVisible = await dashboardPage.isDashboardDisplayed();
|
||||||
|
expect(isDashboardVisible).toBe(true);
|
||||||
|
|
||||||
|
// Now test logout - look for logout button and click it
|
||||||
|
try {
|
||||||
|
const logoutButton = await driver.findElement(By.xpath("//button[contains(text(), 'Logout')]"));
|
||||||
|
await logoutButton.click();
|
||||||
|
await driver.sleep(1000);
|
||||||
|
} catch (e) {
|
||||||
|
// Fallback: clear session manually
|
||||||
|
await driver.manage().deleteAllCookies();
|
||||||
|
await driver.executeScript('localStorage.clear(); sessionStorage.clear();');
|
||||||
|
await driver.get(global.testConfig.baseUrl);
|
||||||
|
await driver.sleep(500);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify we're back at auth page
|
||||||
|
const authContainer = await driver.findElement(By.className('auth-container'));
|
||||||
|
expect(await authContainer.isDisplayed()).toBe(true);
|
||||||
|
|
||||||
|
// Verify dashboard is no longer accessible
|
||||||
|
const dashboardElements = await driver.findElements(By.className('dashboard'));
|
||||||
|
expect(dashboardElements.length).toBe(0);
|
||||||
|
}, 60000);
|
||||||
|
|
||||||
|
test('should persist login across page refresh', async () => {
|
||||||
|
// Register and login a user
|
||||||
|
const timestamp = Date.now();
|
||||||
|
const testUser = {
|
||||||
|
name: `Persist Test ${timestamp}`,
|
||||||
|
email: `persist.${timestamp}@example.com`,
|
||||||
|
password: 'password123'
|
||||||
|
};
|
||||||
|
|
||||||
|
await registrationPage.navigateToRegistration();
|
||||||
|
await registrationPage.register(
|
||||||
|
testUser.name,
|
||||||
|
testUser.email,
|
||||||
|
testUser.password
|
||||||
|
);
|
||||||
|
|
||||||
|
// Wait for auto-login
|
||||||
|
await driver.sleep(3000);
|
||||||
|
expect(await dashboardPage.isDashboardDisplayed()).toBe(true);
|
||||||
|
|
||||||
|
// Refresh the page
|
||||||
|
await driver.navigate().refresh();
|
||||||
|
await driver.sleep(2000);
|
||||||
|
|
||||||
|
// Should still be logged in
|
||||||
|
const isStillLoggedIn = await dashboardPage.isDashboardDisplayed();
|
||||||
|
expect(isStillLoggedIn).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
244
tests/specs/integration/full-auth-flow.test.js
Normal file
244
tests/specs/integration/full-auth-flow.test.js
Normal file
|
|
@ -0,0 +1,244 @@
|
||||||
|
const { By, until } = require('selenium-webdriver');
|
||||||
|
|
||||||
|
describe('Full Authentication Flow', () => {
|
||||||
|
let driver;
|
||||||
|
const timestamp = Date.now();
|
||||||
|
const testUser = {
|
||||||
|
name: `Test User ${timestamp}`,
|
||||||
|
email: `test.user.${timestamp}@example.com`,
|
||||||
|
password: 'SecurePass123!'
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
console.log('🚀 Starting full authentication flow test...');
|
||||||
|
driver = await global.createDriver();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
console.log('🏁 Test complete - cleaning up...');
|
||||||
|
await global.quitDriver(driver);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should complete full authentication flow: register → logout → login → dashboard', async () => {
|
||||||
|
console.log(`\n📧 Test user email: ${testUser.email}\n`);
|
||||||
|
|
||||||
|
// STEP 1: Navigate to application
|
||||||
|
console.log('1️⃣ Navigating to application...');
|
||||||
|
await driver.get(global.testConfig.baseUrl);
|
||||||
|
await driver.sleep(2000);
|
||||||
|
|
||||||
|
// Verify we're on the auth page
|
||||||
|
const authContainer = await driver.wait(
|
||||||
|
until.elementLocated(By.className('auth-container')),
|
||||||
|
10000,
|
||||||
|
'Auth container not found - might already be logged in'
|
||||||
|
);
|
||||||
|
console.log(' ✓ Auth page loaded');
|
||||||
|
|
||||||
|
// STEP 2: Switch to registration form
|
||||||
|
console.log('\n2️⃣ Switching to registration form...');
|
||||||
|
const registerTab = await driver.findElement(By.css('.auth-toggle button:last-child'));
|
||||||
|
await registerTab.click();
|
||||||
|
await driver.sleep(1000);
|
||||||
|
|
||||||
|
// Verify registration form is visible
|
||||||
|
const regForm = await driver.wait(
|
||||||
|
until.elementLocated(By.className('registration-form')),
|
||||||
|
5000
|
||||||
|
);
|
||||||
|
console.log(' ✓ Registration form displayed');
|
||||||
|
|
||||||
|
// STEP 3: Fill and submit registration form
|
||||||
|
console.log('\n3️⃣ Filling registration form...');
|
||||||
|
|
||||||
|
const nameField = await driver.findElement(By.id('name'));
|
||||||
|
await nameField.clear();
|
||||||
|
await nameField.sendKeys(testUser.name);
|
||||||
|
console.log(` ✓ Name: ${testUser.name}`);
|
||||||
|
|
||||||
|
const emailField = await driver.findElement(By.id('email'));
|
||||||
|
await emailField.clear();
|
||||||
|
await emailField.sendKeys(testUser.email);
|
||||||
|
console.log(` ✓ Email: ${testUser.email}`);
|
||||||
|
|
||||||
|
const passwordField = await driver.findElement(By.id('password'));
|
||||||
|
await passwordField.clear();
|
||||||
|
await passwordField.sendKeys(testUser.password);
|
||||||
|
console.log(' ✓ Password: ***');
|
||||||
|
|
||||||
|
const confirmPasswordField = await driver.findElement(By.id('password_confirmation'));
|
||||||
|
await confirmPasswordField.clear();
|
||||||
|
await confirmPasswordField.sendKeys(testUser.password);
|
||||||
|
console.log(' ✓ Password confirmation: ***');
|
||||||
|
|
||||||
|
// Submit registration
|
||||||
|
console.log('\n4️⃣ Submitting registration...');
|
||||||
|
const registerButton = await driver.findElement(By.css('.registration-form button[type="submit"]'));
|
||||||
|
await registerButton.click();
|
||||||
|
|
||||||
|
// Wait for registration to complete
|
||||||
|
await driver.sleep(3000);
|
||||||
|
|
||||||
|
// Check for success message or dashboard
|
||||||
|
let registrationSuccessful = false;
|
||||||
|
|
||||||
|
// First check for success message
|
||||||
|
try {
|
||||||
|
const successMessage = await driver.findElement(By.className('alert-success'));
|
||||||
|
const successText = await successMessage.getText();
|
||||||
|
console.log(` ✓ ${successText}`);
|
||||||
|
registrationSuccessful = true;
|
||||||
|
} catch (e) {
|
||||||
|
console.log(' ℹ️ No success message displayed');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we're automatically logged in (dashboard visible)
|
||||||
|
try {
|
||||||
|
const dashboard = await driver.findElement(By.className('dashboard'));
|
||||||
|
const isDashboardVisible = await dashboard.isDisplayed();
|
||||||
|
if (isDashboardVisible) {
|
||||||
|
console.log(' ✓ Automatically logged in after registration');
|
||||||
|
registrationSuccessful = true;
|
||||||
|
|
||||||
|
// If auto-logged in, we should log out first to test login
|
||||||
|
console.log('\n5️⃣ Logging out to test login flow...');
|
||||||
|
|
||||||
|
// Look for logout button and click it
|
||||||
|
try {
|
||||||
|
const logoutButton = await driver.findElement(By.xpath("//button[contains(text(), 'Logout')]"));
|
||||||
|
await logoutButton.click();
|
||||||
|
await driver.sleep(2000);
|
||||||
|
console.log(' ✓ Logged out successfully');
|
||||||
|
} catch (e) {
|
||||||
|
console.log(' ⚠️ Could not find logout button');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.log(' ℹ️ Not automatically logged in');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for registration errors
|
||||||
|
if (!registrationSuccessful) {
|
||||||
|
try {
|
||||||
|
const errorMessage = await driver.findElement(By.className('alert-error'));
|
||||||
|
const errorText = await errorMessage.getText();
|
||||||
|
console.error(` ✗ Registration error: ${errorText}`);
|
||||||
|
|
||||||
|
// Check for field-specific errors
|
||||||
|
const fieldErrors = await driver.findElements(By.className('error-message'));
|
||||||
|
for (let error of fieldErrors) {
|
||||||
|
const text = await error.getText();
|
||||||
|
if (text) console.error(` - ${text}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error('Registration failed');
|
||||||
|
} catch (e) {
|
||||||
|
if (!e.message.includes('Registration failed')) {
|
||||||
|
console.log(' ℹ️ No error messages found');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// STEP 6: Test login with the registered credentials
|
||||||
|
console.log('\n6️⃣ Testing login with registered credentials...');
|
||||||
|
|
||||||
|
// Make sure we're back at the auth page
|
||||||
|
const isAuthPageVisible = await driver.findElements(By.className('auth-container'));
|
||||||
|
if (isAuthPageVisible.length === 0) {
|
||||||
|
// Navigate back to home if we're not on auth page
|
||||||
|
await driver.get(global.testConfig.baseUrl);
|
||||||
|
await driver.sleep(2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Switch to login form
|
||||||
|
const loginTab = await driver.findElement(By.css('.auth-toggle button:first-child'));
|
||||||
|
await loginTab.click();
|
||||||
|
await driver.sleep(1000);
|
||||||
|
console.log(' ✓ Switched to login form');
|
||||||
|
|
||||||
|
// Fill login form
|
||||||
|
console.log('\n7️⃣ Filling login form...');
|
||||||
|
const loginEmailField = await driver.findElement(By.css('.login-form #email'));
|
||||||
|
await loginEmailField.clear();
|
||||||
|
await loginEmailField.sendKeys(testUser.email);
|
||||||
|
console.log(` ✓ Email: ${testUser.email}`);
|
||||||
|
|
||||||
|
const loginPasswordField = await driver.findElement(By.css('.login-form #password'));
|
||||||
|
await loginPasswordField.clear();
|
||||||
|
await loginPasswordField.sendKeys(testUser.password);
|
||||||
|
console.log(' ✓ Password: ***');
|
||||||
|
|
||||||
|
// Submit login
|
||||||
|
console.log('\n8️⃣ Submitting login...');
|
||||||
|
const loginButton = await driver.findElement(By.css('.login-form button[type="submit"]'));
|
||||||
|
await loginButton.click();
|
||||||
|
|
||||||
|
// Wait for login to complete
|
||||||
|
await driver.sleep(3000);
|
||||||
|
|
||||||
|
// STEP 9: Verify dashboard access
|
||||||
|
console.log('\n9️⃣ Verifying dashboard access...');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const dashboard = await driver.wait(
|
||||||
|
until.elementLocated(By.className('dashboard')),
|
||||||
|
10000
|
||||||
|
);
|
||||||
|
|
||||||
|
const isDashboardVisible = await dashboard.isDisplayed();
|
||||||
|
expect(isDashboardVisible).toBe(true);
|
||||||
|
console.log(' ✓ Dashboard is visible');
|
||||||
|
|
||||||
|
// Look for welcome message or user info
|
||||||
|
try {
|
||||||
|
const dashboardHeading = await driver.findElement(By.css('.dashboard h2'));
|
||||||
|
const headingText = await dashboardHeading.getText();
|
||||||
|
console.log(` ✓ Dashboard heading: "${headingText}"`);
|
||||||
|
} catch (e) {
|
||||||
|
console.log(' ℹ️ No dashboard heading found');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify user info if available
|
||||||
|
try {
|
||||||
|
const userInfo = await driver.findElement(By.className('user-info'));
|
||||||
|
const userText = await userInfo.getText();
|
||||||
|
console.log(` ✓ User info displayed: "${userText}"`);
|
||||||
|
} catch (e) {
|
||||||
|
console.log(' ℹ️ No user info element found');
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('\n✅ Full authentication flow completed successfully!');
|
||||||
|
console.log(` User "${testUser.name}" can register, logout, login, and access the dashboard.`);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
// Login failed - check for error messages
|
||||||
|
console.error('\n❌ Login failed or dashboard not accessible');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const loginError = await driver.findElement(By.css('.login-form .alert-error'));
|
||||||
|
const errorText = await loginError.getText();
|
||||||
|
console.error(` Error message: ${errorText}`);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(' No specific error message found');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Take a screenshot for debugging
|
||||||
|
const screenshot = await driver.takeScreenshot();
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const screenshotPath = path.join(__dirname, `../screenshots/auth-flow-failure-${timestamp}.png`);
|
||||||
|
|
||||||
|
// Create screenshots directory if it doesn't exist
|
||||||
|
const screenshotsDir = path.join(__dirname, '../screenshots');
|
||||||
|
if (!fs.existsSync(screenshotsDir)) {
|
||||||
|
fs.mkdirSync(screenshotsDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.writeFileSync(screenshotPath, screenshot, 'base64');
|
||||||
|
console.log(` 📸 Screenshot saved to: ${screenshotPath}`);
|
||||||
|
|
||||||
|
throw new Error('Dashboard not accessible after login');
|
||||||
|
}
|
||||||
|
|
||||||
|
}, 120000); // 2-minute timeout for the full flow
|
||||||
|
});
|
||||||
|
|
@ -30,7 +30,7 @@ global.createDriver = async () => {
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
// Set implicit wait
|
// Set implicit wait
|
||||||
await driver.manage().setTimeouts({ implicit: 10000 });
|
await driver.manage().setTimeouts({ implicit: 1000 });
|
||||||
|
|
||||||
return driver;
|
return driver;
|
||||||
};
|
};
|
||||||
92
tests/support/helpers/test-data.js
Normal file
92
tests/support/helpers/test-data.js
Normal file
|
|
@ -0,0 +1,92 @@
|
||||||
|
const axios = require('axios');
|
||||||
|
|
||||||
|
class TestDataHelper {
|
||||||
|
constructor(baseUrl = 'http://localhost:8000/api') {
|
||||||
|
this.baseUrl = baseUrl;
|
||||||
|
this.testUsers = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a test user via API
|
||||||
|
*/
|
||||||
|
async createTestUser(userData = {}) {
|
||||||
|
const defaultUser = {
|
||||||
|
name: 'Test User',
|
||||||
|
email: `test.${Date.now()}@example.com`,
|
||||||
|
password: 'password123'
|
||||||
|
};
|
||||||
|
|
||||||
|
const user = { ...defaultUser, ...userData };
|
||||||
|
|
||||||
|
try {
|
||||||
|
// First try to register via normal registration endpoint
|
||||||
|
const response = await axios.post(`${this.baseUrl}/register`, {
|
||||||
|
...user,
|
||||||
|
password_confirmation: user.password
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.data.success) {
|
||||||
|
this.testUsers.push(user.email);
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
user: {
|
||||||
|
email: user.email,
|
||||||
|
password: user.password,
|
||||||
|
name: user.name
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// If user already exists, that's okay for login tests
|
||||||
|
if (error.response?.status === 422 &&
|
||||||
|
error.response?.data?.message?.includes('already been taken')) {
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
user: {
|
||||||
|
email: user.email,
|
||||||
|
password: user.password,
|
||||||
|
name: user.name
|
||||||
|
},
|
||||||
|
existing: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
console.error('Failed to create test user:', error.response?.data || error.message);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensure a specific test user exists (for login tests)
|
||||||
|
*/
|
||||||
|
async ensureTestUserExists() {
|
||||||
|
const testUser = {
|
||||||
|
name: 'E2E Test User',
|
||||||
|
email: 'e2e.test@example.com',
|
||||||
|
password: 'TestPass123!'
|
||||||
|
};
|
||||||
|
|
||||||
|
await this.createTestUser(testUser);
|
||||||
|
return testUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clean up test users created during tests
|
||||||
|
*/
|
||||||
|
async cleanup() {
|
||||||
|
// This would need a cleanup endpoint or database access
|
||||||
|
// For now, we'll keep track of created users for manual cleanup
|
||||||
|
console.log('Test users created:', this.testUsers);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run database seeder (requires artisan command access)
|
||||||
|
*/
|
||||||
|
async seedDatabase() {
|
||||||
|
// This would need to run artisan command
|
||||||
|
// Could be done via a special endpoint or SSH
|
||||||
|
console.log('Note: Run "php artisan db:seed --class=TestUserSeeder" to seed test users');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = TestDataHelper;
|
||||||
|
|
@ -42,8 +42,9 @@ class BasePage {
|
||||||
|
|
||||||
async isElementVisible(selector) {
|
async isElementVisible(selector) {
|
||||||
try {
|
try {
|
||||||
const element = await this.driver.findElement(By.css(selector));
|
const elements = await this.driver.findElements(By.css(selector));
|
||||||
return await element.isDisplayed();
|
if (elements.length === 0) return false;
|
||||||
|
return await elements[0].isDisplayed();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -78,7 +78,9 @@ class RegistrationPage extends BasePage {
|
||||||
|
|
||||||
async getSuccessMessage() {
|
async getSuccessMessage() {
|
||||||
try {
|
try {
|
||||||
return await this.getElementText(this.selectors.successMessage);
|
const elements = await this.driver.findElements({ css: this.selectors.successMessage });
|
||||||
|
if (elements.length === 0) return null;
|
||||||
|
return await elements[0].getText();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
Loading…
Reference in a new issue