Skip to content

Commit

Permalink
test: e2e tests for BTC swaps (#655)
Browse files Browse the repository at this point in the history
Co-authored-by: michael1011 <[email protected]>
  • Loading branch information
maybeast and michael1011 authored Aug 1, 2024
1 parent 1a91eb6 commit 0fe7bcf
Show file tree
Hide file tree
Showing 12 changed files with 445 additions and 19 deletions.
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

0 comments on commit 0fe7bcf

Please sign in to comment.