Working selenium on local
This commit is contained in:
parent
8c68cdfe9f
commit
f1b3c1c8ac
19 changed files with 1479 additions and 0 deletions
|
|
@ -72,6 +72,36 @@ 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:
|
||||
driver: bridge
|
||||
|
|
|
|||
|
|
@ -4,4 +4,8 @@ import react from '@vitejs/plugin-react'
|
|||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
server: {
|
||||
host: '0.0.0.0', // Allow external connections
|
||||
port: 5173
|
||||
}
|
||||
})
|
||||
|
|
|
|||
2
tests/.gitignore
vendored
Normal file
2
tests/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
/node_modules
|
||||
/package-lock.json
|
||||
153
tests/README.md
Normal file
153
tests/README.md
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
# E2E Testing Suite for Trip Planner
|
||||
|
||||
This directory contains end-to-end tests using Selenium WebDriver for the Trip Planner application.
|
||||
|
||||
## Setup
|
||||
|
||||
### Prerequisites
|
||||
|
||||
1. **Docker Setup (Recommended)**
|
||||
- Docker/Podman installed and running
|
||||
- All services running: `podman-compose -f ../docker-compose.dev.yml up -d`
|
||||
|
||||
2. **Local Setup (For visible browser testing)**
|
||||
- Chrome or Chromium browser installed locally
|
||||
- Frontend running on `http://localhost:5173`
|
||||
- Backend running on `http://localhost:8000`
|
||||
|
||||
### Installation
|
||||
|
||||
```bash
|
||||
cd tests
|
||||
npm install
|
||||
```
|
||||
|
||||
## Running Tests
|
||||
|
||||
### Using Docker Selenium (Headless - No visible browser)
|
||||
|
||||
```bash
|
||||
# Run all tests
|
||||
npm test
|
||||
|
||||
# Run all tests in headless mode (explicitly)
|
||||
npm run test:headless
|
||||
|
||||
# Run authentication tests only
|
||||
npm run test:auth
|
||||
|
||||
# Run with verbose output
|
||||
npm run test:debug
|
||||
```
|
||||
|
||||
### Using Local Chrome (Visible browser on your machine)
|
||||
|
||||
```bash
|
||||
# Run all tests with visible browser
|
||||
npm run test:local
|
||||
|
||||
# Run debug test with visible browser
|
||||
npm run test:local:debug
|
||||
|
||||
# Run specific test file locally
|
||||
HEADLESS=false jest --config jest-local.json e2e/visual-auth.test.js
|
||||
```
|
||||
|
||||
### Debugging Tests
|
||||
|
||||
```bash
|
||||
# Simple debug test
|
||||
npm run test:debug-simple
|
||||
|
||||
# Visual authentication test (slow, with pauses)
|
||||
npm run test:local e2e/visual-auth.test.js
|
||||
|
||||
# Screenshot test (saves screenshots to tests/screenshots/)
|
||||
npm test screenshot.test.js
|
||||
```
|
||||
|
||||
## Test Structure
|
||||
|
||||
```
|
||||
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
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
- `HEADLESS=true/false` - Run Chrome in headless mode (Docker only)
|
||||
- `BASE_URL` - Override default frontend URL (default: http://localhost:5173)
|
||||
- `SELENIUM_HUB` - Override Selenium hub URL (default: http://localhost:4444)
|
||||
|
||||
## Common Issues
|
||||
|
||||
### Tests hang with no browser window
|
||||
- You're using Docker Selenium. The browser runs inside a container (invisible)
|
||||
- Use `npm run test:local` to see the browser on your machine
|
||||
|
||||
### Connection refused errors
|
||||
- Ensure all Docker services are running: `podman-compose -f ../docker-compose.dev.yml up -d`
|
||||
- Check frontend is accessible: `curl http://localhost:5173`
|
||||
- Check backend is accessible: `curl http://localhost:8000`
|
||||
|
||||
### Chrome not found (local testing)
|
||||
- Install Chrome: https://www.google.com/chrome/
|
||||
- Or install Chromium: `sudo dnf install chromium` (Fedora) or `sudo apt install chromium-browser` (Ubuntu)
|
||||
|
||||
### Tests can't find auth forms
|
||||
- The app uses a single-page auth guard, not separate /login and /register routes
|
||||
- Forms are toggled on the same page using buttons
|
||||
|
||||
## Writing New Tests
|
||||
|
||||
1. Create test file in `e2e/` directory
|
||||
2. Use page objects from `pages/` for better maintainability
|
||||
3. Add test data to `fixtures/` as needed
|
||||
4. Follow the pattern in existing tests
|
||||
|
||||
Example:
|
||||
```javascript
|
||||
const LoginPage = require('../pages/LoginPage');
|
||||
|
||||
describe('My New Test', () => {
|
||||
let driver;
|
||||
let loginPage;
|
||||
|
||||
beforeAll(async () => {
|
||||
driver = await global.createDriver();
|
||||
loginPage = new LoginPage(driver);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await global.quitDriver(driver);
|
||||
});
|
||||
|
||||
test('should do something', async () => {
|
||||
await loginPage.navigateToLogin();
|
||||
// ... test logic
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## Tips
|
||||
|
||||
- Use `npm run test:local` when debugging to see what's happening
|
||||
- Use `npm test screenshot.test.js` to capture screenshots when tests fail
|
||||
- Add `await driver.sleep(2000)` to slow down tests for debugging
|
||||
- Check Docker logs: `podman logs -f trip-planner-selenium-chrome`
|
||||
63
tests/config/jest.setup.js
Normal file
63
tests/config/jest.setup.js
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
// Jest setup file for Selenium E2E tests
|
||||
const { Builder } = require('selenium-webdriver');
|
||||
const chrome = require('selenium-webdriver/chrome');
|
||||
|
||||
// Global test configuration
|
||||
global.testConfig = {
|
||||
baseUrl: process.env.BASE_URL || 'http://host.docker.internal:5173',
|
||||
seleniumHub: process.env.SELENIUM_HUB || 'http://localhost:4444',
|
||||
timeout: 30000,
|
||||
isHeadless: process.env.HEADLESS === 'true'
|
||||
};
|
||||
|
||||
// Global test utilities
|
||||
global.createDriver = async () => {
|
||||
const chromeOptions = new chrome.Options();
|
||||
|
||||
if (global.testConfig.isHeadless) {
|
||||
chromeOptions.addArguments('--headless');
|
||||
}
|
||||
|
||||
chromeOptions.addArguments('--no-sandbox');
|
||||
chromeOptions.addArguments('--disable-dev-shm-usage');
|
||||
chromeOptions.addArguments('--disable-gpu');
|
||||
chromeOptions.addArguments('--window-size=1920,1080');
|
||||
|
||||
const driver = await new Builder()
|
||||
.forBrowser('chrome')
|
||||
.setChromeOptions(chromeOptions)
|
||||
.usingServer(global.testConfig.seleniumHub)
|
||||
.build();
|
||||
|
||||
// Set implicit wait
|
||||
await driver.manage().setTimeouts({ implicit: 10000 });
|
||||
|
||||
return driver;
|
||||
};
|
||||
|
||||
// Global cleanup function
|
||||
global.quitDriver = async (driver) => {
|
||||
if (driver) {
|
||||
await driver.quit();
|
||||
}
|
||||
};
|
||||
|
||||
// Extend Jest matchers for better assertions
|
||||
expect.extend({
|
||||
async toBeDisplayed(element) {
|
||||
const isDisplayed = await element.isDisplayed();
|
||||
return {
|
||||
message: () => `expected element to ${this.isNot ? 'not ' : ''}be displayed`,
|
||||
pass: isDisplayed
|
||||
};
|
||||
},
|
||||
|
||||
async toHaveText(element, expectedText) {
|
||||
const actualText = await element.getText();
|
||||
const pass = actualText.includes(expectedText);
|
||||
return {
|
||||
message: () => `expected element to contain text "${expectedText}", but got "${actualText}"`,
|
||||
pass
|
||||
};
|
||||
}
|
||||
});
|
||||
63
tests/config/jest.setup.local.js
Normal file
63
tests/config/jest.setup.local.js
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
// Jest setup file for LOCAL Selenium tests (browser runs on host)
|
||||
const { Builder } = require('selenium-webdriver');
|
||||
const chrome = require('selenium-webdriver/chrome');
|
||||
|
||||
// Global test configuration for local testing
|
||||
global.testConfig = {
|
||||
baseUrl: process.env.BASE_URL || 'http://localhost:5173',
|
||||
seleniumHub: null, // Direct connection, no hub
|
||||
timeout: 30000,
|
||||
isHeadless: process.env.HEADLESS === 'true'
|
||||
};
|
||||
|
||||
// Create driver directly without Selenium Grid
|
||||
global.createDriver = async () => {
|
||||
const chromeOptions = new chrome.Options();
|
||||
|
||||
if (global.testConfig.isHeadless) {
|
||||
chromeOptions.addArguments('--headless');
|
||||
}
|
||||
|
||||
chromeOptions.addArguments('--no-sandbox');
|
||||
chromeOptions.addArguments('--disable-dev-shm-usage');
|
||||
chromeOptions.addArguments('--disable-gpu');
|
||||
chromeOptions.addArguments('--window-size=1920,1080');
|
||||
|
||||
// Direct connection to Chrome on host
|
||||
const driver = await new Builder()
|
||||
.forBrowser('chrome')
|
||||
.setChromeOptions(chromeOptions)
|
||||
.build();
|
||||
|
||||
// Set implicit wait
|
||||
await driver.manage().setTimeouts({ implicit: 10000 });
|
||||
|
||||
return driver;
|
||||
};
|
||||
|
||||
// Global cleanup function
|
||||
global.quitDriver = async (driver) => {
|
||||
if (driver) {
|
||||
await driver.quit();
|
||||
}
|
||||
};
|
||||
|
||||
// Extend Jest matchers for better assertions
|
||||
expect.extend({
|
||||
async toBeDisplayed(element) {
|
||||
const isDisplayed = await element.isDisplayed();
|
||||
return {
|
||||
message: () => `expected element to ${this.isNot ? 'not ' : ''}be displayed`,
|
||||
pass: isDisplayed
|
||||
};
|
||||
},
|
||||
|
||||
async toHaveText(element, expectedText) {
|
||||
const actualText = await element.getText();
|
||||
const pass = actualText.includes(expectedText);
|
||||
return {
|
||||
message: () => `expected element to contain text "${expectedText}", but got "${actualText}"`,
|
||||
pass
|
||||
};
|
||||
}
|
||||
});
|
||||
82
tests/config/test-utils.js
Normal file
82
tests/config/test-utils.js
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
const { By, until } = require('selenium-webdriver');
|
||||
|
||||
class TestUtils {
|
||||
constructor(driver) {
|
||||
this.driver = driver;
|
||||
}
|
||||
|
||||
async waitForElement(selector, timeout = 10000) {
|
||||
const element = await this.driver.wait(
|
||||
until.elementLocated(By.css(selector)),
|
||||
timeout,
|
||||
`Element with selector '${selector}' not found within ${timeout}ms`
|
||||
);
|
||||
return element;
|
||||
}
|
||||
|
||||
async waitForElementVisible(selector, timeout = 10000) {
|
||||
const element = await this.waitForElement(selector, timeout);
|
||||
await this.driver.wait(
|
||||
until.elementIsVisible(element),
|
||||
timeout,
|
||||
`Element with selector '${selector}' not visible within ${timeout}ms`
|
||||
);
|
||||
return element;
|
||||
}
|
||||
|
||||
async waitForElementClickable(selector, timeout = 10000) {
|
||||
const element = await this.waitForElementVisible(selector, timeout);
|
||||
await this.driver.wait(
|
||||
until.elementIsEnabled(element),
|
||||
timeout,
|
||||
`Element with selector '${selector}' not clickable within ${timeout}ms`
|
||||
);
|
||||
return element;
|
||||
}
|
||||
|
||||
async waitForText(selector, text, timeout = 10000) {
|
||||
await this.driver.wait(
|
||||
until.elementTextContains(
|
||||
this.driver.findElement(By.css(selector)),
|
||||
text
|
||||
),
|
||||
timeout,
|
||||
`Text '${text}' not found in element '${selector}' within ${timeout}ms`
|
||||
);
|
||||
}
|
||||
|
||||
async clearAndType(element, text) {
|
||||
await element.clear();
|
||||
await element.sendKeys(text);
|
||||
}
|
||||
|
||||
async scrollToElement(element) {
|
||||
await this.driver.executeScript('arguments[0].scrollIntoView(true);', element);
|
||||
// Small delay to ensure scroll completes
|
||||
await this.driver.sleep(500);
|
||||
}
|
||||
|
||||
async takeScreenshot(filename) {
|
||||
const screenshot = await this.driver.takeScreenshot();
|
||||
require('fs').writeFileSync(`screenshots/${filename}`, screenshot, 'base64');
|
||||
}
|
||||
|
||||
async getCurrentUrl() {
|
||||
return await this.driver.getCurrentUrl();
|
||||
}
|
||||
|
||||
async navigateTo(path) {
|
||||
const url = `${global.testConfig.baseUrl}${path}`;
|
||||
await this.driver.get(url);
|
||||
}
|
||||
|
||||
async refreshPage() {
|
||||
await this.driver.navigate().refresh();
|
||||
}
|
||||
|
||||
async goBack() {
|
||||
await this.driver.navigate().back();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = TestUtils;
|
||||
225
tests/e2e/auth.test.js
Normal file
225
tests/e2e/auth.test.js
Normal file
|
|
@ -0,0 +1,225 @@
|
|||
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');
|
||||
});
|
||||
});
|
||||
});
|
||||
34
tests/e2e/debug.test.js
Normal file
34
tests/e2e/debug.test.js
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
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
|
||||
});
|
||||
61
tests/e2e/screenshot.test.js
Normal file
61
tests/e2e/screenshot.test.js
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
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);
|
||||
});
|
||||
77
tests/e2e/simple-auth.test.js
Normal file
77
tests/e2e/simple-auth.test.js
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
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);
|
||||
});
|
||||
136
tests/e2e/visual-auth.test.js
Normal file
136
tests/e2e/visual-auth.test.js
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
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);
|
||||
});
|
||||
58
tests/fixtures/users.json
vendored
Normal file
58
tests/fixtures/users.json
vendored
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
{
|
||||
"validUser": {
|
||||
"name": "Test User",
|
||||
"email": "test@example.com",
|
||||
"password": "password123"
|
||||
},
|
||||
"registrationTestUser": {
|
||||
"name": "Registration Test",
|
||||
"email": "registration.test@example.com",
|
||||
"password": "securepass123"
|
||||
},
|
||||
"invalidUsers": {
|
||||
"invalidEmail": {
|
||||
"name": "Invalid Email User",
|
||||
"email": "invalid-email",
|
||||
"password": "password123"
|
||||
},
|
||||
"shortPassword": {
|
||||
"name": "Short Password User",
|
||||
"email": "shortpass@example.com",
|
||||
"password": "123"
|
||||
},
|
||||
"emptyFields": {
|
||||
"name": "",
|
||||
"email": "",
|
||||
"password": ""
|
||||
},
|
||||
"missingName": {
|
||||
"name": "",
|
||||
"email": "noname@example.com",
|
||||
"password": "password123"
|
||||
},
|
||||
"passwordMismatch": {
|
||||
"name": "Mismatch User",
|
||||
"email": "mismatch@example.com",
|
||||
"password": "password123",
|
||||
"passwordConfirmation": "differentpass"
|
||||
}
|
||||
},
|
||||
"loginTestCases": {
|
||||
"validCredentials": {
|
||||
"email": "test@example.com",
|
||||
"password": "password123"
|
||||
},
|
||||
"invalidCredentials": {
|
||||
"email": "nonexistent@example.com",
|
||||
"password": "wrongpassword"
|
||||
},
|
||||
"emptyCredentials": {
|
||||
"email": "",
|
||||
"password": ""
|
||||
},
|
||||
"invalidEmailFormat": {
|
||||
"email": "invalid-email-format",
|
||||
"password": "password123"
|
||||
}
|
||||
}
|
||||
}
|
||||
6
tests/jest-local.json
Normal file
6
tests/jest-local.json
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"testEnvironment": "node",
|
||||
"testMatch": ["**/e2e/**/*.test.js"],
|
||||
"setupFilesAfterEnv": ["<rootDir>/config/jest.setup.local.js"],
|
||||
"testTimeout": 30000
|
||||
}
|
||||
38
tests/package.json
Normal file
38
tests/package.json
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
{
|
||||
"name": "trip-planner-e2e-tests",
|
||||
"version": "1.0.0",
|
||||
"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"
|
||||
},
|
||||
"keywords": ["selenium", "e2e", "testing"],
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"selenium-webdriver": "^4.24.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"jest": "^29.7.0",
|
||||
"@types/jest": "^29.5.12"
|
||||
},
|
||||
"jest": {
|
||||
"testEnvironment": "node",
|
||||
"testMatch": ["**/e2e/**/*.test.js"],
|
||||
"setupFilesAfterEnv": ["<rootDir>/config/jest.setup.js"],
|
||||
"testTimeout": 30000
|
||||
},
|
||||
"jest-local": {
|
||||
"testEnvironment": "node",
|
||||
"testMatch": ["**/e2e/**/*.test.js"],
|
||||
"setupFilesAfterEnv": ["<rootDir>/config/jest.setup.local.js"],
|
||||
"testTimeout": 30000
|
||||
}
|
||||
}
|
||||
68
tests/pages/BasePage.js
Normal file
68
tests/pages/BasePage.js
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
const { By } = require('selenium-webdriver');
|
||||
const TestUtils = require('../config/test-utils');
|
||||
|
||||
class BasePage {
|
||||
constructor(driver) {
|
||||
this.driver = driver;
|
||||
this.utils = new TestUtils(driver);
|
||||
}
|
||||
|
||||
async navigateTo(path = '/') {
|
||||
await this.utils.navigateTo(path);
|
||||
await this.waitForPageLoad();
|
||||
}
|
||||
|
||||
async waitForPageLoad() {
|
||||
// Wait for React to mount
|
||||
await this.driver.wait(
|
||||
() => this.driver.executeScript('return document.readyState === "complete"'),
|
||||
10000
|
||||
);
|
||||
|
||||
// Additional wait for React components to render
|
||||
await this.driver.sleep(1000);
|
||||
}
|
||||
|
||||
async getCurrentUrl() {
|
||||
return await this.utils.getCurrentUrl();
|
||||
}
|
||||
|
||||
async getPageTitle() {
|
||||
return await this.driver.getTitle();
|
||||
}
|
||||
|
||||
async isElementPresent(selector) {
|
||||
try {
|
||||
await this.driver.findElement(By.css(selector));
|
||||
return true;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async isElementVisible(selector) {
|
||||
try {
|
||||
const element = await this.driver.findElement(By.css(selector));
|
||||
return await element.isDisplayed();
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async getElementText(selector) {
|
||||
const element = await this.utils.waitForElement(selector);
|
||||
return await element.getText();
|
||||
}
|
||||
|
||||
async clickElement(selector) {
|
||||
const element = await this.utils.waitForElementClickable(selector);
|
||||
await element.click();
|
||||
}
|
||||
|
||||
async typeIntoElement(selector, text) {
|
||||
const element = await this.utils.waitForElement(selector);
|
||||
await this.utils.clearAndType(element, text);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = BasePage;
|
||||
64
tests/pages/DashboardPage.js
Normal file
64
tests/pages/DashboardPage.js
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
const BasePage = require('./BasePage');
|
||||
|
||||
class DashboardPage extends BasePage {
|
||||
constructor(driver) {
|
||||
super(driver);
|
||||
|
||||
// Selectors for the Dashboard component
|
||||
this.selectors = {
|
||||
dashboard: '.dashboard',
|
||||
welcomeMessage: '.dashboard h2',
|
||||
userInfo: '.user-info',
|
||||
logoutButton: 'button[onclick*="logout"]'
|
||||
};
|
||||
}
|
||||
|
||||
async navigateToDashboard() {
|
||||
await this.navigateTo('/dashboard');
|
||||
await this.waitForDashboard();
|
||||
}
|
||||
|
||||
async waitForDashboard() {
|
||||
await this.utils.waitForElementVisible(this.selectors.dashboard);
|
||||
}
|
||||
|
||||
async isDashboardDisplayed() {
|
||||
return await this.isElementVisible(this.selectors.dashboard);
|
||||
}
|
||||
|
||||
async getWelcomeMessage() {
|
||||
try {
|
||||
return await this.getElementText(this.selectors.welcomeMessage);
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async getUserInfo() {
|
||||
try {
|
||||
return await this.getElementText(this.selectors.userInfo);
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async logout() {
|
||||
if (await this.isElementVisible(this.selectors.logoutButton)) {
|
||||
await this.clickElement(this.selectors.logoutButton);
|
||||
}
|
||||
}
|
||||
|
||||
async waitForLogout() {
|
||||
// Wait for redirect away from dashboard
|
||||
await this.driver.wait(
|
||||
async () => {
|
||||
const currentUrl = await this.getCurrentUrl();
|
||||
return !currentUrl.includes('/dashboard');
|
||||
},
|
||||
10000,
|
||||
'Logout did not redirect within expected time'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = DashboardPage;
|
||||
140
tests/pages/LoginPage.js
Normal file
140
tests/pages/LoginPage.js
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
const BasePage = require('./BasePage');
|
||||
|
||||
class LoginPage extends BasePage {
|
||||
constructor(driver) {
|
||||
super(driver);
|
||||
|
||||
// Selectors based on the LoginForm component
|
||||
this.selectors = {
|
||||
form: '.login-form',
|
||||
heading: '.login-form h2',
|
||||
emailInput: '#email',
|
||||
passwordInput: '#password',
|
||||
submitButton: 'button[type="submit"]',
|
||||
errorMessage: '.error-message',
|
||||
generalError: '.alert-error',
|
||||
fieldError: '.error-message'
|
||||
};
|
||||
}
|
||||
|
||||
async navigateToLogin() {
|
||||
await this.navigateTo('/');
|
||||
// Click the Login tab if not already active
|
||||
const loginButton = await this.driver.findElement({ css: '.auth-toggle button:first-child' });
|
||||
await loginButton.click();
|
||||
await this.waitForLoginForm();
|
||||
}
|
||||
|
||||
async waitForLoginForm() {
|
||||
await this.utils.waitForElementVisible(this.selectors.form);
|
||||
}
|
||||
|
||||
async isLoginFormDisplayed() {
|
||||
return await this.isElementVisible(this.selectors.form);
|
||||
}
|
||||
|
||||
async getLoginHeading() {
|
||||
return await this.getElementText(this.selectors.heading);
|
||||
}
|
||||
|
||||
async enterEmail(email) {
|
||||
await this.typeIntoElement(this.selectors.emailInput, email);
|
||||
}
|
||||
|
||||
async enterPassword(password) {
|
||||
await this.typeIntoElement(this.selectors.passwordInput, password);
|
||||
}
|
||||
|
||||
async clickSubmit() {
|
||||
await this.clickElement(this.selectors.submitButton);
|
||||
}
|
||||
|
||||
async getSubmitButtonText() {
|
||||
return await this.getElementText(this.selectors.submitButton);
|
||||
}
|
||||
|
||||
async isSubmitButtonDisabled() {
|
||||
const button = await this.utils.waitForElement(this.selectors.submitButton);
|
||||
return await button.getAttribute('disabled') !== null;
|
||||
}
|
||||
|
||||
async login(email, password) {
|
||||
await this.enterEmail(email);
|
||||
await this.enterPassword(password);
|
||||
await this.clickSubmit();
|
||||
}
|
||||
|
||||
async getGeneralErrorMessage() {
|
||||
try {
|
||||
return await this.getElementText(this.selectors.generalError);
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async getEmailErrorMessage() {
|
||||
try {
|
||||
const emailField = await this.utils.waitForElement(this.selectors.emailInput);
|
||||
const parent = await emailField.findElement({ xpath: '..' });
|
||||
const errorElement = await parent.findElement({ css: this.selectors.fieldError });
|
||||
return await errorElement.getText();
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async getPasswordErrorMessage() {
|
||||
try {
|
||||
const passwordField = await this.utils.waitForElement(this.selectors.passwordInput);
|
||||
const parent = await passwordField.findElement({ xpath: '..' });
|
||||
const errorElement = await parent.findElement({ css: this.selectors.fieldError });
|
||||
return await errorElement.getText();
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async hasEmailFieldError() {
|
||||
const emailField = await this.utils.waitForElement(this.selectors.emailInput);
|
||||
const className = await emailField.getAttribute('class');
|
||||
return className.includes('error');
|
||||
}
|
||||
|
||||
async hasPasswordFieldError() {
|
||||
const passwordField = await this.utils.waitForElement(this.selectors.passwordInput);
|
||||
const className = await passwordField.getAttribute('class');
|
||||
return className.includes('error');
|
||||
}
|
||||
|
||||
async waitForSuccessfulLogin() {
|
||||
// Wait for dashboard to appear (auth guard passes)
|
||||
await this.driver.wait(
|
||||
async () => {
|
||||
try {
|
||||
const dashboard = await this.driver.findElement({ css: '.dashboard' });
|
||||
return await dashboard.isDisplayed();
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
10000,
|
||||
'Login did not show dashboard within expected time'
|
||||
);
|
||||
}
|
||||
|
||||
async waitForLoginError() {
|
||||
// Wait for either general error or field errors to appear
|
||||
await this.driver.wait(
|
||||
async () => {
|
||||
const hasGeneralError = await this.isElementVisible(this.selectors.generalError);
|
||||
const hasEmailError = await this.hasEmailFieldError();
|
||||
const hasPasswordError = await this.hasPasswordFieldError();
|
||||
return hasGeneralError || hasEmailError || hasPasswordError;
|
||||
},
|
||||
10000,
|
||||
'No error message appeared within expected time'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = LoginPage;
|
||||
175
tests/pages/RegistrationPage.js
Normal file
175
tests/pages/RegistrationPage.js
Normal file
|
|
@ -0,0 +1,175 @@
|
|||
const BasePage = require('./BasePage');
|
||||
|
||||
class RegistrationPage extends BasePage {
|
||||
constructor(driver) {
|
||||
super(driver);
|
||||
|
||||
// Selectors based on the RegistrationForm component
|
||||
this.selectors = {
|
||||
form: '.registration-form',
|
||||
heading: '.registration-form h2',
|
||||
nameInput: '#name',
|
||||
emailInput: '#email',
|
||||
passwordInput: '#password',
|
||||
passwordConfirmationInput: '#password_confirmation',
|
||||
submitButton: 'button[type="submit"]',
|
||||
successMessage: '.alert-success',
|
||||
generalError: '.alert-error',
|
||||
fieldError: '.error-message'
|
||||
};
|
||||
}
|
||||
|
||||
async navigateToRegistration() {
|
||||
await this.navigateTo('/');
|
||||
// Click the Register tab
|
||||
const registerButton = await this.driver.findElement({ css: '.auth-toggle button:last-child' });
|
||||
await registerButton.click();
|
||||
await this.waitForRegistrationForm();
|
||||
}
|
||||
|
||||
async waitForRegistrationForm() {
|
||||
await this.utils.waitForElementVisible(this.selectors.form);
|
||||
}
|
||||
|
||||
async isRegistrationFormDisplayed() {
|
||||
return await this.isElementVisible(this.selectors.form);
|
||||
}
|
||||
|
||||
async getRegistrationHeading() {
|
||||
return await this.getElementText(this.selectors.heading);
|
||||
}
|
||||
|
||||
async enterName(name) {
|
||||
await this.typeIntoElement(this.selectors.nameInput, name);
|
||||
}
|
||||
|
||||
async enterEmail(email) {
|
||||
await this.typeIntoElement(this.selectors.emailInput, email);
|
||||
}
|
||||
|
||||
async enterPassword(password) {
|
||||
await this.typeIntoElement(this.selectors.passwordInput, password);
|
||||
}
|
||||
|
||||
async enterPasswordConfirmation(password) {
|
||||
await this.typeIntoElement(this.selectors.passwordConfirmationInput, password);
|
||||
}
|
||||
|
||||
async clickSubmit() {
|
||||
await this.clickElement(this.selectors.submitButton);
|
||||
}
|
||||
|
||||
async getSubmitButtonText() {
|
||||
return await this.getElementText(this.selectors.submitButton);
|
||||
}
|
||||
|
||||
async isSubmitButtonDisabled() {
|
||||
const button = await this.utils.waitForElement(this.selectors.submitButton);
|
||||
return await button.getAttribute('disabled') !== null;
|
||||
}
|
||||
|
||||
async register(name, email, password, passwordConfirmation = null) {
|
||||
await this.enterName(name);
|
||||
await this.enterEmail(email);
|
||||
await this.enterPassword(password);
|
||||
await this.enterPasswordConfirmation(passwordConfirmation || password);
|
||||
await this.clickSubmit();
|
||||
}
|
||||
|
||||
async getSuccessMessage() {
|
||||
try {
|
||||
return await this.getElementText(this.selectors.successMessage);
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async getGeneralErrorMessage() {
|
||||
try {
|
||||
return await this.getElementText(this.selectors.generalError);
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async getFieldErrorMessage(fieldSelector) {
|
||||
try {
|
||||
const field = await this.utils.waitForElement(fieldSelector);
|
||||
const parent = await field.findElement({ xpath: '..' });
|
||||
const errorElement = await parent.findElement({ css: this.selectors.fieldError });
|
||||
return await errorElement.getText();
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async getNameErrorMessage() {
|
||||
return await this.getFieldErrorMessage(this.selectors.nameInput);
|
||||
}
|
||||
|
||||
async getEmailErrorMessage() {
|
||||
return await this.getFieldErrorMessage(this.selectors.emailInput);
|
||||
}
|
||||
|
||||
async getPasswordErrorMessage() {
|
||||
return await this.getFieldErrorMessage(this.selectors.passwordInput);
|
||||
}
|
||||
|
||||
async getPasswordConfirmationErrorMessage() {
|
||||
return await this.getFieldErrorMessage(this.selectors.passwordConfirmationInput);
|
||||
}
|
||||
|
||||
async hasFieldError(fieldSelector) {
|
||||
const field = await this.utils.waitForElement(fieldSelector);
|
||||
const className = await field.getAttribute('class');
|
||||
return className.includes('error');
|
||||
}
|
||||
|
||||
async hasNameFieldError() {
|
||||
return await this.hasFieldError(this.selectors.nameInput);
|
||||
}
|
||||
|
||||
async hasEmailFieldError() {
|
||||
return await this.hasFieldError(this.selectors.emailInput);
|
||||
}
|
||||
|
||||
async hasPasswordFieldError() {
|
||||
return await this.hasFieldError(this.selectors.passwordInput);
|
||||
}
|
||||
|
||||
async hasPasswordConfirmationFieldError() {
|
||||
return await this.hasFieldError(this.selectors.passwordConfirmationInput);
|
||||
}
|
||||
|
||||
async waitForSuccessfulRegistration() {
|
||||
await this.utils.waitForElementVisible(this.selectors.successMessage);
|
||||
}
|
||||
|
||||
async waitForRegistrationError() {
|
||||
// Wait for either general error or field errors to appear
|
||||
await this.driver.wait(
|
||||
async () => {
|
||||
const hasGeneralError = await this.isElementVisible(this.selectors.generalError);
|
||||
const hasNameError = await this.hasNameFieldError();
|
||||
const hasEmailError = await this.hasEmailFieldError();
|
||||
const hasPasswordError = await this.hasPasswordFieldError();
|
||||
const hasPasswordConfirmationError = await this.hasPasswordConfirmationFieldError();
|
||||
|
||||
return hasGeneralError || hasNameError || hasEmailError || hasPasswordError || hasPasswordConfirmationError;
|
||||
},
|
||||
10000,
|
||||
'No error message appeared within expected time'
|
||||
);
|
||||
}
|
||||
|
||||
async isFormCleared() {
|
||||
const nameValue = await (await this.utils.waitForElement(this.selectors.nameInput)).getAttribute('value');
|
||||
const emailValue = await (await this.utils.waitForElement(this.selectors.emailInput)).getAttribute('value');
|
||||
const passwordValue = await (await this.utils.waitForElement(this.selectors.passwordInput)).getAttribute('value');
|
||||
const passwordConfirmationValue = await (await this.utils.waitForElement(this.selectors.passwordConfirmationInput)).getAttribute('value');
|
||||
|
||||
return nameValue === '' && emailValue === '' && passwordValue === '' && passwordConfirmationValue === '';
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = RegistrationPage;
|
||||
Loading…
Reference in a new issue