Skip to content

Commit

Permalink
Merge pull request #3998 from LedgerHQ/support/B2CQA-1753_automate_re…
Browse files Browse the repository at this point in the history
…adOnly

[B2CQA-1753] LLM portfolio read only test
  • Loading branch information
abdurrahman-ledger authored Jul 12, 2023
2 parents fd35196 + 2fa32c3 commit 4f327d8
Show file tree
Hide file tree
Showing 23 changed files with 201 additions and 84 deletions.
2 changes: 2 additions & 0 deletions apps/ledger-live-mobile/android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,8 @@ dependencies {
}

androidTestImplementation('com.wix:detox:+')
// Needed for Detox getAttributes on Android : https://github.com/wix/Detox/issues/3147
implementation 'com.google.android.material:material:1.6.0'
implementation 'com.facebook.soloader:soloader:0.10.4+'

// implementation 'com.brentvatne.react:react-native-video'
Expand Down
69 changes: 38 additions & 31 deletions apps/ledger-live-mobile/e2e/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,45 +3,38 @@ import { Direction } from "react-native-modal";

const DEFAULT_TIMEOUT = 60000;
const BASE_DEEPLINK = "ledgerlive://";
const startPositionY = 0.8; // Needed on Android to scroll views : https://github.com/wix/Detox/issues/3918
export const currencyParam = "?currency=";
export const recipientParam = "&recipient=";
export const amountParam = "&amount=";
export const accountIdParam = "?accountId=";

