Some checks failed
E2E Tests / test (push) Has been cancelled
Copies the Playwright end-to-end suite out of the PassDashboard app repo so it can be run and deployed independently. Nothing was removed from the app repo - this is a copy. Carried over verbatim: tests/ (10 spec files, 4 helper modules, README) plus playwright.config.ts and .env.test.example. The suite drives the deployed app over HTTP and imports nothing from src/, which is what makes it standalone. New for this repo: - package.json with only the deps the suite actually uses (@playwright/test, allure-playwright, xlsx, typescript, @types/node) and the e2e scripts. - tsconfig.json covering tests/ - the app repo's tsconfig.app.json only included src/, so these files were never typechecked before. - .gitea/workflows/e2e-tests.yml for Gitea Actions. The app repo derived ENV from github.ref_name; that doesn't apply here since this repo has no per-environment branches, so the target is an explicit workflow_dispatch input and scheduled/push runs default to develop. Reports are uploaded as artifacts rather than deleted. - .gitignore and README covering local setup and the required CI secrets. The Jest unit tests under src/__tests__/ were deliberately left in the app repo: they import application source directly (AuthContext, ProtectedRoute, cryptoUtils, api) and cannot run without it. Verified: npx tsc --noEmit is clean and playwright collects all 43 tests across all 10 spec files. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
100 lines
5.1 KiB
TypeScript
100 lines
5.1 KiB
TypeScript
import { test, expect } from '@playwright/test';
|
|
import { login } from './helpers/auth';
|
|
import { epic, feature, severity, description, tag } from './helpers/allure';
|
|
import { navigateToSettings } from './helpers/navigation';
|
|
|
|
test.describe('Settings Tests', () => {
|
|
test.setTimeout(90000);
|
|
test.beforeEach(async ({ page }) => {
|
|
epic('Settings');
|
|
await login(page);
|
|
await navigateToSettings(page);
|
|
await page.waitForTimeout(2000);
|
|
});
|
|
|
|
test('should display Discount Rate Configuration for the Discounts settings tab', async ({ page }) => {
|
|
feature('Settings Operations');
|
|
tag('functional');
|
|
severity('normal');
|
|
description('Open the Discount rates settings tab and verify the configuration table renders correctly, whether rates are configured or not');
|
|
|
|
console.log('Testing Discount rates settings tab...');
|
|
|
|
await test.step('Open Discount rates tab', async () => {
|
|
const discountRatesTab = page.getByText('Discount rates', { exact: true }).first();
|
|
await expect(discountRatesTab, 'Discount rates tab should be visible').toBeVisible({ timeout: 10000 });
|
|
await discountRatesTab.click();
|
|
await page.waitForTimeout(1500);
|
|
console.log('✓ Opened Discount rates');
|
|
});
|
|
|
|
await test.step('Verify the Discount Rate Configuration table', async () => {
|
|
const ratesTable = page.locator('xpath=//*[contains(text(),"Discount Rate Configuration")]/following::table[1]').first();
|
|
await expect(ratesTable, 'Discount Rate Configuration table should be visible').toBeVisible({ timeout: 10000 });
|
|
|
|
const headers = await ratesTable.locator('thead th').allTextContents();
|
|
console.log('Headers:', JSON.stringify(headers));
|
|
let cardIdx = -1, mdrIdx = -1, vatIdx = -1, capIdx = -1, feeIdx = -1;
|
|
for (let i = 0; i < headers.length; i++) {
|
|
const t = headers[i].trim().toLowerCase();
|
|
if (t === 'card') cardIdx = i;
|
|
else if (t === 'mdr') mdrIdx = i;
|
|
else if (t === 'vat') vatIdx = i;
|
|
else if (t.includes('cap')) capIdx = i;
|
|
else if (t.includes('processing') || t === 'fee') feeIdx = i;
|
|
}
|
|
expect(cardIdx, 'A Card column should exist').toBeGreaterThanOrEqual(0);
|
|
expect(mdrIdx, 'An MDR column should exist').toBeGreaterThanOrEqual(0);
|
|
|
|
// Real data rows only - excludes the hidden measure row (aria-hidden)
|
|
// and an empty-state "No data" placeholder row (both previously got
|
|
// miscounted by earlier code as bogus zero-value rate entries).
|
|
const rows = ratesTable.locator('tbody tr:not([aria-hidden="true"])');
|
|
const rowCount = await rows.count();
|
|
console.log(`Row count (excluding hidden measure row): ${rowCount}`);
|
|
|
|
const bodyText = await ratesTable.locator('tbody').innerText();
|
|
const isEmptyState = /no data/i.test(bodyText) && rowCount <= 1;
|
|
|
|
if (isEmptyState) {
|
|
console.log('✓ No discount rates configured - "No data" empty state correctly shown');
|
|
// Scope to a real text-bearing element - a plain `text=/no data/i`
|
|
// locator also matches a hidden SVG <title> icon label with the same
|
|
// text, which is never actually visible.
|
|
await expect(ratesTable.locator('article, span, div').filter({ hasText: /^No data$/i }).first(), 'Empty state should show "No data"').toBeVisible();
|
|
} else {
|
|
console.log(`✓ Found ${rowCount} configured discount rate row(s), validating each`);
|
|
expect(rowCount, 'There should be at least one configured rate row').toBeGreaterThan(0);
|
|
|
|
// Read a numeric value the same safe way used for the Discounts page
|
|
// breakdown table: the value sits in the last <article> in the cell,
|
|
// separate from an embedded currency/percent icon's <style> content.
|
|
const readCellNum = async (row: import('@playwright/test').Locator, idx: number): Promise<number> => {
|
|
if (idx < 0) return 0;
|
|
const cell = row.locator('td').nth(idx);
|
|
const valArticle = cell.locator('article').last();
|
|
const txt = (await valArticle.textContent().catch(() => '')) || (await cell.textContent().catch(() => '')) || '';
|
|
const n = parseFloat(txt.replace(/[^0-9.\-]/g, ''));
|
|
return isNaN(n) ? 0 : n;
|
|
};
|
|
|
|
for (let r = 0; r < rowCount; r++) {
|
|
const row = rows.nth(r);
|
|
const cardText = (await row.locator('td').nth(cardIdx).innerText().catch(() => '')).trim();
|
|
const mdr = await readCellNum(row, mdrIdx);
|
|
const vat = vatIdx >= 0 ? await readCellNum(row, vatIdx) : 0;
|
|
const cap = capIdx >= 0 ? await readCellNum(row, capIdx) : 0;
|
|
const fee = feeIdx >= 0 ? await readCellNum(row, feeIdx) : 0;
|
|
console.log(` ${cardText}: MDR ${mdr}% | VAT ${vat}% | Cap ${cap} | Fee ${fee}%`);
|
|
|
|
expect(cardText.length, `Row ${r + 1} should have a card scheme name`).toBeGreaterThan(0);
|
|
expect(mdr, `Row ${r + 1} (${cardText}) MDR should be a plausible percentage`).toBeGreaterThanOrEqual(0);
|
|
expect(mdr, `Row ${r + 1} (${cardText}) MDR should be a plausible percentage`).toBeLessThanOrEqual(100);
|
|
}
|
|
}
|
|
});
|
|
|
|
console.log('✓ Discount rates settings test completed');
|
|
});
|
|
});
|