Skip to content

Commit

Permalink
feat: integration tests setup (#45)
Browse files Browse the repository at this point in the history
  • Loading branch information
Romsters authored and pcheremu committed Aug 22, 2024
1 parent cd679cf commit fa58717
Show file tree
Hide file tree
Showing 77 changed files with 8,180 additions and 34 deletions.
1 change: 1 addition & 0 deletions .github/workflows/app-deploy-preview.yml
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,4 @@ jobs:
default_network_value_for_e2e: "/?network=mainnet"
publish_to_allure: true
environmentTags: "and not @featureEnv"

2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ This repository is a monorepo consisting of 4 packages:
- [API](./packages/api) - a service providing Web API for retrieving structured [zkSync Era](https://zksync.io) blockchain data collected by [Worker](./packages/worker). It connects to the Worker's database to be able to query the collected data.
- [App](./packages/app) - a front-end app providing an easy-to-use interface for users to view and inspect transactions, blocks, contracts and more. It makes requests to the [API](./packages/api) to get the data and presents it in a way that's easy to read and understand.

Also the repository contains [integration-test](./packages/integration-tests) package with a set of API and UI tests. Follow this [Readme](./packages/integration-tests/README.md) for more details.

## 🏛 Architecture
The following diagram illustrates how are the block explorer components connected:

Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
"test": "lerna run test",
"test:ci": "lerna run test:ci",
"test:e2e": "lerna run test:e2e",
"test:integration:ui": "lerna run integration-test:ui",
"test:integration:api": "lerna run integration-test:api",
"dev": "lerna run dev",
"build": "lerna run build",
"start": "lerna run start",
Expand Down
69 changes: 35 additions & 34 deletions packages/app/tests/e2e/testId.json
Original file line number Diff line number Diff line change
@@ -1,35 +1,36 @@
{
"badge": "badge",
"statusBadge": "status-badge",
"blocksNumber": "blocks-number",
"blocksTable": "blocks-table",
"byteCodeDropDown": "bytecode-dropdown",
"contractsAddress": "contracts-address",
"contractVerificationButton": "contract-verification-button",
"direction": "direction",
"initiatorsAddress": "initiators-address",
"fromAddress": "from-address",
"optimizationRadioButtons": "radio-buttons",
"pageTitle": "page-title",
"previousInstructionButton": "previous-instruction-navigation-button",
"showInstructionMetadataButton": "show-instruction-metadata-button",
"nextInstructionButton": "next-instruction-navigation-button",
"traceSearchInput": "trace-search-input",
"tokensIcon": "tokens-icon",
"tokenAddress":"tokenAddress",
"tokenAmount":"token-amount",
"tokenAmountPrice":"token-amount-price",
"transactionsHash": "transactions-hash",
"transactionsMethodName": "transactions-method-name",
"timestamp": "timestamp",
"transactionsTable": "transactions-table",
"latestTransactionsTable": "latest-transaction-table",
"latestBatchesTable": "latest-batches-table",
"tokensTable": "tokens-table",
"toAddress": "to-address",
"transferType": "transfer-type",
"transferFromOrigin": "transfer-from-origin",
"transferToOrigin": "transfer-to-origin",
"transferFromOriginTablet": "transfer-from-origin-tablet",
"transferToOriginTablet": "transfer-to-origin-tablet"
}
"badge": "badge",
"statusBadge": "status-badge",
"blocksNumber": "blocks-number",
"blocksTable": "blocks-table",
"byteCodeDropDown": "bytecode-dropdown",
"contractsAddress": "contracts-address",
"contractVerificationButton": "contract-verification-button",
"direction": "direction",
"initiatorsAddress": "initiators-address",
"fromAddress": "from-address",
"optimizationRadioButtons": "radio-buttons",
"pageTitle": "page-title",
"previousInstructionButton": "previous-instruction-navigation-button",
"showInstructionMetadataButton": "show-instruction-metadata-button",
"nextInstructionButton": "next-instruction-navigation-button",
"traceSearchInput": "trace-search-input",
"tokensIcon": "tokens-icon",
"tokenAddress":"tokenAddress",
"tokenAmount":"token-amount",
"tokenAmountPrice":"token-amount-price",
"transactionsHash": "transactions-hash",
"transactionsMethodName": "transactions-method-name",
"timestamp": "timestamp",
"transactionsTable": "transactions-table",
"latestTransactionsTable": "latest-transaction-table",
"latestBatchesTable": "latest-batches-table",
"tokensTable": "tokens-table",
"toAddress": "to-address",
"transferType": "transfer-type",
"transferFromOrigin": "transfer-from-origin",
"transferToOrigin": "transfer-to-origin",
"transferFromOriginTablet": "transfer-from-origin-tablet",
"transferToOriginTablet": "transfer-to-origin-tablet"
}

3 changes: 3 additions & 0 deletions packages/integration-tests/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
extends: ["@matterlabs/eslint-config-vue"],
};
50 changes: 50 additions & 0 deletions packages/integration-tests/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Integration tests for for Block Explorer UI and API

