feat: Goa GEL Blockchain e-Licensing Platform - Full Stack Implementation
Complete implementation of the Goa Government e-Licensing platform with: Backend: - NestJS API with JWT authentication - PostgreSQL database with Knex ORM - Redis caching and session management - MinIO document storage - Hyperledger Besu blockchain integration - Multi-department workflow system - Comprehensive API tests (266/282 passing) Frontend: - Angular 21 with standalone components - Angular Material + TailwindCSS UI - Visual workflow builder - Document upload with progress tracking - Blockchain explorer integration - Role-based dashboards (Admin, Department, Citizen) - E2E tests with Playwright (37 tests) Infrastructure: - Docker Compose orchestration - Blockscout blockchain explorer - Development and production configurations
This commit is contained in:
194
frontend/e2e/dashboard.spec.ts
Normal file
194
frontend/e2e/dashboard.spec.ts
Normal file
@@ -0,0 +1,194 @@
|
||||
import { test, expect, Page } from '@playwright/test';
|
||||
|
||||
// Helper functions
|
||||
async function loginAsCitizen(page: Page) {
|
||||
await page.goto('/auth/login');
|
||||
await page.getByLabel('Email').fill('citizen@example.com');
|
||||
await page.getByLabel('Password').fill('Citizen@123');
|
||||
await page.getByRole('button', { name: 'Sign In' }).click();
|
||||
await page.waitForURL('**/dashboard**', { timeout: 10000 });
|
||||
}
|
||||
|
||||
async function loginAsAdmin(page: Page) {
|
||||
await page.goto('/auth/login');
|
||||
await page.getByLabel('Email').fill('admin@goa.gov.in');
|
||||
await page.getByLabel('Password').fill('Admin@123');
|
||||
await page.getByRole('button', { name: 'Sign In' }).click();
|
||||
await page.waitForURL('**/admin**', { timeout: 10000 });
|
||||
}
|
||||
|
||||
async function loginAsDepartment(page: Page) {
|
||||
await page.goto('/auth/login');
|
||||
await page.getByLabel('Email').fill('fire@goa.gov.in');
|
||||
await page.getByLabel('Password').fill('Fire@123');
|
||||
await page.getByRole('button', { name: 'Sign In' }).click();
|
||||
await page.waitForURL('**/dashboard**', { timeout: 10000 });
|
||||
}
|
||||
|
||||
test.describe('Citizen Dashboard', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await loginAsCitizen(page);
|
||||
});
|
||||
|
||||
test('should display citizen dashboard after login', async ({ page }) => {
|
||||
await expect(page).toHaveURL(/.*dashboard.*/);
|
||||
});
|
||||
|
||||
test('should show welcome message or user info', async ({ page }) => {
|
||||
// Dashboard should display something indicating the user is logged in
|
||||
const hasUserInfo = await page.locator('text=citizen').first().isVisible().catch(() => false) ||
|
||||
await page.locator('text=Dashboard').first().isVisible().catch(() => false) ||
|
||||
await page.locator('text=Welcome').first().isVisible().catch(() => false);
|
||||
|
||||
expect(hasUserInfo).toBe(true);
|
||||
});
|
||||
|
||||
test('should have navigation menu', async ({ page }) => {
|
||||
// Should have some navigation elements
|
||||
const hasNav = await page.locator('nav, [role="navigation"], .sidebar, .nav-menu').first().isVisible().catch(() => false) ||
|
||||
await page.locator('mat-sidenav, mat-toolbar').first().isVisible().catch(() => false);
|
||||
|
||||
expect(hasNav).toBe(true);
|
||||
});
|
||||
|
||||
test('should have link to requests', async ({ page }) => {
|
||||
// Should be able to navigate to requests
|
||||
const requestsLink = page.locator('a[href*="request"]').first();
|
||||
|
||||
if (await requestsLink.isVisible().catch(() => false)) {
|
||||
await requestsLink.click();
|
||||
await page.waitForURL('**/requests**', { timeout: 5000 }).catch(() => {});
|
||||
} else {
|
||||
// Try alternative navigation
|
||||
await page.goto('/requests');
|
||||
await expect(page).toHaveURL(/.*requests.*/);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Department Dashboard', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await loginAsDepartment(page);
|
||||
});
|
||||
|
||||
test('should display department dashboard after login', async ({ page }) => {
|
||||
await expect(page).toHaveURL(/.*dashboard.*/);
|
||||
});
|
||||
|
||||
test('should show department-specific content', async ({ page }) => {
|
||||
// Department dashboard may show pending approvals or assigned requests
|
||||
const hasDepartmentContent =
|
||||
(await page.locator('text=Pending').first().isVisible().catch(() => false)) ||
|
||||
(await page.locator('text=Approval').first().isVisible().catch(() => false)) ||
|
||||
(await page.locator('text=Review').first().isVisible().catch(() => false)) ||
|
||||
(await page.locator('text=Dashboard').first().isVisible().catch(() => false));
|
||||
|
||||
expect(hasDepartmentContent).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Admin Dashboard', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await loginAsAdmin(page);
|
||||
});
|
||||
|
||||
test('should display admin dashboard after login', async ({ page }) => {
|
||||
await expect(page).toHaveURL(/.*admin.*/);
|
||||
});
|
||||
|
||||
test('should show admin menu items', async ({ page }) => {
|
||||
// Admin should see admin-specific navigation
|
||||
const hasAdminItems =
|
||||
(await page.locator('text=Admin').first().isVisible().catch(() => false)) ||
|
||||
(await page.locator('text=Users').first().isVisible().catch(() => false)) ||
|
||||
(await page.locator('text=Departments').first().isVisible().catch(() => false)) ||
|
||||
(await page.locator('text=Workflow').first().isVisible().catch(() => false)) ||
|
||||
(await page.locator('text=Settings').first().isVisible().catch(() => false));
|
||||
|
||||
expect(hasAdminItems).toBe(true);
|
||||
});
|
||||
|
||||
test('should have access to departments management', async ({ page }) => {
|
||||
// Navigate to departments if link exists
|
||||
const deptLink = page.locator('a[href*="department"]').first();
|
||||
|
||||
if (await deptLink.isVisible().catch(() => false)) {
|
||||
await deptLink.click();
|
||||
await page.waitForTimeout(2000);
|
||||
} else {
|
||||
// Navigate directly
|
||||
await page.goto('/admin/departments');
|
||||
await page.waitForTimeout(1000);
|
||||
}
|
||||
});
|
||||
|
||||
test('should have access to workflows management', async ({ page }) => {
|
||||
// Navigate to workflows if link exists
|
||||
const workflowLink = page.locator('a[href*="workflow"]').first();
|
||||
|
||||
if (await workflowLink.isVisible().catch(() => false)) {
|
||||
await workflowLink.click();
|
||||
await page.waitForTimeout(2000);
|
||||
} else {
|
||||
// Navigate directly
|
||||
await page.goto('/admin/workflows');
|
||||
await page.waitForTimeout(1000);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Navigation', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await loginAsCitizen(page);
|
||||
});
|
||||
|
||||
test('should navigate between pages', async ({ page }) => {
|
||||
// Go to dashboard
|
||||
await page.goto('/dashboard');
|
||||
await expect(page).toHaveURL(/.*dashboard.*/);
|
||||
|
||||
// Navigate to requests
|
||||
await page.goto('/requests');
|
||||
await page.waitForTimeout(1000);
|
||||
await expect(page).toHaveURL(/.*requests.*/);
|
||||
});
|
||||
|
||||
test('should handle logout', async ({ page }) => {
|
||||
// Find and click logout button/link
|
||||
const logoutBtn = page.locator('button:has-text("Logout"), a:has-text("Logout"), [aria-label="Logout"], mat-icon:has-text("logout")').first();
|
||||
|
||||
if (await logoutBtn.isVisible()) {
|
||||
await logoutBtn.click();
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
// Should be redirected to login or home
|
||||
const currentUrl = page.url();
|
||||
expect(currentUrl).toMatch(/\/(auth|login|$)/);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Responsive Design', () => {
|
||||
test('should display correctly on mobile viewport', async ({ page }) => {
|
||||
await page.setViewportSize({ width: 375, height: 667 });
|
||||
await loginAsCitizen(page);
|
||||
|
||||
// Page should still be usable
|
||||
await expect(page).toHaveURL(/.*dashboard.*/);
|
||||
|
||||
// Mobile menu toggle might be present
|
||||
const mobileMenu = page.locator('[aria-label="Menu"]').first();
|
||||
|
||||
if (await mobileMenu.isVisible().catch(() => false)) {
|
||||
await mobileMenu.click();
|
||||
await page.waitForTimeout(500);
|
||||
}
|
||||
});
|
||||
|
||||
test('should display correctly on tablet viewport', async ({ page }) => {
|
||||
await page.setViewportSize({ width: 768, height: 1024 });
|
||||
await loginAsCitizen(page);
|
||||
|
||||
await expect(page).toHaveURL(/.*dashboard.*/);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user