Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,38 @@ jobs:
run: npm run build
working-directory: frontend

e2e:
needs: [supply-chain-audit, frontend]
runs-on: ubuntu-latest
if: github.event_name == 'push' || contains(github.event.pull_request.changed_files, 'frontend/')
steps:
- uses: actions/checkout@v4
- name: Use Node.js 20
uses: actions/setup-node@v4
with:
node-version: 20
cache: "npm"
cache-dependency-path: frontend/package-lock.json
- name: Install dependencies
run: npm ci
working-directory: frontend
- name: Install Playwright browsers
run: npx playwright install --with-deps chromium
working-directory: frontend
- name: Run e2e tests
run: npm run test:e2e
working-directory: frontend
env:
NODE_ENV: test
CI: true
- name: Upload Playwright report
if: failure()
uses: actions/upload-artifact@v4
with:
name: playwright-report
path: frontend/playwright-report/
retention-days: 7

env-docs-check:
runs-on: ubuntu-latest
steps:
Expand Down
122 changes: 2 additions & 120 deletions frontend/e2e/criticalFlows.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,69 +59,8 @@ test.beforeEach(async ({ page }: { page: Page }) => {
});
});

// ─── Flow 1: Loan Wizard ───────────────────────────────────────────────────────

test("Borrow: Connect wallet → Request Loan → Wizard steps", async ({ page }: { page: Page }) => {
// Mock Loan Config (min score, etc)
await page.route("**/api/loans/config", async (route: Route) => {
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify({
success: true,
data: { minScore: 500, maxAmount: 10000, interestRatePercent: 8 },
}),
});
});

// Mock User Credit Score
await page.route("**/api/score/*", async (route: Route) => {
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify({ success: true, score: 715 }),
});
});

await page.goto("/en"); // Explicitly go to English locale

// Navigate to Loan Wizard (via Apply button in quick actions)
// Text matches HomePage.quickActions.applyLoan in en.json
const applyBtn = page.getByRole("button", { name: /Apply for Loan/i });
await applyBtn.waitFor();
await applyBtn.click();

// Step 1: Amount & Asset
await expect(page.locator("text=Loan Amount")).toBeVisible();
await page.selectOption('select[name="asset"]', "USDC");
await page.fill('input[placeholder="0.00"]', "1000");
const continueToCollateral = page.getByRole("button", { name: /Continue to Collateral/i });
await continueToCollateral.click();

// Step 2: Collateral & NFT Link
await expect(page.locator("text=Collateral & NFT Link")).toBeVisible();
await page.click('input[type="checkbox"]'); // Accept terms
const continueToSignature = page.getByRole("button", { name: /Continue to Signature/i });
await continueToSignature.click();

// Step 3: Transaction Signature
await expect(page.locator("text=Ready to Sign")).toBeVisible();

// Mock creation request
await page.route("**/api/loans", async (route: Route) => {
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify({ id: "loan_123", status: "pending", txHash: "tx_abc" }),
});
});

await page.click('button:has-text("Sign & Submit Application")');

// Success view
await expect(page.locator("text=Application Submitted")).toBeVisible();
await expect(page.locator("text=Reviewing your application")).toBeVisible();
});
// Loan wizard and repay flows removed: covered by borrower-loan-flow.spec.ts
// and borrower-repay-flow.spec.ts with a single consistent set of route mocks.

// ─── Flow 2: Lending Pool ──────────────────────────────────────────────────────

Expand Down Expand Up @@ -168,63 +107,6 @@ test("Lend: Deposit funds → View updated pool stats", async ({ page }: { page:
await expect(page.locator("text=1,002,500")).toBeVisible();
});

// ─── Flow 3: Repayment ─────────────────────────────────────────────────────────

test("Borrower: Repay loan → Confirm transaction → Check status update", async ({
page,
}: {
page: Page;
}) => {
// Mock existing loans for borrower
await page.route("**/api/loans/borrower/**", async (route: Route) => {
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify({
success: true,
data: {
borrower: MOCK_ADDRESS,
loans: [
{
id: 123,
principal: 1000,
totalOwed: 500,
status: "active",
nextPaymentDeadline: "2026-12-31T00:00:00Z",
},
],
},
}),
});
});

await page.goto("/en");

// Click repay on the specific loan (assuming dashboard has a 'Repay' button in the loans list or card)
const repayBtn = page.getByRole("button", { name: "Repay" }).first();
await repayBtn.click();

// Perform repayment
await expect(page.locator("text=Repayment Amount")).toBeVisible();
await page.fill('input[type="number"]', "500");

// Mock repayment finish
await page.route("**/api/loans/123/repay", async (route: Route) => {
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify({ success: true, txHash: "tx_repay" }),
});
});

await page.click('button:has-text("Review Repayment")');
await page.click('button:has-text("Confirm Payment")'); // assuming it's in the preview modal

// Success message
await expect(page.locator("text=Progress")).toBeVisible(); // transaction progress
await expect(page.locator("text=Repayment Successful")).toBeVisible();
});

// ─── Flow 4: Remittance History ────────────────────────────────────────────────

test("Remittance: View history", async ({ page }: { page: Page }) => {
Expand Down
17 changes: 17 additions & 0 deletions frontend/messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,23 @@
"title": "Community Outreach",
"description": "New borrowers in Ghana are looking for micro-loans for agricultural tools. Help grow the ecosystem!",
"explore": "Explore Opportunities"
},
"disconnected": {
"heading": "Dashboard",
"subheading": "Welcome to RemitLend. Please connect your wallet to view your portfolio.",
"walletNotConnected": "Wallet Not Connected",
"connectPrompt": "Connect your wallet to analyze your on-chain credit score, manage active positions, and track cross-border remittances.",
"connectButton": "Connect Wallet"
},
"reminder": {
"due": "Repayment due in {hours}h — Loan #{loanId}",
"repayNow": "Repay now",
"dismiss": "Dismiss repayment reminder"
},
"creditScore": {
"label": "Credit Score",
"tooltip": "Credit Score: An on-chain signal of repayment reliability. Higher scores can unlock better terms over time.",
"tooltipLabel": "Credit score info"
}
},
"ActivityPage": {
Expand Down
17 changes: 17 additions & 0 deletions frontend/messages/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,23 @@
"title": "Alcance comunitario",
"description": "Nuevos prestatarios en Ghana buscan micropréstamos para herramientas agrícolas. ¡Ayuda a hacer crecer el ecosistema!",
"explore": "Explorar oportunidades"
},
"disconnected": {
"heading": "Tablero",
"subheading": "Bienvenido a RemitLend. Por favor conecta tu billetera para ver tu portafolio.",
"walletNotConnected": "Billetera no conectada",
"connectPrompt": "Conecta tu billetera para analizar tu puntuación crediticia on-chain, gestionar posiciones activas y rastrear remesas internacionales.",
"connectButton": "Conectar billetera"
},
"reminder": {
"due": "Pago pendiente en {hours}h — Préstamo #{loanId}",
"repayNow": "Pagar ahora",
"dismiss": "Descartar recordatorio de pago"
},
"creditScore": {
"label": "Puntuación crediticia",
"tooltip": "Puntuación crediticia: una señal on-chain de confiabilidad de pago. Las puntuaciones más altas pueden desbloquear mejores condiciones con el tiempo.",
"tooltipLabel": "Información sobre puntuación crediticia"
}
},
"ActivityPage": {
Expand Down
Loading
Loading