Skip to content

Commit

Permalink
Merge pull request LedgerHQ#8796 from LedgerHQ/support/qaa_377_detox_…
Browse files Browse the repository at this point in the history
…speculos_send

[QAA-377][Speculos][Detox] Adapt LLM send test
  • Loading branch information
abdurrahman-ledger authored Jan 8, 2025
2 parents 114d419 + 0a850af commit e785a43
Show file tree
Hide file tree
Showing 40 changed files with 396 additions and 37 deletions.
30 changes: 26 additions & 4 deletions .github/workflows/test-mobile-e2e-reusable.yml
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ jobs:
allure-report-ios:
name: "Allure Reports Export on Server"
runs-on: [ledger-live-medium]
if: ${{ always() && (inputs.slack_notif || github.event_name == 'push') }}
if: ${{ !inputs.speculos_tests && (inputs.slack_notif || github.event_name == 'push') }}
needs: [detox-tests-ios]
outputs:
report-url: ${{ steps.upload.outputs.report-url }}
Expand Down Expand Up @@ -200,7 +200,9 @@ jobs:
AVD_RAM_SIZE: 4096M
AVD_OPTIONS: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
outputs:
status: ${{ steps.detox.outcome }}
status_1: ${{ steps.set-output.outputs.status_1 }}
status_2: ${{ steps.set-output.outputs.status_2 }}
status_3: ${{ steps.set-output.outputs.status_3 }}
strategy:
fail-fast: false
matrix:
Expand Down Expand Up @@ -315,6 +317,10 @@ jobs:
with:
name: "android-test-artifacts-${{ matrix.shardIndex }}"
path: apps/ledger-live-mobile/artifacts/
- name: Set job output based on detox result
id: set-output
if: always()
run: echo "status_${{ matrix.shardIndex }}=${{ steps.detox.outcome }}" >> $GITHUB_OUTPUT

