Skip to content

Commit

Permalink
feat: add test of stake tx
Browse files Browse the repository at this point in the history
  • Loading branch information
jake4take committed Dec 28, 2024
1 parent b670095 commit 3e8876d
Showing 15 changed files with 257 additions and 33 deletions.
11 changes: 11 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -11,6 +11,16 @@ on:
options:
- mainnet
- testnet
WALLETS:
description: 'Select wallet to run tests'
required: true
type: choice
default: All
options:
- All
- metamask
- okx


jobs:
test:
@@ -23,6 +33,7 @@ jobs:
WALLET_SECRET_PHRASE: ${{ secrets.WALLET_SECRET_PHRASE }}
WALLET_PASSWORD: ${{ secrets.WALLET_PASSWORD }}
STAND_ENV: ${{ github.event.inputs.STAND_ENV || 'testnet' }}
WALLETS: ${{ github.event.inputs.WALLETS || 'All' }}
STAND_TYPE: stand
NODE_OPTIONS: --max-old-space-size=4096

2 changes: 1 addition & 1 deletion apps/demo-react/components/action-item/action-item.tsx
Original file line number Diff line number Diff line change
@@ -12,7 +12,7 @@ export const ActionItem: FC<PropsWithChildren<ActionItemProps>> = (props) => {

return (
<WrapperStyle>
<ActionStyle title={title} walletAction action={action}>
<ActionStyle title={title} walletAction action={action} data-testid={title+'Block'}>
{children}
</ActionStyle>
</WrapperStyle>
19 changes: 18 additions & 1 deletion playwright-tests/config/config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import { STAND_CONFIGS, STAND_LINK, STAND_ENV } from './env.config';
import {
STAND_CONFIGS,
STAND_LINK,
STAND_ENV,
WALLETS,
TestWalletConfig,
} from './env.config';
import * as dotenv from 'dotenv';
import * as path from 'path';

@@ -9,6 +15,7 @@ export const REEF_KNOT_CONFIG = {
STAND_CONFIG: getStandConfig(),
STAND_URL: getStandUrl(),
STAND_ENV: process.env.STAND_ENV,
WALLETS: getWalletsForTestRun(),
};

function getStandConfig() {
@@ -36,3 +43,13 @@ function getStandUrl() {
);
}
}

function getWalletsForTestRun() {
const wallets: TestWalletConfig[] = [];
if (!process.env.WALLETS || process.env.WALLETS === 'All') {
WALLETS.forEach((wallet) => wallets.push(wallet));
} else {
wallets.push(WALLETS.get(process.env.WALLETS));
}
return wallets;
}
10 changes: 10 additions & 0 deletions playwright-tests/config/env.config.ts
Original file line number Diff line number Diff line change
@@ -53,3 +53,13 @@ export const STAND_CONFIGS = new Map<string, StandConfig>([
},
],
]);

export interface TestWalletConfig {
name: string;
connectWalletEvent: string;
}

export const WALLETS = new Map<string, TestWalletConfig>([
['metamask', { name: 'metamask', connectWalletEvent: 'metaMask connected' }],
['okx', { name: 'okx', connectWalletEvent: 'okx connected' }],
]);
2 changes: 2 additions & 0 deletions playwright-tests/pages/components/index.ts
Original file line number Diff line number Diff line change
@@ -2,3 +2,5 @@ export * from './header';
export * from './stats';
export * from './wallet-list-modal';
export * from './wallet-modal';
export * from './toast';
export * from './stake-block';
15 changes: 15 additions & 0 deletions playwright-tests/pages/components/stake-block.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Locator, Page } from '@playwright/test';

export class StakeBlock {
page: Page;
mainComponent: Locator;
referralAddressInput: Locator;
stakeBtn: Locator;

constructor(page: Page) {
this.page = page;
this.mainComponent = this.page.getByTestId('StakeBlock');
this.referralAddressInput = this.mainComponent.locator('input');
this.stakeBtn = this.mainComponent.locator('button :has-text("Stake")');
}
}
2 changes: 2 additions & 0 deletions playwright-tests/pages/components/stats.ts
Original file line number Diff line number Diff line change
@@ -9,6 +9,7 @@ export class StatsBlock {
ethBalance: Locator;
stethBalance: Locator;
wstethBalance: Locator;
amountInput: Locator;

constructor(page: Page) {
this.page = page;
@@ -19,5 +20,6 @@ export class StatsBlock {
this.ethBalance = this.mainComponent.getByTestId('ETH');
this.stethBalance = this.mainComponent.getByTestId('stETH');
this.wstethBalance = this.mainComponent.getByTestId('wstETH');
this.amountInput = this.mainComponent.getByTestId('amountInput');
}
}
17 changes: 17 additions & 0 deletions playwright-tests/pages/components/toast.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Locator, Page } from '@playwright/test';

