Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test: e2e tests for BTC swaps #655

Merged
merged 12 commits into from
Aug 1, 2024
Merged
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
69 changes: 56 additions & 13 deletions .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,59 @@ name: CI
on: [push, pull_request]

jobs:
ci:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Use Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
cache: "npm"
- run: npm ci
- run: npm run prettier-check
- run: npm run test
- run: npm run tsc
ci:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

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

- run: npm ci

- run: npm run prettier-check

- run: npm run test

- run: npm run tsc

e2e:
timeout-minutes: 60
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: lts/*

- name: Start regtest
env:
COMPOSE_PROFILES: ci
run: |
git submodule init
git submodule update
chmod -R 777 regtest
cd regtest
./start.sh

- name: Install dependencies
run: npm ci

- name: Install Playwright Browsers
run: npm run playwright:install

- name: Run Playwright tests
env:
CI: true
run: npm run test:e2e

- uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report
path: playwright-report/
retention-days: 30
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,7 @@ ts-out/
coverage/
node_modules/
public/config.json
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "regtest"]
path = regtest
url = https://github.com/BoltzExchange/regtest.git
61 changes: 61 additions & 0 deletions e2e/chainSwaps.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { expect, test } from "@playwright/test";

import {
elementsSendToAddress,
generateBitcoinBlock,
generateLiquidBlock,
getBitcoinAddress,
} from "./utils";

test.describe("Chain swap", () => {
test.beforeEach(async () => {
await generateBitcoinBlock();
});

test("BTC/L-BTC", async ({ page }) => {
await page.goto("/");

const assetSelector = page.locator("div[class='asset asset-LN'] div");
await assetSelector.click();

const lbtcAsset = page.locator("div[data-testid='select-L-BTC']");
await lbtcAsset.click();

const receiveAmount = "0.01";
const inputReceiveAmount = page.locator(
"input[data-testid='receiveAmount']",
);
await inputReceiveAmount.fill(receiveAmount);

const inputSendAmount = page.locator("input[data-testid='sendAmount']");
const sendAmount = "0.0100168";
await expect(inputSendAmount).toHaveValue(sendAmount);

const inputOnchainAddress = page.locator(
"input[data-testid='onchainAddress']",
);
await inputOnchainAddress.fill(await getBitcoinAddress());

const buttonCreateSwap = page.locator(
"button[data-testid='create-swap-button']",
);
await buttonCreateSwap.click();

const skipDownload = page.getByText("Skip download");
await skipDownload.click();

const buttons = page.locator("div[data-testid='pay-onchain-buttons']");
const copyAddressButton = buttons.getByText("address");
expect(copyAddressButton).toBeDefined();
await copyAddressButton.click();

const sendAddress = await page.evaluate(() => {
return navigator.clipboard.readText();
});
expect(sendAddress).toBeDefined();

await elementsSendToAddress(sendAddress, sendAmount);
await generateLiquidBlock();
// TODO: verify amounts
});
});
62 changes: 62 additions & 0 deletions e2e/reverseSwap.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { expect, test } from "@playwright/test";

import {
generateBitcoinBlock,
getBitcoinAddress,
getBitcoinWalletTx,
payInvoiceLnd,
} from "./utils";

test.describe("reverseSwap", () => {
test.beforeEach(async () => {
await generateBitcoinBlock();
});

test("Reverse swap BTC/BTC", async ({ page }) => {
await page.goto("/");

const receiveAmount = "0.01";
const inputReceiveAmount = page.locator(
"input[data-testid='receiveAmount']",
);
await inputReceiveAmount.fill(receiveAmount);

const inputSendAmount = page.locator("input[data-testid='sendAmount']");
await expect(inputSendAmount).toHaveValue("0.01005558");

const inputOnchainAddress = page.locator(
"input[data-testid='onchainAddress']",
);
await inputOnchainAddress.fill(await getBitcoinAddress());

const buttonCreateSwap = page.locator(
"button[data-testid='create-swap-button']",
);
await buttonCreateSwap.click();

const payInvoiceTitle = page.locator(
"h2[data-testid='pay-invoice-title']",
);
await expect(payInvoiceTitle).toHaveText(
"Pay this invoice about 0.01005558 BTC",
);

const spanLightningInvoice = page.locator("span[class='btn']");
await spanLightningInvoice.click();

const lightningInvoice = await page.evaluate(() => {
return navigator.clipboard.readText();
});
expect(lightningInvoice).toBeDefined();

await payInvoiceLnd(lightningInvoice);

const txIdLink = page.getByText("open claim transaction");

const txId = (await txIdLink.getAttribute("href")).split("/").pop();
expect(txId).toBeDefined();

const txInfo = JSON.parse(await getBitcoinWalletTx(txId));
expect(txInfo.amount.toString()).toEqual(receiveAmount);
});
});
55 changes: 55 additions & 0 deletions e2e/submarineSwap.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { expect, test } from "@playwright/test";

import {
bitcoinSendToAddress,
generateBitcoinBlock,
generateInvoiceLnd,
} from "./utils";

test.describe("Submarine swap", () => {
test.beforeEach(async () => {
await generateBitcoinBlock();
});

test("Submarine swap BTC/BTC", async ({ page }) => {
await page.goto("/");

const divFlipAssets = page.locator("#flip-assets");
await divFlipAssets.click();

const receiveAmount = "0.01";
const inputReceiveAmount = page.locator(
"input[data-testid='receiveAmount']",
);
await inputReceiveAmount.fill(receiveAmount);

const inputSendAmount = page.locator("input[data-testid='sendAmount']");
const sendAmount = "0.01005302";
await expect(inputSendAmount).toHaveValue(sendAmount);

const invoiceInput = page.locator("textarea[data-testid='invoice']");
await invoiceInput.fill(
JSON.parse(await generateInvoiceLnd(1000000)).payment_request,
);
const buttonCreateSwap = page.locator(
"button[data-testid='create-swap-button']",
);
await buttonCreateSwap.click();

const skipDownload = page.getByText("Skip download");
await skipDownload.click();

const copyAddressButton = page.getByText("address");
expect(copyAddressButton).toBeDefined();
await copyAddressButton.click();

const sendAddress = await page.evaluate(() => {
return navigator.clipboard.readText();
});
expect(sendAddress).toBeDefined();
await bitcoinSendToAddress(sendAddress, sendAmount);

await generateBitcoinBlock();
// TODO: verify amounts
});
});
67 changes: 67 additions & 0 deletions e2e/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { exec } from "child_process";
import { promisify } from "util";

const execAsync = promisify(exec);

const executeInScriptsContainer =
'docker exec boltz-scripts bash -c "source /etc/profile.d/utils.sh && ';

const execCommand = async (command: string): Promise<string> => {
try {
const { stdout, stderr } = await execAsync(
`${executeInScriptsContainer}${command}"`,
{ shell: "/bin/bash" },
);

if (stderr) {
throw new Error(`Error executing command: ${stderr}`);
}

return stdout.trim();
} catch (error) {
console.error(`Failed to execute command: ${command}`, error);
throw error;
}
};

export const getBitcoinAddress = async (): Promise<string> => {
return execCommand("bitcoin-cli-sim-client getnewaddress");
};

export const bitcoinSendToAddress = async (
address: string,
amount: string,
): Promise<string> => {
return execCommand(
`bitcoin-cli-sim-client sendtoaddress "${address}" ${amount}`,
);
};

export const elementsSendToAddress = async (
address: string,
amount: string,
): Promise<string> => {
return execCommand(
`elements-cli-sim-client sendtoaddress "${address}" ${amount}`,
);
};

export const generateBitcoinBlock = async (): Promise<string> => {
return execCommand("bitcoin-cli-sim-client -generate");
};

export const generateLiquidBlock = async (): Promise<string> => {
return execCommand("elements-cli-sim-client -generate");
};

export const getBitcoinWalletTx = async (txId: string): Promise<string> => {
return execCommand(`bitcoin-cli-sim-client gettransaction ${txId}`);
};

export const payInvoiceLnd = async (invoice: string): Promise<string> => {
return execCommand(`lncli-sim 1 payinvoice -f ${invoice}`);
};

export const generateInvoiceLnd = async (amount: number): Promise<string> => {
return execCommand(`lncli-sim 1 addinvoice --amt ${amount}`);
};
Loading