Files
pn-new-crm/tests/e2e/smoke/05-invoices.spec.ts
Matt 67d7e6e3d5
Some checks failed
Build & Push Docker Images / build-and-push (push) Has been cancelled
Build & Push Docker Images / deploy (push) Has been cancelled
Build & Push Docker Images / lint (push) Has been cancelled
Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:51 +01:00

109 lines
4.0 KiB
TypeScript

import { test, expect } from '@playwright/test';
import { login, navigateTo, PORT_SLUG } from './helpers';
test.describe('Invoicing', () => {
test.beforeEach(async ({ page }) => {
await login(page, 'super_admin');
});
test('navigate to invoices page', async ({ page }) => {
await navigateTo(page, '/invoices');
await page.waitForLoadState('networkidle');
const heading = page.getByText(/invoices/i).first();
await expect(heading).toBeVisible({ timeout: 10_000 });
});
test('create a new invoice with 3 line items', async ({ page }) => {
await navigateTo(page, '/invoices');
await page.waitForLoadState('networkidle');
// Click "New Invoice" (use first() for strict mode)
const newBtn = page.getByRole('link', { name: /new invoice/i }).first()
.or(page.getByRole('button', { name: /new invoice/i }).first());
await newBtn.first().click();
// Step 1: Client Info
await page.waitForURL(`**/${PORT_SLUG}/invoices/new**`, { timeout: 10_000 });
await page.fill('#clientName', 'Invoice Test Client');
await page.fill('#billingEmail', 'billing@test.com');
const dueDate = new Date();
dueDate.setDate(dueDate.getDate() + 30);
await page.fill('#dueDate', dueDate.toISOString().split('T')[0]!);
await page.getByRole('button', { name: /next/i }).click();
await page.waitForTimeout(1000);
// Step 2: Line Items — add 3 items
for (let i = 0; i < 3; i++) {
await page.getByRole('button', { name: /add line item/i }).click();
await page.waitForTimeout(300);
}
await page.locator('input[name="lineItems.0.description"]').fill('Berth Rental - Annual');
await page.locator('input[name="lineItems.0.quantity"]').fill('1');
await page.locator('input[name="lineItems.0.unitPrice"]').fill('50000');
await page.locator('input[name="lineItems.1.description"]').fill('Utilities Package');
await page.locator('input[name="lineItems.1.quantity"]').fill('12');
await page.locator('input[name="lineItems.1.unitPrice"]').fill('150');
await page.locator('input[name="lineItems.2.description"]').fill('Maintenance Fee');
await page.locator('input[name="lineItems.2.quantity"]').fill('4');
await page.locator('input[name="lineItems.2.unitPrice"]').fill('500');
// Verify subtotal appears (53800 formatted per locale)
await expect(page.getByText(/53[,.]?800/).first()).toBeVisible({ timeout: 5_000 });
// Click Next to Review
await page.getByRole('button', { name: /next/i }).click();
await page.waitForTimeout(1000);
// Step 3: Review — verify summary
await expect(page.getByText('Invoice Test Client')).toBeVisible();
await expect(page.getByText(/53[,.]?800/).first()).toBeVisible();
// Submit
await page.getByRole('button', { name: /create invoice/i }).click();
// Should redirect to invoice detail or list
await page.waitForURL(
(url) => url.pathname.includes('/invoices') && !url.pathname.includes('/new'),
{ timeout: 15_000 },
);
});
test('invoice shows as Draft', async ({ page }) => {
await navigateTo(page, '/invoices');
await page.waitForLoadState('networkidle');
await page.waitForTimeout(2000);
await expect(page.getByText(/draft/i).first()).toBeVisible({ timeout: 10_000 });
});
test('invoice detail page loads', async ({ page }) => {
await navigateTo(page, '/invoices');
await page.waitForLoadState('networkidle');
await page.waitForTimeout(2000);
// Click into the first invoice
const invoiceLink = page.locator('table a, table [role="link"]').first();
if (await invoiceLink.isVisible({ timeout: 5_000 }).catch(() => false)) {
await invoiceLink.click();
await page.waitForTimeout(3000);
const url = page.url();
expect(url.includes('/invoices/')).toBeTruthy();
} else {
// Try clicking the first row
const row = page.locator('table tbody tr').first();
if (await row.isVisible({ timeout: 3_000 }).catch(() => false)) {
await row.click();
await page.waitForTimeout(3000);
}
}
});
});