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
1 change: 1 addition & 0 deletions .envrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
use flake
62 changes: 62 additions & 0 deletions .github/actions/nix.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
name: Setup Nix
description: Fast, cached Nix setup (flakes) for devimint + E2E tests

inputs:
cachix-auth-token:
description: Optional auth token to PUSH to fedimint Cachix (read-only pulls need none)
required: false
enable-fedimint-cache:
description: 'Enable fedimint public Cachix cache (true/false)'
default: 'true'
required: false
print-config:
description: 'Print nix show-config for debugging (true/false)'
default: 'false'
required: false

runs:
using: composite
steps:
- name: Install Nix (Determinate Systems)
uses: DeterminateSystems/nix-installer-action@v12
with:
extra-conf: |
experimental-features = nix-command flakes
# Reasonable timeouts to fail fast in CI
connect-timeout = 15
stalled-download-timeout = 15

- name: Enable common binary caches (magic-nix-cache)
uses: DeterminateSystems/magic-nix-cache-action@v6

- name: Add fedimint Cachix (pull or push)
if: inputs.enable-fedimint-cache == 'true'
uses: cachix/cachix-action@v15
with:
name: fedimint
authToken: ${{ inputs.cachix-auth-token }}
# Even if push fails (no token) we still want read access
continue-on-error: true

- name: Show Nix config (optional)
if: inputs.print-config == 'true'
shell: bash
run: |
echo '--- nix show-config (filtered) ---'
nix show-config | grep -E 'substituters|trusted-public-keys|experimental-features'
echo '--- nix path-info devShell dry-run (if flake present) ---'
if [ -f flake.nix ]; then
nix build .#devShells.$(nix eval --raw --expr 'builtins.currentSystem').default --dry-run 2>&1 | sed -n '1,120p'
fi

- name: Summary
shell: bash
run: |
echo "Nix setup complete." >> $GITHUB_STEP_SUMMARY
echo "Fedimint cache: ${{ inputs.enable-fedimint-cache }}" >> $GITHUB_STEP_SUMMARY
if [ -n "${{ inputs.cachix-auth-token }}" ]; then
echo "Fedimint cache push: enabled (token provided)" >> $GITHUB_STEP_SUMMARY
else
echo "Fedimint cache push: not enabled (no token)" >> $GITHUB_STEP_SUMMARY
fi

10 changes: 10 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
version: 2
updates:
- package-ecosystem: github-actions
directory: '/'
schedule:
interval: daily
- package-ecosystem: 'npm'
directory: '/'
schedule:
interval: 'daily'
22 changes: 16 additions & 6 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,31 @@ concurrency:
cancel-in-progress: true

jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'
- run: npm ci
- run: npm test -- --ci --runInBand

deploy:
needs: test
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
- name: Checkout Repo
uses: actions/checkout@v4

- name: Setup Node.js
- name: Setup Nodejs
uses: actions/setup-node@v4
with:
node-version: 20
cache: "npm"

cache: 'npm'
- name: Install Dependencies
run: npm install

