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

feature: Playwright e2e #77

Merged
merged 14 commits into from
Sep 10, 2024
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
gbarkhatov marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { expect, test } from "@playwright/test";

test.describe("App", () => {
gbarkhatov marked this conversation as resolved.
Show resolved Hide resolved
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" });
gbarkhatov marked this conversation as resolved.
Show resolved Hide resolved

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;
gbarkhatov marked this conversation as resolved.
Show resolved Hide resolved

// 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' },
// },
gbarkhatov marked this conversation as resolved.
Show resolved Hide resolved
],

/* Run your local dev server before starting the tests */
webServer: {
command: "npm run dev",
url: baseURL,
timeout: 120 * 1000,
gbarkhatov marked this conversation as resolved.
Show resolved Hide resolved
reuseExistingServer: true,
},
});
Loading