export class Toast {
page: Page;
successToast: Locator;
errorToast: Locator;

constructor(page: Page) {
this.page = page;
this.successToast = this.page.locator(
'.Toastify__toast--success :has-text("Success")',
);
this.errorToast = this.page.locator(
'.Toastify__toast--error :has-text("Error")',
);
}
}
37 changes: 35 additions & 2 deletions playwright-tests/pages/reefKnot.page.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,32 @@
import { Header, WalletListModal, StatsBlock, WalletModal } from './components';
import { Page, test } from '@playwright/test';
import {
Header,
WalletListModal,
StatsBlock,
WalletModal,
StakeBlock,
Toast,
} from './components';
import { Locator, Page, test } from '@playwright/test';
import { TIMEOUT } from '@test-data';
import { waitForCallback } from '@services';

export class ReefKnotPage {
readonly page: Page;
header: Header;
walletModal: WalletModal;
statsBlock: StatsBlock;
stakeBlock: StakeBlock;
walletListModal: WalletListModal;
toast: Toast;

constructor(page: Page) {
this.page = page;
this.header = new Header(this.page);
this.walletModal = new WalletModal(this.page);
this.statsBlock = new StatsBlock(this.page);
this.stakeBlock = new StakeBlock(this.page);
this.walletListModal = new WalletListModal(this.page);
this.toast = new Toast(this.page);
}

async goto(param = '') {
@@ -55,4 +67,25 @@ export class ReefKnotPage {
return localStorage.getItem(names);
}, name);
}

async clickStakeButton() {
const [txPage] = await Promise.all([
this.waitForPage(TIMEOUT.RPC_WAIT),
this.stakeBlock.stakeBtn.click(),
]);
return txPage;
}

async waitForBalance(locator: Locator, timeout = TIMEOUT.RPC_WAIT) {
return await waitForCallback(
async (locator: Locator) => {
return await locator.evaluate((element) => {
const balance = parseFloat(element.textContent);
return balance ? String(balance) : null;
});
},
locator,
timeout,
);
}
}
39 changes: 38 additions & 1 deletion playwright-tests/services/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/*
/**
* Function to trim digits after decimal point
*
* Example:
@@ -14,3 +14,40 @@ export function toCut(floatAmount: string, decimalPlaces: number) {
}
return floatAmount.slice(0, respLength);
}

/**
* Repeatedly calls an asynchronous callback function with the specified arguments until it returns a truthy value
* or the timeout is reached.
*
* @param callback - An asynchronous function that takes arguments of type T and returns a promise.
* @param args - The arguments to pass to the callback function.
* @param timeout - The maximum amount of time (in milliseconds) to wait for the callback to return a truthy value.
* @returns A promise that resolves with the callback's result if it returns a truthy value within the timeout.
* @throws An error if the timeout is reached before the callback returns a truthy value.
*
* @template T - The type of the arguments to be passed to the callback function.
*/
export async function waitForCallback<T>(
callback: (args: T) => Promise<any>,
args: T,
timeout: number,
): Promise<any> {
let shouldTerminate = false;
setTimeout(() => {
shouldTerminate = true;
}, timeout);

let result;
while (!shouldTerminate) {
result = await callback(args).catch(() => {
console.error('Callback failed');
});
if (result) return result;
}

throw new Error(
`callback still not done after ${
timeout / 1000
} sec.\nCallback result: ${result}`,
);
}
1 change: 1 addition & 0 deletions playwright-tests/test-data/timeout.data.ts
Original file line number Diff line number Diff line change
@@ -2,5 +2,6 @@ export const TIMEOUT = {
LOW: 500,
DEFAULT: 1000,
MEDIUM: 5000,
HIGH: 30000,
RPC_WAIT: 60000,
};
16 changes: 6 additions & 10 deletions playwright-tests/tests/matomo-event-connected-wallet.spec.ts
Original file line number Diff line number Diff line change
@@ -4,13 +4,9 @@ import { ReefKnotService } from '@services';
import { ReefKnotPage } from '@pages';
import { BrowserService, initBrowserWithWallet } from '@browser';
import { qase } from 'playwright-qase-reporter';
import { REEF_KNOT_CONFIG } from '@config';

const wallets = [
{ name: 'metamask', expectedEvent: 'metaMask connected' },
{ name: 'okx', expectedEvent: 'okx connected' },
];

