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:
|
||||
- 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:
|
||||
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
|
||||
/package-lock.json
|
||||
node_modules/
|
||||
screenshots/
|
||||
*.log
|
||||
.env
|
||||
.env.local
|
||||
coverage/
|
||||
test-results/
|
||||
|
|
|
|||
|
|
@ -70,23 +70,26 @@ npm test screenshot.test.js
|
|||
|
||||
```
|
||||
tests/
|
||||
├── config/
|
||||
│ ├── jest.setup.js # Docker Selenium configuration
|
||||
│ ├── jest.setup.local.js # Local Chrome configuration
|
||||
│ └── test-utils.js # Helper utilities
|
||||
├── e2e/
|
||||
│ ├── auth.test.js # Main authentication test suite
|
||||
│ ├── debug.test.js # Simple connection test
|
||||
│ ├── visual-auth.test.js # Slow visual test for debugging
|
||||
│ └── screenshot.test.js # Takes screenshots for debugging
|
||||
├── fixtures/
|
||||
│ └── users.json # Test user data
|
||||
├── pages/
|
||||
│ ├── BasePage.js # Base page object
|
||||
│ ├── LoginPage.js # Login page object
|
||||
│ ├── RegistrationPage.js # Registration page object
|
||||
│ └── DashboardPage.js # Dashboard page object
|
||||
└── screenshots/ # Screenshot output directory
|
||||
├── specs/
|
||||
│ ├── auth/
|
||||
│ │ └── auth-clean.test.js # Clean authentication tests
|
||||
│ └── integration/
|
||||
│ └── full-auth-flow.test.js # Full user journey test
|
||||
├── support/
|
||||
│ ├── config/
|
||||
│ │ ├── jest.setup.js # Docker Selenium configuration
|
||||
│ │ ├── jest.setup.local.js # Local Chrome configuration
|
||||
│ │ └── test-utils.js # Helper utilities
|
||||
│ ├── pages/
|
||||
│ │ ├── BasePage.js # Base page object
|
||||
│ │ ├── LoginPage.js # Login page object
|
||||
│ │ ├── RegistrationPage.js # Registration page object
|
||||
│ │ └── DashboardPage.js # Dashboard page object
|
||||
│ ├── fixtures/
|
||||
│ │ └── users.json # Test user data
|
||||
│ └── helpers/
|
||||
│ └── test-data.js # Test data management
|
||||
└── screenshots/ # Screenshot output directory
|
||||
```
|
||||
|
||||
## 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",
|
||||
"testMatch": ["**/e2e/**/*.test.js"],
|
||||
"setupFilesAfterEnv": ["<rootDir>/config/jest.setup.local.js"],
|
||||
"testMatch": ["**/specs/**/*.test.js"],
|
||||
"setupFilesAfterEnv": ["<rootDir>/support/config/jest.setup.local.js"],
|
||||
"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",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "jest",
|
||||
"test:headless": "HEADLESS=true jest",
|
||||
"test:watch": "jest --watch",
|
||||
"test:debug": "HEADLESS=false jest --detectOpenHandles",
|
||||
"test:auth": "jest e2e/auth.test.js",
|
||||
"test:debug-simple": "HEADLESS=false jest e2e/debug.test.js --verbose",
|
||||
"test:local": "jest --config jest-local.json",
|
||||
"test:local:debug": "HEADLESS=false jest --config jest-local.json e2e/debug.test.js --verbose"
|
||||
"test": "jest --config jest-local.json",
|
||||
"test:docker": "jest",
|
||||
"test:headless": "HEADLESS=true jest --config jest-local.json",
|
||||
"test:watch": "jest --config jest-local.json --watch",
|
||||
"test:auth": "jest --config jest-local.json specs/auth",
|
||||
"test:integration": "jest --config jest-local.json specs/integration",
|
||||
"test:full-auth": "jest --config jest-local.json specs/integration/full-auth-flow.test.js"
|
||||
},
|
||||
"keywords": ["selenium", "e2e", "testing"],
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"selenium-webdriver": "^4.24.0"
|
||||
"selenium-webdriver": "^4.24.0",
|
||||
"axios": "^1.6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"jest": "^29.7.0",
|
||||
|
|
@ -25,14 +25,14 @@
|
|||
},
|
||||
"jest": {
|
||||
"testEnvironment": "node",
|
||||
"testMatch": ["**/e2e/**/*.test.js"],
|
||||
"setupFilesAfterEnv": ["<rootDir>/config/jest.setup.js"],
|
||||
"testMatch": ["**/specs/**/*.test.js"],
|
||||
"setupFilesAfterEnv": ["<rootDir>/support/config/jest.setup.js"],
|
||||
"testTimeout": 30000
|
||||
},
|
||||
"jest-local": {
|
||||
"testEnvironment": "node",
|
||||
"testMatch": ["**/e2e/**/*.test.js"],
|
||||
"setupFilesAfterEnv": ["<rootDir>/config/jest.setup.local.js"],
|
||||
"testMatch": ["**/specs/**/*.test.js"],
|
||||
"setupFilesAfterEnv": ["<rootDir>/support/config/jest.setup.local.js"],
|
||||
"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();
|
||||
|
||||
// Set implicit wait
|
||||
await driver.manage().setTimeouts({ implicit: 10000 });
|
||||
await driver.manage().setTimeouts({ implicit: 1000 });
|
||||
|
||||
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) {
|
||||
try {
|
||||
const element = await this.driver.findElement(By.css(selector));
|
||||
return await element.isDisplayed();
|
||||
const elements = await this.driver.findElements(By.css(selector));
|
||||
if (elements.length === 0) return false;
|
||||
return await elements[0].isDisplayed();
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -78,7 +78,9 @@ class RegistrationPage extends BasePage {
|
|||
|
||||
async getSuccessMessage() {
|
||||
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) {
|
||||
return null;
|
||||
}
|
||||
Loading…
Reference in a new issue