Expand Down
27 changes: 27 additions & 0 deletions .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: E2E Tests
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
test:
timeout-minutes: 60
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: lts/*
- name: Install dependencies
run: npm install
- name: Install Playwright Browsers
run: npx playwright install --with-deps
- name: Run Playwright tests
run: npx playwright test
- uses: actions/upload-artifact@v5
if: ${{ !cancelled() }}
with:
name: playwright-report
path: playwright-report/
retention-days: 30
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,9 @@ dist-ssr
*.njsproj
*.sln
*.sw?

# Playwright
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,9 @@ fedimint-web-wallet/

- Clone the repository

git clone https://github.com/Harshdev098/fedimint-web-wallet.git
```
git clone https://github.com/Harshdev098/fedimint-web-wallet.git
```

- Install the dependencies

Expand Down
1 change: 1 addition & 0 deletions __mocks__/fileMock.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default 'test-file-stub';
15 changes: 15 additions & 0 deletions e2e/faucet.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { test, expect } from '@playwright/test';
import { TestFaucet } from './setupTest/TestFaucet';

test.describe('FaucetService', () => {
test('creates a faucet invoice and returns a valid BOLT11 string', async () => {
const faucet = new TestFaucet();
const amountSat = 1000;

const invoice = await faucet.createFaucetInvoice(amountSat);

expect(typeof invoice).toBe('string');
expect(invoice.length).toBeGreaterThan(10);
expect(invoice).toMatch(/^ln[a-z0-9]+/i);
});
});
46 changes: 46 additions & 0 deletions e2e/setupTest/TestFaucet.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
export class TestFaucet {
FAUCET_URL = 'http://127.0.0.1:15243';

async getInviteCode() {
const res = await fetch(`${this.FAUCET_URL}/connect-string`);
if (res.ok) {
return await res.text();
} else {
throw new Error(`Failed to get invite code: ${await res.text()}`);
}
}

async getFaucetGatewayApi() {
const res = await fetch(`${this.FAUCET_URL}/gateway-api`);
if (res.ok) {
return await res.text();
} else {
throw new Error(`Failed to get gateway: ${await res.text()}`);
}
}

async payFaucetInvoice(invoice: string) {
const res = await fetch(`${this.FAUCET_URL}/pay`, {
method: 'POST',
body: invoice.trim(),
headers: { 'Content-Type': 'text/plain' },
});
if (res.ok) {
return await res.text();
} else {
throw new Error(`Failed to pay faucet invoice: ${await res.text()}`);
}
}

async createFaucetInvoice(amount: number) {
const res = await fetch(`${this.FAUCET_URL}/invoice`, {
method: 'POST',
body: amount.toString(),
});
if (res.ok) {
return await res.text();
} else {
throw new Error(`Failed to generate faucet invoice: ${await res.text()}`);
}
}
}
62 changes: 62 additions & 0 deletions e2e/wallet.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { test, expect } from '@playwright/test';
import { TestFaucet } from './setupTest/TestFaucet';

test.setTimeout(120_000);

test.describe('Federation Join + Lightning Flow', () => {
let faucet: TestFaucet;

test.beforeEach(() => {
faucet = new TestFaucet();
});

test('join federation and do a receive/send cycle', async ({ page }) => {
await page.goto('/');
await expect(page).toHaveTitle(/Fedimint/);

// Step 1: Join federation
await test.step('Join federation with invite code', async () => {
const inviteCode = await faucet.getInviteCode();
expect(inviteCode).toBeTruthy();

await page.getByTestId('invite-code-input').fill(inviteCode);
await page.getByTestId('continue-button').click();

await expect(page.getByTestId('federation-title')).toContainText('Fedi Testnet');
await expect(page.getByTestId('wallet-main')).toContainText('Onchain deposit:');
});

// Step 2: Create invoice
let invoice: string;
await test.step('Create invoice', async () => {
await page.getByRole('button', { name: /Receive/ }).click();
await page.getByTestId('amount-input').fill('2');
await page.getByTestId('description-input').fill('this is an invoice');
await page.getByRole('button', { name: /Create/ }).click();

invoice = await page.getByTestId('invoice-output').inputValue();
expect(invoice).toMatch(/^ln/i);
});

// Step 3: Pay invoice via faucet
await test.step('Pay invoice using faucet', async () => {
await faucet.payFaucetInvoice(invoice);
await expect(page.getByText(/Payment Received/)).toBeVisible();
});

// Step 4: Verify balance increased
await test.step('Verify balance', async () => {
await expect(page.getByTestId('wallet-balance')).toContainText('2');
});

await page.getByRole('button', { name: ' Ecash' }).click();

await page.getByRole('textbox', { name: 'Enter amount in sat:' }).fill('1');
await page.getByRole('button', { name: ' Generate & Spend' }).click();

const notes = await page.getByRole('textbox', { name: 'Generated notes:' }).inputValue();

await page.getByRole('textbox', { name: 'Enter or Scan the notes:' }).fill(notes);
await page.getByRole('button', { name: ' Confirm Redeem' }).click();
});
});
Loading
Loading