Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/thin-plants-sparkle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tenkeylabs/dappwright': patch
---

chore: bumps Coinbase to 3.96.0 and implements new token balance error handling
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
"test": "playwright test test/",
"test:ci": "playwright test --headed --timeout 200000",
"test:debug": "playwright test --debug --timeout 0 test/",
"test:metamask:debug": "playwright test --debug --timeout 0 test/ --project MetaMask",
"test:coinbase:debug": "playwright test --debug --timeout 0 test/ --project Coinbase",
"test:dapp": "node --require ts-node/register test/dapp/start.ts",
"changeset:publish": "yarn build && changeset publish"
},
Expand Down
73 changes: 35 additions & 38 deletions src/wallets/coinbase/actions.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ElementHandle, Page } from 'playwright-core';
import { Locator, Page } from 'playwright-core';
import { waitForChromeState } from '../../helpers';
import { AddNetwork, AddToken } from '../../types';
import { performPopupAction } from '../metamask/actions';
Expand All @@ -24,6 +24,7 @@ export async function getStarted(

// Import Wallet
await page.getByTestId('btn-import-recovery-phrase').click();
await page.getByRole('button', { name: 'Acknowledge' }).click();
await page.getByTestId('secret-input').fill(seed);
await page.getByTestId('btn-import-wallet').click();
await page.getByTestId('setPassword').fill(password);
Expand Down Expand Up @@ -96,19 +97,19 @@ export const confirmTransaction = (page: Page) => async (): Promise<void> => {
export const addNetwork =
(page: Page) =>
async (options: AddNetwork): Promise<void> => {
// Add network flow closes current screen and opens another, direct access is cleaner for now
const settingsPage = await page.context().newPage();
await settingsPage.goto(`${page.url()}?internalPopUpRequest=true&action=addCustomNetwork`);
await settingsPage.getByTestId('custom-network-name-input').fill(options.networkName);
await settingsPage.getByTestId('custom-network-rpc-url-input').fill(options.rpc);
await settingsPage.getByTestId('custom-network-chain-id-input').fill(options.chainId.toString());
await settingsPage.getByTestId('custom-network-currency-symbol-input').fill(options.symbol);
await settingsPage.getByTestId('custom-network-save').click();
await page.getByTestId('settings-navigation-link').click();
await page.getByTestId('network-setting-cell-pressable').click();
await page.getByTestId('add-custom-network').click();
await page.getByTestId('custom-network-name-input').fill(options.networkName);
await page.getByTestId('custom-network-rpc-url-input').fill(options.rpc);
await page.getByTestId('custom-network-chain-id-input').fill(options.chainId.toString());
await page.getByTestId('custom-network-currency-symbol-input').fill(options.symbol);
await page.getByTestId('custom-network-save').click();

// Check for error messages
let errorNode;
try {
errorNode = await settingsPage.waitForSelector('//span[@data-testid="text-input-error-label"]', {
errorNode = await page.waitForSelector('//span[@data-testid="text-input-error-label"]', {
timeout: 50,
});
} catch {
Expand All @@ -117,22 +118,17 @@ export const addNetwork =

if (errorNode) {
const errorMessage = await errorNode.textContent();
await settingsPage.close();
throw new SyntaxError(errorMessage);
}

await settingsPage.waitForEvent('close');

// New network isn't reflected until page is reloaded
await page.bringToFront();
await page.reload();
goHome(page);
};

export const deleteNetwork =
(page: Page) =>
async (name: string): Promise<void> => {
await page.getByTestId('settings-navigation-link').click();
await page.getByTestId('network-setting').click();
await page.getByTestId('network-setting-cell-pressable').click();

// Search for network then click on the first result
await page.getByTestId('network-list-search').fill(name);
Expand All @@ -156,39 +152,40 @@ export const hasNetwork =
export const getTokenBalance =
(page: Page) =>
async (tokenSymbol: string): Promise<number> => {
const readFromCryptoTab = async (): Promise<ElementHandle<SVGElement | HTMLElement>> => {
const tokenValueRegex = new RegExp(String.raw` ${tokenSymbol}`);

const readFromCryptoTab = async (): Promise<Locator | undefined> => {
await page.bringToFront();
await page.getByTestId('portfolio-selector-nav-tabLabel--crypto').click();
return await page.waitForSelector(
`//button[contains(@data-testid, "asset-item")][contains(@data-testid, "${tokenSymbol}")]`,
{
timeout: 500,
},
);
const tokenItem = page.getByTestId(/asset-item.*cell-pressable/).filter({
hasText: tokenValueRegex,
});

await page.waitForTimeout(500);

return (await tokenItem.isVisible()) ? tokenItem : null;
};

const readFromTestnetTab = async (): Promise<ElementHandle<SVGElement | HTMLElement>> => {
const readFromTestnetTab = async (): Promise<Locator | undefined> => {
await page.getByTestId('portfolio-selector-nav-tabLabel--testnet').click();
return await page.waitForSelector(
`//button[contains(@data-testid, "asset-item")][contains(@data-testid, "${tokenSymbol}")]`,
{
timeout: 500,
},
);

const tokenItem = page.getByTestId(/asset-item.*cell-pressable/).filter({
hasText: tokenValueRegex,
});

await page.waitForTimeout(500);

return (await tokenItem.isVisible()) ? tokenItem : null;
};

const readAttempts = [readFromCryptoTab, readFromTestnetTab];

let button: ElementHandle<SVGElement | HTMLElement>;
let button: Locator | undefined;
for (const readAttempt of readAttempts) {
try {
button = await readAttempt();
} catch {
// Failed to read token value
}
button = await readAttempt();
}

if (!button) return 0;
if (!button) throw new Error(`Token ${tokenSymbol} not found`);

const text = await button.textContent();
const currencyAmount = text.replaceAll(/ |,/g, '').split(tokenSymbol)[2];
Expand Down
2 changes: 1 addition & 1 deletion src/wallets/coinbase/coinbase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import {

export class CoinbaseWallet extends Wallet {
static id = 'coinbase' as WalletIdOptions;
static recommendedVersion = '3.70.0';
static recommendedVersion = '3.96.0';
static releasesUrl = 'https://api.github.com/repos/TenKeyLabs/coinbase-wallet-archive/releases';
static homePath = '/index.html';

Expand Down
15 changes: 6 additions & 9 deletions test/2-wallet.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { expect } from '@playwright/test';
import { CoinbaseWallet, Dappwright, MetaMaskWallet } from '../src';
import { Dappwright, MetaMaskWallet } from '../src';
import { openAccountMenu } from '../src/wallets/metamask/actions/helpers';
import { forCoinbase, forMetaMask } from './helpers/itForWallet';
import { testWithWallet as test } from './helpers/walletTest';
Expand Down Expand Up @@ -180,25 +180,22 @@ test.describe('when interacting with the wallet', () => {
});

test('should return token balance', async ({ wallet }) => {
const tokenBalance: number = await wallet.getTokenBalance('GO');
let tokenBalance: number;

await forMetaMask(wallet, async () => {
tokenBalance = await wallet.getTokenBalance('GO');
expect(tokenBalance).toEqual(999.9996);
});

// Unable to get local balance from Coinbase wallet. This is Sepolia value for now.
await forCoinbase(wallet, async () => {
expect(tokenBalance).toEqual(999.999);
tokenBalance = await wallet.getTokenBalance('ETH');
// expect(tokenBalance).toEqual(999.999);
});
});

test('should return 0 token balance when token not found', async ({ wallet }) => {
if (wallet instanceof CoinbaseWallet) {
const tokenBalance: number = await wallet.getTokenBalance('TKLBUCKS');
expect(tokenBalance).toEqual(0);
} else {
test.skip();
}
await expect(wallet.getTokenBalance('TKLBUCKS')).rejects.toThrowError(new Error('Token TKLBUCKS not found'));
});
});

Expand Down
1 change: 1 addition & 0 deletions test/3-dapp.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ test.describe('when interacting with dapps', () => {
test.beforeEach(async ({ page }) => {
await page.reload();
await page.click('.connect-button');
await page.click('.switch-network-local-test-button');
await page.waitForSelector('#connected');
});

Expand Down
Loading