Skip to content

Commit

Permalink
Merge pull request #32 from khulnasoft-lab/playwright
Browse files Browse the repository at this point in the history
Playwright
  • Loading branch information
gitworkflows authored Apr 10, 2024
2 parents c76a7f8 + fec924a commit f031f17
Show file tree
Hide file tree
Showing 10 changed files with 511 additions and 56 deletions.
18 changes: 17 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,22 @@ jobs:
- name: Run Tests
run: yarn test:unit

# Playwright section
- name: Install Playwright Browsers
run: yarn playwright install --with-deps

- name: Run Playwright tests
run: yarn playwright test

- uses: actions/upload-artifact@v4
if: ${{ !cancelled() }}
with:
name: playwright-report-${{ matrix.node }}
path: playwright-report/
retention-days: 30

# End Playwright section

- name: Run Component tests
uses: cypress-io/github-action@v6
with:
Expand Down Expand Up @@ -83,4 +99,4 @@ jobs:
env:
GITHUB_TOKEN: ${{ github.token }}
with:
asset_paths: '["./tradeUI.zip"]'
asset_paths: '["./tradeUI.zip"]'
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,7 @@ yarn-error.log*


components.d.ts
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/
51 changes: 51 additions & 0 deletions e2e/helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { Page } from '@playwright/test';

export async function setLoginInfo(page) {
await page.goto('/');
await page.evaluate(() => {
localStorage.setItem(
'ftAuthLoginInfo',
JSON.stringify({
'ftbot.0': {
botName: 'TestBot',
apiUrl: 'http://localhost:3000',
accessToken: 'access_token_tesst',
refreshToken: 'refresh_test',
autoRefresh: true,
},
}),
);
localStorage.setItem('ftSelectedBot', 'ftbot.0');
});
}

export async function defaultMocks(page: Page) {
page.route('**/api/v1/**', (route) => {
route.fulfill({
headers: { 'access-control-allow-origin': '*' },
json: {},
});
});

await page.route('**/api/v1/ping', (route) => {
return route.fulfill({ path: './cypress/fixtures/ping.json' });
});
await page.route('**/api/v1/show_config', (route) => {
return route.fulfill({ path: './cypress/fixtures/show_config.json' });
});
await page.route('**/api/v1/pair_candles?*', (route) => {
return route.fulfill({ path: './cypress/fixtures/pair_candles_btc_1m.json' });
});
}

export function getWaitForResponse(page: Page, url: string) {
const urlMapping = {
'@Ping': '**/api/v1/ping',
'@ShowConf': '**/api/v1/show_config',
'@PairCandles': '**/api/v1/pair_candles',
'@Logs': '**/api/v1/logs',
};
const urlMap = urlMapping[url] ?? url;

return page.waitForResponse(urlMap);
}
32 changes: 32 additions & 0 deletions e2e/logs.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { test, expect } from '@playwright/test';

import { setLoginInfo, defaultMocks, getWaitForResponse } from './helpers';

test.describe('Logs', () => {
test('Displays and reloads logs', async ({ page }) => {
///
await defaultMocks(page);
await setLoginInfo(page);
// const pingPromise = page.route('**/*ping*',

// const logsPromise = page.waitForResponse('**/api/v1/logs');
await page.route('**/api/v1/logs', (route) => {
return route.fulfill({ path: './cypress/fixtures/logs.json' });
});

const logs = getWaitForResponse(page, '@Logs');
const ping = getWaitForResponse(page, '@ShowConf');
await page.goto('/logs', { waitUntil: 'networkidle' });
await Promise.all([logs, ping]);

await expect(page.locator('span', { hasText: 'Checking exchange' })).toBeVisible();
await expect(page.locator('span', { hasText: 'Checking exchange' })).toHaveText(
/Checking exchange.../,
{},
);
// const logsPromise = page.waitForResponse('**/api/v1/logs');
const logsPromise = getWaitForResponse(page, '@Logs');
await page.getByRole('button', { name: 'Reload Logs' }).click();
await logsPromise;
});
});
151 changes: 151 additions & 0 deletions e2e/trade.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import { test, expect } from '@playwright/test';

import { setLoginInfo, defaultMocks } from './helpers';

