Skip to content

Commit

Permalink
Merge main into branch issue-#702
Browse files Browse the repository at this point in the history
  • Loading branch information
tiagofilipenunes committed Nov 9, 2023
2 parents 26e2516 + 2e666cc commit d3106a9
Show file tree
Hide file tree
Showing 98 changed files with 1,654 additions and 1,049 deletions.
4 changes: 4 additions & 0 deletions e2e/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ 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
- `ETH/USDC`: Trade Buy
- `USDC/USDT`: Trade Sell

## Common Errors

Expand Down
120 changes: 59 additions & 61 deletions e2e/pages/strategy.spec.ts
Original file line number Diff line number Diff line change
@@ -1,83 +1,81 @@
import { test, expect } from '@playwright/test';
import { navigateTo, screenshot, waitFor } from '../utils/operators';
import { mockApi } from '../utils/mock-api';
import { setupImposter } from '../utils/debug';
import { setupImposter } from '../utils/DebugDriver';
import { CreateStrategyDriver, MyStrategyDriver } from '../utils/strategy';
import { NotificationDriver } from '../utils/NotificationDriver';
import { checkApproval } from '../utils/modal';

test.describe('Strategies', () => {
test.beforeEach(async ({ page }) => {
await Promise.all([mockApi(page), setupImposter(page)]);
});
test('First Strategy Page', async ({ page }) => {
await navigateTo(page, '/');
await page.getByTestId('first-strategy').waitFor({ state: 'visible' });
const driver = new MyStrategyDriver(page);
await driver.firstStrategy().waitFor({ state: 'visible' });
await screenshot(page, 'first-strategy');
});

test('Create Limit Strategy ETH->DAI', async ({ page }) => {
test.setTimeout(120_000);
await waitFor(page, 'balance-DAI', 20_000);
const configs = [
{
base: 'ETH',
quote: 'DAI',
buy: {
price: '1500',
budget: '10',
},
sell: {
price: '1700',
budget: '2',
},
},
];

await navigateTo(page, '/');
await page.getByTestId('create-strategy-desktop').click();

// Select Base
await page.getByTestId('select-base-token').click();
await waitFor(page, 'modal-container');
await page.getByLabel('Select Token').fill('eth');
await page.getByTestId('select-token-ETH').click();
await page.getByTestId('modal-container').waitFor({ state: 'detached' });
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);

// Select Quote
await page.getByTestId('select-quote-token').click();
await waitFor(page, 'modal-container');
await page.getByLabel('Select Token').fill('dai');
await page.getByTestId('select-token-DAI').click();
await page.getByText('Next Step').click();
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');

// Fill Buy fields
const buy = page.getByTestId('buy-section');
await buy.getByTestId('input-limit').fill('1500');
await buy.getByTestId('input-budget').fill('10');
await expect(buy.getByTestId('outcome-value')).toHaveText('0.006666 ETH');
await expect(buy.getByTestId('outcome-quote')).toHaveText('1,500 DAI');
// 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}`);

// Fill Sell fields
const sell = page.getByTestId('sell-section');
await sell.getByTestId('input-limit').fill('1700');
await sell.getByTestId('input-budget').fill('2');
await expect(sell.getByTestId('outcome-value')).toHaveText('3,400 DAI');
await expect(sell.getByTestId('outcome-quote')).toHaveText('1,700 DAI');
await createForm.submit();

await page.getByText('Create Strategy').click();
await checkApproval(page, [base, quote]);

// Accept approval
const approvalModal = await waitFor(page, 'approval-modal');
const ethMsg = approvalModal.getByTestId('msg-ETH');
await expect(ethMsg).toHaveText('Pre-Approved');
await approvalModal.getByTestId('approve-DAI').click();
const daiApprovalMsg = await waitFor(page, 'msg-DAI');
await expect(daiApprovalMsg).toHaveText('Approved');
await approvalModal.getByText('Create Strategy').click();
await page.waitForURL('/', { timeout: 10_000 });

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.'
);

// Verfiy notification
const notif = page.getByTestId('notification-create-strategy');
await expect(notif.getByTestId('notif-title')).toHaveText('Success');
await expect(notif.getByTestId('notif-description')).toHaveText(
'New strategy was successfully created.'
);

// Verify strategy data
const strategies = page.locator('[data-testid="strategy-list"] > li');
await strategies.waitFor({ state: 'visible' });
await expect(strategies).toHaveCount(1);
const [strategy] = await strategies.all();
await expect(strategy.getByTestId('token-pair')).toHaveText('ETH/DAI');
await expect(strategy.getByTestId('status')).toHaveText('Active');
await expect(strategy.getByTestId('total-budget')).toHaveText('$3,344');
await expect(strategy.getByTestId('buy-budget')).toHaveText('10 DAI');
await expect(strategy.getByTestId('buy-budget-fiat')).toHaveText('$10.00');
await expect(strategy.getByTestId('sell-budget-fiat')).toHaveText('$3,334');
});
// 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');
});
}
});
91 changes: 91 additions & 0 deletions e2e/pages/trade.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/* eslint-disable prettier/prettier */
import { test, expect } from '@playwright/test';
import { mockApi } from '../utils/mock-api';
import { DebugDriver, setupImposter } 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)]);
});

const configs = [
{
mode: 'buy' as const,
source: 'USDC',
target: 'ETH',
sourceValue: '100',
targetValue: '0.059554032010090174',
},
{
mode: 'sell' as const,
source: 'USDC',
target: 'USDT',
sourceValue: '100',
targetValue: '100.047766',
},
];

