Upgrade Playwright suite to v2.5.1 SQLite deep testing

This commit is contained in:
A R R R Associates
2026-06-24 20:05:36 +05:30
parent 89ba3e85a1
commit ba1075ef19
33 changed files with 16167 additions and 240 deletions
+233
View File
@@ -0,0 +1,233 @@
/**
* =============================================================================
* UAT_Partners_Billing -- Partners portal + firm-level billing
* =============================================================================
*
* Covers:
* PART-* : Partner portal (/partner/...) and partner-facing review routes
* BILL-* : Firm billing — invoices, payments, receipts, fee structures
* RBAC-* : Access control checks across both modules
* SEC-* : CSRF rejection and anonymous access probes
*
* Required .env additions:
* INVOICE_A_ID= # a seeded firm invoice id (status: draft)
* PAYMENT_A_ID= # a seeded payment id against INVOICE_A_ID
* PARTNER_TASK_A_ID= # a seeded service task assigned for partner review
*
* Tests skip gracefully when env vars are absent.
* =============================================================================
*/
const { test, expect } = require('@playwright/test');
require('dotenv').config();
const { login } = require('../fixtures/auth');
const { expectNoBackendError, readBody, blockedOrNotFound } = require('../fixtures/v204-helpers');
const { expectBlockedOrSafe } = require('../fixtures/assertions');
async function safeGoto(page, route) {
const resp = await page.goto(route).catch(() => null);
await page.waitForLoadState('domcontentloaded').catch(() => {});
return resp;
}
function idOr(envKey, fallback = '1') {
return process.env[envKey] || fallback;
}
function skipIfMissing(envKey) {
if (!process.env[envKey]) test.skip(true, `Set ${envKey} in .env after seeding`);
}
// ---------------------------------------------------------------------------
// Partners portal
// ---------------------------------------------------------------------------
test.describe('PART: Partner portal', () => {
test('[V25-PART-001] PART-001 Partner dashboard loads', async ({ page }) => {
await login(page, 'Partner');
const resp = await safeGoto(page, '/partner/dashboard');
await expectNoBackendError(page);
expect(resp.status()).toBeLessThan(500);
});
test('[V25-PART-002] PART-002 Partner client list loads', async ({ page }) => {
await login(page, 'Partner');
await safeGoto(page, '/partner/clients');
await expectNoBackendError(page);
});
test('[V25-PART-003] PART-003 Partner reviews list loads', async ({ page }) => {
await login(page, 'Partner');
await safeGoto(page, '/partner/reviews');
await expectNoBackendError(page);
});
test('[V25-PART-004] PART-004 Partner task review page loads', async ({ page }) => {
skipIfMissing('PARTNER_TASK_A_ID');
await login(page, 'Partner');
await safeGoto(page, `/partner/tasks/${idOr('PARTNER_TASK_A_ID')}/review`);
await expectNoBackendError(page);
});
test('[V25-PART-005] PART-005 Partner task review CSRF-less POST is rejected', async ({ request }) => {
skipIfMissing('PARTNER_TASK_A_ID');
const resp = await request.post(
`${process.env.BASE_URL}/partner/tasks/${idOr('PARTNER_TASK_A_ID')}/review`,
{ form: { status: 'approved', csrf_token: '' } }
).catch(() => null);
if (!resp || [404, 405].includes(resp.status())) test.skip(true, 'Route not available');
expect([400, 401, 403, 422].includes(resp.status())).toBeTruthy();
});
test('[V25-PART-006] PART-006 Staff cannot access partner dashboard', async ({ page }) => {
await login(page, 'Staff');
const resp = await safeGoto(page, '/partner/dashboard');
await expectNoBackendError(page);
await expectBlockedOrSafe(page, resp);
});
test('[V25-PART-007] PART-007 Client cannot access partner reviews', async ({ page }) => {
await login(page, 'Client');
const resp = await safeGoto(page, '/partner/reviews');
await expectNoBackendError(page);
await expectBlockedOrSafe(page, resp);
});
test('[V25-PART-008] PART-008 Anonymous access to partner dashboard is blocked', async ({ page }) => {
const resp = await safeGoto(page, '/partner/dashboard');
await expectBlockedOrSafe(page, resp);
});
test('[V25-PART-009] PART-009 Non-existent task review ID returns safe response', async ({ page }) => {
await login(page, 'Partner');
const resp = await safeGoto(page, '/partner/tasks/999999999/review');
await expectNoBackendError(page);
await expectBlockedOrSafe(page, resp);
});
});
// ---------------------------------------------------------------------------
// Firm billing — invoices
// ---------------------------------------------------------------------------
test.describe('BILL: Invoices and payments', () => {
test('[V25-BILL-001] BILL-001 Invoice list loads for Firm Admin', async ({ page }) => {
await login(page, 'Firm Admin');
await safeGoto(page, '/billing');
await expectNoBackendError(page);
});
test('[V25-BILL-002] BILL-002 Invoice create form loads', async ({ page }) => {
await login(page, 'Firm Admin');
await safeGoto(page, '/billing/invoices/new');
await expectNoBackendError(page);
});
test('[V25-BILL-003] BILL-003 Invoice detail page loads', async ({ page }) => {
skipIfMissing('INVOICE_A_ID');
await login(page, 'Firm Admin');
await safeGoto(page, `/billing/invoices/${idOr('INVOICE_A_ID')}`);
await expectNoBackendError(page);
});
test('[V25-BILL-004] BILL-004 Invoice print page loads', async ({ page }) => {
skipIfMissing('INVOICE_A_ID');
await login(page, 'Firm Admin');
await safeGoto(page, `/billing/invoices/${idOr('INVOICE_A_ID')}/print`);
await expectNoBackendError(page);
});
test('[V25-BILL-005] BILL-005 Invoice payments subpage loads', async ({ page }) => {
skipIfMissing('INVOICE_A_ID');
await login(page, 'Firm Admin');
await safeGoto(page, `/billing/invoices/${idOr('INVOICE_A_ID')}/payments`);
await expectNoBackendError(page);
});
test('[V25-BILL-006] BILL-006 Payment receipt loads', async ({ page }) => {
skipIfMissing('PAYMENT_A_ID');
await login(page, 'Firm Admin');
await safeGoto(page, `/billing/payments/${idOr('PAYMENT_A_ID')}/receipt`);
await expectNoBackendError(page);
});
test('[V25-BILL-007] BILL-007 Payments list loads', async ({ page }) => {
await login(page, 'Firm Admin');
await safeGoto(page, '/billing/payments');
await expectNoBackendError(page);
});
test('[V25-BILL-008] BILL-008 Fee structures list loads', async ({ page }) => {
await login(page, 'Firm Admin');
await safeGoto(page, '/billing/fee-structures/list');
await expectNoBackendError(page);
});
test('[V25-BILL-009] BILL-009 Invoice post CSRF-less POST is rejected', async ({ request }) => {
skipIfMissing('INVOICE_A_ID');
const resp = await request.post(
`${process.env.BASE_URL}/billing/invoices/${idOr('INVOICE_A_ID')}/issue`,
{ form: { csrf_token: '' } }
).catch(() => null);
if (!resp || [404, 405].includes(resp.status())) test.skip(true, 'Route not available');
expect([400, 401, 403, 422].includes(resp.status())).toBeTruthy();
});
test('[V25-BILL-010] BILL-010 New payment CSRF-less POST is rejected', async ({ request }) => {
skipIfMissing('INVOICE_A_ID');
const resp = await request.post(
`${process.env.BASE_URL}/billing/invoices/${idOr('INVOICE_A_ID')}/payments/new`,
{ form: { amount: '1000', csrf_token: '' } }
).catch(() => null);
if (!resp || [404, 405].includes(resp.status())) test.skip(true, 'Route not available');
expect([400, 401, 403, 422].includes(resp.status())).toBeTruthy();
});
test('[V25-BILL-011] BILL-011 Staff cannot access invoice list', async ({ page }) => {
await login(page, 'Staff');
const resp = await safeGoto(page, '/billing');
await expectNoBackendError(page);
await expectBlockedOrSafe(page, resp);
});
test('[V25-BILL-012] BILL-012 Client cannot access billing admin', async ({ page }) => {
await login(page, 'Client');
const resp = await safeGoto(page, '/billing/invoices/new');
await expectNoBackendError(page);
await expectBlockedOrSafe(page, resp);
});
test('[V25-BILL-013] BILL-013 Anonymous access to billing is blocked', async ({ page }) => {
const resp = await safeGoto(page, '/billing');
await expectBlockedOrSafe(page, resp);
});
test('[V25-BILL-014] BILL-014 Non-existent invoice ID returns safe response', async ({ page }) => {
await login(page, 'Firm Admin');
const resp = await safeGoto(page, '/billing/invoices/999999999');
await expectNoBackendError(page);
await expectBlockedOrSafe(page, resp);
});
test('[V25-BILL-015] BILL-015 Invoice create with blank form shows validation, not 500', async ({ page }) => {
await login(page, 'Firm Admin');
await safeGoto(page, '/billing/invoices/new');
const submit = page.locator('button[type="submit"], input[type="submit"]').first();
if (await submit.count()) {
await submit.click().catch(() => {});
await page.waitForLoadState('domcontentloaded').catch(() => {});
}
await expectNoBackendError(page);
});
test('[V25-BILL-016] BILL-016 Fee structure import template download works', async ({ page }) => {
await login(page, 'Firm Admin');
const resp = await safeGoto(page, '/billing/fee-structures/template');
// should download or redirect, not crash
expect(resp.status()).toBeLessThan(500);
});
});