Skip to content

Commit

Permalink
feature: Playwright e2e initial (#77)
Browse files Browse the repository at this point in the history
* initial

* rm fixtures

* retries and workers

* connect wallet

* setup wallet connection

* rm unused test suite

* injectbtc

* split

* rm dotenv

* rm process.env.ci

* Balance and address checks after connection

* Create staking transaction

* STAKING_AMOUNT constant

* constants
  • Loading branch information
gbarkhatov authored Sep 10, 2024
1 parent ac2bbd0 commit 3f88fad
Show file tree
Hide file tree
Showing 12 changed files with 358 additions and 3 deletions.
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -259,3 +259,12 @@ yarn-error.log*
next-env.d.ts

.env

# E2E Playwright
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/

# E2E Chrome Extensions
extensions
8 changes: 8 additions & 0 deletions e2e/app.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { expect, test } from "@playwright/test";

test.describe("App", () => {
test("should have a title", async ({ page }) => {
await page.goto("/");
await expect(page).toHaveTitle(/Staking Dashboard/);
});
});
20 changes: 20 additions & 0 deletions e2e/balanceAddress.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { expect, test } from "@playwright/test";

import { setupWalletConnection } from "./helper/connect";

test.describe("Balance and address checks after connection", () => {
test.beforeEach(async ({ page }) => {
await page.goto("/");
await setupWalletConnection(page);
});

test("balance is correct", async ({ page }) => {
const balance = await page.getByTestId("balance").textContent();
expect(balance).toBe("0.12345678 BTC");
});

test("address is correct", async ({ page }) => {
const address = await page.getByTestId("address").textContent();
expect(address).toBe("bc1p...97sd");
});
});
6 changes: 6 additions & 0 deletions e2e/constants/staking.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { satoshiToBtc } from "@/utils/btcConversions";

export const STAKING_AMOUNT_SAT = 50000;
export const STAKING_AMOUNT_BTC = satoshiToBtc(STAKING_AMOUNT_SAT);
export const STAKING_TX_HASH =
"47af61d63bcc6c513561d9a1198d082052cc07a81f50c6f130653f0a6ecc0fc1";
54 changes: 54 additions & 0 deletions e2e/helper/connect.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { Page, expect } from "@playwright/test";

import { injectBTCWallet } from "./injectBTCWallet";

export const clickConnectButton = async (page: Page) => {
const connectButton = page.getByRole("button", {
name: "Connect to BTC",
});
await connectButton.click();
};

export const acceptTermsAndConditions = async (page: Page) => {
const termsCheckbox = page
.locator("label")
.filter({ hasText: "I certify that I have read" });

const inscriptionsCheckbox = page
.locator("label")
.filter({ hasText: "I certify that there are no" });

const hwCheckbox = page
.locator("label")
.filter({ hasText: "I acknowledge that Keystone via QR code" });

await termsCheckbox.click();
await inscriptionsCheckbox.click();
await hwCheckbox.click();

expect(await termsCheckbox.isChecked()).toBe(true);
expect(await inscriptionsCheckbox.isChecked()).toBe(true);
expect(await hwCheckbox.isChecked()).toBe(true);
};

export const clickInjectableWalletButton = async (page: Page) => {
const browserButton = page
.getByTestId("modal")
.getByRole("button", { name: "Browser" });
await browserButton.click();
};

export const clickConnectWalletButton = async (page: Page) => {
const connectWalletButton = page.getByTestId("modal").getByRole("button", {
name: "Connect to BTC network",
});
await connectWalletButton.click();
};

export const setupWalletConnection = async (page: Page) => {
await injectBTCWallet(page);
await clickConnectButton(page);
await acceptTermsAndConditions(page);
await clickInjectableWalletButton(page);
await clickConnectWalletButton(page);
};
47 changes: 47 additions & 0 deletions e2e/helper/injectBTCWallet.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { Page } from "@playwright/test";

// Sample wallet implementation for E2E testing purposes
export const injectBTCWallet = async (page: Page) => {
// Inject the wallet methods into window.btcwallet
await page.evaluate(() => {
// wallet
const btcWallet = {
connectWallet: () => {
return btcWallet;
},
getWalletProviderName: () => "BTC Wallet",
getAddress: () =>
"bc1p8gjpy0vyfdq3tty8sy0v86dvl69rquc85n2gpuztll9wxh9cpars7r97sd",
getPublicKeyHex: () =>
"024c6e2954c75bcb53aa13b7cd5d8bcdb4c9a4dd0784d68b115bd4408813b45608",
on: () => {},
getNetwork: () => "mainnet",
getBTCTipHeight: () => 859568,
getNetworkFees: () => ({
fastestFee: 1,
halfHourFee: 1,
hourFee: 1,
economyFee: 1,
minimumFee: 1,
}),
getUtxos: () => [
{
txid: "fa4908ad8876655ccb5ffba6a9eab58e1b785af73703cd58b19526c099d67c05",
vout: 0,
value: 12345678,
scriptPubKey:
"51203a24123d844b4115ac87811ec3e9acfe8a307307a4d480f04bffcae35cb80f47",
},
],
getInscriptions: () => [],
signPsbt: (_psbtHex: string) => {
return "70736274ff0100fd040102000000028a12de07985b7d06d83d9683eb3c0a86284fa3cbb2df998aed61009d700748ba0200000000fdffffff4ca53ae433b535b660a2dca99724199b2219a617508eed2ccf88762683a622430200000000fdffffff0350c3000000000000225120cf7c40c6fb1395430816dbb5e1ba9f172ef25573a3b609efa1723559cd82d5590000000000000000496a4762626234004c6e2954c75bcb53aa13b7cd5d8bcdb4c9a4dd0784d68b115bd4408813b45608094f5861be4128861d69ea4b66a5f974943f100f55400bf26f5cce124b4c9af7009604450000000000002251203a24123d844b4115ac87811ec3e9acfe8a307307a4d480f04bffcae35cb80f47340e0d000001012b50ba0000000000002251203a24123d844b4115ac87811ec3e9acfe8a307307a4d480f04bffcae35cb80f470108420140f94b4114bf4c77c449fefb45d60a86831a73897e58b03ba8250e1bf877912cdcc48d106fa266e8aa4085a43e9ad348652fb7b1ad0d820b6455c06edd92cadfef0001012b79510000000000002251203a24123d844b4115ac87811ec3e9acfe8a307307a4d480f04bffcae35cb80f470108420140e7abc0544c68c94a154e9136397ad8ab7d4dce0545c7c0db89aeb9a455e9377fb1c116ca20cdcb1c1ef4c9335a85c34499f45918ee37b010b69220626c4a8d7100000000";
},
pushTx: (_txHex: string) => {
return "47af61d63bcc6c513561d9a1198d082052cc07a81f50c6f130653f0a6ecc0fc1";
},
};

window.btcwallet = btcWallet;
});
};
54 changes: 54 additions & 0 deletions e2e/staking.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { expect, test } from "@playwright/test";

import {
STAKING_AMOUNT_BTC,
STAKING_AMOUNT_SAT,
STAKING_TX_HASH,
} from "./constants/staking";
import { setupWalletConnection } from "./helper/connect";

test.describe("Create staking transaction", () => {
test.beforeEach(async ({ page }) => {
await page.goto("/");
await setupWalletConnection(page);
});

test("prepare the staking", async ({ page }) => {
const previewButton = page.locator("button").filter({ hasText: "Preview" });

// Selects the first finality provider in the list
await page.locator("#finality-providers>div>div").first().click();
expect(previewButton).toBeDisabled();

// Preview available after filling the amount
await page.getByPlaceholder("BTC").fill(`${STAKING_AMOUNT_BTC}`);
expect(previewButton).toBeEnabled();

await previewButton.click();
const stakeButton = page.locator("button").filter({ hasText: "Stake" });
await stakeButton.click();

// Success modal
const success = page
.getByTestId("modal")
.locator("div")
.filter({ hasText: "Congratulations!" });
expect(success).toBeVisible();

// Check for local storage
const item = await page.evaluate(() =>
localStorage.getItem(
"bbn-staking-delegations-4c6e2954c75bcb53aa13b7cd5d8bcdb4c9a4dd0784d68b115bd4408813b45608",
),
);
expect(item).not.toBeNull();

const parsed = JSON.parse(item as string);
expect(parsed).toHaveLength(1);

// Check the staking delegation tx hash and staking value
const [delegation] = parsed;
expect(delegation.stakingValueSat).toBe(STAKING_AMOUNT_SAT);
expect(delegation.stakingTxHashHex).toBe(STAKING_TX_HASH);
});
});
64 changes: 64 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 6 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@
"prepare": "husky",
"sort-imports": "eslint --fix .",
"test": "jest",
"test:watch": "jest --watch"
"test:watch": "jest --watch",
"test:e2e": "playwright test",
"test:e2e:ui": "playwright test --ui",
"test:e2e:headed": "playwright test --headed",
"test:e2e:report": "playwright show-report"
},
"engines": {
"node": ">=22.0.0"
Expand Down Expand Up @@ -53,6 +57,7 @@
"@babel/preset-react": "^7.24.7",
"@babel/preset-typescript": "^7.24.7",
"@bitcoin-js/tiny-secp256k1-asmjs": "^2.2.3",
"@playwright/test": "^1.46.0",
"@tanstack/eslint-plugin-query": "^5.28.11",
"@tanstack/react-query-devtools": "^5.28.14",
"@testing-library/jest-dom": "^6.4.6",
Expand Down
82 changes: 82 additions & 0 deletions playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { defineConfig, devices } from "@playwright/test";
import path from "path";

// Use process.env.PORT by default and fallback to port 3000
const PORT = process.env.PORT || 3000;

// Set webServer.url and use.baseURL with the location of the WebServer respecting the correct set port
const baseURL = `http://localhost:${PORT}`;

/**
* See https://playwright.dev/docs/test-configuration.
*/
export default defineConfig({
// Test directory
testDir: path.join(__dirname, "e2e"),
/* Run tests in files in parallel */
fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: false,
/* Retry on CI only */
retries: 2,
/* Opt out of parallel tests on CI. */
workers: 2,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: "html",
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Base URL to use in actions like `await page.goto('/')`. */
// Use baseURL so to make navigations relative.
// More information: https://playwright.dev/docs/api/class-testoptions#test-options-base-url
baseURL,

/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: "on-first-retry",
},

/* Configure projects for major browsers */
projects: [
{
name: "chromium",
use: { ...devices["Desktop Chrome"] },
},

// {
// name: 'firefox',
// use: { ...devices['Desktop Firefox'] },
// },

// {
// name: 'webkit',
// use: { ...devices['Desktop Safari'] },
// },

/* Test against mobile viewports. */
// {
// name: 'Mobile Chrome',
// use: { ...devices['Pixel 5'] },
// },
// {
// name: 'Mobile Safari',
// use: { ...devices['iPhone 12'] },
// },

/* Test against branded browsers. */
// {
// name: 'Microsoft Edge',
// use: { ...devices['Desktop Edge'], channel: 'msedge' },
// },
// {
// name: 'Google Chrome',
// use: { ...devices['Desktop Chrome'], channel: 'chrome' },
// },
],

/* Run your local dev server before starting the tests */
webServer: {
command: "npm run dev",
url: baseURL,
timeout: 120 * 1000,
reuseExistingServer: true,
},
});
Loading

0 comments on commit 3f88fad

Please sign in to comment.