Based on Jest.io/TypeScript/TDD.

## Installation

```bash
npm install
```

## Preparing a local environment

Make sure you have `Docker` installed. Before running the tests you need to spin up a local environment (use `docker-compose.yaml` from the root directory):
```bash
docker-compose up
```

## Running API tests
--
all tests:

```bash
npm run integration-test:api
```
## Running UI tests
--
all tests:

```bash
npm run integration-test:ui
```

If you need to run the exact test or/and suite you can change
key-words from `it` to `fit` (for the test) and `describe` to `fdescribe` for suite.

If you need to exclude some specific test/suites, you can change keywords `it` to `xit` and/or
`describe` to `xdescribe`.

The test solution contains two main folders: [src](./src) and [tests](./tests).
[src](./src) folder contains:
- essential [scenarios](./src/playbook/scenarios/)
- predefined [entities](./src/entities.ts) and [config](./src/config.ts) config files
- [contracts](./src/playbook/contracts/) folder with a set of contracts
- [deploy](./src/playbook/deploy/) folder with a set of deploy scripts
- [buffer](./src/playbook/buffer/) folder as a temporary storage of transaction hashes and addresses
- [utils](./src/playbook/utils/) folder with utils scripts

[tests](./tests) folder contains sets of:
- [api](./tests/api/) endpoints tests, which cover essential part of [worker](../packages/worker/) and [api](../packages/api/) functionality
- [ui](./tests/ui/) UI tests, which cover essential part of [Block explorer](../packages/app/) functionality
12 changes: 12 additions & 0 deletions packages/integration-tests/jest.config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"moduleFileExtensions": ["js", "json", "ts"],
"rootDir": ".",
"testEnvironment": "node",
"testRegex": ".test.ts$",
"modulePathIgnorePatterns": ["<rootDir>/src/playbook"],
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
},
"reporters": ["default"],
"maxWorkers": 1
}
48 changes: 48 additions & 0 deletions packages/integration-tests/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
{
"name": "integration-tests",
"version": "0.0.0",
"title": "Integration tests",
"description": "The test solution for transaction processing`",
"repository": "https://github.com/matter-labs/block-explorer",
"private": true,
"author": "Matter Labs",
"license": "MIT",
"scripts": {
"postinstall": "cd src/playbook && npm install",
"integration-test:api": "jest --verbose --testPathPattern=tokens.test.ts && jest --verbose --testPathPattern=deposit.test.ts && jest --verbose --testPathPattern=common && jest --verbose --testPathPattern=transactions",
"integration-test:ui": "npx playwright test",
"block-explorer:start": "docker-compose up",
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-pattern 'repos' --ignore-pattern 'git add .'"
},
"devDependencies": {
"@matterlabs/eslint-config-vue": "^1.1.4",
"@matterlabs/hardhat-zksync-deploy": "^0.6.3",
"@matterlabs/hardhat-zksync-solc": "^0.3.14",
"@matterlabs/prettier-config": "^1.0.2",
"@matterlabs/zksync-contracts": "^0.6.1",
"@nomiclabs/hardhat-ethers": "^2.2.2",
"@openzeppelin/contracts": "^4.6.0",
"@openzeppelin/contracts-upgradeable": "^4.6.0",
"@playwright/test": "^1.37.1",
"@types/jest": "^29.2.4",
"@types/node": "^18.15.0",
"dotenv": "^16.0.3",
"eslint": "^8.31.0",
"ethers": "^5.7.2",
"hardhat": "^2.12.0",
"jest": "^29.6.2",
"lint-staged": "^13.1.0",
"prettier": "^2.8.2",
"supertest": "^6.3.3",
"ts-jest": "^29.0.5",
"ts-node": "^10.9.1",
"zksync-web3": "^0.14.3"
},
"dependencies": {
"ts-jest-resolver": "^2.0.0"
},
"prettier": "@matterlabs/prettier-config",
"lint-staged": {
"*.{vue,js,ts}": "npm run lint"
}
}
21 changes: 21 additions & 0 deletions packages/integration-tests/playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { defineConfig, devices } from "@playwright/test";
import { config } from "tests/ui/config";