export function waitForElementByID(elementId: string, timeout?: number) {
return waitFor(element(by.id(elementId)))
.toBeVisible()
.withTimeout(timeout || DEFAULT_TIMEOUT);
export function waitForElementById(id: string, timeout: number = DEFAULT_TIMEOUT) {
return waitFor(getElementById(id)).toBeVisible().withTimeout(timeout);
}

export function waitForElementByText(text: string, timeout?: number) {
return waitFor(element(by.text(text)))
.toBeVisible()
.withTimeout(timeout || DEFAULT_TIMEOUT);
export function waitForElementByText(text: string, timeout: number = DEFAULT_TIMEOUT) {
return waitFor(getElementByText(text)).toBeVisible().withTimeout(timeout);
}

export function getElementById(id: string) {
return element(by.id(id));
export function getElementById(id: string, index = 0) {
return element(by.id(id)).atIndex(index);
}

export function getElementByText(text: string) {
return element(by.text(text));
export function getElementByText(text: string, index = 0) {
return element(by.text(text)).atIndex(index);
}

export function tapById(id: string, index?: number) {
return element(by.id(id))
.atIndex(index || 0)
.tap();
export function tapById(id: string, index = 0) {
return getElementById(id, index).tap();
}

export function tapByText(text: string, index?: number) {
return element(by.text(text))
.atIndex(index || 0)
.tap();
export function tapByText(text: string, index = 0) {
return getElementByText(text, index).tap();
}

export function tapByElement(elem: Detox.IndexableNativeElement, index = 0) {
return elem.atIndex(index || 0).tap();
export function tapByElement(elem: Detox.NativeElement) {
return elem.tap();
}

export async function typeTextById(id: string, text: string, focus = true) {
Expand All @@ -51,34 +44,48 @@ export async function typeTextById(id: string, text: string, focus = true) {
return getElementById(id).typeText(text);
}

export async function typeTextByElement(
elem: Detox.IndexableNativeElement,
text: string,
focus = true,
) {
export async function typeTextByElement(elem: Detox.NativeElement, text: string, focus = true) {
if (focus) {
await tapByElement(elem);
}

await elem.typeText(text);
}

export async function clearTextByElement(elem: Detox.IndexableNativeElement) {
export async function clearTextByElement(elem: Detox.NativeElement) {
return elem.clearText();
}

export async function scrollToText(
text: string,
scrollViewId: string,
index = 0,
pixels = 100,
direction: Direction = "down",
) {
await waitFor(getElementByText(text))
await waitFor(getElementByText(text, index))
.toBeVisible()
.whileElement(by.id(scrollViewId)) // where some is your ScrollView testID
.whileElement(by.id(scrollViewId))
.scroll(pixels, direction);
}

export async function scrollToId(
// Index broken on Android : https://github.com/wix/Detox/issues/2931
id: string,
scrollViewId: string,
pixels = 100,
direction: Direction = "down",
) {
await waitFor(element(by.id(id)))
.toBeVisible()
.whileElement(by.id(scrollViewId))
.scroll(pixels, direction, NaN, startPositionY);
}

export async function getTextOfElement(id: string, index = 0) {
const attributes = await getElementById(id, index).getAttributes();
return !("elements" in attributes) ? attributes.text : attributes.elements[index].text;
}

/**
* Waits for a specified amount of time
* /!\ Do not use it to wait for a specific element, use waitFor instead.
Expand Down
4 changes: 2 additions & 2 deletions apps/ledger-live-mobile/e2e/models/accounts/accountPage.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { currencyParam, openDeeplink, waitForElementByID } from "../../helpers";
import { currencyParam, openDeeplink, waitForElementById } from "../../helpers";

const baseLink = "accounts";

export default class accountPage {
async waitForAccountPageToLoad(coin: string) {
await waitForElementByID(`accounts-title-${coin}`);
await waitForElementById(`accounts-title-${coin}`);
}

async openViaDeeplink(currencyLong?: string) {
Expand Down
4 changes: 2 additions & 2 deletions apps/ledger-live-mobile/e2e/models/accounts/accountsPage.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { getElementById, openDeeplink, waitForElementByID } from "../../helpers";
import { getElementById, openDeeplink, waitForElementById } from "../../helpers";

const baseLink = "accounts";

Expand All @@ -9,6 +9,6 @@ export default class accountsPage {
await openDeeplink(baseLink);
}
async waitForAccountsPageToLoad() {
await waitForElementByID("accounts-list-title");
await waitForElementById("accounts-list-title");
}
}
8 changes: 4 additions & 4 deletions apps/ledger-live-mobile/e2e/models/manager/managerPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,19 @@ import {
getElementById,
openDeeplink,
tapByText,
waitForElementByID,
waitForElementById,
waitForElementByText,
} from "../../helpers";
import * as bridge from "../../bridge/server";

const proceedButtonId = "Proceed";
const baseLink = "myledger";

export default class ManagerPage {
proceedButtonId = "onboarding-paired-continue";
getManagerTitle = () => getElementById("manager-title");
getPairDeviceButton = () => getElementById("pair-device");
getProceedButton = () => getElementById(proceedButtonId);
waitProceedButton = () => waitForElementByID(proceedButtonId);
getProceedButton = () => getElementById(this.proceedButtonId);
waitProceedButton = () => waitForElementById(this.proceedButtonId);

async openViaDeeplink() {
await openDeeplink(baseLink);
Expand Down
51 changes: 38 additions & 13 deletions apps/ledger-live-mobile/e2e/models/onboarding/onboardingSteps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,33 +4,58 @@ import {
tapByElement,
getElementById,
waitForElementByText,
waitForElementById,
tapById,
isAndroid,
} from "../../helpers";
import { device } from "detox";
import * as bridge from "../../bridge/server";

export default class OnboardingSteps {
onboardingGetStartedButton = () => getElementByText("Get started");
setupLedgerButton = () => getElementByText("Set up your Ledger");
accessWalletButton = () => getElementByText("Access your wallet");
connectLedgerButton = () => getElementByText("Connect your Ledger");
continueButton = () => getElementByText("Continue");
pairNanoButton = () => getElementByText("Let’s pair my Nano"); // Yes it's a weird character, no do not replace it as it won't find the text
getStartedButtonId = "onboarding-getStarted-button";
devicePairedContinueButtonId = "onboarding-paired-continue";
exploreWithoutDeviceButtonId = "discoverLive-exploreWithoutADevice";
discoverLiveTitle = (index: number) => `onboarding-discoverLive-${index}-title`;
onboardingGetStartedButton = () => getElementById(this.getStartedButtonId);
accessWalletButton = () =>
getElementById("Onboarding PostWelcome - Selection|Access an existing wallet");
noLedgerYetButton = () => getElementById("onboarding-noLedgerYet");
exploreAppButton = () => getElementById("onboarding-noLedgerYetModal-explore");
exploreWithoutDeviceButton = () => getElementById(this.exploreWithoutDeviceButtonId);
connectLedgerButton = () => getElementById("Existing Wallet | Connect");
continueButton = () => getElementById(this.devicePairedContinueButtonId);
pairDeviceButton = () => getElementById("pair-device");
pairNanoButton = () => getElementById("Onboarding-PairNewNano");
nanoDeviceButton = (name = "") => getElementByText(`Nano X de ${name}`);
maybeLaterButton = () => getElementById("Maybe later");
maybeLaterButton = () => getElementById("notifications-prompt-later");

async startOnboarding() {
await waitForElementByText("Get started");
await waitForElementById(this.getStartedButtonId);
await tapByElement(this.onboardingGetStartedButton());
}

async chooseToSetupLedger() {
await tapByElement(this.setupLedgerButton());
}

async chooseToAccessYourWallet() {
await tapByElement(this.accessWalletButton());
}

async chooseNoLedgerYet() {
await tapByElement(this.noLedgerYetButton());
}

async chooseToExploreApp() {
// Fixme : Found a way to skip discover carousel without disabling synchro (animations prevent click)
if (isAndroid()) await device.disableSynchronization();
await tapByElement(this.exploreAppButton());
if (!isAndroid()) await device.disableSynchronization();
for (let i = 0; i < 4; i++) {
await waitForElementById(this.discoverLiveTitle(i));
await tapById(this.discoverLiveTitle(i));
}
await waitForElementById(this.exploreWithoutDeviceButtonId);
await tapById(this.exploreWithoutDeviceButtonId);
await device.enableSynchronization();
}

async selectYourDevice(device: string) {
await tapByText(device);
}
Expand Down Expand Up @@ -59,7 +84,7 @@ export default class OnboardingSteps {
}

async openLedgerLive() {
await waitForElementByText("Continue");
await waitForElementById(this.devicePairedContinueButtonId);
await tapByElement(this.continueButton());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export default class GeneralSettingsPage {
isLocalized = (localization: string) => getElementByText(localization);

async togglePassword() {
await this.passwordSettingsSwitch().atIndex(0).tap();
await this.passwordSettingsSwitch().tap();
}

async enterNewPassword(passwordText: string) {
Expand Down
4 changes: 2 additions & 2 deletions apps/ledger-live-mobile/e2e/models/settings/settingsPage.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { getElementById, tapByElement, waitForElementByID } from "../../helpers";
import { getElementById, tapByElement, waitForElementById } from "../../helpers";

export default class SettingsPage {
generalSettingsButton = () => getElementById("general-settings-card");

async navigateToGeneralSettings() {
await waitForElementByID("general-settings-card");
await waitForElementById("general-settings-card");
await tapByElement(this.generalSettingsButton());
}
}
21 changes: 19 additions & 2 deletions apps/ledger-live-mobile/e2e/models/wallet/portfolioPage.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
import { getElementById, openDeeplink, tapByElement, waitForElementByID } from "../../helpers";
import {
getElementById,
getTextOfElement,
openDeeplink,
tapByElement,
waitForElementById,
} from "../../helpers";

const baseLink = "portfolio";
export default class PortfolioPage {
zeroBalance = "$0.00";
graphCardBalanceId = "graphCard-balance";
assetBalanceId = "asset-balance";
readOnlyPortfolioId = "PortfolioReadOnlyList";
emptyPortfolioComponent = () => getElementById("PortfolioEmptyAccount");
portfolioSettingsButton = () => getElementById("settings-icon");
transferButton = () => getElementById("transfer-button");
Expand All @@ -20,7 +30,14 @@ export default class PortfolioPage {
}

async waitForPortfolioPageToLoad() {
await waitForElementByID("settings-icon");
await waitForElementById("settings-icon");
}

async waitForPortfolioReadOnly() {
await waitForElementById(this.readOnlyPortfolioId);
expect(await getTextOfElement(this.graphCardBalanceId)).toBe(this.zeroBalance);
for (let index = 0; index < 4; index++)
expect(await getTextOfElement(this.assetBalanceId, index)).toBe(this.zeroBalance);
}

async openViaDeeplink() {
Expand Down
2 changes: 1 addition & 1 deletion apps/ledger-live-mobile/e2e/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ beforeAll(async () => {
locale: "en-US",
},
});
}, 350000);
}, 2000000);

afterAll(async () => {
serverBridge.close();
Expand Down
2 changes: 1 addition & 1 deletion apps/ledger-live-mobile/e2e/specs/onboarding.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ describe("Onboarding", () => {
await onboardingSteps.chooseToAccessYourWallet();
});

it("choosesto connect Ledger", async () => {
it("chooses to connect Ledger", async () => {
await onboardingSteps.chooseToConnectYourLedger();
});

Expand Down
34 changes: 34 additions & 0 deletions apps/ledger-live-mobile/e2e/specs/onboardingReadOnly.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { expect } from "detox";
import OnboardingSteps from "../models/onboarding/onboardingSteps";
import PortfolioPage from "../models/wallet/portfolioPage";

let onboardingSteps: OnboardingSteps;
let portfolioPage: PortfolioPage;

describe("Onboarding - Read Only", () => {
beforeAll(() => {
onboardingSteps = new OnboardingSteps();
portfolioPage = new PortfolioPage();
});

it("starts Onboarding", async () => {
await onboardingSteps.startOnboarding();
});

it("selects I don't have a Ledger Yet", async () => {
await onboardingSteps.chooseNoLedgerYet();
});

it("chooses to explore the app goes through dicover carousel", async () => {
await onboardingSteps.chooseToExploreApp();
});

it("opens Ledger Live", async () => {
await portfolioPage.waitForPortfolioPageToLoad();
await expect(portfolioPage.portfolioSettingsButton()).toBeVisible();
});

it("should see an empty portfolio page", async () => {
await portfolioPage.waitForPortfolioReadOnly();
});
});
2 changes: 1 addition & 1 deletion apps/ledger-live-mobile/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@
"allure-js-commons": "1.3.2",
"babel-jest": "^28.0.0",
"babel-plugin-module-resolver": "^4.1.0",
"detox": "^20.6.0",
"detox": "20.7.1",
"eslint-import-resolver-typescript": "^3.5.5",
"eslint-plugin-detox": "^1.0.0",
"eslint-plugin-i18next": "^6.0.1",
Expand Down
7 changes: 6 additions & 1 deletion apps/ledger-live-mobile/src/components/AssetRowLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,12 @@ const AssetRowLayout = ({
)}
</Flex>
<Flex flexDirection="row" alignItems="flex-end" flexShrink={0}>
<Text variant="large" fontWeight="semiBold" color="neutral.c100">
<Text
variant="large"
fontWeight="semiBold"
color="neutral.c100"
testID="asset-balance"
>
<CounterValue currency={currency} value={balance} joinFragmentsSeparator="" />
</Text>
</Flex>
Expand Down
1 change: 1 addition & 0 deletions apps/ledger-live-mobile/src/components/GraphCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ function GraphCard({
color={"neutral.c100"}
numberOfLines={1}
adjustsFontSizeToFit
testID={"graphCard-balance"}
>
<CurrencyUnitValue
unit={unit}
Expand Down
Loading

1 comment on commit 4f327d8

@vercel
Copy link

@vercel vercel bot commented on 4f327d8 Jul 12, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.