diff --git a/packages/wallets/src/xdefi/xdefi.constants.ts b/packages/wallets/src/ctrl/ctrl.constants.ts similarity index 52% rename from packages/wallets/src/xdefi/xdefi.constants.ts rename to packages/wallets/src/ctrl/ctrl.constants.ts index 0ad4d4e0..0307b40b 100644 --- a/packages/wallets/src/xdefi/xdefi.constants.ts +++ b/packages/wallets/src/ctrl/ctrl.constants.ts @@ -1,11 +1,11 @@ import { CommonWalletConfig } from '../wallets.constants'; -export const XDEFI_COMMON_CONFIG: CommonWalletConfig = { - WALLET_NAME: 'xdefi', - CONNECTED_WALLET_NAME: 'XDEFI', +export const CTRL_COMMON_CONFIG: CommonWalletConfig = { + WALLET_NAME: 'ctrl', + CONNECTED_WALLET_NAME: 'Ctrl', RPC_URL_PATTERN: 'https://mainnet.infura.io/v3/**', STORE_EXTENSION_ID: 'hmeobnfnfcmdkdcmlblgagmfpfboieaf', - CONNECT_BUTTON_NAME: 'XDEFI', + CONNECT_BUTTON_NAME: 'Ctrl', SIMPLE_CONNECT: false, - EXTENSION_START_PATH: '/app.html', + EXTENSION_START_PATH: '/popup.html', }; diff --git a/packages/wallets/src/ctrl/ctrl.page.ts b/packages/wallets/src/ctrl/ctrl.page.ts new file mode 100644 index 00000000..ee0ec64b --- /dev/null +++ b/packages/wallets/src/ctrl/ctrl.page.ts @@ -0,0 +1,145 @@ +import { WalletPage } from '../wallet.page'; +import { test, BrowserContext, Page } from '@playwright/test'; +import { WalletConfig } from '../wallets.constants'; +import { LoginPage, OnboardingPage, WalletOperations } from './pages'; +import { closeUnnecessaryPages } from './helper'; + +export class CtrlPage implements WalletPage { + page: Page | undefined; + onboardingPage: OnboardingPage; + loginPage: LoginPage; + + constructor( + private browserContext: BrowserContext, + private extensionUrl: string, + public config: WalletConfig, + ) {} + + /** Init all page objects Classes included to wallet */ + async initLocators() { + this.page = await this.browserContext.newPage(); + this.onboardingPage = new OnboardingPage( + this.page, + this.browserContext, + this.extensionUrl, + this.config, + ); + this.loginPage = new LoginPage(this.page, this.config); + } + + /** Open the home page of the wallet extension */ + async goto() { + await this.page.goto( + this.extensionUrl + this.config.COMMON.EXTENSION_START_PATH, + ); + } + + /** Navigate to home page of OXK Wallet extension: + * - open the wallet extension + * - unlock extension (if needed) + * - cancel awaited transactions (if needed) + */ + async navigate() { + await test.step('Navigate to Ctrl', async () => { + await this.initLocators(); + await this.goto(); + await this.loginPage.unlock(); + // await this.walletOperations.cancelAllTxInQueue(); + }); + } + + async setup() { + await test.step('Setup', async () => { + await this.initLocators(); + if (await this.onboardingPage.isNeedToGoThroughOnboarding()) { + await this.onboardingPage.firstTimeSetup(); + await this.goto(); + await this.onboardingPage.closeWalletTour(); + await this.onboardingPage.createWalletPassword(); + } + await closeUnnecessaryPages(this.browserContext); + }); + } + + /** Click `Connect` button */ + async connectWallet(page: Page) { + await test.step('Connect OKX wallet', async () => { + const operationPage = new WalletOperations(page); + await operationPage.connectBtn.waitFor({ + state: 'visible', + timeout: 10000, + }); + await operationPage.connectBtn.click(); + // need wait the page to be closed after the extension is connected + await new Promise((resolve) => { + operationPage.page.on('close', () => { + resolve(); + }); + }); + }); + } + + importKey(): Promise { + throw new Error('Method not implemented.'); + } + + assertTxAmount(): Promise { + throw new Error('Method not implemented.'); + } + + confirmTx(): Promise { + throw new Error('Method not implemented.'); + } + + cancelTx(): Promise { + throw new Error('Method not implemented.'); + } + + approveTokenTx(): Promise { + throw new Error('Method not implemented.'); + } + + openLastTxInEthplorer(): Promise { + throw new Error('Method not implemented.'); + } + + getTokenBalance(): Promise { + throw new Error('Method not implemented.'); + } + + confirmAddTokenToWallet(): Promise { + throw new Error('Method not implemented.'); + } + + assertReceiptAddress(): Promise { + throw new Error('Method not implemented.'); + } + + getWalletAddress(): Promise { + throw new Error('Method not implemented.'); + } + + setupNetwork(): Promise { + throw new Error('Method not implemented.'); + } + + addNetwork(): Promise { + throw new Error('Method not implemented.'); + } + + changeNetwork(): Promise { + throw new Error('Method not implemented.'); + } + + changeWalletAccountByName(): Promise { + throw new Error('Method not implemented.'); + } + + changeWalletAccountByAddress(): Promise { + throw new Error('Method not implemented.'); + } + + isWalletAddressExist(): Promise { + throw new Error('Method not implemented.'); + } +} diff --git a/packages/wallets/src/ctrl/helper.ts b/packages/wallets/src/ctrl/helper.ts new file mode 100644 index 00000000..6ad88a2d --- /dev/null +++ b/packages/wallets/src/ctrl/helper.ts @@ -0,0 +1,8 @@ +import { BrowserContext } from '@playwright/test'; + +export async function closeUnnecessaryPages(browserContext: BrowserContext) { + const pages = browserContext.pages().slice(1); + for (const page of pages) { + await page.close(); + } +} diff --git a/packages/wallets/src/ctrl/index.ts b/packages/wallets/src/ctrl/index.ts new file mode 100644 index 00000000..cf36ea65 --- /dev/null +++ b/packages/wallets/src/ctrl/index.ts @@ -0,0 +1,3 @@ +export * from './ctrl.page'; +export * from './ctrl.constants'; +export * from './helper'; diff --git a/packages/wallets/src/ctrl/pages/index.ts b/packages/wallets/src/ctrl/pages/index.ts new file mode 100644 index 00000000..1d6628c9 --- /dev/null +++ b/packages/wallets/src/ctrl/pages/index.ts @@ -0,0 +1,3 @@ +export * from './onboarding.page'; +export * from './login.page'; +export * from './walletOperations.page'; diff --git a/packages/wallets/src/ctrl/pages/login.page.ts b/packages/wallets/src/ctrl/pages/login.page.ts new file mode 100644 index 00000000..46296c75 --- /dev/null +++ b/packages/wallets/src/ctrl/pages/login.page.ts @@ -0,0 +1,28 @@ +import { Locator, Page, test } from '@playwright/test'; +import { WalletConfig } from '../../wallets.constants'; + +export class LoginPage { + page: Page; + unlockBtn: Locator; + passwordInput: Locator; + homeBtn: Locator; + + constructor(page: Page, public config: WalletConfig) { + this.page = page; + this.unlockBtn = this.page.getByTestId('unlock-btn'); + this.passwordInput = this.page.locator('input[type="password"]'); + } + + async unlock() { + await test.step('Unlock wallet', async () => { + try { + await this.unlockBtn.waitFor({ state: 'visible', timeout: 2000 }); + await this.passwordInput.fill(this.config.PASSWORD); + await this.unlockBtn.click(); + await this.homeBtn.waitFor({ state: 'visible' }); + } catch { + console.log('The Wallet unlocking is not needed'); + } + }); + } +} diff --git a/packages/wallets/src/ctrl/pages/onboarding.page.ts b/packages/wallets/src/ctrl/pages/onboarding.page.ts new file mode 100644 index 00000000..0759f47b --- /dev/null +++ b/packages/wallets/src/ctrl/pages/onboarding.page.ts @@ -0,0 +1,97 @@ +import { BrowserContext, Locator, Page, test } from '@playwright/test'; +import { WalletConfig } from '../../wallets.constants'; + +export class OnboardingPage { + page: Page; + alreadyHaveWalletBtn: Locator; + importRecoveryPhraseBtn: Locator; + passwordInput: Locator; + nextBtn: Locator; + importBtn: Locator; + closeTourBtn: Locator; + notNowBtn: Locator; + createPasswordBtn: Locator; + confirmPasswordBtn: Locator; + + constructor( + page: Page, + private browserContext: BrowserContext, + private extensionUrl: string, + public config: WalletConfig, + ) { + this.page = page; + this.alreadyHaveWalletBtn = this.page.getByTestId( + 'i-already-have-a-wallet-btn', + ); + this.importRecoveryPhraseBtn = this.page.getByText( + 'Import with Recovery Phrase', + ); + this.passwordInput = this.page.locator('input[type="password"]'); + this.nextBtn = this.page.getByTestId('next-btn'); + this.importBtn = this.page.getByTestId('import-btn'); + this.closeTourBtn = this.page.locator( + '[testID="home-page-tour-close-icon"]', + ); + this.notNowBtn = this.page.getByText('Not now'); + this.createPasswordBtn = this.page.getByText('create a password'); + this.confirmPasswordBtn = this.page.getByTestId('button'); + } + + async isNeedToGoThroughOnboarding() { + return await test.step('Open the onboarding page', async () => { + // Need to open onboarding page cause the extension does not redirect from home url automatically + await this.page.goto( + this.extensionUrl + '/tabs/onboarding.html#onboarding', + ); + + const btn = this.page.getByTestId('i-already-have-a-wallet-btn'); + try { + await btn.waitFor({ + state: 'visible', + timeout: 5000, + }); + } catch { + console.log('Onboarding process is not needed'); + } + return btn.isVisible(); + }); + } + + async firstTimeSetup() { + await test.step('First time set up', async () => { + // Need to wait some time for button enabling + await this.page.waitForTimeout(1000); + await this.alreadyHaveWalletBtn.click({ force: true }); + + await test.step('Import wallet with recovery phrase', async () => { + await this.importRecoveryPhraseBtn.click(); + const seedWords = this.config.SECRET_PHRASE.split(' '); + for (let i = 0; i < seedWords.length; i++) { + await this.passwordInput.nth(i).fill(seedWords[i]); + } + await this.nextBtn.click(); + await this.importBtn.waitFor({ state: 'visible', timeout: 60000 }); + await this.importBtn.click(); + await this.nextBtn.click(); + }); + }); + } + + async closeWalletTour() { + await test.step('Close wallet tour', async () => { + await this.closeTourBtn.waitFor({ state: 'visible', timeout: 2000 }); + await this.closeTourBtn.click(); + await this.notNowBtn.waitFor({ state: 'visible', timeout: 2000 }); + await this.notNowBtn.click({ force: true }); + }); + } + + async createWalletPassword() { + await test.step('Create wallet password', async () => { + await this.createPasswordBtn.click(); + await this.passwordInput.nth(0).fill(this.config.PASSWORD); + await this.passwordInput.nth(1).fill(this.config.PASSWORD); + await this.confirmPasswordBtn.click(); + }); + } +} diff --git a/packages/wallets/src/ctrl/pages/walletOperations.page.ts b/packages/wallets/src/ctrl/pages/walletOperations.page.ts new file mode 100644 index 00000000..e4f69193 --- /dev/null +++ b/packages/wallets/src/ctrl/pages/walletOperations.page.ts @@ -0,0 +1,11 @@ +import { Locator, Page } from '@playwright/test'; + +export class WalletOperations { + page: Page; + connectBtn: Locator; + + constructor(page: Page) { + this.page = page; + this.connectBtn = this.page.getByTestId('connect-dapp-button'); + } +} diff --git a/packages/wallets/src/index.ts b/packages/wallets/src/index.ts index d01a0d41..f041a47a 100644 --- a/packages/wallets/src/index.ts +++ b/packages/wallets/src/index.ts @@ -5,6 +5,6 @@ export * from './metamask'; export * from './trustwallet'; export * from './coinbase'; export * from './exodus'; -export * from './xdefi'; export * from './okx'; export * from './bitget'; +export * from './ctrl'; diff --git a/packages/wallets/src/okx/okx.page.ts b/packages/wallets/src/okx/okx.page.ts index ab042a20..8af865f9 100644 --- a/packages/wallets/src/okx/okx.page.ts +++ b/packages/wallets/src/okx/okx.page.ts @@ -65,7 +65,7 @@ export class OkxPage implements WalletPage { }); } - /** Checks the wallet is set correctly and starts ot wallet setup as the first time (if needed) */ + /** Checks the wallet is set correctly and starts the wallet setup as the first time (if needed) */ async setup() { await test.step('Setup', async () => { await this.navigate(); diff --git a/packages/wallets/src/xdefi/index.ts b/packages/wallets/src/xdefi/index.ts deleted file mode 100644 index b50948ca..00000000 --- a/packages/wallets/src/xdefi/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './xdefi.page'; -export * from './xdefi.constants'; diff --git a/packages/wallets/src/xdefi/xdefi.page.ts b/packages/wallets/src/xdefi/xdefi.page.ts deleted file mode 100644 index caa2cf95..00000000 --- a/packages/wallets/src/xdefi/xdefi.page.ts +++ /dev/null @@ -1,122 +0,0 @@ -import { WalletConfig } from '../wallets.constants'; -import { WalletPage } from '../wallet.page'; -import expect from 'expect'; -import { test, BrowserContext, Page } from '@playwright/test'; - -export class XdefiPage implements WalletPage { - page: Page | undefined; - - constructor( - private browserContext: BrowserContext, - private extensionUrl: string, - public config: WalletConfig, - ) {} - - async navigate() { - await test.step('Navigate to xdefi', async () => { - this.page = await this.browserContext.newPage(); - await this.page.goto( - this.extensionUrl + this.config.COMMON.EXTENSION_START_PATH, - ); - await this.page.reload(); - await this.page.waitForTimeout(1000); - }); - } - - async setup() { - await test.step('Setup', async () => { - // added explicit route to /onboarding due to unexpected first time route from /home.html to /onboarding - page is close - this.page = await this.browserContext.newPage(); - await this.page.goto(this.extensionUrl + '/onboarding.html'); - if (!this.page) throw "Page isn't ready"; - const firstTime = await this.page.waitForSelector( - "text=Let's get started", - ); - if (firstTime) await this.firstTimeSetup(); - }); - } - - async importTokens(token: string) { - await test.step('Import token', async () => { - await this.navigate(); - if (!this.page) throw "Page isn't ready"; - await this.page.click('button[data-testid="addAssetsBtn"]'); - await this.page.click('li[data-testid="customTab"]'); - await this.page.type('input[name="address"]', token); - await this.page.click('button[data-testid="nextBtn"]'); - }); - } - - async firstTimeSetup() { - await test.step('First time setup', async () => { - if (!this.page) throw "Page isn't ready"; - await this.page.click('text=Restore XDEFI Wallet'); - await this.page.click('text=Restore with secret phrase'); - const inputs = this.page.locator('input[data-testid=input]'); - const seedWords = this.config.SECRET_PHRASE.split(' '); - for (let i = 0; i < seedWords.length; i++) { - await inputs.nth(i).fill(seedWords[i]); - } - await this.page.click('text=Next'); - await this.page.fill('input[name="password"]', this.config.PASSWORD); - await this.page.fill('input[name="cpassword"]', this.config.PASSWORD); - await this.page.click('div[data-testid=termsAndConditionsCheckbox]'); - await this.page.click('button[type="submit"]'); - await this.page.fill( - 'input[name="walletName"]', - this.config.COMMON.WALLET_NAME, - ); - await this.page.click('button[type="submit"]'); - await this.page.click('div[data-testid=prioritiesXdefiToggle]'); - await this.page.click('button[data-testid=nextBtn]'); - }); - } - - async importKey(key: string) { - await test.step('Import key', async () => { - if (!this.page) throw "Page isn't ready"; - await this.navigate(); - await this.page.click('button[data-testid="menuBtn"]'); - await this.page.click('li[data-testid="walletManagementBtn"]'); - await this.page.click('div[data-testid="importWalletBtn"]'); - await this.page.click('text=Seed Phrase'); - await this.page.fill('textarea[name="[phrase"]', key); - await this.page.click('svg:below(input)'); - await this.page.click('button[data-testid="importBtn"]'); - }); - } - - async connectWallet(page: Page) { - await test.step('Connect wallet', async () => { - await page.click('button[data-testid="nextBtn"]'); - await page - .locator('svg[data-testid="checkbox-unchecked"]') - .first() - .locator('..') - .click(); - await page.click('button[data-testid="connectBtn"]'); - await page.close(); - }); - } - - async assertTxAmount(page: Page, expectedAmount: string) { - await test.step('Assert TX Amount', async () => { - expect(await page.locator(`text=${expectedAmount} ETH`).count()).toBe(1); - }); - } - - async confirmTx(page: Page) { - await test.step('Confirm TX', async () => { - await page.click('button[data-testid="confirmBtn"]'); - }); - } - - // eslint-disable-next-line - async signTx(page: Page) {} - - // eslint-disable-next-line - async assertReceiptAddress(page: Page, expectedAddress: string) {} - - // eslint-disable-next-line - async addNetwork(networkName: string, networkUrl: string, chainId: number, tokenSymbol: string) {} -} diff --git a/wallets-testing/browser/browser.constants.ts b/wallets-testing/browser/browser.constants.ts index d0ea2bae..c253cd43 100644 --- a/wallets-testing/browser/browser.constants.ts +++ b/wallets-testing/browser/browser.constants.ts @@ -4,9 +4,9 @@ import { TrustWalletPage, ExodusPage, CoinbasePage, - XdefiPage, OkxPage, BitgetPage, + CtrlPage, } from '@lidofinance/wallets-testing-wallets'; import { EthereumPage } from '@lidofinance/wallets-testing-widgets'; @@ -16,9 +16,9 @@ export const WALLET_PAGES = { trust: TrustWalletPage, exodus: ExodusPage, coinbase: CoinbasePage, - xdefi: XdefiPage, okx: OkxPage, bitget: BitgetPage, + ctrl: CtrlPage, }; export const WIDGET_PAGES = { diff --git a/wallets-testing/package.json b/wallets-testing/package.json index 1a50b71b..0452ffb0 100644 --- a/wallets-testing/package.json +++ b/wallets-testing/package.json @@ -15,7 +15,7 @@ "start:prod": "node dist/main", "test": "playwright test -c ./dist", "test:smoke": "playwright test -c ./dist smoke", - "test:widgets": "playwright test -c ./dist widgets" + "test:widgets": "playwright test -c ./dist widgets --retries=10" }, "dependencies": { "@lidofinance/wallets-testing-extensions": "*", diff --git a/wallets-testing/playwright.config.ts b/wallets-testing/playwright.config.ts index f22f8e7a..90828bd9 100644 --- a/wallets-testing/playwright.config.ts +++ b/wallets-testing/playwright.config.ts @@ -23,7 +23,7 @@ const config: PlaywrightTestConfig = { /* 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, + forbidOnly: true, //todo revert /* Retry on CI only */ retries: process.env.CI ? 2 : 0, /* Opt out of parallel tests on CI. */ @@ -46,7 +46,7 @@ const config: PlaywrightTestConfig = { // baseURL: 'http://localhost:3000', /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ - trace: 'on-first-retry', + trace: 'retain-on-failure', }, /* Folder for test artifacts such as screenshots, videos, traces, etc. */ diff --git a/wallets-testing/test/widgets/ethereum.spec.ts b/wallets-testing/test/widgets/ethereum.spec.ts index df801a45..d8c5f0f2 100644 --- a/wallets-testing/test/widgets/ethereum.spec.ts +++ b/wallets-testing/test/widgets/ethereum.spec.ts @@ -7,9 +7,9 @@ import { TRUST_WALLET_COMMON_CONFIG, COINBASE_COMMON_CONFIG, EXODUS_COMMON_CONFIG, - XDEFI_COMMON_CONFIG, OKX_COMMON_CONFIG, BITGET_COMMON_CONFIG, + CTRL_COMMON_CONFIG, } from '@lidofinance/wallets-testing-wallets'; import { ETHEREUM_WIDGET_CONFIG } from '@lidofinance/wallets-testing-widgets'; import { BrowserModule } from '../../browser/browser.module'; @@ -63,8 +63,9 @@ test.describe('Ethereum', () => { await browserService.connectWallet(); }); - test.skip(`Xdefi wallet connect`, async () => { - await browserService.setup(XDEFI_COMMON_CONFIG, ETHEREUM_WIDGET_CONFIG); + // todo remove .only + test.only(`Ctrl connect`, async () => { + await browserService.setup(CTRL_COMMON_CONFIG, ETHEREUM_WIDGET_CONFIG); await browserService.connectWallet(); });