export default defineConfig({
testDir: "tests/ui",
fullyParallel: true,
projects: [
{
name: "chromium",
use: {
...devices["Desktop Chrome"],
launchOptions: {
headless: config.headless,
},
contextOptions: {
viewport: config.mainWindowSize,
},
},
},
],
});
18 changes: 18 additions & 0 deletions packages/integration-tests/src/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Wallets } from "./entities";

export const localConfig = {
gasLimit: { gasLimit: 8000000 },
l2GasLimit: 8000000,
L1Network: "http://localhost:8545",
L2Network: "http://localhost:3050",
privateKey: Wallets.richWalletPrivateKey,
extendedTimeout: 1200 * 1000,
standardTimeout: 60 * 1000,
extendedPause: 20 * 1000,
standardPause: 5 * 1000,
minimalPause: 1 * 1000,
};

export const environment = {
blockExplorerAPI: "http://localhost:3020",
};
69 changes: 69 additions & 0 deletions packages/integration-tests/src/entities.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
export enum Buffer {
greeterL2 = "./buffer/greeterL2.txt",
executeGreeterTx = "./buffer/executeGreeterTx.txt",
NFTtoL1 = "./buffer/NFTtoL1.txt",
NFTtoL2 = "./buffer/NFTtoL2.txt",
L1 = "./buffer/L1.txt",
L2 = "./buffer/L2.txt",
L2deposited = "./buffer/L2deposited.txt",
paymaster = "./buffer/paymaster.txt",
paymasterTx = "./buffer/paymasterTx.txt",
addressMultiTransferETH = "./buffer/multiTransferETH.txt",
txMultiTransferETH = "./buffer/txMultiTransferETH.txt",
txMultiTransferCustomTokenI = "./buffer/txMultiTransferCustomTokenI.txt",
txMultiTransferCustomTokenII = "./buffer/txMultiTransferCustomTokenII.txt",
addressMultiCallMiddle = "./buffer/multiCallMiddle.txt",
addressMultiCallCaller = "./buffer/multiCallCaller.txt",
addressMultiCallRoot = "./buffer/multiCallRoot.txt",
txMultiCallMiddle = "./buffer/txMultiCallMiddle.txt",
txMultiCallCaller = "./buffer/txMultiCallCaller.txt",
txMultiCallRoot = "./buffer/txmultiCallRoot.txt",
txUseMultiCallContracts = "./buffer/txUseMultiCallContracts.txt",
emptyWalletPrivateKey = "./buffer/emptyWalletPrivateKey.txt",
emptyWalletAddress = "./buffer/emptyWalletAddress.txt",
failedState = "./buffer/failedState.txt",
customToken = "./buffer/customToken.txt",
}

