feat: add role-editor, project-editor, milestone tests
This commit is contained in:
101
tests/milestone.spec.ts
Normal file
101
tests/milestone.spec.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
const BASE_URL = process.env.BASE_URL || process.env.FRONTEND_URL || 'http://frontend:3000';
|
||||
|
||||
const ADMIN_USERNAME = 'admin';
|
||||
const ADMIN_PASSWORD = 'admin123';
|
||||
|
||||
test.describe('Milestone Editor', () => {
|
||||
test('login -> create project -> create milestone through frontend form', async ({ page }) => {
|
||||
// Step 1: Login
|
||||
console.log('🔐 Logging in...');
|
||||
await page.goto(`${BASE_URL}/login`);
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
await page.fill('input[type="text"], input[name="username"]', ADMIN_USERNAME);
|
||||
await page.fill('input[type="password"], input[name="password"]', ADMIN_PASSWORD);
|
||||
await page.click('button[type="submit"], button:has-text("Sign in")');
|
||||
|
||||
// Wait for navigation
|
||||
await page.waitForURL(`${BASE_URL}/**`, { timeout: 10000 });
|
||||
await page.waitForLoadState('networkidle');
|
||||
console.log('✅ Logged in');
|
||||
|
||||
// Step 2: Create Project
|
||||
console.log('📁 Creating project...');
|
||||
await page.click('a:has-text("📁 Projects")');
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
await page.waitForSelector('button:has-text("+ New")', { timeout: 10000 });
|
||||
await page.click('button:has-text("+ New")');
|
||||
await page.waitForSelector('input[placeholder="Project name"]', { timeout: 10000 });
|
||||
|
||||
const projectName = 'Test Project ' + Date.now();
|
||||
console.log('Creating project with name:', projectName);
|
||||
await page.fill('input[placeholder="Project name"]', projectName);
|
||||
await page.fill('input[placeholder="Description (optional)"]', 'Project for milestone testing');
|
||||
|
||||
await page.click('button:has-text("Create")');
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
await page.waitForSelector('.project-card', { timeout: 10000 });
|
||||
console.log('✅ Project created');
|
||||
|
||||
// Step 3: Click on project to open it
|
||||
console.log('👆 Opening project...');
|
||||
await page.click('.project-card');
|
||||
await page.waitForLoadState('networkidle');
|
||||
await page.waitForTimeout(1500);
|
||||
console.log('✅ Project opened');
|
||||
|
||||
// Step 4: Create milestone through frontend
|
||||
console.log('🎯 Creating milestone...');
|
||||
|
||||
// Wait for page to load
|
||||
await page.waitForLoadState('networkidle');
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
// Scroll to milestone section
|
||||
await page.evaluate(() => window.scrollTo(0, 500));
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Click the "+ New" button for milestones
|
||||
await page.click('button:has-text("+ New")');
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
// Wait for modal
|
||||
await page.waitForSelector('.modal', { timeout: 5000 });
|
||||
console.log('Modal opened');
|
||||
|
||||
// Fill in the milestone title
|
||||
const milestoneName = 'Test Milestone ' + Date.now();
|
||||
console.log('Milestone name:', milestoneName);
|
||||
await page.fill('input[placeholder="Milestone title"]', milestoneName);
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Click the Create button inside the modal (the one with class btn-primary)
|
||||
await page.locator('.modal button.btn-primary').click();
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
// Wait for modal to close
|
||||
await page.waitForSelector('.modal', { state: 'detached', timeout: 5000 }).catch(() => {});
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
// Step 5: Verify milestone was created
|
||||
console.log('🔍 Verifying milestone...');
|
||||
|
||||
// Get the heading text
|
||||
const headingText = await page.locator('h3').filter({ hasText: /Milestones/i }).textContent();
|
||||
console.log('Heading text:', headingText);
|
||||
|
||||
// Check if milestone count > 0
|
||||
const hasMilestone = headingText && /\(1?\d+\)/.test(headingText) && !headingText.includes('(0)');
|
||||
console.log('Has milestone:', hasMilestone);
|
||||
|
||||
// Verify milestone exists in UI
|
||||
expect(hasMilestone).toBe(true);
|
||||
console.log('✅ Milestone verified in UI');
|
||||
|
||||
console.log('🎉 All milestone tests passed!');
|
||||
});
|
||||
});
|
||||
133
tests/project-editor.spec.ts
Normal file
133
tests/project-editor.spec.ts
Normal file
@@ -0,0 +1,133 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
const BASE_URL = process.env.BASE_URL || process.env.FRONTEND_URL || 'http://frontend:3000';
|
||||
|
||||
// Test credentials from globalSetup
|
||||
const ADMIN_USERNAME = 'admin';
|
||||
const ADMIN_PASSWORD = 'admin123';
|
||||
|
||||
test.describe('Project Editor', () => {
|
||||
test('login -> create project -> delete project -> logout', async ({ page }) => {
|
||||
// Step 1: Login
|
||||
console.log('🔐 Logging in...');
|
||||
await page.goto(`${BASE_URL}/login`);
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
await page.fill('input[type="text"], input[name="username"]', ADMIN_USERNAME);
|
||||
await page.fill('input[type="password"], input[name="password"]', ADMIN_PASSWORD);
|
||||
|
||||
// Capture login response
|
||||
const loginPromise = page.waitForResponse(response =>
|
||||
response.url().includes('/auth/token') && response.request().method() === 'POST'
|
||||
, { timeout: 10000 }).catch(() => null);
|
||||
|
||||
await page.click('button[type="submit"], button:has-text("Sign in")');
|
||||
|
||||
const loginResponse = await loginPromise;
|
||||
if (loginResponse) {
|
||||
console.log('Login response status:', loginResponse.status());
|
||||
console.log('Login response:', (await loginResponse.text()).substring(0, 200));
|
||||
} else {
|
||||
console.log('No /auth/token response');
|
||||
}
|
||||
|
||||
// Wait for navigation
|
||||
await page.waitForURL(`${BASE_URL}/**`, { timeout: 10000 });
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
// Debug: check localStorage
|
||||
const token = await page.evaluate(() => localStorage.getItem('token'));
|
||||
console.log('Token after login:', token ? 'present' : 'missing');
|
||||
|
||||
// Debug: check current URL
|
||||
console.log('Current URL:', page.url());
|
||||
|
||||
console.log('✅ Logged in');
|
||||
|
||||
// Step 2: Create Project
|
||||
console.log('📁 Creating project...');
|
||||
// Click Projects in sidebar
|
||||
await page.click('a:has-text("📁 Projects")');
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
// Debug: log all buttons on page
|
||||
const buttons = await page.locator('button').allTextContents();
|
||||
console.log('Buttons on page:', buttons);
|
||||
|
||||
// Click create project button - it's "+ New"
|
||||
await page.waitForSelector('button:has-text("+ New")', { timeout: 10000 });
|
||||
await page.click('button:has-text("+ New")');
|
||||
// Wait for the form to appear (look for the project name input)
|
||||
await page.waitForSelector('input[placeholder="Project name"]', { timeout: 10000 });
|
||||
|
||||
// Fill project form
|
||||
const projectName = 'Test Project ' + Date.now();
|
||||
console.log('Creating project with name:', projectName);
|
||||
await page.fill('input[placeholder="Project name"]', projectName);
|
||||
await page.fill('input[placeholder="Description (optional)"]', 'Project for E2E testing');
|
||||
|
||||
// Submit project form
|
||||
await page.click('button:has-text("Create")');
|
||||
|
||||
// Wait a bit for submission
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
// Check if there are any errors in console
|
||||
const errors = await page.evaluate(() => {
|
||||
return window.__errors || [];
|
||||
});
|
||||
if (errors.length > 0) {
|
||||
console.log('Console errors:', errors);
|
||||
}
|
||||
|
||||
// Wait for project to be created (and appear in the grid) - check for any project card
|
||||
await page.waitForSelector('.project-card', { timeout: 10000 });
|
||||
console.log('✅ Project created');
|
||||
|
||||
// Step 3: Delete the created project
|
||||
console.log('🗑️ Deleting project...');
|
||||
|
||||
// Click on the project card to open it
|
||||
await page.click('.project-card');
|
||||
await page.waitForLoadState('networkidle');
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
// Look for delete button
|
||||
const deleteBtn = page.locator('button:has-text("Delete")');
|
||||
const deleteBtnVisible = await deleteBtn.isVisible().catch(() => false);
|
||||
console.log('Delete button visible:', deleteBtnVisible);
|
||||
|
||||
if (deleteBtnVisible) {
|
||||
// Set up prompt handler for the confirmation dialog - use dialog.defaultValue() to get the project name pattern
|
||||
page.on('dialog', async dialog => {
|
||||
console.log('Dialog message:', dialog.message());
|
||||
// Extract project name from the message - format: "Type the project name "XXX" to confirm deletion:"
|
||||
const match = dialog.message().match(/Type the project name "(.*)" to confirm/);
|
||||
if (match) {
|
||||
await dialog.accept(match[1]);
|
||||
} else {
|
||||
await dialog.accept();
|
||||
}
|
||||
});
|
||||
|
||||
await deleteBtn.click();
|
||||
await page.waitForTimeout(2000);
|
||||
console.log('✅ Project deleted');
|
||||
} else {
|
||||
console.log('Delete button not found');
|
||||
}
|
||||
|
||||
// Step 4: Logout
|
||||
console.log('🚪 Logging out...');
|
||||
|
||||
// Click logout button in sidebar
|
||||
await page.click('button:has-text("Log out")');
|
||||
|
||||
// Wait for token to be cleared
|
||||
await page.waitForFunction(() => !localStorage.getItem('token'), { timeout: 10000 });
|
||||
console.log('✅ Logged out');
|
||||
|
||||
// Verify we're logged out by checking for login button
|
||||
await page.waitForSelector('button:has-text("Log in")', { timeout: 10000 });
|
||||
});
|
||||
});
|
||||
168
tests/role-editor.spec.ts
Normal file
168
tests/role-editor.spec.ts
Normal file
@@ -0,0 +1,168 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
const BASE_URL = process.env.BASE_URL || process.env.FRONTEND_URL || 'http://frontend:3000';
|
||||
|
||||
const ADMIN_USERNAME = 'admin';
|
||||
const ADMIN_PASSWORD = 'admin123';
|
||||
|
||||
test.describe('Role Editor', () => {
|
||||
test('admin cannot edit admin role, can edit guest role, can create and delete temp-tester role', async ({ page }) => {
|
||||
// Step 1: Login
|
||||
console.log('🔐 Logging in...');
|
||||
await page.goto(`${BASE_URL}/login`);
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
await page.fill('input[type="text"], input[name="username"]', ADMIN_USERNAME);
|
||||
await page.fill('input[type="password"], input[name="password"]', ADMIN_PASSWORD);
|
||||
await page.click('button[type="submit"], button:has-text("Sign in")');
|
||||
|
||||
// Wait for navigation
|
||||
await page.waitForURL(`${BASE_URL}/**`, { timeout: 10000 });
|
||||
await page.waitForLoadState('networkidle');
|
||||
console.log('✅ Logged in');
|
||||
|
||||
// Step 2: Navigate to Roles
|
||||
console.log('🔐 Navigating to Roles...');
|
||||
await page.click('a:has-text("🔐 Roles")');
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
// Wait for role list to load
|
||||
await page.waitForSelector('.role-editor-page', { timeout: 10000 });
|
||||
|
||||
// Step 3: Check admin and guest roles exist
|
||||
console.log('👀 Checking admin and guest roles exist...');
|
||||
const roleElements = await page.locator('.role-editor-page div[style*="border"] strong').allTextContents();
|
||||
console.log('Roles found:', roleElements);
|
||||
|
||||
const hasAdmin = roleElements.some(r => r.toLowerCase().includes('admin'));
|
||||
const hasGuest = roleElements.some(r => r.toLowerCase().includes('guest'));
|
||||
expect(hasAdmin).toBe(true);
|
||||
expect(hasGuest).toBe(true);
|
||||
console.log('✅ admin and guest roles exist');
|
||||
|
||||
// Step 4: Click on guest role first (simpler test)
|
||||
console.log('🔧 Clicking guest role...');
|
||||
await page.click('.role-editor-page >> text=guest');
|
||||
await page.waitForTimeout(1500);
|
||||
|
||||
// Wait for permission checkboxes to load
|
||||
await page.waitForSelector('input[type="checkbox"]', { timeout: 5000 });
|
||||
console.log('Permission checkboxes loaded');
|
||||
|
||||
// Try to save guest role (without changes first to verify save works)
|
||||
console.log('💾 Trying to save guest role...');
|
||||
const saveBtn1 = page.locator('button:has-text("Save Changes")');
|
||||
await saveBtn1.scrollIntoViewIfNeeded();
|
||||
await saveBtn1.click({ force: true });
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
// Check for success message
|
||||
const successMsg1 = await page.locator('text=Saved successfully').first().isVisible().catch(() => false);
|
||||
console.log('Success message shown:', successMsg1);
|
||||
expect(successMsg1).toBe(true);
|
||||
console.log('✅ Can modify guest role verified');
|
||||
|
||||
// Step 5: Navigate to Roles again
|
||||
console.log('🔐 Navigating to Roles again...');
|
||||
await page.click('a:has-text("🔐 Roles")');
|
||||
await page.waitForLoadState('networkidle');
|
||||
await page.waitForSelector('.role-editor-page', { timeout: 10000 });
|
||||
|
||||
// Step 9: Check if temp-tester role exists, if not create it
|
||||
console.log('🔍 Checking for temp-tester role...');
|
||||
const allRoles = await page.locator('.role-editor-page >> text=temp-tester').count();
|
||||
|
||||
if (allRoles > 0) {
|
||||
console.log('✅ temp-tester role already exists');
|
||||
} else {
|
||||
console.log('➕ Creating temp-tester role...');
|
||||
await page.click('button:has-text("+ Create New Role")');
|
||||
await page.waitForSelector('input[placeholder="e.g., developer, manager"]', { timeout: 5000 });
|
||||
|
||||
await page.fill('input[placeholder="e.g., developer, manager"]', 'temp-tester');
|
||||
await page.fill('input[placeholder="Role description"]', 'Temporary tester role');
|
||||
await page.click('button:has-text("Create")');
|
||||
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
// Verify role was created
|
||||
const newRolesCount = await page.locator('.role-editor-page >> text=temp-tester').count();
|
||||
expect(newRolesCount).toBeGreaterThan(0);
|
||||
console.log('✅ temp-tester role created');
|
||||
}
|
||||
|
||||
// Step 10: Navigate to Roles and verify temp-tester exists
|
||||
console.log('🔐 Final verification - navigating to Roles...');
|
||||
await page.click('a:has-text("🔐 Roles")');
|
||||
await page.waitForLoadState('networkidle');
|
||||
await page.waitForSelector('.role-editor-page', { timeout: 10000 });
|
||||
|
||||
const finalRolesCount = await page.locator('.role-editor-page >> text=temp-tester').count();
|
||||
expect(finalRolesCount).toBeGreaterThan(0);
|
||||
console.log('✅ temp-tester role exists verified');
|
||||
|
||||
// Step 11: Delete the temp-tester role and verify
|
||||
console.log('🗑️ Deleting temp-tester role...');
|
||||
|
||||
// Navigate to Roles page fresh
|
||||
await page.goto(`${BASE_URL}/roles`);
|
||||
await page.waitForLoadState('networkidle');
|
||||
await page.waitForTimeout(1500);
|
||||
|
||||
// Click on temp-tester role to select it
|
||||
await page.click('.role-editor-page >> text=temp-tester');
|
||||
await page.waitForTimeout(1500);
|
||||
|
||||
// Wait for permission checkboxes to load
|
||||
await page.waitForSelector('input[type="checkbox"]', { timeout: 5000 }).catch(() => {});
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Scroll to make delete button visible
|
||||
await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight));
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Check if delete button is visible
|
||||
const deleteBtn = page.locator('button:has-text("Delete Role")');
|
||||
const deleteBtnVisible = await deleteBtn.isVisible().catch(() => false);
|
||||
console.log('Delete button visible:', deleteBtnVisible);
|
||||
|
||||
if (deleteBtnVisible) {
|
||||
// Set up dialog handler BEFORE clicking delete
|
||||
page.on('dialog', async dialog => {
|
||||
console.log('Dialog message:', dialog.message());
|
||||
await dialog.accept();
|
||||
});
|
||||
|
||||
// Click delete button
|
||||
await deleteBtn.click();
|
||||
|
||||
// Wait for the delete to complete
|
||||
await page.waitForTimeout(3000);
|
||||
} else {
|
||||
console.log('Delete button not visible - role might be protected');
|
||||
}
|
||||
|
||||
// Navigate away and back to refresh
|
||||
await page.goto(`${BASE_URL}/projects`);
|
||||
await page.waitForTimeout(1000);
|
||||
await page.goto(`${BASE_URL}/roles`);
|
||||
await page.waitForLoadState('networkidle');
|
||||
await page.waitForTimeout(1500);
|
||||
|
||||
// Verify temp-tester is no longer in the list
|
||||
const roleCards = await page.locator('.role-editor-page div[style*="border"]').all();
|
||||
let tempTesterFound = false;
|
||||
for (const card of roleCards) {
|
||||
const cardText = await card.textContent();
|
||||
if (cardText && cardText.toLowerCase().includes('temp-tester')) {
|
||||
tempTesterFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
console.log('temp-tester found after delete:', tempTesterFound);
|
||||
expect(tempTesterFound).toBe(false);
|
||||
console.log('✅ temp-tester role deleted and verified');
|
||||
|
||||
console.log('🎉 All role editor tests passed!');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user