Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Revert "Revert "Brody/swap v2 e2e (#5915)" (#5987)" #5988

Merged
merged 27 commits into from
Aug 25, 2024
Merged
Show file tree
Hide file tree
Changes from 24 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
14 changes: 8 additions & 6 deletions e2e/3_homeScreen.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ import {
checkIfExists,
checkIfExistsByText,
swipe,
waitAndTap,
afterAllcleanApp,
tap,
delayTime,
} from './helpers';

const RAINBOW_TEST_WALLET = 'rainbowtestwallet.eth';
Expand Down Expand Up @@ -41,19 +42,20 @@ describe('Home Screen', () => {
});

it('tapping "Swap" opens the swap screen', async () => {
await waitAndTap('swap-button');
await tap('swap-button');
await delayTime('long');
await checkIfExists('swap-screen');
await swipe('swap-screen', 'down', 'slow');
await swipe('swap-screen', 'down', 'fast');
});

it('tapping "Send" opens the send screen', async () => {
await waitAndTap('send-button');
await tap('send-button');
await checkIfVisible('send-asset-form-field');
await swipe('send-asset-form-field', 'down');
await swipe('send-asset-form-field', 'down', 'fast');
});

it('tapping "Copy" shows copy address toast', async () => {
await waitAndTap('receive-button');
await tap('receive-button');
await checkIfVisible('address-copied-toast');
});
});
144 changes: 144 additions & 0 deletions e2e/9_swaps.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
/*
* // Other tests to consider:
* - Flip assets
* - exchange button onPress
* - disable button states once https://github.com/rainbow-me/rainbow/pull/5785 gets merged
* - swap execution
* - token search (both from userAssets and output token list)
* - custom gas panel
* - flashbots
* - slippage
* - explainer sheets
* - switching wallets inside of swap screen
*/

import {
importWalletFlow,
sendETHtoTestWallet,
checkIfVisible,
beforeAllcleanApp,
afterAllcleanApp,
fetchElementAttributes,
tap,
tapByText,
delayTime,
swipeUntilVisible,
tapAndLongPressByText,
tapAndLongPress,
swipe,
} from './helpers';

import { expect } from '@jest/globals';
import { WALLET_VARS } from './testVariables';

describe('Swap Sheet Interaction Flow', () => {
beforeAll(async () => {
await beforeAllcleanApp({ hardhat: true });
});
afterAll(async () => {
await afterAllcleanApp({ hardhat: true });
});

it('Import a wallet and go to welcome', async () => {
await importWalletFlow(WALLET_VARS.EMPTY_WALLET.PK);
});

it('Should send ETH to test wallet', async () => {
// send 20 eth
await sendETHtoTestWallet();
});

it('Should show Hardhat Toast after pressing Connect To Hardhat', async () => {
await tap('dev-button-hardhat');
await checkIfVisible('testnet-toast-Hardhat');

// doesn't work atm
// validate it has the expected funds of 20 eth
// const attributes = await fetchElementAttributes('fast-coin-info');
// expect(attributes.label).toContain('Ethereum');
// expect(attributes.label).toContain('20');
});

it('Should open swap screen with 50% inputAmount for inputAsset', async () => {
await device.disableSynchronization();
await tap('swap-button');
await delayTime('long');

await swipeUntilVisible('token-to-buy-dai-1', 'token-to-buy-list', 'up', 100);
await swipe('token-to-buy-list', 'up', 'slow', 0.1);

await tap('token-to-buy-dai-1');
await delayTime('medium');
const swapInput = await fetchElementAttributes('swap-asset-input');

expect(swapInput.label).toContain('ETH');
expect(swapInput.label).toContain('10');
});

it('Should be able to go to review and execute a swap', async () => {
await tap('swap-bottom-action-button');
const inputAssetActionButton = await fetchElementAttributes('swap-input-asset-action-button');
const outputAssetActionButton = await fetchElementAttributes('swap-output-asset-action-button');
const holdToSwapButton = await fetchElementAttributes('swap-bottom-action-button');

expect(inputAssetActionButton.label).toBe('ETH 􀆏');
expect(outputAssetActionButton.label).toBe('DAI 􀆏');
expect(holdToSwapButton.label).toBe('􀎽 Hold to Swap');

await tapAndLongPress('swap-bottom-action-button', 1500);

// TODO: This doesn't work so need to figure this out eventually...
// await checkIfVisible('profile-screen');
});

it.skip('Should be able to verify swap is happening', async () => {
// await delayTime('very-long');
// const activityListElements = await fetchElementAttributes('wallet-activity-list');
// expect(activityListElements.label).toContain('ETH');
// expect(activityListElements.label).toContain('DAI');
// await tapByText('Swapping');
// await delayTime('long');
// const transactionSheet = await checkIfVisible('transaction-details-sheet');
// expect(transactionSheet).toBeTruthy();
});

it.skip('Should open swap screen from ProfileActionRowButton with largest user asset', async () => {
/**
* tap swap button
* wait for Swap header to be visible
* grab highest user asset balance from userAssetsStore
* expect inputAsset.uniqueId === highest user asset uniqueId
*/
});

it.skip('Should open swap screen from asset chart with that asset selected', async () => {
/**
* tap any user asset (store const uniqueId here)
* wait for Swap header to be visible
* expect inputAsset.uniqueId === const uniqueId ^^
*/
});

it.skip('Should open swap screen from dapp browser control panel with largest user asset', async () => {
/**
* tap swap button
* wait for Swap header to be visible
* grab highest user asset balance from userAssetsStore
* expect inputAsset.uniqueId === highest user asset uniqueId
*/
});

it.skip('Should not be able to type in output amount if cross-chain quote', async () => {
/**
* tap swap button
* wait for Swap header to be visible
* select different chain in output list chain selector
* select any asset in output token list
* focus output amount
* attempt to type any number in the SwapNumberPad
* attempt to remove a character as well
*
* ^^ expect both of those to not change the outputAmount
*/
});
});
45 changes: 34 additions & 11 deletions e2e/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ import { JsonRpcProvider } from '@ethersproject/providers';
import { Wallet } from '@ethersproject/wallet';
import { expect, device, element, by, waitFor } from 'detox';
import { parseEther } from '@ethersproject/units';
import { IosElementAttributes, AndroidElementAttributes } from 'detox/detox';

