Skip to content

Commit

Permalink
Merge branch 'main' into dependabot/npm_and_yarn/adobe/css-tools-4.3.2
Browse files Browse the repository at this point in the history
  • Loading branch information
pingustar authored Jan 16, 2024
2 parents 20dc2fd + f7e8025 commit 5904382
Show file tree
Hide file tree
Showing 152 changed files with 6,208 additions and 1,778 deletions.
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
20
29 changes: 21 additions & 8 deletions e2e/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,38 +3,46 @@
### Local

Install Playwright browsers

```shell
yarn playwright install
```

E2E tests call Tenderly API to create a fork of mainnet at a specific blockNumber. You need to have a [Tenderly account](https://tenderly.co/).

Add in your .env :
Add in your .env :

```bash
TENDERLY_ACCOUNT=bancor # account or organisation
TENDERLY_PROJECT=carbon-test-forks
TENDERLY_ACCESS_KEY=...
```

Run in headless mode :
Run in headless mode :

```shell
yarn e2e
```

Run in UI mode :

```shell
yarn e2e --ui
```

### CI

E2E run in a github action on:

- every commit
- when draft PR become ready for review

### Screenshot

Screenshot are taken during E2E and images are pushed automatically on the current branch

Screenshot are only taking :
Screenshot are only taking :

- In Github Action
- If PR is not draft

Expand All @@ -44,7 +52,8 @@ We do not run screenshot on draft commit as we would need to merge the local bra

## Tips

Wait for an element before asserting.
Wait for an element before asserting.

```typescript
// Wait for the modal with utils `waitFor`
await waitFor(page, 'modal');
Expand All @@ -57,14 +66,17 @@ await expect(list.toCount(1));
```

TestId must be unique in the context. When testing a list, select the element first:

```typescript
const list = page.locator('[data-testid="strategy-list"] > li');
const [first] = await list.all();
await expect(first.getTestId('strategy-pair')).toHaveText('ETH/DAI');
```

As we use the same network for all tests, mutating a strategy in one test might impact the other. For now, we'll try to use different pairs of token for each test to avoid side effect :
- `ETH/DAI`: Create strategy
As we use the same network for all tests, mutating a strategy in one test might impact the other. For now, we'll try to use different pairs of token for each test to avoid side effect :

- `ETH/DAI`: Create recurring strategy
- `ETH/BNT`: Create overlapping strategy
- `ETH/USDC`: Trade Buy
- `USDC/USDT`: Trade Sell

Expand All @@ -79,10 +91,11 @@ As we use the same network for all tests, mutating a strategy in one test might
- **Error**: `Page is closed`
- **Solution**: Increase timeout of test
- **Description**: This error happens if test is longer that the defaut timeout (30s). It can append at any stage of the test
- **Example**:
- **Example**:

```typescript
test('Create strategy', ({ page }) => {
test.setTimeout(60_000); // timeout of 60s for this test
...
})
```
```
150 changes: 84 additions & 66 deletions e2e/pages/strategy.spec.ts
Original file line number Diff line number Diff line change
@@ -1,81 +1,99 @@
import { test, expect } from '@playwright/test';
import { navigateTo, screenshot, waitFor } from '../utils/operators';
import { test } from '@playwright/test';
import * as recurring from '../tests/strategy/recurring/';
import * as disposable from '../tests/strategy/disposable/';
import * as overlapping from '../tests/strategy/overlapping/';

import { StrategyType } from './../utils/strategy/template';
import { navigateTo, screenshot } from '../utils/operators';
import { mockApi } from '../utils/mock-api';
import { setupImposter } from '../utils/DebugDriver';
import { CreateStrategyDriver, MyStrategyDriver } from '../utils/strategy';
import { NotificationDriver } from '../utils/NotificationDriver';
import { checkApproval } from '../utils/modal';
import { DebugDriver, removeFork, setupFork } from '../utils/DebugDriver';
import {
MyStrategyDriver,
OverlappingStrategyTestCase,
RecurringStrategyTestCase,
} from '../utils/strategy';

type TestCase = (RecurringStrategyTestCase | OverlappingStrategyTestCase) & {
type: StrategyType;
};

const testCases: TestCase[] = [
{
type: 'recurring',
setting: 'limit_limit',
base: 'ETH',
quote: 'DAI',
buy: {
min: '1500',
max: '1500',
budget: '10',
budgetFiat: '10',
},
sell: {
min: '1700',
max: '1700',
budget: '2',
budgetFiat: '3334',
},
},
{
type: 'overlapping',
base: 'BNT',
quote: 'USDC',
buy: {
min: '0.3',
max: '0.545454',
budget: '12.501572',
budgetFiat: '12.5',
},
sell: {
min: '0.33',
max: '0.6',
budget: '30',
budgetFiat: '12.61',
},
spread: '10', // Need a large spread for tooltip test
},
];

const testDescription = (testCase: TestCase) => {
if (testCase.type === 'overlapping') return 'Overlapping';
if (testCase.type === 'disposable') return `Disposable ${testCase.setting}`;
return `Recurring ${testCase.setting.split('_').join(' ')}`;
};

test.describe('Strategies', () => {
test.beforeEach(async ({ page }) => {
await Promise.all([mockApi(page), setupImposter(page)]);
test.beforeEach(async ({ page }, testInfo) => {
testInfo.setTimeout(180_000);
await setupFork(testInfo);
const debug = new DebugDriver(page);
await debug.visit();
await debug.setRpcUrl(testInfo);
await Promise.all([mockApi(page), debug.setupImposter(), debug.setE2E()]);
});
test.afterEach(async ({}, testInfo) => {
await removeFork(testInfo);
});

test('First Strategy Page', async ({ page }) => {
await navigateTo(page, '/');
const driver = new MyStrategyDriver(page);
await driver.firstStrategy().waitFor({ state: 'visible' });
await screenshot(page, 'first-strategy');
});

const configs = [
{
base: 'ETH',
quote: 'DAI',
buy: {
price: '1500',
budget: '10',
},
sell: {
price: '1700',
budget: '2',
},
},
];

for (const config of configs) {
const { base, quote } = config;
test(`Create Limit Strategy ${base}->${quote}`, async ({ page }) => {
test.setTimeout(180_000);
await waitFor(page, `balance-${quote}`, 30_000);

await navigateTo(page, '/');
const myStrategies = new MyStrategyDriver(page);
const createForm = new CreateStrategyDriver(page, config);
await myStrategies.createStrategy();
await createForm.selectBase();
await createForm.selectQuote();
const buy = await createForm.fillLimit('buy');
const sell = await createForm.fillLimit('sell');

// Assert 100% outcome
await expect(buy.outcomeValue()).toHaveText(`0.006666 ${base}`);
await expect(buy.outcomeQuote()).toHaveText(`1,500 ${quote}`);
await expect(sell.outcomeValue()).toHaveText(`3,400 ${quote}`);
await expect(sell.outcomeQuote()).toHaveText(`1,700 ${quote}`);

await createForm.submit();

await checkApproval(page, [base, quote]);

await page.waitForURL('/', { timeout: 10_000 });

// Verfiy notification
const notif = new NotificationDriver(page, 'create-strategy');
await expect(notif.getTitle()).toHaveText('Success');
await expect(notif.getDescription()).toHaveText(
'New strategy was successfully created.'
);
const testStrategies = {
recurring,
disposable,
overlapping,
};

// Verify strategy data
const strategies = await myStrategies.getAllStrategies();
await expect(strategies).toHaveCount(1);
const strategy = await myStrategies.getStrategy(1);
await expect(strategy.pair()).toHaveText(`${base}/${quote}`);
await expect(strategy.status()).toHaveText('Active');
await expect(strategy.totalBudget()).toHaveText('$3,344');
await expect(strategy.buyBudget()).toHaveText(`10 ${quote}`);
await expect(strategy.buyBudgetFiat()).toHaveText('$10.00');
await expect(strategy.sellBudgetFiat()).toHaveText('$3,334');
for (const testCase of testCases) {
test.describe(testDescription(testCase), () => {
const testSuite = testStrategies[testCase.type];
for (const [, testFn] of Object.entries(testSuite)) {
testFn(testCase);
}
});
}
});
37 changes: 23 additions & 14 deletions e2e/pages/trade.spec.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,27 @@
/* eslint-disable prettier/prettier */
import { test, expect } from '@playwright/test';
import { mockApi } from '../utils/mock-api';
import { DebugDriver, setupImposter } from '../utils/DebugDriver';
import { DebugDriver, removeFork, setupFork } from '../utils/DebugDriver';
import { TradeDriver } from '../utils/TradeDriver';
import { navigateTo } from '../utils/operators';
import { checkApproval } from '../utils/modal';
import { NotificationDriver } from '../utils/NotificationDriver';

test.describe('Trade', () => {
test.beforeEach(async ({ page }) => {
await Promise.all([mockApi(page), setupImposter(page)]);
test.beforeEach(async ({ page }, testInfo) => {
testInfo.setTimeout(180_000);
const debug = new DebugDriver(page);
await debug.visit();
await setupFork(testInfo);
await debug.setRpcUrl(testInfo);
await Promise.all([mockApi(page), debug.setupImposter(), debug.setE2E()]);
});

const configs = [
test.afterEach(async ({}, testInfo) => {
await removeFork(testInfo);
});

const testCases = [
{
mode: 'buy' as const,
source: 'USDC',
Expand All @@ -29,8 +38,8 @@ test.describe('Trade', () => {
},
];

for (const config of configs) {
const { mode, source, target, sourceValue, targetValue } = config;
for (const testCase of testCases) {
const { mode, source, target, sourceValue, targetValue } = testCase;
const testName =
mode === 'buy'
? `Buy ${target} with ${source}`
Expand All @@ -41,13 +50,13 @@ test.describe('Trade', () => {
// Store current balance
const debug = new DebugDriver(page);
const balance = {
source: await debug.getBalance(config.source).textContent(),
target: await debug.getBalance(config.target).textContent(),
source: await debug.getBalance(testCase.source).textContent(),
target: await debug.getBalance(testCase.target).textContent(),
};

// Test Trade
await navigateTo(page, '/trade?*');
const driver = new TradeDriver(page, config);
const driver = new TradeDriver(page, testCase);

// Select pair
await driver.selectPair();
Expand All @@ -63,7 +72,7 @@ test.describe('Trade', () => {
await driver.submit();

// Token approval
await checkApproval(page, [config.source]);
await checkApproval(page, [testCase.source]);

// Verify notification
const notif = new NotificationDriver(page, 'trade');
Expand All @@ -80,12 +89,12 @@ test.describe('Trade', () => {
// Check balance diff
await navigateTo(page, '/debug');

const sourceDelta = Number(balance.source) - Number(config.sourceValue);
const sourceDelta = Number(balance.source) - Number(testCase.sourceValue);
const nextSource = new RegExp(sourceDelta.toString());
await expect(debug.getBalance(config.source)).toHaveText(nextSource);
const targetDelta = Number(balance.target) + Number(config.targetValue);
await expect(debug.getBalance(testCase.source)).toHaveText(nextSource);
const targetDelta = Number(balance.target) + Number(testCase.targetValue);
const nextTarget = new RegExp(targetDelta.toString());
await expect(debug.getBalance(config.target)).toHaveText(nextTarget);
await expect(debug.getBalance(testCase.target)).toHaveText(nextTarget);
});
}
});
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified e2e/screenshots/first-strategy.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
23 changes: 0 additions & 23 deletions e2e/setup.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,9 @@
import { chromium, firefox, webkit, type FullConfig } from '@playwright/test';
import { CreateForkBody, createFork, forkRpcUrl } from './utils/tenderly';

const forkConfig: CreateForkBody = {
network_id: '1',
alias: 'E2E-CI',
// Sep-12-2023
block_number: 18120000,
};

const browsers = { chromium, firefox, webkit };
type BrowserName = keyof typeof browsers;

async function globalSetup(config: FullConfig) {
console.log('Setting up Tenderly Fork');
console.time('Fork is setup');

// Create a Fork
const fork = await createFork(forkConfig);
process.env['TENDERLY_FORK_ID'] = fork.id;
const rpcUrl = forkRpcUrl(fork.id);
// On each browser, fill the Debug form and save localStorage in storageState
const setupProjects = config.projects.map(async (project) => {
if (!(project.name in browsers)) return;
const { baseURL, storageState } = project.use;
Expand All @@ -28,17 +12,10 @@ async function globalSetup(config: FullConfig) {
const browser = await browsers[project.name as BrowserName].launch();
const page = await browser.newPage();
await page.goto(`${baseURL}/debug`);
// SET RPC-URL
await page.getByLabel('RPC URL').fill(rpcUrl);
await page.getByTestId('unchecked-signer').click();
await page.getByTestId('save-rpc').click();
await page.waitForURL(`${baseURL}/debug`);
await page.context().storageState({ path: storageState as string });
await browser.close();
});
await Promise.all(setupProjects);
console.timeEnd('Fork is setup');
console.log('RPC URL:', rpcUrl);
}

export default globalSetup;
Loading

0 comments on commit 5904382

Please sign in to comment.