for (const config of configs) {
const { mode, source, target, sourceValue, targetValue } = config;
const testName =
mode === 'buy'
? `Buy ${target} with ${source}`
: `Sell ${source} for ${target}`;

test(testName, async ({ page }) => {
test.setTimeout(120_000);
// Store current balance
const debug = new DebugDriver(page);
const balance = {
source: await debug.getBalance(config.source).textContent(),
target: await debug.getBalance(config.target).textContent(),
};

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

// Select pair
await driver.selectPair();
await driver.setPay();
await expect(driver.getReceiveInput()).toHaveValue(targetValue);

// Verify routing
const routing = await driver.openRouting();
await expect(routing.getSource()).toHaveValue(sourceValue);
await expect(routing.getTarget()).toHaveValue(targetValue);
await routing.close();

await driver.submit();

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

// Verify notification
const notif = new NotificationDriver(page, 'trade');
await expect(notif.getTitle()).toHaveText('Success', { timeout: 10_000 });
await expect(notif.getDescription()).toHaveText(
`Trading ${sourceValue} ${source} for ${target} was successfully completed.`,
{ timeout: 10_000 }
);

// Verify form empty
expect(driver.getPayInput()).toHaveValue('');
expect(driver.getReceiveInput()).toHaveValue('');

// Check balance diff
await navigateTo(page, '/debug');

const sourceDelta = Number(balance.source) - Number(config.sourceValue);
const nextSource = new RegExp(sourceDelta.toString());
await expect(debug.getBalance(config.source)).toHaveText(nextSource);
const targetDelta = Number(balance.target) + Number(config.targetValue);
const nextTarget = new RegExp(targetDelta.toString());
await expect(debug.getBalance(config.target)).toHaveText(nextTarget);
});
}
});
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.
8 changes: 8 additions & 0 deletions e2e/utils/debug.ts → e2e/utils/DebugDriver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,11 @@ export const setupImposter = async (
// Example: await waitFor(page, 'balance-DAI');
}
};

export class DebugDriver {
constructor(private page: Page) {}

getBalance(token: string) {
return this.page.getByTestId(`balance-${token}`);
}
}
26 changes: 26 additions & 0 deletions e2e/utils/NotificationDriver.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Page } from '@playwright/test';

type NotificationType =
| 'reject'
| 'approve'
| 'revoke'
| 'approve-error'
| 'create-strategy'
| 'renew-strategy'
| 'edit-strategy-name'
| 'withdraw-strategy'
| 'delete-strategy'
| 'change-rates-strategy'
| 'trade';

export class NotificationDriver {
private notif = this.page.getByTestId(`notification-${this.type}`);
constructor(private page: Page, private type: NotificationType) {}

getTitle() {
return this.notif.getByTestId('notif-title');
}
getDescription() {
return this.notif.getByTestId('notif-description');
}
}
56 changes: 56 additions & 0 deletions e2e/utils/TradeDriver.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/* eslint-disable prettier/prettier */
import { Page } from '@playwright/test';
import { waitFor } from './operators';
import { closeModal, waitModalClose, waitModalOpen } from './modal';

interface TradeConfig {
mode: 'buy' | 'sell';
source: string;
target: string;
sourceValue: string;
targetValue: string;
}

export class TradeDriver {
public form = this.page.getByTestId(`${this.config.mode}-form`);

constructor(private page: Page, private config: TradeConfig) {}

getPayInput() {
return this.form.getByLabel('You Pay');
}

getReceiveInput() {
return this.form.getByLabel('You Receive');
}

async selectPair() {
const { mode, target, source } = this.config;
await this.page.getByTestId('select-trade-pair').click();
await waitModalOpen(this.page);
const pair = mode === 'buy' ? [target, source] : [source, target];
this.page.getByTestId('search-token-pair').fill(`${pair.join(' ')}`);
const select = await waitFor(this.page, `select-${pair.join('_')}`);
await select.click();
await waitModalClose(this.page);
}

setPay() {
const { sourceValue } = this.config;
return this.form.getByLabel('You Pay').fill(sourceValue);
}

async openRouting() {
await this.form.getByTestId('routing').click();
const modal = await waitFor(this.page, 'modal-container');
return {
getSource: () => modal.getByTestId('confirm-source'),
getTarget: () => modal.getByTestId('confirm-target'),
close: () => closeModal(this.page),
};
}

submit() {
return this.form.getByTestId('submit').click();
}
}
27 changes: 27 additions & 0 deletions e2e/utils/modal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Page, expect } from '@playwright/test';
import { waitFor } from './operators';

export const waitModalOpen = (page: Page) => waitFor(page, 'modal-container');
export const waitModalClose = (page: Page) => {
return page.getByTestId('modal-container').waitFor({ state: 'detached' });
};
export const closeModal = async (page: Page) => {
await page.getByTestId('modal-close').click();
return waitModalClose(page);
};

export const checkApproval = async (page: Page, tokens: string[]) => {
const modal = await waitModalOpen(page);
for (const token of tokens) {
if (token === 'ETH') {
const msg = modal.getByTestId(`msg-${token}`);
await expect(msg).toHaveText('Pre-Approved');
} else {
await modal.getByTestId(`approve-${token}`).click();
const msg = await waitFor(page, `msg-${token}`);
await expect(msg).toHaveText('Approved');
}
}
await modal.getByTestId('approve-submit').click();
await closeModal(page);
};
2 changes: 1 addition & 1 deletion e2e/utils/operators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export const screenshot = (target: Page | Locator, name: string) => {

const urlNames = {
'/': 'My Strategies',
'/trade': 'Trade',
'/trade?*': 'Trade',
'/explorer': 'Explorer',
'/debug': 'Debug',
};
Expand Down
Loading

0 comments on commit d3106a9

Please sign in to comment.