Fix Playwright BASE_URL handling and failed UAT report tests

This commit is contained in:
A R R R Associates
2026-06-26 20:38:14 +05:30
parent 21c90749fb
commit ce9935c684
21 changed files with 98 additions and 53 deletions
+26
View File
@@ -0,0 +1,26 @@
require('dotenv').config();
const DEFAULT_BASE_URL = 'https://erp-test.vavalam.com';
function normalizeBaseURL(value) {
const raw = (value || '').trim() || DEFAULT_BASE_URL;
return raw.replace(/\/+$/, '');
}
const BASE_URL = normalizeBaseURL(
process.env.BASE_URL ||
process.env.APP_BASE_URL ||
process.env.PLAYWRIGHT_BASE_URL ||
DEFAULT_BASE_URL
);
function absoluteUrl(route = '/') {
if (/^https?:\/\//i.test(route)) return route;
return new URL(route, `${BASE_URL}/`).toString();
}
module.exports = {
BASE_URL,
DEFAULT_BASE_URL,
absoluteUrl,
};
+2 -1
View File
@@ -1,5 +1,6 @@
const { defineConfig } = require('@playwright/test');
require('dotenv').config();
const { BASE_URL } = require('./fixtures/url');
module.exports = defineConfig({
testDir: './tests',
@@ -15,7 +16,7 @@ module.exports = defineConfig({
['list']
],
use: {
baseURL: process.env.BASE_URL || 'http://127.0.0.1:8000',
baseURL: BASE_URL,
trace: 'retain-on-failure',
screenshot: 'only-on-failure',
video: 'retain-on-failure'
+1 -1
View File
@@ -1,8 +1,8 @@
// Standalone runner: executes the API-level (no-browser) security checks
// from the v2.0.4 suite against a live ERP. Produces JSON results.
const { request } = require('@playwright/test');
const { BASE_URL, absoluteUrl } = require('./fixtures/url');
const BASE_URL = process.env.BASE_URL || 'http://127.0.0.1:8000';
const results = [];
function record(id, title, status, detail) {
+2 -1
View File
@@ -1,6 +1,7 @@
const { test, expect } = require('@playwright/test');
require('dotenv').config();
const { BASE_URL, absoluteUrl } = require('../fixtures/url');
const { login } = require('../fixtures/auth');
const { expectNoBackendError, readBody, blockedOrNotFound } = require('../fixtures/v204-helpers');
const { expectBlockedOrSafe } = require('../fixtures/assertions');
@@ -31,7 +32,7 @@ async function expectPublicNoSensitive(page) {
}
async function apiCall(request, method, route) {
const url = `${process.env.BASE_URL}${route}`;
const url = absoluteUrl(route);
const opts = { data: { csrf_token: '', test_payload: 'uat-vapt' }, headers: { 'Content-Type': 'application/json' } };
if (method === 'GET') return await request.get(url).catch(() => null);
if (method === 'POST') return await request.post(url, opts).catch(() => null);
+2 -1
View File
@@ -1,6 +1,7 @@
const { test, expect } = require('@playwright/test');
require('dotenv').config();
const { BASE_URL, absoluteUrl } = require('../fixtures/url');
const { login } = require('../fixtures/auth');
const { expectNoBackendError, readBody, blockedOrNotFound } = require('../fixtures/v204-helpers');
const { expectBlockedOrSafe } = require('../fixtures/assertions');
@@ -31,7 +32,7 @@ async function expectPublicNoSensitive(page) {
}
async function apiCall(request, method, route) {
const url = `${process.env.BASE_URL}${route}`;
const url = absoluteUrl(route);
const opts = { data: { csrf_token: '', test_payload: 'uat-vapt' }, headers: { 'Content-Type': 'application/json' } };
if (method === 'GET') return await request.get(url).catch(() => null);
if (method === 'POST') return await request.post(url, opts).catch(() => null);
+2 -1
View File
@@ -1,6 +1,7 @@
const { test, expect } = require('@playwright/test');
require('dotenv').config();
const { BASE_URL, absoluteUrl } = require('../fixtures/url');
const { login } = require('../fixtures/auth');
const { expectNoBackendError, readBody, blockedOrNotFound } = require('../fixtures/v204-helpers');
const { expectBlockedOrSafe } = require('../fixtures/assertions');
@@ -31,7 +32,7 @@ async function expectPublicNoSensitive(page) {
}
async function apiCall(request, method, route) {
const url = `${process.env.BASE_URL}${route}`;
const url = absoluteUrl(route);
const opts = { data: { csrf_token: '', test_payload: 'uat-vapt' }, headers: { 'Content-Type': 'application/json' } };
if (method === 'GET') return await request.get(url).catch(() => null);
if (method === 'POST') return await request.post(url, opts).catch(() => null);
+9 -8
View File
@@ -25,6 +25,7 @@
const { test, expect } = require('@playwright/test');
require('dotenv').config();
const { BASE_URL, absoluteUrl } = require('../fixtures/url');
const { login } = require('../fixtures/auth');
const { expectNoBackendError, readBody, blockedOrNotFound, uploadPath } = require('../fixtures/v204-helpers');
const { expectBlockedOrSafe } = require('../fixtures/assertions');
@@ -92,7 +93,7 @@ test.describe('CONS: Consultant portal', () => {
test('[V25-CONS-007] CONS-007 Conversion request review CSRF-less POST is rejected', async ({ request }) => {
skipIfMissing('CONVERSION_REQUEST_ID');
const resp = await request.post(
`${process.env.BASE_URL}/consultants/conversion-requests/${idOr('CONVERSION_REQUEST_ID')}/review`,
`${BASE_URL}/consultants/conversion-requests/${idOr('CONVERSION_REQUEST_ID')}/review`,
{ form: { decision: 'approve', csrf_token: '' } }
).catch(() => null);
if (!resp || [404, 405].includes(resp.status())) test.skip(true, 'Route not available');
@@ -115,7 +116,7 @@ test.describe('CONS: Consultant portal', () => {
test('[V25-CONS-010] CONS-010 Service request status update CSRF-less POST is rejected', async ({ request }) => {
skipIfMissing('SERVICE_REQUEST_ID');
const resp = await request.post(
`${process.env.BASE_URL}/consultants/service-requests/${idOr('SERVICE_REQUEST_ID')}/status`,
`${BASE_URL}/consultants/service-requests/${idOr('SERVICE_REQUEST_ID')}/status`,
{ form: { status: 'approved', csrf_token: '' } }
).catch(() => null);
if (!resp || [404, 405].includes(resp.status())) test.skip(true, 'Route not available');
@@ -182,7 +183,7 @@ test.describe('DOC-STOR: Document storage and permanent vault', () => {
test('[V25-DOC-STOR-004] DOC-STOR-004 Permanent vault upload CSRF-less POST is rejected', async ({ request }) => {
skipIfMissing('CLIENT_A_ID');
const resp = await request.post(
`${process.env.BASE_URL}/documents/permanent/clients/${idOr('CLIENT_A_ID')}/upload`,
`${BASE_URL}/documents/permanent/clients/${idOr('CLIENT_A_ID')}/upload`,
{ form: { csrf_token: '' } }
).catch(() => null);
if (!resp || [404, 405].includes(resp.status())) test.skip(true, 'Route not available');
@@ -216,14 +217,14 @@ test.describe('DOC-STOR: Document storage and permanent vault', () => {
});
test('[V25-DOC-STOR-009] DOC-STOR-009 Storage agent jobs endpoint requires auth', async ({ request }) => {
const resp = await request.get(`${process.env.BASE_URL}/documents/storage-agent/jobs/pending`).catch(() => null);
const resp = await request.get(`${BASE_URL}/documents/storage-agent/jobs/pending`).catch(() => null);
if (!resp) test.skip(true, 'Request failed at network level');
expect(resp.status()).toBeLessThan(500);
expect([400, 401, 403, 404, 405, 422].includes(resp.status())).toBeTruthy();
});
test('[V25-DOC-STOR-010] DOC-STOR-010 Storage agent download-requests endpoint requires auth', async ({ request }) => {
const resp = await request.get(`${process.env.BASE_URL}/documents/storage-agent/download-requests/pending`).catch(() => null);
const resp = await request.get(`${BASE_URL}/documents/storage-agent/download-requests/pending`).catch(() => null);
if (!resp) test.skip(true, 'Request failed at network level');
expect(resp.status()).toBeLessThan(500);
expect([400, 401, 403, 404, 405, 422].includes(resp.status())).toBeTruthy();
@@ -232,7 +233,7 @@ test.describe('DOC-STOR: Document storage and permanent vault', () => {
test('[V25-DOC-STOR-011] DOC-STOR-011 Storage node toggle CSRF-less POST is rejected', async ({ request }) => {
skipIfMissing('STORAGE_NODE_ID');
const resp = await request.post(
`${process.env.BASE_URL}/documents/storage-nodes/${idOr('STORAGE_NODE_ID')}/toggle`,
`${BASE_URL}/documents/storage-nodes/${idOr('STORAGE_NODE_ID')}/toggle`,
{ form: { csrf_token: '' } }
).catch(() => null);
if (!resp || [404, 405].includes(resp.status())) test.skip(true, 'Route not available');
@@ -256,7 +257,7 @@ test.describe('DOC-STOR: Document storage and permanent vault', () => {
test('[V25-DOC-STOR-014] DOC-STOR-014 Permanent document delete CSRF-less POST is rejected', async ({ request }) => {
skipIfMissing('PERM_DOCUMENT_ID');
const resp = await request.post(
`${process.env.BASE_URL}/documents/permanent/${idOr('PERM_DOCUMENT_ID')}/delete`,
`${BASE_URL}/documents/permanent/${idOr('PERM_DOCUMENT_ID')}/delete`,
{ form: { csrf_token: '' } }
).catch(() => null);
if (!resp || [404, 405].includes(resp.status())) test.skip(true, 'Route not available');
@@ -271,7 +272,7 @@ test.describe('DOC-STOR: Document storage and permanent vault', () => {
});
test('[V25-DOC-STOR-016] DOC-STOR-016 Storage agent agent-package download requires node auth', async ({ request }) => {
const resp = await request.get(`${process.env.BASE_URL}/documents/storage-nodes/download-agent-package`).catch(() => null);
const resp = await request.get(`${BASE_URL}/documents/storage-nodes/download-agent-package`).catch(() => null);
if (!resp || [404, 405].includes(resp.status())) test.skip(true, 'Route not available');
expect(resp.status()).toBeLessThan(500);
expect([400, 401, 403, 404, 422].includes(resp.status())).toBeTruthy();
+2 -1
View File
@@ -1,6 +1,7 @@
const { test, expect } = require('@playwright/test');
require('dotenv').config();
const { BASE_URL, absoluteUrl } = require('../fixtures/url');
const { login } = require('../fixtures/auth');
const { expectNoBackendError, readBody, blockedOrNotFound } = require('../fixtures/v204-helpers');
const { expectBlockedOrSafe } = require('../fixtures/assertions');
@@ -31,7 +32,7 @@ async function expectPublicNoSensitive(page) {
}
async function apiCall(request, method, route) {
const url = `${process.env.BASE_URL}${route}`;
const url = absoluteUrl(route);
const opts = { data: { csrf_token: '', test_payload: 'uat-vapt' }, headers: { 'Content-Type': 'application/json' } };
if (method === 'GET') return await request.get(url).catch(() => null);
if (method === 'POST') return await request.post(url, opts).catch(() => null);
+2 -1
View File
@@ -1,6 +1,7 @@
const { test, expect } = require('@playwright/test');
require('dotenv').config();
const { BASE_URL, absoluteUrl } = require('../fixtures/url');
const { login } = require('../fixtures/auth');
const { expectNoBackendError, readBody, blockedOrNotFound } = require('../fixtures/v204-helpers');
const { expectBlockedOrSafe } = require('../fixtures/assertions');
@@ -31,7 +32,7 @@ async function expectPublicNoSensitive(page) {
}
async function apiCall(request, method, route) {
const url = `${process.env.BASE_URL}${route}`;
const url = absoluteUrl(route);
const opts = { data: { csrf_token: '', test_payload: 'uat-vapt' }, headers: { 'Content-Type': 'application/json' } };
if (method === 'GET') return await request.get(url).catch(() => null);
if (method === 'POST') return await request.post(url, opts).catch(() => null);
+2 -1
View File
@@ -1,6 +1,7 @@
const { test, expect } = require('@playwright/test');
require('dotenv').config();
const { BASE_URL, absoluteUrl } = require('../fixtures/url');
const { login } = require('../fixtures/auth');
const { expectNoBackendError, readBody, blockedOrNotFound } = require('../fixtures/v204-helpers');
const { expectBlockedOrSafe } = require('../fixtures/assertions');
@@ -31,7 +32,7 @@ async function expectPublicNoSensitive(page) {
}
async function apiCall(request, method, route) {
const url = `${process.env.BASE_URL}${route}`;
const url = absoluteUrl(route);
const opts = { data: { csrf_token: '', test_payload: 'uat-vapt' }, headers: { 'Content-Type': 'application/json' } };
if (method === 'GET') return await request.get(url).catch(() => null);
if (method === 'POST') return await request.post(url, opts).catch(() => null);
+10 -9
View File
@@ -28,6 +28,7 @@
const { test, expect } = require('@playwright/test');
require('dotenv').config();
const { BASE_URL, absoluteUrl } = require('../fixtures/url');
const { login } = require('../fixtures/auth');
const { expectNoBackendError, readBody, blockedOrNotFound } = require('../fixtures/v204-helpers');
const { expectBlockedOrSafe } = require('../fixtures/assertions');
@@ -132,7 +133,7 @@ test.describe('EMP-HR: Attendance management', () => {
});
test('[V25-EMP-ATT-003] EMP-ATT-003 Manual attendance CSRF-less POST is rejected', async ({ request }) => {
const resp = await request.post(`${process.env.BASE_URL}/employees/attendance/manual`, {
const resp = await request.post(`${BASE_URL}/employees/attendance/manual`, {
form: { employee_id: idOr('EMPLOYEE_A_ID'), attendance_date: '2025-01-01', status: 'present', csrf_token: '' },
}).catch(() => null);
if (!resp || [404, 405].includes(resp.status())) test.skip(true, 'Route not available');
@@ -142,7 +143,7 @@ test.describe('EMP-HR: Attendance management', () => {
test('[V25-EMP-ATT-004] EMP-ATT-004 Attendance review CSRF-less POST is rejected', async ({ request }) => {
skipIfMissing('ATTENDANCE_ID');
const resp = await request.post(
`${process.env.BASE_URL}/employees/attendance/${idOr('ATTENDANCE_ID')}/review`,
`${BASE_URL}/employees/attendance/${idOr('ATTENDANCE_ID')}/review`,
{ form: { approval_status: 'approved', csrf_token: '' } }
).catch(() => null);
if (!resp || [404, 405].includes(resp.status())) test.skip(true, 'Route not available');
@@ -184,7 +185,7 @@ test.describe('EMP-HR: Leave management', () => {
test('[V25-EMP-LEAVE-005] EMP-LEAVE-005 Leave review CSRF-less POST is rejected', async ({ request }) => {
skipIfMissing('LEAVE_REQUEST_ID');
const resp = await request.post(
`${process.env.BASE_URL}/employees/leave/${idOr('LEAVE_REQUEST_ID')}/review`,
`${BASE_URL}/employees/leave/${idOr('LEAVE_REQUEST_ID')}/review`,
{ form: { status: 'approved', csrf_token: '' } }
).catch(() => null);
if (!resp || [404, 405].includes(resp.status())) test.skip(true, 'Route not available');
@@ -270,7 +271,7 @@ test.describe('EMP-HR: Employee documents', () => {
test('[V25-EMP-DOC-004] EMP-DOC-004 Document review CSRF-less POST is rejected', async ({ request }) => {
skipIfMissing('EMP_DOCUMENT_ID');
const resp = await request.post(
`${process.env.BASE_URL}/employees/documents/${idOr('EMP_DOCUMENT_ID')}/review`,
`${BASE_URL}/employees/documents/${idOr('EMP_DOCUMENT_ID')}/review`,
{ form: { status: 'verified', csrf_token: '' } }
).catch(() => null);
if (!resp || [404, 405].includes(resp.status())) test.skip(true, 'Route not available');
@@ -313,7 +314,7 @@ test.describe('EMP-HR: Payroll', () => {
test('[V25-EMP-PAY-005] EMP-PAY-005 Payroll run generate CSRF-less POST is rejected', async ({ request }) => {
skipIfMissing('PAYROLL_RUN_ID');
const resp = await request.post(
`${process.env.BASE_URL}/employees/payroll/runs/${idOr('PAYROLL_RUN_ID')}/generate`,
`${BASE_URL}/employees/payroll/runs/${idOr('PAYROLL_RUN_ID')}/generate`,
{ form: { csrf_token: '' } }
).catch(() => null);
if (!resp || [404, 405].includes(resp.status())) test.skip(true, 'Route not available');
@@ -323,7 +324,7 @@ test.describe('EMP-HR: Payroll', () => {
test('[V25-EMP-PAY-006] EMP-PAY-006 Payroll run approve CSRF-less POST is rejected', async ({ request }) => {
skipIfMissing('PAYROLL_RUN_ID');
const resp = await request.post(
`${process.env.BASE_URL}/employees/payroll/runs/${idOr('PAYROLL_RUN_ID')}/approve`,
`${BASE_URL}/employees/payroll/runs/${idOr('PAYROLL_RUN_ID')}/approve`,
{ form: { csrf_token: '' } }
).catch(() => null);
if (!resp || [404, 405].includes(resp.status())) test.skip(true, 'Route not available');
@@ -421,7 +422,7 @@ test.describe('EMP-ESS: Employee self-service portal', () => {
});
test('[V25-EMP-ESS-010] EMP-ESS-010 Leave application CSRF-less POST is rejected', async ({ request }) => {
const resp = await request.post(`${process.env.BASE_URL}/employee/leave/apply`, {
const resp = await request.post(`${BASE_URL}/employee/leave/apply`, {
form: { leave_type_id: '1', from_date: '2025-06-01', to_date: '2025-06-01', csrf_token: '' },
}).catch(() => null);
if (!resp || [404, 405].includes(resp.status())) test.skip(true, 'Route not available');
@@ -498,7 +499,7 @@ test.describe('EMP-SEC: Security probes', () => {
await login(page, 'Firm Admin');
await safeGoto(page, '/employees/imports');
// Attempt to POST commit without having a session preview
const resp = await page.request.post(`${process.env.BASE_URL}/employees/imports/commit`, {
const resp = await page.request.post(`${BASE_URL}/employees/imports/commit`, {
form: { csrf_token: 'invalid' },
}).catch(() => null);
if (!resp) test.skip(true, 'Request failed at network level');
@@ -509,7 +510,7 @@ test.describe('EMP-SEC: Security probes', () => {
skipIfMissing('PAYROLL_RUN_ID');
// Staff should not be able to trigger payroll — attempt raw API call
const resp = await request.post(
`${process.env.BASE_URL}/employees/payroll/runs/${idOr('PAYROLL_RUN_ID')}/generate`,
`${BASE_URL}/employees/payroll/runs/${idOr('PAYROLL_RUN_ID')}/generate`,
{ form: { csrf_token: 'invalid' } }
).catch(() => null);
if (!resp || [404, 405].includes(resp.status())) test.skip(true, 'Route not available');
+2 -1
View File
@@ -1,5 +1,6 @@
const { test, expect, request } = require('@playwright/test');
require('dotenv').config();
const { BASE_URL, absoluteUrl } = require('../fixtures/url');
const matrix = require('../data/generated-test-matrix.json');
const { login, logout, fillFirst, clickFirst } = require('../fixtures/auth');
const { expectNoServerError, expectBlockedOrSafe, expectSecurityHeaders, expectCookieFlags } = require('../fixtures/assertions');
@@ -58,7 +59,7 @@ async function runSecuritySpecific(page, context, v) {
return;
}
if (/SEC-007|GEN-011/i.test(id)) {
const api = await request.newContext({ baseURL: process.env.BASE_URL });
const api = await request.newContext({ baseURL: BASE_URL });
const resp = await api.post('/notice-cases/new', { form: { title: 'csrf-test-no-token' } });
expect([400,401,403,404,405,422,303].includes(resp.status())).toBeTruthy();
return;
+2 -1
View File
@@ -1,6 +1,7 @@
const { test, expect } = require('@playwright/test');
require('dotenv').config();
const { BASE_URL, absoluteUrl } = require('../fixtures/url');
const { login } = require('../fixtures/auth');
const { expectNoBackendError, readBody, blockedOrNotFound } = require('../fixtures/v204-helpers');
const { expectBlockedOrSafe } = require('../fixtures/assertions');
@@ -31,7 +32,7 @@ async function expectPublicNoSensitive(page) {
}
async function apiCall(request, method, route) {
const url = `${process.env.BASE_URL}${route}`;
const url = absoluteUrl(route);
const opts = { data: { csrf_token: '', test_payload: 'uat-vapt' }, headers: { 'Content-Type': 'application/json' } };
if (method === 'GET') return await request.get(url).catch(() => null);
if (method === 'POST') return await request.post(url, opts).catch(() => null);
+2 -1
View File
@@ -1,6 +1,7 @@
const { test, expect } = require('@playwright/test');
require('dotenv').config();
const { BASE_URL, absoluteUrl } = require('../fixtures/url');
const { login } = require('../fixtures/auth');
const { expectNoBackendError, readBody, blockedOrNotFound } = require('../fixtures/v204-helpers');
const { expectBlockedOrSafe } = require('../fixtures/assertions');
@@ -31,7 +32,7 @@ async function expectPublicNoSensitive(page) {
}
async function apiCall(request, method, route) {
const url = `${process.env.BASE_URL}${route}`;
const url = absoluteUrl(route);
const opts = { data: { csrf_token: '', test_payload: 'uat-vapt' }, headers: { 'Content-Type': 'application/json' } };
if (method === 'GET') return await request.get(url).catch(() => null);
if (method === 'POST') return await request.post(url, opts).catch(() => null);
+12 -11
View File
@@ -22,6 +22,7 @@
const { test, expect } = require('@playwright/test');
require('dotenv').config();
const { BASE_URL, absoluteUrl } = require('../fixtures/url');
const { login } = require('../fixtures/auth');
const { expectNoBackendError, readBody, blockedOrNotFound } = require('../fixtures/v204-helpers');
const { expectBlockedOrSafe } = require('../fixtures/assertions');
@@ -98,7 +99,7 @@ test.describe('CASE: Notice case sub-pages', () => {
test('[V25-CASE-008] CASE-008 Notice case document delete CSRF-less POST is rejected', async ({ request }) => {
skipIfMissing('CASE_DOCUMENT_A_ID');
const resp = await request.post(
`${process.env.BASE_URL}/notice-cases/documents/${idOr('CASE_DOCUMENT_A_ID')}/delete`,
`${BASE_URL}/notice-cases/documents/${idOr('CASE_DOCUMENT_A_ID')}/delete`,
{ form: { csrf_token: '' } }
).catch(() => null);
if (!resp || [404, 405].includes(resp.status())) test.skip(true, 'Route not available');
@@ -108,7 +109,7 @@ test.describe('CASE: Notice case sub-pages', () => {
test('[V25-CASE-009] CASE-009 Notice case upload CSRF-less POST is rejected', async ({ request }) => {
skipIfMissing('NOTICE_CASE_A_ID');
const resp = await request.post(
`${process.env.BASE_URL}/notice-cases/${idOr('NOTICE_CASE_A_ID')}/documents/upload`,
`${BASE_URL}/notice-cases/${idOr('NOTICE_CASE_A_ID')}/documents/upload`,
{ form: { csrf_token: '' } }
).catch(() => null);
if (!resp || [404, 405].includes(resp.status())) test.skip(true, 'Route not available');
@@ -179,7 +180,7 @@ test.describe('SVC: Services depth', () => {
test('[V25-SVC-005] SVC-005 Bulk import preview CSRF-less POST is rejected', async ({ request }) => {
const resp = await request.post(
`${process.env.BASE_URL}/services/bulk-imports/service-master`,
`${BASE_URL}/services/bulk-imports/service-master`,
{ form: { csrf_token: '' } }
).catch(() => null);
if (!resp || [404, 405].includes(resp.status())) test.skip(true, 'Route not available');
@@ -203,7 +204,7 @@ test.describe('SVC: Services depth', () => {
test('[V25-SVC-008] SVC-008 Subscription lock CSRF-less POST is rejected', async ({ request }) => {
skipIfMissing('SUBSCRIPTION_A_ID');
const resp = await request.post(
`${process.env.BASE_URL}/services/${idOr('SUBSCRIPTION_A_ID')}/lock`,
`${BASE_URL}/services/${idOr('SUBSCRIPTION_A_ID')}/lock`,
{ form: { csrf_token: '' } }
).catch(() => null);
if (!resp || [404, 405].includes(resp.status())) test.skip(true, 'Route not available');
@@ -212,7 +213,7 @@ test.describe('SVC: Services depth', () => {
test('[V25-SVC-009] SVC-009 Bulk lock CSRF-less POST is rejected', async ({ request }) => {
const resp = await request.post(
`${process.env.BASE_URL}/services/bulk-lock`,
`${BASE_URL}/services/bulk-lock`,
{ form: { csrf_token: '' } }
).catch(() => null);
if (!resp || [404, 405].includes(resp.status())) test.skip(true, 'Route not available');
@@ -222,7 +223,7 @@ test.describe('SVC: Services depth', () => {
test('[V25-SVC-010] SVC-010 Subscription generate CSRF-less POST is rejected', async ({ request }) => {
skipIfMissing('SUBSCRIPTION_A_ID');
const resp = await request.post(
`${process.env.BASE_URL}/services/subscriptions/${idOr('SUBSCRIPTION_A_ID')}/generate`,
`${BASE_URL}/services/subscriptions/${idOr('SUBSCRIPTION_A_ID')}/generate`,
{ form: { csrf_token: '' } }
).catch(() => null);
if (!resp || [404, 405].includes(resp.status())) test.skip(true, 'Route not available');
@@ -245,7 +246,7 @@ test.describe('SVC: Services depth', () => {
test('[V25-SVC-013] SVC-013 Task bulk-update CSRF-less POST is rejected', async ({ request }) => {
const resp = await request.post(
`${process.env.BASE_URL}/services/tasks/bulk-update`,
`${BASE_URL}/services/tasks/bulk-update`,
{ form: { csrf_token: '' } }
).catch(() => null);
if (!resp || [404, 405].includes(resp.status())) test.skip(true, 'Route not available');
@@ -270,7 +271,7 @@ test.describe('WORK: Work detail and task operations', () => {
test('[V25-WORK-002] WORK-002 Task status update CSRF-less POST is rejected', async ({ request }) => {
skipIfMissing('TASK_A_ID');
const resp = await request.post(
`${process.env.BASE_URL}/work/tasks/${idOr('TASK_A_ID')}/status`,
`${BASE_URL}/work/tasks/${idOr('TASK_A_ID')}/status`,
{ form: { status: 'completed', csrf_token: '' } }
).catch(() => null);
if (!resp || [404, 405].includes(resp.status())) test.skip(true, 'Route not available');
@@ -280,7 +281,7 @@ test.describe('WORK: Work detail and task operations', () => {
test('[V25-WORK-003] WORK-003 Task comment POST CSRF-less is rejected', async ({ request }) => {
skipIfMissing('TASK_A_ID');
const resp = await request.post(
`${process.env.BASE_URL}/work/tasks/${idOr('TASK_A_ID')}/comment`,
`${BASE_URL}/work/tasks/${idOr('TASK_A_ID')}/comment`,
{ form: { message: 'test comment', csrf_token: '' } }
).catch(() => null);
if (!resp || [404, 405].includes(resp.status())) test.skip(true, 'Route not available');
@@ -304,7 +305,7 @@ test.describe('WORK: Work detail and task operations', () => {
test('[V25-WORK-006] WORK-006 Task upload CSRF-less POST is rejected', async ({ request }) => {
skipIfMissing('TASK_A_ID');
const resp = await request.post(
`${process.env.BASE_URL}/documents/tasks/${idOr('TASK_A_ID')}/upload`,
`${BASE_URL}/documents/tasks/${idOr('TASK_A_ID')}/upload`,
{ form: { csrf_token: '' } }
).catch(() => null);
if (!resp || [404, 405].includes(resp.status())) test.skip(true, 'Route not available');
@@ -322,7 +323,7 @@ test.describe('WORK: Work detail and task operations', () => {
skipIfMissing('TASK_A_ID');
// Raw API attempt without valid session for a different user
const resp = await request.post(
`${process.env.BASE_URL}/work/tasks/${idOr('TASK_A_ID')}/status`,
`${BASE_URL}/work/tasks/${idOr('TASK_A_ID')}/status`,
{ form: { status: 'completed', csrf_token: 'invalid' } }
).catch(() => null);
if (!resp || [404, 405].includes(resp.status())) test.skip(true, 'Route not available');
+4 -3
View File
@@ -20,6 +20,7 @@
const { test, expect } = require('@playwright/test');
require('dotenv').config();
const { BASE_URL, absoluteUrl } = require('../fixtures/url');
const { login } = require('../fixtures/auth');
const { expectNoBackendError, readBody, blockedOrNotFound } = require('../fixtures/v204-helpers');
const { expectBlockedOrSafe } = require('../fixtures/assertions');
@@ -73,7 +74,7 @@ test.describe('PART: Partner portal', () => {
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`,
`${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');
@@ -169,7 +170,7 @@ test.describe('BILL: Invoices and payments', () => {
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`,
`${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');
@@ -179,7 +180,7 @@ test.describe('BILL: Invoices and payments', () => {
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`,
`${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');
+2 -1
View File
@@ -1,6 +1,7 @@
const { test, expect } = require('@playwright/test');
require('dotenv').config();
const { BASE_URL, absoluteUrl } = require('../fixtures/url');
const { login } = require('../fixtures/auth');
const { expectNoBackendError, readBody, blockedOrNotFound } = require('../fixtures/v204-helpers');
const { expectBlockedOrSafe } = require('../fixtures/assertions');
@@ -31,7 +32,7 @@ async function expectPublicNoSensitive(page) {
}
async function apiCall(request, method, route) {
const url = `${process.env.BASE_URL}${route}`;
const url = absoluteUrl(route);
const opts = { data: { csrf_token: '', test_payload: 'uat-vapt' }, headers: { 'Content-Type': 'application/json' } };
if (method === 'GET') return await request.get(url).catch(() => null);
if (method === 'POST') return await request.post(url, opts).catch(() => null);
+2 -1
View File
@@ -1,6 +1,7 @@
const { test, expect } = require('@playwright/test');
require('dotenv').config();
const { BASE_URL, absoluteUrl } = require('../fixtures/url');
const { login } = require('../fixtures/auth');
const { expectNoBackendError, readBody, blockedOrNotFound } = require('../fixtures/v204-helpers');
const { expectBlockedOrSafe } = require('../fixtures/assertions');
@@ -31,7 +32,7 @@ async function expectPublicNoSensitive(page) {
}
async function apiCall(request, method, route) {
const url = `${process.env.BASE_URL}${route}`;
const url = absoluteUrl(route);
const opts = { data: { csrf_token: '', test_payload: 'uat-vapt' }, headers: { 'Content-Type': 'application/json' } };
if (method === 'GET') return await request.get(url).catch(() => null);
if (method === 'POST') return await request.post(url, opts).catch(() => null);
+8 -7
View File
@@ -1,5 +1,6 @@
const { test, expect, request } = require('@playwright/test');
require('dotenv').config();
const { BASE_URL, absoluteUrl } = require('../fixtures/url');
const { login, fillFirst, clickFirst } = require('../fixtures/auth');
const { expectBlockedOrSafe, expectCookieFlags, expectSecurityHeaders } = require('../fixtures/assertions');
const {
@@ -17,7 +18,7 @@ const tenantBName = process.env.TENANT_B_NAME || 'UAT Tenant B';
test.describe('v2.0.4 additional security / FY / context checks', () => {
test('V204-SEC-001 forgot-password API must not expose reset token', async () => {
const api = await request.newContext({ baseURL: process.env.BASE_URL });
const api = await request.newContext({ baseURL: BASE_URL });
const email = process.env.FIRM_ADMIN_EMAIL || process.env.SYSTEM_ADMIN_EMAIL || 'admin@auditfirm.local';
const candidates = [
{ url: '/auth/forgot-password', opts: { data: { email } } },
@@ -39,7 +40,7 @@ test.describe('v2.0.4 additional security / FY / context checks', () => {
});
test('V204-SEC-002 reset-password with random token is rejected safely', async () => {
const api = await request.newContext({ baseURL: process.env.BASE_URL });
const api = await request.newContext({ baseURL: BASE_URL });
const resp = await api.post('/auth/reset-password', {
data: { token: 'invalid-token-for-vapt', password: 'NewPassword@123' },
}).catch(() => null);
@@ -49,7 +50,7 @@ test.describe('v2.0.4 additional security / FY / context checks', () => {
});
test('V204-SEC-003 API token/login brute force attempts do not create 500 errors', async () => {
const api = await request.newContext({ baseURL: process.env.BASE_URL });
const api = await request.newContext({ baseURL: BASE_URL });
const email = process.env.SYSTEM_ADMIN_EMAIL || 'admin@auditfirm.local';
for (let i = 0; i < 6; i++) {
const resp = await api.post('/auth/token', {
@@ -63,7 +64,7 @@ test.describe('v2.0.4 additional security / FY / context checks', () => {
test('V204-CTX-001 public tenant/branch/FY headers are ignored without secret', async ({ browser }) => {
const context = await browser.newContext({
baseURL: process.env.BASE_URL,
baseURL: BASE_URL,
extraHTTPHeaders: {
'X-Tenant-Code': process.env.TENANT_B_CODE || 'UAT-B',
'X-Branch-Code': process.env.BRANCH_B_CODE || 'UAT-BB',
@@ -82,7 +83,7 @@ test.describe('v2.0.4 additional security / FY / context checks', () => {
test('V204-CTX-002 wrong context secret must not enable spoofed headers', async ({ browser }) => {
const context = await browser.newContext({
baseURL: process.env.BASE_URL,
baseURL: BASE_URL,
extraHTTPHeaders: {
'X-Tenant-Code': process.env.TENANT_B_CODE || 'UAT-B',
'X-Branch-Code': process.env.BRANCH_B_CODE || 'UAT-BB',
@@ -106,7 +107,7 @@ test.describe('v2.0.4 additional security / FY / context checks', () => {
}
if (!process.env.CONTEXT_HEADER_SECRET) test.fail(true, 'TRUST_CONTEXT_HEADERS=true but CONTEXT_HEADER_SECRET is empty');
const context = await browser.newContext({
baseURL: process.env.BASE_URL,
baseURL: BASE_URL,
extraHTTPHeaders: {
'X-Tenant-Code': process.env.TENANT_A_CODE || 'UAT-A',
'X-Branch-Code': process.env.BRANCH_A_CODE || 'UAT-BA',
@@ -222,7 +223,7 @@ test.describe('v2.0.4 additional security / FY / context checks', () => {
});
test('V204-STORAGE-001 storage agent endpoints require node authentication', async () => {
const api = await request.newContext({ baseURL: process.env.BASE_URL });
const api = await request.newContext({ baseURL: BASE_URL });
const endpoints = [
'/documents/storage-agent/jobs/pending',
'/documents/storage-agent/download-requests/pending',
+2 -1
View File
@@ -1,5 +1,6 @@
const { test, expect, request } = require('@playwright/test');
require('dotenv').config();
const { BASE_URL, absoluteUrl } = require('../fixtures/url');
const { login, fillFirst, clickFirst } = require('../fixtures/auth');
const { expectBlockedOrSafe, expectNoServerError, expectSecurityHeaders } = require('../fixtures/assertions');
@@ -14,7 +15,7 @@ test('VAPT-TARGET-002 unauthorized protected URL redirects or blocks', async ({
});
test('VAPT-TARGET-003 CSRF negative request should not create case', async () => {
const api = await request.newContext({ baseURL: process.env.BASE_URL });
const api = await request.newContext({ baseURL: BASE_URL });
const resp = await api.post('/notice-cases/new', { form: { title: 'csrf-negative', reference_no: 'CSRF-NEG' } });
expect([400,401,403,404,405,422,303].includes(resp.status())).toBeTruthy();
});
+2 -1
View File
@@ -1,6 +1,7 @@
const { test, expect } = require('@playwright/test');
require('dotenv').config();
const { BASE_URL, absoluteUrl } = require('../fixtures/url');
const { login } = require('../fixtures/auth');
const { expectNoBackendError, readBody, blockedOrNotFound } = require('../fixtures/v204-helpers');
const { expectBlockedOrSafe } = require('../fixtures/assertions');
@@ -31,7 +32,7 @@ async function expectPublicNoSensitive(page) {
}
async function apiCall(request, method, route) {
const url = `${process.env.BASE_URL}${route}`;
const url = absoluteUrl(route);
const opts = { data: { csrf_token: '', test_payload: 'uat-vapt' }, headers: { 'Content-Type': 'application/json' } };
if (method === 'GET') return await request.get(url).catch(() => null);
if (method === 'POST') return await request.post(url, opts).catch(() => null);