function tradeMocks(page) {
const mapping = [
{ name: '@Status', url: '**/api/v1/status', fixture: 'status_empty.json' },
{ name: '@Profit', url: '**/api/v1/profit', fixture: 'profit.json' },
{ name: '@Trades', url: '**/api/v1/trades*', fixture: 'trades.json' },
{ name: '@Balance', url: '**/api/v1/balance', fixture: 'balance.json' },
{ name: '@Whitelist', url: '**/api/v1/whitelist', fixture: 'whitelist.json' },
{ name: '@Blacklist', url: '**/api/v1/blacklist', fixture: 'blacklist.json' },
{ name: '@Locks', url: '**/api/v1/locks', fixture: 'locks_empty.json' },
{ name: '@Performance', url: '**/api/v1/performance', fixture: 'performance.json' },
{
name: '@ReloadConfig',
method: 'POST',
url: '**/api/v1/reload_config',
fixture: 'reload_config.json',
},
];
mapping.forEach((item) => {
page.route(item.url, (route) => {
return route.fulfill({ path: `./cypress/fixtures/${item.fixture}` });
});
});
}

test.describe('Trade', () => {
test.beforeEach(async ({ page }) => {
await defaultMocks(page);
await setLoginInfo(page);

await tradeMocks(page);
});
test('Trade page', async ({ page }) => {
await Promise.all([
page.goto('/trade'),
// Wait for network requests
// page.waitForResponse('**/ping'),
page.waitForResponse('**/status'),
page.waitForResponse('**/profit'),
page.waitForResponse('**/balance'),
// page.waitForResponse('**/trades'),
page.waitForResponse('**/whitelist'),
page.waitForResponse('**/blacklist'),
page.waitForResponse('**/locks'),
]);

// // Check visibility of elements
await expect(page.locator('.drag-header', { hasText: 'Multi Pane' })).toBeInViewport();
await expect(page.locator('.drag-header', { hasText: 'Chart' })).toBeInViewport();
// Pairlist elements
await expect(page.locator('button', { hasText: 'BTC/USDT' })).toBeInViewport();
await expect(page.locator('button', { hasText: 'ETH/USDT' })).toBeInViewport();

// // Click on Performance button and wait for response
await Promise.all([
page.waitForResponse('**/performance'),
page.click('button:has-text("Performance")'),
]);

// // Check visibility of Profit USDT
await expect(page.locator('th:has-text("Profit USDT")')).toBeInViewport();

// // Test messageBox behavior

const dialogModal = page.getByRole('dialog');
const modalButton = page.locator(
'#MsgBoxModal .modal-dialog > .modal-content > .modal-footer > .btn-secondary:has-text("Cancel")',
);
await expect(dialogModal).not.toBeVisible();
await expect(dialogModal).not.toBeInViewport();

await expect(modalButton).not.toBeVisible();

await page.getByRole('button', { name: 'Stop Trading - Also stops' }).click();

// Modal open
await expect(dialogModal).toBeVisible();
await expect(dialogModal).toBeInViewport();
await expect(modalButton).toBeInViewport();

// // Close modal
await modalButton.click();

// // Modal closed
await expect(modalButton).not.toBeVisible();
await expect(modalButton).not.toBeInViewport();

// // Click on General tab
const performancePair = page.locator('td:has-text("XRP/USDT")');
await expect(performancePair).toBeInViewport();
await page.click('button[role="tab"]:has-text("General")');

// // Check visibility of elements
await expect(performancePair).not.toBeInViewport();
const openTrades = page.locator('.drag-header:has-text("Open Trades")');
openTrades.scrollIntoViewIfNeeded();
await expect(openTrades).toBeInViewport();
const closedTrades = page.locator('.drag-header:has-text("Closed Trades")');
closedTrades.scrollIntoViewIfNeeded();
await expect(closedTrades).toBeInViewport();
await expect(page.locator('span:has-text("TRX/USDT")')).toBeInViewport();
await expect(page.locator('td:has-text("8070.5")')).toBeInViewport();

// Scroll to top
const multiPane = page.locator('.drag-header', { hasText: 'Multi Pane' });
await expect(multiPane).toBeVisible();
await multiPane.scrollIntoViewIfNeeded();
await expect(multiPane).toBeInViewport();

// // Click on Reload Config button
await page.getByRole('button', { name: 'Reload Config' }).click();
// await page.locator('button[title*="Reload Config "]').click();
await expect(dialogModal).toBeVisible();
await expect(dialogModal).toBeInViewport();

const modalOkButton = page.locator(
'#MsgBoxModal .modal-dialog > .modal-content > .modal-footer > .btn-primary:has-text("Ok")',
);
await expect(modalOkButton).toBeVisible();
await modalOkButton.click();

await expect(page.getByText('Config reloaded successfully.')).toBeInViewport();
});
test('Trade page - drag and drop', async ({ page }) => {
await page.goto('/trade');

await page.locator('#avatar-drop').click();
const multiPane = page.locator('.drag-header', { hasText: 'Multi Pane' });

await page.getByLabel('Lock layout').uncheck();

const chartHeader = await page.locator('.drag-header:has-text("Chart")');
await expect(multiPane).toBeInViewport();
await expect(chartHeader).toBeInViewport();

// Test drag and drop functionality
const chartHeaderbb = await chartHeader.boundingBox();
if (chartHeaderbb) {
await chartHeader.hover();
await page.mouse.down();

await page.mouse.move(chartHeaderbb?.x + chartHeaderbb.width / 2, chartHeaderbb?.y + 200);
await page.mouse.up();
await expect(multiPane).toBeInViewport();
await expect(chartHeader).toBeInViewport();
}
});
});
17 changes: 11 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,15 @@
"cy:open": "cypress open",
"cy:run": "cypress run",
"cy:open-ct": "cypress open-ct",
"cy:run-ct": "cypress run --component"
"cy:run-ct": "cypress run --component",
"test:e2e": "yarn playwright test",
"test:e2e-chromium": "yarn playwright test --project=chromium",
"test:e2e-msedge": "yarn playwright test --project=msedge"
},
"dependencies": {
"@noction/vue-draggable-grid": "1.10.1",
"@noction/vue-draggable-grid": "1.9.16",
"@popperjs/core": "^2.11.8",
"@vuepic/vue-datepicker": "^8.3.1",
"@vuepic/vue-datepicker": "^8.3.2",
"@vueuse/core": "^10.9.0",
"@vueuse/integrations": "^10.9.0",
"axios": "^1.6.8",
Expand All @@ -44,16 +47,18 @@
"@cypress/vite-dev-server": "^5.0.7",
"@cypress/vue": "^6.0.0",
"@iconify-json/mdi": "^1.1.64",
"@playwright/test": "^1.40.0",
"@types/echarts": "^4.9.22",
"@typescript-eslint/eslint-plugin": "^7.4.0",
"@typescript-eslint/parser": "^7.4.0",
"@types/node": "^20.9.2",
"@typescript-eslint/eslint-plugin": "^7.5.0",
"@typescript-eslint/parser": "^7.5.0",
"@vitejs/plugin-vue": "^5.0.4",
"@vue/compiler-sfc": "3.4.21",
"@vue/eslint-config-prettier": "^9.0.0",
"@vue/eslint-config-typescript": "^13.0.0",
"@vue/runtime-dom": "^3.4.21",
"@vue/test-utils": "^2.4.5",
"cypress": "^13.7.1",
"cypress": "^13.7.2",
"eslint": "^8.57.0",
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-vue": "^9.24.0",
Expand Down
80 changes: 80 additions & 0 deletions playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { defineConfig, devices } from '@playwright/test';

/**
* Read environment variables from file.
* https://github.com/motdotla/dotenv
*/
// require('dotenv').config();

/**
* See https://playwright.dev/docs/test-configuration.
*/
export default defineConfig({
testDir: './e2e',
/* Run tests in files in parallel */
fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!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: {
/* Base URL to use in actions like `await page.goto('/')`. */
baseURL: 'http://localhost:3000',

/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
},

/* Configure projects for major browsers */
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'], channel: 'chrome' },
},
{
name: 'msedge',
use: { ...devices['Desktop Edge'], channel: 'msedge' }, // or "msedge-beta" or 'msedge-dev'
},
{
name: 'firefox',
use: { ...devices['Desktop Firefox'] },
},

{
name: 'webkit',
use: { ...devices['Desktop Safari'] },
},

/* Test against mobile viewports. */
// {
// name: 'Mobile Chrome',
// use: { ...devices['Pixel 5'] },
// },
// {
// name: 'Mobile Safari',
// use: { ...devices['iPhone 12'] },
// },

/* Test against branded browsers. */
// {
// name: 'Microsoft Edge',
// use: { ...devices['Desktop Edge'], channel: 'msedge' },
// },
// {
// name: 'Google Chrome',
// use: { ...devices['Desktop Chrome'], channel: 'chrome' },
// },
],

/* Run your local dev server before starting the tests */
webServer: {
command: 'yarn dev',
url: 'http://localhost:3000',
reuseExistingServer: !process.env.CI,
},
});
Loading

0 comments on commit f031f17

Please sign in to comment.