wallets.forEach((wallet) => {
REEF_KNOT_CONFIG.WALLETS.forEach((wallet) => {
test.describe(
`ReefKnot. Matomo events (${wallet.name})`,
{ tag: [Tags.connectedWallet, `@${wallet.name}`] },
@@ -37,20 +33,20 @@ wallets.forEach((wallet) => {
test(qase(432, `Connect ${wallet.name} wallet`), async () => {
await qase.groupParameters({
wallet: wallet.name,
eventName: wallet.expectedEvent,
eventName: wallet.connectWalletEvent,
});

await test.step('Connect wallet and check console.log', async () => {
const [consoleMessage] = await Promise.all([
reefKnotPage.page.waitForEvent('console', (msg) =>
msg.text().includes(wallet.expectedEvent),
msg.text().includes(wallet.connectWalletEvent),
),
reefKnotService.connectWallet(),
]);
expect(
consoleMessage.text(),
`The request parameter "${consoleMessage.text()}" should match the value "${wallet.expectedEvent}"`,
).toContain(wallet.expectedEvent);
`The request parameter "${consoleMessage.text()}" should match the value "${wallet.connectWalletEvent}"`,
).toContain(wallet.connectWalletEvent);
});
});
},
82 changes: 82 additions & 0 deletions playwright-tests/tests/stake-tx-progress.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { expect, test } from '@playwright/test';
import { Tags, TIMEOUT } from '@test-data';
import { BrowserService, initBrowserWithWallet } from '@browser';
import { ReefKnotService, toCut } from '@services';
import { ReefKnotPage } from '@pages';
import { qase } from 'playwright-qase-reporter';
import { REEF_KNOT_CONFIG } from '@config';

REEF_KNOT_CONFIG.WALLETS.forEach((wallet) => {
test.describe(
`ReefKnot. Stake transaction progress (${wallet.name})`,
{ tag: [Tags.connectedWallet, `@${wallet.name}`] },
async () => {
const stakeAmount = '0.0003';
let browserService: BrowserService;
let reefKnotService: ReefKnotService;
let reefKnotPage: ReefKnotPage;

test.beforeAll(async () => {
({ browserService, reefKnotService } = await initBrowserWithWallet(
wallet.name,
));
reefKnotPage = reefKnotService.reefKnotPage;
await reefKnotPage.goto();
await reefKnotPage.allowUseCookies();
await reefKnotService.connectWallet();
});

test.afterAll(async () => {
await reefKnotService.disconnectWalletForce();
await browserService.teardown();
});

test(`Stake ${stakeAmount} ETH`, async () => {
await qase.groupParameters({
wallet: wallet.name,
txAmount: stakeAmount,
});

const newStEthBalance =
await test.step('Calculate the stETH amount result', async () => {
const stEthBalance = parseFloat(
await reefKnotPage.waitForBalance(
reefKnotPage.statsBlock.stethBalance,
),
);
return toCut(String(stEthBalance + parseFloat(stakeAmount)), 4);
});

const txPage =
await test.step('Fill the amount input and click to Submit button', async () => {
await reefKnotPage.statsBlock.amountInput.fill(stakeAmount);
return await reefKnotPage.clickStakeButton();
});

await reefKnotService.walletPage.confirmTx(txPage, true);

await test.step('Waiting for transaction success', async () => {
await expect(
reefKnotPage.stakeBlock.stakeBtn,
'The Stake button should be disabled',
).toBeDisabled();
await reefKnotPage.toast.successToast.waitFor({
state: 'visible',
timeout: TIMEOUT.RPC_WAIT,
});
await expect(
reefKnotPage.stakeBlock.stakeBtn,
'The Stake button should be enabled after success tx',
).toBeEnabled();
});

await test.step('Check the new stETH balance', async () => {
await expect(
reefKnotPage.statsBlock.stethBalance,
'The displayed stETH balance should be updated after transaction success',
).toContainText(newStEthBalance, { timeout: TIMEOUT.HIGH });
});
});
},
);
});
5 changes: 2 additions & 3 deletions playwright-tests/tests/wallet-connection.spec.ts
Original file line number Diff line number Diff line change
@@ -4,10 +4,9 @@ import { ReefKnotPage } from '@pages';
import { expect, test } from '@playwright/test';
import { qase } from 'playwright-qase-reporter';
import { BrowserService, initBrowserWithWallet } from '@browser';
import { REEF_KNOT_CONFIG } from '@config';

const wallets = [{ name: 'metamask' }, { name: 'okx' }];

wallets.forEach((wallet) => {
REEF_KNOT_CONFIG.WALLETS.forEach((wallet) => {
test.describe.serial(
`ReefKnot. Wallet connection (${wallet.name})`,
{ tag: [Tags.connectedWallet, `@${wallet.name}`] },
Loading

0 comments on commit 3e8876d

Please sign in to comment.