export enum Logger {
deposit = "DEPOSIT",
withdraw = "WITHDRAW",
transfer = "TRANSFER",
txHashStartsWith = "0x",
textSeparator = "======================= ",
txFailedState = "FAILED STATE",
}

export enum Token {
CUST_Address = "0x0928008B245A76E105E02C522b5d309c0887ecA5",
customL2TokenName = "L2 ERC20 token",
customL2TokenSymbol = "L2",
customL2TokenDecimals = 18,
ETHER_PULL_Address = "0x0000000000000000000000000000000000008001",
ETHER_Address = "0x0000000000000000000000000000000000000000",
ETHER_ERC20_Address = "0x000000000000000000000000000000000000800A",
}

export enum TransactionsType {
fee = "fee",
transfer = "transfer",
refund = "refund",
}

export enum TransactionsStatus {
failed = "failed",
}

export enum Wallets {
mainWalletAddress = "0x586607935E1462ab762F438E0A7b2968A4158975",
secondWalletAddress = "0x26A4c5Dfe2cA3c9E7E8C417B689F41b6b5745C37",
richWalletAddress = "0x36615Cf349d7F6344891B1e7CA7C72883F5dc049",
mainWalletPrivateKey = "0x06ac1584dd1cf69f97a784b2b7812cd0c65a867ec997add028cdf56483c1c299",
secondWalletPrivateKey = "e14e6e0b3b610411cf15c3a5aa3252cac9e0a40a9bbe67ceb3b5d506f56576fd",
richWalletPrivateKey = "0x7726827caac94a7f9e1b160f7ea819f172f7b6f9d2a97f992c38edeab82d4110",
}

export enum BlockExplorer {
baseUrl = "http://localhost:3010",
localNetwork = "/?network=local",
}
38 changes: 38 additions & 0 deletions packages/integration-tests/src/helper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { execSync } from "child_process";
import { promises as fs } from "fs";
import * as path from "path";

import { Logger } from "./entities";

export class Helper {
async txHashLogger(txType: string, txValue: string, tokenName?: string) {
const logMessage = `TxHash for ${txType} ${Logger.textSeparator} ${txValue}`;

if (tokenName === undefined) {
return console.log(logMessage);
} else {
return console.log(logMessage, ` ${tokenName}`);
}
}

async executeScript(script: string) {
const output = execSync(script, { encoding: "utf-8" });

try {
console.log(`> Run NPM Script "${script}":\n`, output);
return output;
} catch (e) {
console.log(e);
}
}

async getStringFromFile(fileName: string) {
const absoluteRoute = path.join(__dirname, "..", fileName);

try {
return await fs.readFile(absoluteRoute, { encoding: "utf-8" });
} catch {
console.log(`There is no the expected file: ${fileName}`);
}
}
}
Empty file.
28 changes: 28 additions & 0 deletions packages/integration-tests/src/playbook/contracts/Caller.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.0;

interface IGreeter2 {
function callGreeter() external view returns (string memory);
function setGreet(string memory greeting) external;
}

contract GCaller {
address public myAddress;
address public callAddress;
string private defaultGreeting = "Hi from Caller";

constructor(address _callAddress) {
myAddress = address(this);
callAddress = _callAddress;
}

function newSetGreet(string memory _greeting) external {
bytes memory greetingBytes = bytes(_greeting);
string memory greeting = (greetingBytes.length > 0) ? _greeting : defaultGreeting;
IGreeter2(callAddress).setGreet(greeting);
}

function newCallGreeter() external view returns (string memory) {
return IGreeter2(callAddress).callGreeter();
}
}
Loading

0 comments on commit fa58717

Please sign in to comment.