allure-report-android:
name: "Allure Reports Export on Server"
Expand All @@ -323,7 +329,7 @@ jobs:
outputs:
report-url: ${{ steps.upload.outputs.report-url }}
result: ${{ steps.summary.outputs.test_result }}
status: ${{ needs.detox-tests-android.outputs.status }}
finalStatus: ${{ steps.aggregate.outputs.finalStatus }}
needs: [detox-tests-android]
steps:
- name: checkout
Expand All @@ -344,6 +350,22 @@ jobs:
with:
allure-results-path: android-test-artifacts
platform: android
- name: Aggregate test results
id: aggregate
run: |
if [ "${{ env.SPECULOS_RUN }}" == "true" ]; then
statuses=("${{ needs.detox-tests-android.outputs.status_1 }}" "${{ needs.detox-tests-android.outputs.status_2 }}" "${{ needs.detox-tests-android.outputs.status_3 }}")
else
statuses=("${{ needs.detox-tests-android.outputs.status_1 }}")
fi
finalStatus="success"
for status in "${statuses[@]}"; do
if [ "$status" != "success" ]; then
finalStatus="failure"
break
fi
done
echo "finalStatus=$finalStatus" >> $GITHUB_OUTPUT
upload-to-xray:
name: "Upload to Xray"
Expand Down Expand Up @@ -409,7 +431,7 @@ jobs:
env:
IOS_STATUS: ${{ needs.allure-report-ios.outputs.status }}
IOS_REPORT_URL: ${{ needs.allure-report-ios.outputs.report-url }}
ANDROID_STATUS: ${{ needs.allure-report-android.outputs.status }}
ANDROID_STATUS: ${{ needs.allure-report-android.outputs.finalStatus }}
ANDROID_REPORT_URL: ${{ needs.allure-report-android.outputs.report-url }}
steps:
- name: format message
Expand Down
4 changes: 4 additions & 0 deletions apps/ledger-live-mobile/e2e/bridge/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,13 @@ const retryDelay = 500; // Initial retry delay in milliseconds
export function init() {
const wsPort = LaunchArguments.value()["wsPort"] || "8099";
const mock = LaunchArguments.value()["mock"];
const disable_broadcast = LaunchArguments.value()["disable_broadcast"];

log(`[E2E Bridge Client]: wsPort=${wsPort}, mock=${mock}`);

if (mock == "0") setEnv("MOCK", "");
setEnv("DISABLE_TRANSACTION_BROADCAST", disable_broadcast != "0");

if (ws) {
ws.close();
}
Expand Down
14 changes: 14 additions & 0 deletions apps/ledger-live-mobile/e2e/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,17 @@ export function getWebElementByTag(tag: string, index = 0) {
return web.element(by.web.tag(tag)).atIndex(index);
}

export async function IsIdVisible(id: string | RegExp) {
try {
await waitFor(element(by.id(id)))
.toBeVisible()
.withTimeout(1000);
return true;
} catch {
return false;
}
}

export async function tapById(id: string | RegExp, index = 0) {
return getElementById(id, index).tap();
}
Expand Down Expand Up @@ -180,6 +191,9 @@ export async function launchApp() {
detoxURLBlacklistRegex:
'\\(".*sdk.*.braze.*",".*.googleapis.com/.*",".*clients3.google.com.*"\\)',
mock: getEnv("MOCK") ? getEnv("MOCK") : "0",
disable_broadcast: getEnv("DISABLE_TRANSACTION_BROADCAST")
? getEnv("DISABLE_TRANSACTION_BROADCAST")
: "1",
},
languageAndLocale: {
language: "en-US",
Expand Down
43 changes: 43 additions & 0 deletions apps/ledger-live-mobile/e2e/models/send.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { Currency } from "@ledgerhq/live-common/e2e/enum/Currency";
import { Application } from "../page";
import { Transaction } from "@ledgerhq/live-common/e2e/models/Transaction";

export async function verifyAppValidationSendInfo(
app: Application,
transaction: Transaction,
amount: string,
) {
const currenciesForValidationAmount = [
Currency.sepETH,
Currency.DOT,
Currency.POL,
Currency.ALGO,
Currency.ADA,
Currency.DOGE,
Currency.SOL,
Currency.TRX,
Currency.XLM,
Currency.XRP,
Currency.ATOM,
Currency.BCH,
];

const currenciesForValidationRecipient = [Currency.sepETH, Currency.POL];
const currenciesForValidationSender = [Currency.ATOM];

const currency = transaction.accountToCredit.currency;
const addressRecipient = transaction.accountToCredit.address;
const addressSender = transaction.accountToDebit.address;

if (currenciesForValidationAmount.includes(currency)) {
await app.send.expectValidationAmount(amount);
}

if (currenciesForValidationRecipient.includes(currency)) {
await app.send.expectValidationAddress(addressRecipient);
}

if (currenciesForValidationSender.includes(currency)) {
await app.send.expectValidationAddress(addressSender);
}
}
15 changes: 14 additions & 1 deletion apps/ledger-live-mobile/e2e/page/accounts/account.page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,13 @@ export default class AccountPage {
accountAdvancedLogRow = () => getElementById("account-advanced-log-row");
accountDeleteRow = () => getElementById("account-settings-delete-row");
accountDeleteConfirm = () => getElementById("delete-account-confirmation-button");
operationHistorySectionId = (accountId: string) => `operations-history-${accountId}`;
operationHistorySection = "operations-history-";
operationHistorySectionRegexp = new RegExp(this.operationHistorySection + ".*");
operationHistorySectionId = (accountId: string) => this.operationHistorySection + accountId;
accountScreenScrollView = "account-screen-scrollView";
accountAdvancedLogsId = "account-advanced-logs";
receiveButton = () => getElementById("account-quick-action-button-Receive");
sendButton = () => getElementById("account-quick-action-button-Send");

@Step("Open account settings")
async openAccountSettings() {
Expand Down Expand Up @@ -41,6 +44,11 @@ export default class AccountPage {
await expect(getElementById(id)).toBeVisible();
}

@Step("Scroll to transaction history")
async scrollToTransactions() {
await scrollToId(this.operationHistorySectionRegexp, this.accountScreenScrollView);
}

@Step("Expect account balance to be visible")
async expectAccountBalanceVisible(accountId: string) {
await expect(this.accountGraph(accountId)).toBeVisible();
Expand All @@ -60,4 +68,9 @@ export default class AccountPage {
async tapReceive() {
await tapByElement(this.receiveButton());
}

@Step("Tap on send button")
async tapSend() {
await tapByElement(this.sendButton());
}
}
7 changes: 7 additions & 0 deletions apps/ledger-live-mobile/e2e/page/common.page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export default class CommonPage {
searchBarId = "common-search-field";
searchBar = () => getElementById(this.searchBarId);
successCloseButtonId = "success-close-button";
successViewDetailsButtonId = "success-view-details-button";
closeButton = () => getElementById("NavigationHeaderCloseButton");

accoundCardId = (id: string) => "account-card-" + id;
Expand Down Expand Up @@ -53,6 +54,12 @@ export default class CommonPage {
await tapById(this.successCloseButtonId);
}

@Step("Tap on view details")
async successViewDetails() {
await waitForElementById(this.successViewDetailsButtonId);
await tapById(this.successViewDetailsButtonId);
}

async selectAccount(accountId: string) {
const id = this.accoundCardId(accountId);
await waitForElementById(id);
Expand Down
4 changes: 2 additions & 2 deletions apps/ledger-live-mobile/e2e/page/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,10 @@ export class Application {
public transfertMenu = new TransfertMenuDrawer();
public walletTabNavigator = new WalletTabNavigatorPage();

constructor() {
constructor(userdata?: string) {
if (!getEnv("MOCK")) {
// Create a temporary userdata file for Speculos tests
const originalUserdata = "skip-onboarding";
const originalUserdata = userdata || "skip-onboarding";
this.userdataSpeculos = `temp-userdata-${Date.now()}`;
this.userdataPath = getUserdataPath(this.userdataSpeculos);
const originalFilePath = getUserdataPath(originalUserdata);
Expand Down
8 changes: 7 additions & 1 deletion apps/ledger-live-mobile/e2e/page/speculos.page.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import { expectValidAddressDevice } from "@ledgerhq/live-common/e2e/speculos";
import { expectValidAddressDevice, signSendTransaction } from "@ledgerhq/live-common/e2e/speculos";
import { Account } from "@ledgerhq/live-common/e2e/enum/Account";
import { Transaction } from "@ledgerhq/live-common/e2e/models/Transaction";

export default class SpeculosPage {
@Step("Verify receive address correctness on device")
async expectValidAddressDevice(account: Account, addressDisplayed: string) {
await expectValidAddressDevice(account, addressDisplayed);
}

@Step("Sign Send Transaction")
async signSendTransaction(tx: Transaction) {
await signSendTransaction(tx);
}
}
18 changes: 16 additions & 2 deletions apps/ledger-live-mobile/e2e/page/trade/operationDetails.page.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,34 @@
import { getElementById } from "../../helpers";
import { getElementById, scrollToId, waitForElementById } from "../../helpers";
import { expect } from "detox";

export default class OperationDetailsPage {
title = () => getElementById("operationDetails-title");
titleId = "operationDetails-title";
title = () => getElementById(this.titleId);
account = () => getElementById("operationDetails-account");
amount = () => getElementById("operationDetails-amount");
recipientId = "operationDetails-recipient0";

async isOpened() {
await expect(this.title()).toBeVisible();
}

@Step("Wait for operation details")
async waitForOperationDetails() {
await waitForElementById(this.titleId);
}

@Step("Check account details")
async checkAccount(account: string) {
await expect(this.account()).toHaveText(account);
}

async checkAmount(amount: string) {
await expect(this.amount()).toHaveText(amount);
}

@Step("Check recipient details")
async checkRecipient(recipient: string) {
await scrollToId(this.recipientId);
await expect(getElementById(this.recipientId)).toHaveText(recipient);
}
}
41 changes: 41 additions & 0 deletions apps/ledger-live-mobile/e2e/page/trade/send.page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,24 @@ import {
openDeeplink,
typeTextById,
tapByElement,
IsIdVisible,
} from "../../helpers";
import { expect } from "detox";

const baseLink = "send";

export default class SendPage {
summaryAmount = () => getElementById("send-summary-amount");
summaryRecipient = () => getElementById("send-summary-recipient");
validationAmountId = "send-validation-amount";
validationAddressId = "send-validation-address";
getStep1HeaderTitle = () => getElementById("send-header-step1-title");
recipientContinueButtonId = "recipient-continue-button";
recipientInputId = "recipient-input";
amountInputId = "amount-input";
amountContinueButton = () => getElementById("amount-continue-button");
summaryContinueButton = () => getElementById("summary-continue-button");
highFreeConfirmButtonID = "confirmation-modal-confirm-button";

async openViaDeeplink() {
await openDeeplink(baseLink);
Expand All @@ -42,6 +47,12 @@ export default class SendPage {
await tapById(this.recipientContinueButtonId);
}

@Step("Set recipient and continue")
async setRecipientAndContinue(address: string) {
await this.setRecipient(address);
await this.recipientContinue();
}

async setAmount(amount: string) {
const element = getElementById(this.amountInputId);
await element.replaceText(amount);
Expand All @@ -52,11 +63,41 @@ export default class SendPage {
await tapByElement(this.amountContinueButton());
}

@Step("Set amount and continue")
async setAmountAndContinue(amount: string) {
await this.setAmount(amount);
await this.amountContinue();
}

async summaryContinue() {
await tapByElement(this.summaryContinueButton());
}

@Step("Expect amount in summary")
async expectSummaryAmount(amount: string) {
await expect(this.summaryAmount()).toHaveText(amount);
}

@Step("Expect recipient in summary")
async expectSummaryRecepient(recipient: string) {
await expect(this.summaryRecipient()).toHaveText(recipient);
}

@Step("Dismiss high fee modal if visible")
async dismissHighFeeModal() {
if (await IsIdVisible(this.highFreeConfirmButtonID))
await tapById(this.highFreeConfirmButtonID);
}

@Step("Expect amount in device validation screen")
async expectValidationAmount(amount: string) {
await waitForElementById(this.validationAmountId);
await expect(getElementById(this.validationAmountId)).toHaveText(amount);
}

@Step("Expect address in device validation screen")
async expectValidationAddress(recipient: string) {
await waitForElementById(this.validationAddressId);
await expect(getElementById(this.validationAddressId)).toHaveText(recipient);
}
}
Loading

0 comments on commit e785a43

Please sign in to comment.