Commit 3e8c18a2 authored by Sharafitdinova's avatar Sharafitdinova Committed by GitHub

Merge branch 'main' into fix-ci-cd

parents 3268d85d 11eb4feb
__snapshots__/** filter=lfs diff=lfs merge=lfs -text
name: playwright
on: [pull_request]
jobs:
test:
runs-on: ubuntu-latest
container:
image: mcr.microsoft.com/playwright:v1.27.0-focal
steps:
- run: apt-get update && apt-get install git-lfs
- uses: actions/checkout@v3
with:
lfs: 'true'
- uses: actions/setup-node@v2
with:
node-version: '16'
- name: Install dependencies
run: yarn
- name: Run your tests
run: HOME=/root yarn test-ct
- name: Upload test results
if: always()
uses: actions/upload-artifact@v2
with:
name: playwright-report
path: playwright-report
\ No newline at end of file
......@@ -42,3 +42,6 @@ yarn-error.log*
.sentryclirc
**.decrypted~**
/test-results/
/playwright-report/
/playwright/.cache/
#!/bin/sh
command -v git-lfs >/dev/null 2>&1 || { echo >&2 "\nThis repository is configured for Git LFS but 'git-lfs' was not found on your path. If you no longer wish to use Git LFS, remove this hook by deleting '.git/hooks/post-checkout'.\n"; exit 2; }
git lfs post-checkout "$@"
#!/bin/sh
command -v git-lfs >/dev/null 2>&1 || { echo >&2 "\nThis repository is configured for Git LFS but 'git-lfs' was not found on your path. If you no longer wish to use Git LFS, remove this hook by deleting '.git/hooks/post-commit'.\n"; exit 2; }
git lfs post-commit "$@"
#!/bin/sh
command -v git-lfs >/dev/null 2>&1 || { echo >&2 "\nThis repository is configured for Git LFS but 'git-lfs' was not found on your path. If you no longer wish to use Git LFS, remove this hook by deleting '.git/hooks/post-merge'.\n"; exit 2; }
git lfs post-merge "$@"
#!/bin/sh
command -v git-lfs >/dev/null 2>&1 || { echo >&2 "\nThis repository is configured for Git LFS but 'git-lfs' was not found on your path. If you no longer wish to use Git LFS, remove this hook by deleting '.git/hooks/pre-push'.\n"; exit 2; }
git lfs pre-push "$@"
......@@ -9,6 +9,7 @@ Core technologies what used in the project are
- [Next.js](https://nextjs.org/) as application framework
- [Chakra](https://chakra-ui.com/) as component library; our theme customization could be found in `/theme` folder
- [css-modules](https://github.com/css-modules/css-modules) as lib for styling custom components
- [playwright](https://playwright.dev/) as a tool for components visual testing
And of course our premier language is [Typescript](https://www.typescriptlang.org/)
......@@ -26,6 +27,11 @@ For local development please follow next steps:
- for custom network setup create `.env.local` file with all required environment variables from the [list](#environment-variables) and run `yarn dev`
- navigate to the host from logs output
## Components visual testing
We use [playwright experimental components testing](https://playwright.dev/docs/test-components) for visual (screenshots) CI check. Test renders a single component in headless browser in docker, generates screenshots and then compares this screenshot with a reference one.
To perform testing locally you need to install docker and run `yarn test-docker`
## Environment variables
### Variables list
The app instance could be customized by passing following variables to NodeJS environment at runtime.
......
export const WEI = BigInt(10 ** 18);
export const GWEI = BigInt(10 ** 9);
import BigNumber from 'bignumber.js';
export const WEI = new BigNumber(10 ** 18);
export const GWEI = new BigNumber(10 ** 9);
export const WEI_IN_GWEI = WEI.dividedBy(GWEI);
......@@ -16,7 +16,9 @@
"start:docker:poa_core": "docker run -p 3000:3000 --env-file ./configs/envs/.env.poa_core --env-file ./configs/envs/.env.common --env-file ./configs/envs/.env.secrets blockscout",
"lint:eslint": "./node_modules/.bin/eslint . --ext .js,.jsx,.ts,.tsx",
"prepare": "husky install",
"format-svg": "./node_modules/.bin/svgo -r ./icons"
"format-svg": "./node_modules/.bin/svgo -r ./icons",
"test-ct": "playwright test -c playwright-ct.config.ts",
"test-docker": "docker run --rm --network host -v $(pwd):/work/ -w /work/ -it mcr.microsoft.com/playwright:v1.27.0-focal ./run-tests.sh"
},
"dependencies": {
"@chakra-ui/react": "2.3.1",
......@@ -29,6 +31,7 @@
"@tanstack/react-query": "^4.0.10",
"@tanstack/react-query-devtools": "^4.0.10",
"@types/react-scroll": "^1.8.4",
"bignumber.js": "^9.1.0",
"dayjs": "^1.11.5",
"ethers": "^5.7.1",
"framer-motion": "^6",
......@@ -46,6 +49,7 @@
"use-font-face-observer": "^1.2.1"
},
"devDependencies": {
"@playwright/experimental-ct-react": "^1.26.1",
"@types/node": "17.0.36",
"@types/react": "18.0.9",
"@types/react-dom": "18.0.5",
......@@ -58,6 +62,7 @@
"eslint-plugin-regexp": "^1.7.0",
"husky": "^8.0.0",
"lint-staged": ">=10",
"playwright": "^1.26.1",
"svgo": "^2.8.0",
"typescript": "4.7.2"
},
......
import type { PlaywrightTestConfig } from '@playwright/experimental-ct-react';
import { devices } from '@playwright/experimental-ct-react';
/**
* See https://playwright.dev/docs/test-configuration.
*/
const config: PlaywrightTestConfig = {
testDir: './',
testMatch: /.*\.pw\.tsx/,
/* The base directory, relative to the config file, for snapshot files created with toMatchSnapshot and toHaveScreenshot. */
snapshotDir: './__snapshots__',
/* Maximum time one test can run for. */
timeout: 10 * 1000,
/* Run tests in files in parallel */
fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: Boolean(process.env.CI),
/* Retry on CI only */
retries: process.env.CI ? 2 : 0,
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : undefined,
/* 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: {
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
/* Port to use for Playwright component endpoint. */
ctPort: 3100,
headless: true,
},
/* Configure projects for major browsers */
projects: [
{
name: 'chromium',
use: {
...devices['Desktop Chrome'],
},
},
{
name: 'firefox',
use: {
...devices['Desktop Firefox'],
},
},
{
name: 'webkit',
use: {
...devices['Desktop Safari'],
},
},
],
};
export default config;
import { ChakraProvider } from '@chakra-ui/react';
import type { ColorMode } from '@chakra-ui/react';
import React from 'react';
import theme from '../theme';
type Props = {
children: React.ReactNode;
colorMode?: ColorMode;
}
const RenderWithChakra = ({ children, colorMode = 'light' }: Props) => {
return (
<ChakraProvider theme={{ ...theme, config: { ...theme.config, initialColorMode: colorMode } }}>
{ children }
</ChakraProvider>
);
};
export default RenderWithChakra;
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Testing Page</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/playwright/index.ts"></script>
</body>
</html>
// Import styles, initialize component theme here.
// import '../src/common.css';
export {};
#!/bin/sh
yarn install
yarn test-ct
\ No newline at end of file
import { Box, Text, chakra } from '@chakra-ui/react';
import { utils, constants } from 'ethers';
import BigNumber from 'bignumber.js';
import React from 'react';
import { WEI, GWEI } from 'lib/consts';
interface Props {
value: string;
unit?: 'wei' | 'gwei' | 'ether';
currency?: string;
exchangeRate?: string;
className?: string;
accuracy?: number;
accuracyUsd?: number;
}
const CurrencyValue = ({ value, currency = '', unit = 'wei', exchangeRate, className }: Props) => {
const valueBn = utils.parseUnits(value, unit);
const exchangeRateBn = utils.parseUnits(exchangeRate || '0', 'ether');
const usdBn = valueBn.mul(exchangeRateBn).div(constants.WeiPerEther);
const CurrencyValue = ({ value, currency = '', unit = 'wei', exchangeRate, className, accuracy, accuracyUsd }: Props) => {
let unitBn: BigNumber.Value;
switch (unit) {
case 'wei':
unitBn = WEI;
break;
case 'gwei':
unitBn = GWEI;
break;
default:
unitBn = new BigNumber(1);
}
const valueBn = new BigNumber(value);
const valueCurr = valueBn.dividedBy(unitBn);
const exchangeRateBn = new BigNumber(exchangeRate || 0);
const usdBn = valueCurr.times(exchangeRateBn);
return (
<Box as="span" className={ className }>
<Text as="span">
{ Number(utils.formatUnits(valueBn)).toLocaleString() }{ currency ? ` ${ currency }` : '' }</Text>
{ accuracy ? valueCurr.toFixed(accuracy) : valueCurr.toFixed() }{ currency ? ` ${ currency }` : '' }
</Text>
{ exchangeRate !== undefined && exchangeRate !== null &&
<Text as="span" variant="secondary" whiteSpace="pre" fontWeight={ 400 }> (${ utils.formatUnits(usdBn) })</Text> }
// TODO: mb need to implement rounding to the first significant digit
<Text as="span" variant="secondary" whiteSpace="pre" fontWeight={ 400 }> (${ accuracyUsd ? usdBn.toFixed(accuracyUsd) : usdBn.toFixed() })</Text>
}
</Box>
);
};
......
import { test, expect } from '@playwright/experimental-ct-react';
import React from 'react';
import RenderWithChakra from '../../playwright/RenderWithChakra';
import Utilization from './Utilization';
test.use({ viewport: { width: 100, height: 50 } });
test('Utilization light', async({ mount }) => {
const component = await mount(
<RenderWithChakra>
<Utilization value={ 0.1 }/>
</RenderWithChakra>,
);
await expect(component).toHaveScreenshot();
});
test('Utilization dark', async({ mount }) => {
const component = await mount(
<RenderWithChakra colorMode="dark">
<Utilization value={ 0.1 }/>
</RenderWithChakra>,
);
await expect(component).toHaveScreenshot();
});
import { Grid, GridItem, Text, Box, Icon, Link, Flex } from '@chakra-ui/react';
import { useQuery } from '@tanstack/react-query';
import appConfig from 'configs/app/config';
import { utils } from 'ethers';
import BigNumber from 'bignumber.js';
import { useRouter } from 'next/router';
import React from 'react';
import { scroller, Element } from 'react-scroll';
......@@ -10,6 +9,7 @@ import type { Transaction } from 'types/api/transaction';
import clockIcon from 'icons/clock.svg';
import flameIcon from 'icons/flame.svg';
import { WEI, WEI_IN_GWEI } from 'lib/consts';
// import errorIcon from 'icons/status/error.svg';
// import successIcon from 'icons/status/success.svg';
import dayjs from 'lib/date/dayjs';
......@@ -164,17 +164,17 @@ const TxDetails = () => {
title="Gas price"
hint="Price per unit of gas specified by the sender. Higher gas prices can prioritize transaction inclusion during times of high usage."
>
<Text mr={ 1 }>{ utils.formatUnits(utils.parseUnits(String(data.gas_price), 'wei')) } { appConfig.network.currency }</Text>
<Text variant="secondary">({ Number(utils.formatUnits(utils.parseUnits(String(data.gas_price), 'wei'), 'gwei')) } Gwei)</Text>
<Text mr={ 1 }>{ BigNumber(data.gas_price).dividedBy(WEI).toFixed() } { selectedNetwork?.currency }</Text>
<Text variant="secondary">({ BigNumber(data.gas_price).dividedBy(WEI_IN_GWEI).toFixed() } Gwei)</Text>
</DetailsInfoItem>
<DetailsInfoItem
title="Gas limit & usage by txn"
hint="Actual gas amount used by the transaction."
>
<Text>{ utils.commify(data.gas_used) }</Text>
<Text>{ BigNumber(data.gas_used).toFormat() }</Text>
<TextSeparator/>
<Text >{ utils.commify(data.gas_limit) }</Text>
<Utilization ml={ 4 } value={ utils.parseUnits(data.gas_used).mul(10_000).div(utils.parseUnits(data.gas_limit)).toNumber() / 10_000 }/>
<Text >{ BigNumber(data.gas_limit).toFormat() }</Text>
<Utilization ml={ 4 } value={ BigNumber(data.gas_used).dividedBy(BigNumber(data.gas_limit)).toNumber() }/>
</DetailsInfoItem>
{ (data.base_fee_per_gas || data.max_fee_per_gas || data.max_priority_fee_per_gas) && (
<DetailsInfoItem
......@@ -185,21 +185,21 @@ const TxDetails = () => {
{ data.base_fee_per_gas && (
<Box>
<Text as="span" fontWeight="500">Base: </Text>
<Text fontWeight="600" as="span">{ utils.formatUnits(utils.parseUnits(String(data.base_fee_per_gas), 'wei'), 'gwei') }</Text>
<Text fontWeight="600" as="span">{ BigNumber(data.base_fee_per_gas).dividedBy(WEI_IN_GWEI).toFixed() }</Text>
</Box>
) }
{ data.max_fee_per_gas && (
<Box>
<TextSeparator/>
<Text as="span" fontWeight="500">Max: </Text>
<Text fontWeight="600" as="span">{ utils.formatUnits(utils.parseUnits(String(data.max_fee_per_gas), 'wei'), 'gwei') }</Text>
<Text fontWeight="600" as="span">{ BigNumber(data.max_fee_per_gas).dividedBy(WEI_IN_GWEI).toFixed() }</Text>
</Box>
) }
{ data.max_priority_fee_per_gas && (
<Box>
<TextSeparator/>
<Text as="span" fontWeight="500">Max priority: </Text>
<Text fontWeight="600" as="span">{ utils.formatUnits(utils.parseUnits(String(data.max_priority_fee_per_gas), 'wei'), 'gwei') }</Text>
<Text fontWeight="600" as="span">{ BigNumber(data.max_priority_fee_per_gas).dividedBy(WEI_IN_GWEI).toFixed() }</Text>
</Box>
) }
</DetailsInfoItem>
......
This source diff could not be displayed because it is too large. You can view the blob instead.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment