Add frontend testing - Playwright
This commit is contained in:
145
frontend/tests/dashboard/dashboard-smoke.spec.ts
Normal file
145
frontend/tests/dashboard/dashboard-smoke.spec.ts
Normal file
@@ -0,0 +1,145 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('Dashboard Smoke Tests', () => {
|
||||
// Use authenticated state
|
||||
test.use({ storageState: 'tests/.auth/user.json' });
|
||||
|
||||
test('should load dashboard successfully', async ({ page }) => {
|
||||
await page.goto('/app/dashboard');
|
||||
|
||||
// Verify dashboard loads
|
||||
await expect(page.locator('body')).toContainText(/dashboard|panel de control/i);
|
||||
|
||||
// Should not show any error messages
|
||||
const errorMessages = page.locator('[role="alert"]').filter({ hasText: /error|failed/i });
|
||||
await expect(errorMessages).toHaveCount(0);
|
||||
});
|
||||
|
||||
test('should display key dashboard sections', async ({ page }) => {
|
||||
await page.goto('/app/dashboard');
|
||||
|
||||
// Wait for content to load
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
// Check for common dashboard sections
|
||||
const sections = [
|
||||
/health.*status|estado.*salud/i,
|
||||
/action.*queue|cola.*acciones/i,
|
||||
/production|producción/i,
|
||||
/orders|pedidos/i,
|
||||
];
|
||||
|
||||
// At least some sections should be visible
|
||||
let visibleSections = 0;
|
||||
|
||||
for (const sectionPattern of sections) {
|
||||
const section = page.locator('body').filter({ hasText: sectionPattern });
|
||||
if (await section.isVisible().catch(() => false)) {
|
||||
visibleSections++;
|
||||
}
|
||||
}
|
||||
|
||||
expect(visibleSections).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test('should display unified Add button', async ({ page }) => {
|
||||
await page.goto('/app/dashboard');
|
||||
|
||||
// Look for Add button
|
||||
const addButton = page.getByRole('button', { name: /^add$|^añadir$|^\+$/i });
|
||||
|
||||
if (await addButton.isVisible().catch(() => false)) {
|
||||
await expect(addButton).toBeVisible();
|
||||
}
|
||||
});
|
||||
|
||||
test('should navigate to different sections from dashboard', async ({ page }) => {
|
||||
await page.goto('/app/dashboard');
|
||||
|
||||
// Look for navigation links
|
||||
const navigationLinks = [
|
||||
{ pattern: /operations|operaciones/i, expectedUrl: /operations/ },
|
||||
{ pattern: /analytics|analítica/i, expectedUrl: /analytics/ },
|
||||
{ pattern: /settings|configuración/i, expectedUrl: /settings/ },
|
||||
];
|
||||
|
||||
for (const { pattern, expectedUrl } of navigationLinks) {
|
||||
const link = page.getByRole('link', { name: pattern }).first();
|
||||
|
||||
if (await link.isVisible().catch(() => false)) {
|
||||
await link.click();
|
||||
|
||||
// Verify navigation
|
||||
await expect(page).toHaveURL(expectedUrl, { timeout: 5000 });
|
||||
|
||||
// Go back to dashboard
|
||||
await page.goto('/app/dashboard');
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
break; // Test one navigation link
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('should load data without errors', async ({ page }) => {
|
||||
// Listen for console errors
|
||||
const errors: string[] = [];
|
||||
page.on('console', (msg) => {
|
||||
if (msg.type() === 'error') {
|
||||
errors.push(msg.text());
|
||||
}
|
||||
});
|
||||
|
||||
// Listen for failed network requests
|
||||
const failedRequests: string[] = [];
|
||||
page.on('response', (response) => {
|
||||
if (response.status() >= 400) {
|
||||
failedRequests.push(`${response.status()} ${response.url()}`);
|
||||
}
|
||||
});
|
||||
|
||||
await page.goto('/app/dashboard');
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
// Should not have critical console errors
|
||||
const criticalErrors = errors.filter((err) =>
|
||||
err.toLowerCase().includes('failed') || err.toLowerCase().includes('error')
|
||||
);
|
||||
|
||||
// Allow some non-critical errors but not too many
|
||||
expect(criticalErrors.length).toBeLessThan(5);
|
||||
});
|
||||
|
||||
test('should be responsive on mobile viewport', async ({ page }) => {
|
||||
// Set mobile viewport
|
||||
await page.setViewportSize({ width: 375, height: 667 });
|
||||
|
||||
await page.goto('/app/dashboard');
|
||||
|
||||
// Dashboard should still load
|
||||
await expect(page.locator('body')).toContainText(/dashboard|panel de control/i);
|
||||
|
||||
// Content should be visible (not cut off)
|
||||
const body = page.locator('body');
|
||||
await expect(body).toBeVisible();
|
||||
});
|
||||
|
||||
test('should handle refresh without losing state', async ({ page }) => {
|
||||
await page.goto('/app/dashboard');
|
||||
|
||||
// Wait for initial load
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
// Get some state (e.g., URL)
|
||||
const urlBefore = page.url();
|
||||
|
||||
// Refresh page
|
||||
await page.reload();
|
||||
|
||||
// Should still be on dashboard
|
||||
await expect(page).toHaveURL(urlBefore);
|
||||
|
||||
// Should still show dashboard content
|
||||
await expect(page.locator('body')).toContainText(/dashboard|panel de control/i);
|
||||
});
|
||||
});
|
||||
174
frontend/tests/dashboard/purchase-order.spec.ts
Normal file
174
frontend/tests/dashboard/purchase-order.spec.ts
Normal file
@@ -0,0 +1,174 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { waitForApiCall } from '../helpers/utils';
|
||||
|
||||
test.describe('Purchase Order Management', () => {
|
||||
// Use authenticated state
|
||||
test.use({ storageState: 'tests/.auth/user.json' });
|
||||
|
||||
test('should display action queue with pending purchase orders', async ({ page }) => {
|
||||
await page.goto('/app/dashboard');
|
||||
|
||||
// Wait for page to load
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
// Look for action queue section
|
||||
const actionQueue = page.locator('[data-testid="action-queue"], :has-text("Action Queue"), :has-text("Cola de Acciones")').first();
|
||||
|
||||
// Action queue should exist (even if empty)
|
||||
if (await actionQueue.isVisible().catch(() => false)) {
|
||||
await expect(actionQueue).toBeVisible();
|
||||
}
|
||||
});
|
||||
|
||||
test('should approve a pending purchase order', async ({ page }) => {
|
||||
await page.goto('/app/dashboard');
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
// Look for pending POs
|
||||
const pendingPO = page.locator('[data-testid="pending-po"], [data-testid*="purchase-order"]').first();
|
||||
|
||||
if (await pendingPO.isVisible().catch(() => false)) {
|
||||
// Get the PO details before approval
|
||||
const poText = await pendingPO.textContent();
|
||||
|
||||
// Find and click Approve button
|
||||
const approveButton = pendingPO.getByRole('button', { name: /approve|aprobar/i });
|
||||
|
||||
if (await approveButton.isVisible().catch(() => false)) {
|
||||
await approveButton.click();
|
||||
|
||||
// Should show success message
|
||||
await expect(page.locator('body')).toContainText(/approved|aprobado|success|éxito/i, {
|
||||
timeout: 5000,
|
||||
});
|
||||
|
||||
// PO should be removed from queue or marked as approved
|
||||
await page.waitForTimeout(1000);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('should reject a pending purchase order', async ({ page }) => {
|
||||
await page.goto('/app/dashboard');
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
// Look for pending POs
|
||||
const pendingPO = page.locator('[data-testid="pending-po"], [data-testid*="purchase-order"]').first();
|
||||
|
||||
if (await pendingPO.isVisible().catch(() => false)) {
|
||||
// Find and click Reject button
|
||||
const rejectButton = pendingPO.getByRole('button', { name: /reject|rechazar|decline/i });
|
||||
|
||||
if (await rejectButton.isVisible().catch(() => false)) {
|
||||
await rejectButton.click();
|
||||
|
||||
// Might show confirmation dialog
|
||||
const confirmButton = page.getByRole('button', { name: /confirm|confirmar|yes|sí/i });
|
||||
|
||||
if (await confirmButton.isVisible({ timeout: 2000 }).catch(() => false)) {
|
||||
await confirmButton.click();
|
||||
}
|
||||
|
||||
// Should show success message
|
||||
await expect(page.locator('body')).toContainText(/rejected|rechazado|declined/i, {
|
||||
timeout: 5000,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('should view purchase order details', async ({ page }) => {
|
||||
await page.goto('/app/dashboard');
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
// Look for pending POs
|
||||
const pendingPO = page.locator('[data-testid="pending-po"], [data-testid*="purchase-order"]').first();
|
||||
|
||||
if (await pendingPO.isVisible().catch(() => false)) {
|
||||
// Click on PO to view details
|
||||
await pendingPO.click();
|
||||
|
||||
// Should show PO details (modal or new page)
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
// Look for detail view indicators
|
||||
const detailsVisible = await page.locator('body').textContent();
|
||||
expect(detailsVisible).toMatch(/details|detalles|items|productos|supplier|proveedor/i);
|
||||
}
|
||||
});
|
||||
|
||||
test('should create new purchase order from Add button', async ({ page }) => {
|
||||
await page.goto('/app/dashboard');
|
||||
|
||||
// Click unified Add button
|
||||
const addButton = page.getByRole('button', { name: /^add$|^añadir$|^\+$/i }).first();
|
||||
|
||||
if (await addButton.isVisible().catch(() => false)) {
|
||||
await addButton.click();
|
||||
|
||||
// Wait for wizard/modal
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Look for purchase order option
|
||||
const poOption = page.getByRole('button', { name: /purchase.*order|orden.*compra/i });
|
||||
|
||||
if (await poOption.isVisible().catch(() => false)) {
|
||||
await poOption.click();
|
||||
|
||||
// Should show PO creation form
|
||||
await expect(page.locator('body')).toContainText(/supplier|proveedor|product|producto/i);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('should filter purchase orders by status', async ({ page }) => {
|
||||
await page.goto('/app/operations/procurement');
|
||||
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
// Look for filter options
|
||||
const filterDropdown = page.locator('select, [role="combobox"]').filter({ hasText: /status|estado|filter/i }).first();
|
||||
|
||||
if (await filterDropdown.isVisible().catch(() => false)) {
|
||||
// Select "Pending" filter
|
||||
await filterDropdown.click();
|
||||
|
||||
const pendingOption = page.getByRole('option', { name: /pending|pendiente/i });
|
||||
|
||||
if (await pendingOption.isVisible().catch(() => false)) {
|
||||
await pendingOption.click();
|
||||
|
||||
// Wait for filtered results
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
// Verify only pending POs are shown
|
||||
const poItems = page.locator('[data-testid*="purchase-order"]');
|
||||
const count = await poItems.count();
|
||||
|
||||
// Should have at least filtered the list
|
||||
expect(count).toBeGreaterThanOrEqual(0);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('should search for purchase orders', async ({ page }) => {
|
||||
await page.goto('/app/operations/procurement');
|
||||
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
// Look for search input
|
||||
const searchInput = page.getByPlaceholder(/search|buscar/i);
|
||||
|
||||
if (await searchInput.isVisible().catch(() => false)) {
|
||||
// Enter search term
|
||||
await searchInput.fill('supplier');
|
||||
|
||||
// Wait for search results
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
// Should show filtered results
|
||||
const bodyText = await page.locator('body').textContent();
|
||||
expect(bodyText).toBeTruthy();
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user