forked from gmx-io/gmx-interface
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'master' of https://github.com/gmx-io/gmx-interface
- Loading branch information
Showing
160 changed files
with
8,787 additions
and
2,150 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
autotests/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
node_modules | ||
test-results | ||
.env | ||
|
||
.yarn/* | ||
!.yarn/patches | ||
!.yarn/plugins | ||
!.yarn/releases | ||
!.yarn/sdks | ||
!.yarn/versions |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
# Autotests | ||
|
||
This repository contains the autotests for the GMX interface. | ||
|
||
## Prerequisites | ||
|
||
- Git [Installation guide](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) | ||
- Node.js >= 20 [Installation guide](https://nodejs.org/en/download/) | ||
- Yarn >= 1.22.5 [Installation guide](https://classic.yarnpkg.com/en/docs/install) | ||
|
||
## Install | ||
|
||
1. Clone the repository to your local machine. | ||
2. Install the dependencies by running `yarn`. | ||
3. Install playwright dependencies by running `npx playwright install` | ||
|
||
## How to run autotests locally | ||
|
||
1. Setup `.env` file in same directory as tests with the following content: | ||
|
||
```bash | ||
GMX_BASE_URL=<URL to GMX interface for testing> | ||
SEED=<SEED PHRASE FOR WALLET> | ||
PWDEBUG=<true if want to see tests UI> | ||
USE_METAMASK=<true if using real metamask> | ||
PW_WORKERS=<number of workers for parallel tests, preferable 1> | ||
NETWORK=arbitrum or fuji | ||
``` | ||
2. Run tests: `yarn test` | ||
## Writing tests | ||
Tests consist of test cases and page objects. The tests are designed to be simple, any complex interactions should be incapsulated in page objects. Each page object is either a [extended Locator](./src/elements/base-page.ts#L5) or inheritor of [BasePage](./src/elements/base-page.ts#12). Locators are extended interface to use only data-qa attributes for locating elements and implementing following API: | ||
- `.selector` - returns locator's selector string including data-qa attribute | ||
- `.waitForSelector()` - waits for element to appear on page | ||
- `.waitForVisible()` - waits for element to be visible | ||
BasePage is a class that provides basic methods for interacting with the page and manipulating locators. These classes are used to keep tests cases clean, declarative and simple, also to reuse same UI interactions in different pages. Each tests accepts [GmxApp object](./src/elements/page-objects.ts#L382) as a parameter, all interactions with APP should be accessible through this object. | ||
Ideally tests should be isolated from each other, so that they can be run in any order and not depend on each other. In other case use `test.describe.serial` to group tests that depend on each other. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
{ | ||
"name": "gmx-ui-autotests", | ||
"version": "1.0.0", | ||
"main": "index.js", | ||
"scripts": { | ||
"test": " npx playwright test", | ||
"postinstall": "npx playwright install" | ||
}, | ||
"author": "", | ||
"license": "ISC", | ||
"description": "", | ||
"dependencies": { | ||
"@depay/web3-mock": "^14.18.0", | ||
"@playwright/test": "^1.45.1", | ||
"@tenkeylabs/dappwright": "^2.8.5", | ||
"dotenv": "16.4.5" | ||
}, | ||
"devDependencies": { | ||
"@types/node": "^20.14.11" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import { PlaywrightTestConfig } from "@playwright/test"; | ||
|
||
const config: PlaywrightTestConfig = { | ||
timeout: process.env.PW_TIMEOUT ? Number(process.env.PW_TIMEOUT) : 60_000, | ||
testDir: "./src/tests", | ||
testMatch: "*.spec.ts", | ||
workers: process.env.PW_WORKERS ? Number(process.env.PW_WORKERS) : 1, | ||
use: { | ||
viewport: { width: 1600, height: 1200 }, | ||
}, | ||
}; | ||
|
||
export default config; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
import { BrowserContext, test as baseTest } from "@playwright/test"; | ||
import dappwright, { Dappwright, MetaMaskWallet } from "@tenkeylabs/dappwright"; | ||
import { GmxApp } from "./elements/page-objects"; | ||
|
||
import { config } from "dotenv"; | ||
import { mockWeb3 } from "./mocks/web3"; | ||
|
||
config(); | ||
|
||
let savedContext: BrowserContext | undefined; | ||
let savedWallet: Dappwright | undefined; | ||
|
||
export const test = baseTest.extend<{ | ||
context: BrowserContext; | ||
wallet: Dappwright; | ||
appUrl: string; | ||
gmx: GmxApp; | ||
}>({ | ||
page: async ({ context }, use) => { | ||
const page = await context.newPage(); | ||
|
||
if (!process.env.USE_METAMASK) { | ||
/** | ||
* @todo Fix this mock to work with rainbowkit | ||
*/ | ||
await mockWeb3(page, () => { | ||
Web3Mock.mock({ | ||
blockchain: "ethereum", | ||
accounts: { return: [process.env.ACCOUNT] }, | ||
}); | ||
}); | ||
} | ||
|
||
await use(page); | ||
}, | ||
|
||
context: async ({ appUrl }, use) => { | ||
if (savedContext && savedWallet) { | ||
await use(savedContext); | ||
return; | ||
} | ||
|
||
const [wallet, metamaskPage, ctx] = await dappwright.bootstrap("", { | ||
wallet: "metamask", | ||
version: MetaMaskWallet.recommendedVersion, | ||
seed: process.env.SEED, | ||
headless: !Boolean(process.env.PWDEBUG), | ||
}); | ||
|
||
if (process.env.USE_METAMASK) { | ||
console.log("[Preparing Metamask]"); | ||
if (process.env.CHAIN?.toLocaleLowerCase() === "arbitrum") { | ||
await metamaskPage.locator('[data-testid="network-display"]').click(); | ||
await metamaskPage.locator('button:has-text("Add Network")').click(); | ||
await metamaskPage.locator('//h6[contains(text(), "Arbitrum One")]/../../../*[2]/button').click(); | ||
await metamaskPage.locator('[data-testid="confirmation-submit-button"]').click(); | ||
await metamaskPage.locator('//button/h6[contains(text(), "Switch to Arbitrum One")]').click(); | ||
await metamaskPage.waitForTimeout(1000); | ||
await metamaskPage.locator('[data-testid="detected-token-banner"] button').click(); | ||
await metamaskPage.waitForTimeout(1000); | ||
await metamaskPage | ||
.locator('//*[contains(@class, "popover-footer")]/button[contains(text(), "Import")]') | ||
.click(); | ||
} else { | ||
await wallet.addNetwork({ | ||
chainId: 43113, | ||
networkName: "Avalanche Fuji", | ||
rpc: "https://avalanche-fuji-c-chain.publicnode.com", | ||
symbol: "AVAX", | ||
}); | ||
} | ||
} | ||
|
||
console.log("[Preparing GMX App]"); | ||
const page = await ctx.newPage(); | ||
await page.goto(appUrl); | ||
const gmx = new GmxApp(page, wallet, appUrl); | ||
await gmx.header.connectWallet(); | ||
await gmx.closeAllToasts(); | ||
|
||
savedContext = ctx; | ||
savedWallet = wallet; | ||
|
||
await use(ctx); | ||
}, | ||
|
||
wallet: async ({ context }, use) => { | ||
if (savedWallet) { | ||
await use(savedWallet); | ||
return; | ||
} | ||
|
||
const metamask = await dappwright.getWallet("metamask", context); | ||
|
||
await use(metamask); | ||
}, | ||
|
||
appUrl: async ({}, use) => use(process.env.GMX_BASE_URL || "https://app.gmx.io"), | ||
|
||
gmx: async ({ wallet, page, appUrl }, use) => { | ||
const gmx = new GmxApp(page, wallet, appUrl); | ||
await use(gmx); | ||
}, | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
import { Locator, Page } from "@playwright/test"; | ||
import { Dappwright } from "@tenkeylabs/dappwright"; | ||
|
||
declare module "@playwright/test" { | ||
interface Locator { | ||
selector: string; | ||
waitForVisible(): void; | ||
waitForSelector(): void; | ||
waitForDetached(): void; | ||
} | ||
} | ||
|
||
export class BasePage { | ||
public root: Locator; | ||
public wallet: Dappwright; | ||
|
||
private wrapLocatorWithRoot(locator: Locator, selector: string) { | ||
locator.selector = selector; | ||
locator.waitForVisible = async () => { | ||
if (process.env.PWDEBUG) { | ||
console.log(`Waiting for ${selector} to be visible`); | ||
} | ||
|
||
await this.page.waitForSelector(selector, { | ||
state: "visible", | ||
}); | ||
}; | ||
|
||
locator.waitForDetached = async () => { | ||
if (process.env.PWDEBUG) { | ||
console.log(`Waiting for ${selector} to be detached`); | ||
} | ||
|
||
await this.page.waitForSelector(selector, { | ||
state: "detached", | ||
}); | ||
}; | ||
|
||
locator.waitForSelector = async () => { | ||
if (process.env.PWDEBUG) { | ||
console.log(`Waiting for selector = ${selector}`); | ||
} | ||
|
||
await this.page.waitForSelector(selector); | ||
}; | ||
|
||
return locator; | ||
} | ||
|
||
constructor( | ||
public page: Page, | ||
wallet: Dappwright, | ||
root?: Locator | ||
) { | ||
this.page = page; | ||
this.wallet = wallet; | ||
this.root = root ? root : page.locator("body"); | ||
if (root) { | ||
this.root = root; | ||
} else { | ||
this.root = page.locator("body"); | ||
this.wrapLocatorWithRoot(this.root, "body"); | ||
} | ||
} | ||
|
||
locator(selector: string, root?: Locator) { | ||
const locatorSelector = | ||
selector.startsWith(".") || selector.startsWith("//") || selector.startsWith("[") | ||
? selector | ||
: `[data-qa="${selector}"]`; | ||
const locator = (root ?? this.root).locator(locatorSelector); | ||
return this.wrapLocatorWithRoot(locator, locatorSelector); | ||
} | ||
|
||
async has(locator: string | Locator, timeout = 5000) { | ||
const selector = typeof locator === "string" ? this.locator(locator).selector : locator.selector; | ||
|
||
try { | ||
await this.page.waitForSelector(selector, { state: "attached", timeout }); | ||
return true; | ||
} catch (e) { | ||
return false; | ||
} | ||
} | ||
|
||
async waitForTransactionToBeSent() { | ||
/** | ||
* @todo Implement me! | ||
*/ | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import { Locator, Page } from "@playwright/test"; | ||
import { Dappwright } from "@tenkeylabs/dappwright"; | ||
import { BasePage } from "./base-page"; | ||
|
||
export class Tabs<T extends string> extends BasePage { | ||
tabs: Record<string, Locator>; | ||
|
||
setTabs(tabs: Record<T, Locator>) { | ||
this.tabs = tabs; | ||
} | ||
|
||
async select(value: T) { | ||
const tab = this.tabs[value]; | ||
|
||
const style = await tab.evaluate((el) => { | ||
return window.getComputedStyle(el).pointerEvents; | ||
}); | ||
|
||
if (style !== "none") { | ||
await tab.click(); | ||
} | ||
} | ||
} | ||
|
||
export class Modal extends BasePage { | ||
constructor(page: Page, wallet: Dappwright, modalAttribute: string) { | ||
super(page, wallet); | ||
this.root = this.locator(modalAttribute); | ||
} | ||
} |
Oops, something went wrong.