-
Notifications
You must be signed in to change notification settings - Fork 4
feat: init cli tests #99
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
base: develop
Are you sure you want to change the base?
Changes from 7 commits
29d2cb0
42fe360
93ab03c
2a5988f
e1df114
cc30a97
640f88e
691ffed
6eb2ab8
cd879d9
33fdfab
739f37b
aeafdbe
6f16177
2241eeb
51727be
bc1971b
d5d9538
927c9a1
0cea950
759630b
6ade16f
eb21d93
9693ccc
ef172f4
35785b7
7f8f7b0
5b7d814
536f25a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| name: Test CLI | ||
| run-name: Test CLI | ||
|
|
||
| on: | ||
| workflow_dispatch: | ||
| inputs: | ||
| CHAIN_ID: | ||
| description: 'Chain id for run tests' | ||
| required: true | ||
| type: choice | ||
| default: '560048' | ||
| options: | ||
| - '560048' | ||
| pull_request: | ||
|
|
||
| jobs: | ||
| test: | ||
| runs-on: ubuntu-latest | ||
| env: | ||
| # Required envs | ||
| CHAIN_ID: ${{ github.event.inputs.CHAIN_ID || '560048'}} | ||
| # Node.js configuration | ||
| NODE_OPTIONS: --max-old-space-size=4096 | ||
|
|
||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| with: | ||
| ref: ${{ github.event.pull_request.head.sha }} | ||
|
|
||
| - uses: actions/setup-node@v4 | ||
| with: | ||
| node-version: '20' | ||
| cache: 'yarn' | ||
|
|
||
| - name: Install dependencies (root) | ||
| run: yarn install --immutable | ||
|
|
||
| - name: Install Foundry | ||
| uses: foundry-rs/foundry-toolchain@v1 | ||
|
|
||
| - name: Install dependencies in tests/base | ||
| working-directory: tests/base | ||
| run: yarn install --immutable | ||
|
|
||
| - name: Run tests | ||
| working-directory: tests/base | ||
| run: | | ||
| echo -e "\033[34m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\033[0m" | ||
| echo -e "🚀 Running on CHAIN_ID: \033[1;34m$CHAIN_ID\033[0m" | ||
| echo -e "🌱 Branch: \033[1;34m${GITHUB_HEAD_REF:-$GITHUB_REF_NAME}\033[0m" | ||
| echo -e "\033[34m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\033[0m" | ||
| xvfb-run --auto-servernum -- yarn test | ||
|
|
||
| - uses: actions/upload-artifact@v4 | ||
| if: always() | ||
| with: | ||
| name: playwright-report | ||
| path: tests/base/playwright-report/ | ||
| retention-days: 30 | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
|
|
||
| # Hoodi - `560048` | ||
| CHAIN_ID= | ||
|
|
||
| # Optional. Tests are run using the default public RPC endpoint, but you may provide a custom one if the free RPC is slow or unstable | ||
| EL_URL= |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
|
|
||
| # playwright | ||
| test-results/ | ||
| playwright-report/ | ||
| logs/ | ||
| .yalc | ||
| .yarn/* |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,130 @@ | ||
| # 🧪 Lido Staking Vault CLI – Playwright Test Suite | ||
|
|
||
| This project provides **Playwright-based tests** for the `lido-staking-vault-cli`. | ||
| It spins up a **forked Ethereum test network**, bootstraps **smart contracts via CLI**, and runs tests. | ||
|
|
||
| --- | ||
|
|
||
| ## ⚙️ Prerequisites | ||
|
|
||
| - **Node.js ≥ 18** | ||
| - **Yarn** (recommended) | ||
|
|
||
| --- | ||
|
|
||
| ## 📦 Installation | ||
|
|
||
| From the **root project directory**: | ||
|
|
||
| ```bash | ||
| yarn && yarn build | ||
| ``` | ||
|
|
||
| Then navigate to the test suite directory: | ||
|
|
||
| ```bash | ||
| cd tests/base/ | ||
| yarn | ||
| ``` | ||
|
|
||
| > ⚠️ Both installation steps are required — one for the root project and one for the test package. | ||
|
|
||
| --- | ||
|
|
||
| ## 🧩 Configuration | ||
|
|
||
| 1. Copy the example environment file: | ||
|
|
||
| ```bash | ||
| cp .env.example .env | ||
| ``` | ||
|
|
||
| 2. Fill in the required parameters in `.env`: | ||
|
|
||
| ```bash | ||
| CHAIN_ID=`560048` or `1` (available after stVaults to be released) | ||
| RPC_URL=<optional override> | ||
| ``` | ||
|
|
||
| > Tests are run using the default public RPC endpoint, but you may provide a custom one if the free RPC is slow or unstable. | ||
|
|
||
| --- | ||
|
|
||
| ## 🚀 Running Tests | ||
|
|
||
| Run the full Playwright test suite: | ||
|
|
||
| ```bash | ||
| yarn test-widget | ||
| ``` | ||
|
|
||
| This command expands to: | ||
|
|
||
| ```bash | ||
| yarn playwright test --project=lsv_cli_tests | ||
| ``` | ||
|
|
||
| Playwright automatically runs the setup project before executing the main test suite. | ||
|
|
||
| --- | ||
|
|
||
| ## 🧱 Test Workflow Overview | ||
|
|
||
| - **`playwright.config.ts`** defines two Playwright projects: | ||
|
|
||
| - `setup lsv_cli before all` — runs the global setup and initializes the environment. | ||
| - `lsv_cli_tests` — contains the actual test specifications. | ||
|
|
||
| - **`tests/base/tests/globalSetup.ts`**: | ||
|
|
||
| - Creates a vault through the CLI. | ||
| - Grants required roles. | ||
| - Persists the fork state to `state.json`. | ||
|
|
||
| - **`tests/base/tests/test.fixture.ts`**: | ||
|
|
||
| - Reuses the saved fork state. | ||
| - Starts/stops the forked node. | ||
| - Exposes vault metadata and role accounts to the test cases. | ||
|
|
||
| --- | ||
|
|
||
| ## 🔁 Fork and Global Setup | ||
|
|
||
| The suite uses `EthereumNodeService` from `@lidofinance/wallets-testing-nodes` to launch an **Anvil-based fork** of the Ethereum network. | ||
|
|
||
| During the **`setup lsv_cli before all`** project: | ||
|
|
||
| 1. `globalSetup.ts` starts the fork with `--dump-state` enabled. | ||
| 2. The CLI creates a fresh vault and grants node-operator-related roles. | ||
| 3. The resulting chain snapshot is written to `state.json`. | ||
| 4. `VAULT_ADDRESS` and `DASHBOARD_ADDRESS` are written to the environment for later test use. | ||
|
|
||
| Since Playwright runs global setup and test workers in separate processes, those environment variables are dynamically assigned during the global setup stage. | ||
| Subsequent workers load the snapshot via `--load-state=./state.json`, ensuring **deterministic test state** and **fast startup times** for all runs. | ||
|
|
||
| --- | ||
|
|
||
| ## 🧑🔧 Debugging Tip | ||
|
|
||
| For long debugging sessions or active test development, you can temporarily disable global setup to speed up iteration. | ||
|
|
||
| 1. Comment out the following lines in `globalSetup.ts`: | ||
|
|
||
| ```ts | ||
| // console.log(`Vault address ${vaultData.vaultAddress}`); | ||
| // console.log(`Dashboard address ${vaultData.dashboardAddress}`); | ||
| ``` | ||
|
|
||
| 2. Run the tests once to trigger `globalSetup.ts` — this will generate and log the required addresses. | ||
| 3. Copy the printed `VAULT_ADDRESS` and `DASHBOARD_ADDRESS` values into your `.env` file. | ||
| 4. Comment out the entire content of `globalSetup.ts`. | ||
| 5. Run tests again — they will now reuse the existing fork state and skip the global precondition setup. | ||
|
|
||
| > **Note:** You may need to occasionally refresh `VAULT_ADDRESS` and `DASHBOARD_ADDRESS`. Since vault and dashboard contracts are created sequentially, their addresses change predictably. If someone creates a new vault on the testnet after your snapshot, the stored addresses may become invalid. In that case, rerun the global setup to regenerate and update your `.env` values. | ||
|
|
||
| --- | ||
|
|
||
| ✅ **Summary:** | ||
| This test suite automates end-to-end validation of `lido-staking-vault-cli` flows within a reproducible, isolated environment. | ||
| It provides deterministic results, faster execution, and confidence in vault creation and role management logic. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| import { | ||
| NetworkConfig, | ||
| NETWORKS_CONFIG, | ||
| } from '@lidofinance/wallets-testing-wallets'; | ||
| import { ENV_CONFIG } from './env.validation'; | ||
|
|
||
| export const getStandConfig = (): StandConfig => { | ||
| const config = STAND_CONFIGS.get(ENV_CONFIG.CHAIN_ID); | ||
|
|
||
| if (!config) { | ||
| throw new Error( | ||
| `CONFIG_VALIDATION_ERROR: CHAIN_ID is not correctly defined (value is "${ENV_CONFIG.CHAIN_ID}"). Please fix it in the .env file.`, | ||
| ); | ||
| } | ||
|
|
||
| return config; | ||
| }; | ||
|
|
||
| export interface StandConfig { | ||
| networkConfig: NetworkConfig; | ||
| deployed: string; | ||
| contracts: { | ||
| stake: string; | ||
|
DiRaiks marked this conversation as resolved.
Outdated
|
||
| wrap: string; | ||
| withdraw: string; | ||
| }; | ||
| } | ||
|
|
||
| export const STAND_ENV = { | ||
| hoodiTestnet: '560048', | ||
| }; | ||
|
|
||
| export const STAND_CONFIGS = new Map<string, StandConfig>([ | ||
| [ | ||
| STAND_ENV.hoodiTestnet, | ||
| { | ||
| deployed: 'deployed-hoodi-vaults.json', | ||
| networkConfig: { | ||
| ...NETWORKS_CONFIG.testnet.ETHEREUM_HOODI, | ||
| rpcUrl: process.env.RPC_URL | ||
| ? process.env.RPC_URL | ||
| : NETWORKS_CONFIG.testnet.ETHEREUM_HOODI.rpcUrl, | ||
| }, | ||
| contracts: { | ||
| stake: '0x3508A952176b3c15387C97BE809eaffB1982176a', | ||
| wrap: '0x7E99eE3C66636DE415D2d7C880938F2f40f94De4', | ||
| withdraw: '0xfe56573178f1bcdf53F01A6E9977670dcBBD9186', | ||
| }, | ||
| }, | ||
| ], | ||
| // mainnnet | ||
| ]); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| import path from 'path'; | ||
|
|
||
| import { z } from 'zod'; | ||
| import { config as envConfig } from 'dotenv'; | ||
|
|
||
| const envSchema = z.object({ | ||
| CHAIN_ID: z.string(), | ||
| }); | ||
|
|
||
| envConfig({ path: path.resolve(__dirname, '../.env') }); | ||
| class LsvCliEnvs { | ||
| CHAIN_ID!: string; | ||
|
|
||
| constructor() { | ||
| this.validateEnv(); | ||
| } | ||
|
|
||
| private validateEnv() { | ||
| const result = envSchema.safeParse(process.env); | ||
|
|
||
| if (!result.success) { | ||
| throw new Error( | ||
| `.env validation error for test run: ${JSON.stringify( | ||
| result.error.format(), | ||
| )}`, | ||
| ); | ||
| } | ||
|
|
||
| const validatedEnv = result.data; | ||
|
|
||
| this.CHAIN_ID = validatedEnv.CHAIN_ID; | ||
| } | ||
| } | ||
|
|
||
| export const ENV_CONFIG = new LsvCliEnvs(); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| import { ReporterDescription } from '@playwright/test'; | ||
|
|
||
| const htmlReporter: ReporterDescription = [ | ||
| 'html', | ||
| { outputFolder: 'playwright-report', open: 'never' }, | ||
| ]; | ||
| const consoleReporter: ReporterDescription = [ | ||
| 'list', | ||
| { printSteps: !process.env.CI }, | ||
| ]; | ||
| const githubReporter: ReporterDescription = ['github']; | ||
|
|
||
| export const getReportConfig: () => ReporterDescription[] = function () { | ||
| const reporterConfig: ReporterDescription[] = [htmlReporter, consoleReporter]; | ||
| if (process.env.CI) { | ||
| reporterConfig.push(githubReporter); | ||
| } | ||
| return reporterConfig; | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| { | ||
| "name": "lsv_cli_tests", | ||
| "version": "0.0.1", | ||
| "private": true, | ||
| "scripts": { | ||
| "lsvCLI": "tsx ../../index.ts", | ||
| "test": "yarn playwright test --project=lsv_cli_tests" | ||
| }, | ||
| "dependencies": { | ||
| "@lidofinance/wallets-testing-nodes": "^1.6.0", | ||
| "@lidofinance/wallets-testing-wallets": "^1.39.0", | ||
| "@playwright/test": "^1.56.1", | ||
| "zod": "^3.24.1" | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do you need zod?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Initially, I used it for several required env variables, but in this case I can use it without Zod. |
||
| }, | ||
| "devDependencies": { | ||
| "add": "^2.0.6", | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do you need "add"?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My bad. Removed |
||
| "dotenv": "^16.4.5", | ||
| "tsx": "^4.19.3", | ||
| "yarn": "^1.22.22" | ||
|
DiRaiks marked this conversation as resolved.
Outdated
|
||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| import type { PlaywrightTestConfig } from '@playwright/test'; | ||
| import { getReportConfig } from './config/report.config'; | ||
|
|
||
| export const pwConfig: PlaywrightTestConfig = { | ||
| timeout: 180 * 1000, | ||
| testDir: './tests', | ||
| fullyParallel: false, | ||
| forbidOnly: !!process.env.CI, | ||
| retries: 0, | ||
| // eslint-disable-next-line sonarjs/no-all-duplicated-branches | ||
| workers: process.env.CI ? 1 : 1, | ||
|
DiRaiks marked this conversation as resolved.
Outdated
|
||
| reporter: getReportConfig(), | ||
| use: { | ||
| actionTimeout: 15000, | ||
|
DiRaiks marked this conversation as resolved.
Outdated
|
||
| trace: 'retain-on-failure', | ||
| }, | ||
| projects: [ | ||
| { | ||
| name: 'lsv_cli_tests', | ||
| dependencies: ['setup lsv_cli before all'], | ||
| timeout: 220 * 1000, | ||
| }, | ||
| { | ||
| name: 'setup lsv_cli before all', | ||
| testMatch: /globalSetup\.ts/, | ||
| timeout: 350 * 1000, // for Setup project we have to increase timeout | ||
| }, | ||
| ], | ||
| }; | ||
|
|
||
| export default pwConfig; | ||
Uh oh!
There was an error while loading. Please reload this page.