const TESTING_WALLET = '0x3Cb462CDC5F809aeD0558FBEe151eD5dC3D3f608';
const TESTING_WALLET = '0x3637f053D542E6D00Eee42D656dD7C59Fa33a62F';

const DEFAULT_TIMEOUT = 20_000;
const android = device.getPlatform() === 'android';
Expand Down Expand Up @@ -47,7 +48,6 @@ export async function importWalletFlow(customSeed?: string) {
await authenticatePin('1234');
}
await device.enableSynchronization();
await delayTime('very-long');
await checkIfVisible('wallet-screen');
}

Expand All @@ -70,6 +70,16 @@ export async function tap(elementId: string | RegExp) {
}
}

interface CustomElementAttributes {
elements: Array<IosElementAttributes | AndroidElementAttributes>;
}

type ElementAttributes = IosElementAttributes & AndroidElementAttributes & CustomElementAttributes;

export const fetchElementAttributes = async (testId: string): Promise<ElementAttributes> => {
return (await element(by.id(testId)).getAttributes()) as ElementAttributes;
};

export async function waitAndTap(elementId: string | RegExp, timeout = DEFAULT_TIMEOUT) {
await delayTime('medium');
try {
Expand Down Expand Up @@ -188,17 +198,17 @@ export async function clearField(elementId: string | RegExp) {
}
}

export async function tapAndLongPress(elementId: string | RegExp) {
export async function tapAndLongPress(elementId: string | RegExp, duration?: number) {
try {
return await element(by.id(elementId)).longPress();
return await element(by.id(elementId)).longPress(duration);
} catch (error) {
throw new Error(`Error long-pressing element by id "${elementId}": ${error}`);
}
}

export async function tapAndLongPressByText(text: string | RegExp) {
export async function tapAndLongPressByText(text: string | RegExp, duration?: number) {
try {
return await element(by.text(text)).longPress();
return await element(by.text(text)).longPress(duration);
} catch (error) {
throw new Error(`Error long-pressing element by text "${text}": ${error}`);
}
Expand Down Expand Up @@ -263,15 +273,28 @@ export async function scrollTo(scrollviewId: string | RegExp, edge: Direction) {
}
}

export async function swipeUntilVisible(elementId: string | RegExp, scrollViewId: string, direction: Direction, pctVisible = 75) {
export async function swipeUntilVisible(
elementId: string | RegExp,
scrollViewId: string,
direction: Direction,
percentageVisible?: number
) {
let stop = false;

console.log('starting');

while (!stop) {
try {
await waitFor(element(by.id(elementId)))
.toBeVisible(pctVisible)
.withTimeout(500);
console.log(`Trying to find element: ${elementId}`);
await checkIfVisible(elementId, 500);
// await waitFor(element(by.id(elementId)))
// .toBeVisible(percentageVisible)
// .withTimeout(500);

console.log('Element is visible, stopping');
stop = true;
} catch {
} catch (e) {
console.log(`Element not visible, swiping ${direction}`);
await swipe(scrollViewId, direction, 'slow', 0.2);
}
}
Expand Down
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,10 @@
"nuke": "./scripts/nuke.sh",
"detox:android": "detox build -c android.emu.debug && detox test -c android.emu.debug --loglevel verbose",
"detox:android:release": "detox build -c android.emu.release && detox test -c android.emu.release",
"detox:ios:tests": "detox test -c ios.sim.debug --maxWorkers 3 -- --bail 1",
"detox:ios": "detox build -c ios.sim.debug | xcpretty --color && yarn detox:ios:tests",
"detox:ios:release": "detox build -c ios.sim.release && detox test -c ios.sim.release --maxWorkers 3 -- --bail 1",
"detox:ios:build": "detox build -c ios.sim.debug | xcpretty --color ",
"detox:ios:tests": "detox test -c ios.sim.debug --maxWorkers 2 -- --bail 1",
"detox:ios": "yarn detox:ios:build && yarn detox:ios:tests",
"detox:ios:release": "detox build -c ios.sim.release && detox test -c ios.sim.release --maxWorkers 2 -- --bail 1",
"ds:install": "cd src/design-system/docs && yarn install",
"ds": "cd src/design-system/docs && yarn dev",
"fast": "yarn install && yarn setup && yarn install-pods-fast",
Expand Down
6 changes: 4 additions & 2 deletions src/__swaps__/screens/Swap/components/CoinRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ interface InputCoinRowProps {
onPress: (asset: ParsedSearchAsset | null) => void;
output?: false | undefined;
uniqueId: string;
testID?: string;
}

type PartialAsset = Pick<SearchAsset, 'address' | 'chainId' | 'colors' | 'icon_url' | 'mainnetAddress' | 'name' | 'symbol' | 'uniqueId'>;
Expand All @@ -62,11 +63,12 @@ interface OutputCoinRowProps extends PartialAsset {
output: true;
nativePriceChange?: string;
isTrending?: boolean;
testID?: string;
}

type CoinRowProps = InputCoinRowProps | OutputCoinRowProps;

export function CoinRow({ isFavorite, onPress, output, uniqueId, ...assetProps }: CoinRowProps) {
export function CoinRow({ isFavorite, onPress, output, uniqueId, testID, ...assetProps }: CoinRowProps) {
const inputAsset = userAssetsStore(state => (output ? undefined : state.getUserAsset(uniqueId)));
const outputAsset = output ? (assetProps as PartialAsset) : undefined;

Expand Down Expand Up @@ -116,7 +118,7 @@ export function CoinRow({ isFavorite, onPress, output, uniqueId, ...assetProps }
if (!address || !chainId) return null;

return (
<Box style={{ height: COIN_ROW_WITH_PADDING_HEIGHT, width: '100%' }}>
<Box testID={testID} style={{ height: COIN_ROW_WITH_PADDING_HEIGHT, width: '100%' }}>
<Columns alignVertical="center">
<Column>
<ButtonPressAnimation disallowInterruption onPress={onPressHandler} scaleTo={0.95}>
Expand Down
7 changes: 6 additions & 1 deletion src/__swaps__/screens/Swap/components/SwapActionButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ function SwapButton({
disabled,
opacity,
children,
testID,
}: {
asset: DerivedValue<ExtendedAnimatedAssetWithColors | null>;
borderRadius?: number;
Expand All @@ -47,6 +48,7 @@ function SwapButton({
disabled?: DerivedValue<boolean | undefined>;
opacity?: DerivedValue<number | undefined>;
children?: React.ReactNode;
testID?: string;
}) {
const { isDarkMode } = useColorMode();
const fallbackColor = useForegroundColor('label');
Expand Down Expand Up @@ -110,6 +112,7 @@ function SwapButton({
return (
<Animated.View style={buttonWrapperStyles}>
<Box
testID={testID}
as={Animated.View}
paddingHorizontal={{ custom: small ? 14 : 20 - (outline ? 2 : 0) }}
paddingLeft={small && icon ? '10px' : undefined}
Expand Down Expand Up @@ -225,6 +228,7 @@ export const SwapActionButton = ({
scaleTo,
style,
disabled,
testID,
...props
}: {
asset: DerivedValue<ExtendedAnimatedAssetWithColors | null>;
Expand All @@ -248,6 +252,7 @@ export const SwapActionButton = ({
style?: ViewStyle;
disabled?: DerivedValue<boolean | undefined>;
opacity?: DerivedValue<number | undefined>;
testID?: string;
}) => {
const disabledWrapper = useAnimatedStyle(() => {
return {
Expand All @@ -268,7 +273,7 @@ export const SwapActionButton = ({
style={[hugContent && feedActionButtonStyles.buttonWrapper, style]}
>
{/* eslint-disable-next-line react/jsx-props-no-spreading */}
<SwapButton {...props} disabled={disabled}>
<SwapButton {...props} disabled={disabled} testID={testID}>
{holdProgress && <HoldProgress holdProgress={holdProgress} />}
</SwapButton>
</GestureHandlerButton>
Expand Down
Loading
Loading