diff --git a/packages/e2e-tests/helpers/constants.js b/packages/e2e-tests/helpers/constants.js index daf725a62b..945694f106 100644 --- a/packages/e2e-tests/helpers/constants.js +++ b/packages/e2e-tests/helpers/constants.js @@ -70,6 +70,11 @@ export const WalletWordsSize = Object.freeze({ Shelley: 15, Daedalus: 24, }); +export const CardanoNetworks = Object.freeze({ + MN: 'mainnet', + PP: 'preprod', + PV: 'preview', +}); export const adaInLovelaces = 1000000; export const projectRootDir = path.resolve(__dirname, '..'); diff --git a/packages/e2e-tests/helpers/ledgerEmulatorController.js b/packages/e2e-tests/helpers/ledgerEmulatorController.js new file mode 100644 index 0000000000..d8e250d1c8 --- /dev/null +++ b/packages/e2e-tests/helpers/ledgerEmulatorController.js @@ -0,0 +1,75 @@ +import { sleep } from '../utils/utils'; + +const SPECULOS_ENDPOINT = 'http://localhost:5001'; + +class LedgerEmulatorControllerError extends Error {} + +export const CARDANO_IS_READY = 'Cardano is ready'; + +export class LedgerEmulatorController { + constructor(logger) { + this.logger = logger; + } + + async _click(button) { + this.logger.info(`LedgerEmulator::_click is called. Button: ${button}`); + await fetch(`${SPECULOS_ENDPOINT}/button/${button}`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: '{"action":"press-and-release"}', + }); + } + + async clickBoth() { + await this._click('both'); + } + + async clickLeft() { + await this._click('left'); + } + + async clickRight() { + await this._click('right'); + } + + /** + * The function reads a text from the current ledger screen + * @returns {string} + */ + async readScreen() { + this.logger.info(`LedgerEmulator::readScreen is called`); + try { + const eventsResponse = await fetch(`${SPECULOS_ENDPOINT}/events?currentscreenonly=true`); + if (!eventsResponse.ok) { + throw new LedgerEmulatorController('Not able to receive events for the current screen'); + } + const eventsObj = await eventsResponse.json(); + const eventsText = eventsObj.events.map(evt => evt.text).join(' '); + this.logger.info(`LedgerEmulator::readScreen The current screen text: "${eventsText}"`); + + return eventsText; + } catch (error) { + console.error(error); + throw new LedgerEmulatorController('Some error happen: ', error); + } + } + + async isReadyForAction(timeoutMilliSec, repeatPeriodMilliSec) { + this.logger.info(`LedgerEmulator::isReadyForSigning is called`); + const endTime = Date.now() + timeoutMilliSec; + while (Date.now() < endTime) { + const currentText = await this.readScreen(); + if (currentText !== CARDANO_IS_READY) { + this.logger.info(`LedgerEmulator::isReadyForSigning Ledger is ready.`); + return true; + } + this.logger.info( + `LedgerEmulator::isReadyForSigning Ledger is not ready. Waiting for ${repeatPeriodMilliSec} ms` + ); + await sleep(repeatPeriodMilliSec); + } + return false; + } +} diff --git a/packages/e2e-tests/helpers/windowManager.js b/packages/e2e-tests/helpers/windowManager.js index 8f4909d82f..d9f1aa15a9 100644 --- a/packages/e2e-tests/helpers/windowManager.js +++ b/packages/e2e-tests/helpers/windowManager.js @@ -8,6 +8,7 @@ export const popupConnectorWindowTitle = 'Yoroi Dapp Connector'; export const extensionTabName = 'Yoroi'; export const faqTabName = 'Yoroi - EMURGO'; export const trezorConnectTabName = 'Trezor'; +export const ledgerConnectTabName = 'Ledger Connect | Yoroi'; export const backgroungTabName = 'background'; export const serviceWorkersTabName = 'chrome://serviceworker-internals'; export const serviceWorkersLink = 'chrome://serviceworker-internals'; diff --git a/packages/e2e-tests/pages/addNewWallet.page.js b/packages/e2e-tests/pages/addNewWallet.page.js index 004fe0ee57..956762b5cc 100644 --- a/packages/e2e-tests/pages/addNewWallet.page.js +++ b/packages/e2e-tests/pages/addNewWallet.page.js @@ -1,5 +1,6 @@ import { halfMinute, fiveSeconds, quarterSecond } from '../helpers/timeConstants.js'; import WalletCommonBase from './walletCommonBase.page.js'; +import { CardanoNetworks } from '../helpers/constants.js'; class AddNewWallet extends WalletCommonBase { // locators @@ -15,21 +16,41 @@ class AddNewWallet extends WalletCommonBase { locator: 'connectHardwareWalletButton', method: 'id', }; - // ::start trezor connect section - cardanoNetworkButtonLocator = { - locator: '.PickCurrencyOptionDialog_cardano', - method: 'css', + mainnetNetworkButtonLocator = { + locator: 'connectHWWallet-selectMainnetNetwork-button', + method: 'id', + }; + preprodNetworkButtonLocator = { + locator: 'connectHWWallet-selectPreprodNetwork-button', + method: 'id', + }; + previewNetworkButtonLocator = { + locator: 'connectHWWallet-selectPreviewNetwork-button', + method: 'id', }; + // ::start HW connect section trezorHWButtonLocator = { locator: '.WalletConnectHWOptionDialog_connectTrezor', method: 'css', }; + ledgerHWButtonLocator = { + locator: '.WalletConnectHWOptionDialog_connectLedger', + method: 'css', + }; checkDialogLocator = { locator: '.CheckDialog', method: 'css', }; nextButtonLocator = { - locator: 'primaryButton', + locator: 'dialog-next-button', + method: 'id', + }; + connectButtonLocator = { + locator: 'dialog-connect-button', + method: 'id', + }; + saveButtonLocator = { + locator: 'dialog-save-button', method: 'id', }; connectDialogLocator = { @@ -40,7 +61,7 @@ class AddNewWallet extends WalletCommonBase { locator: '//input[starts-with(@id, "walletName-")]', method: 'xpath', }; - // ::end trezor connect section + // ::end HW connect section // functions async isDisplayed() { @@ -82,19 +103,37 @@ class AddNewWallet extends WalletCommonBase { await this.click(this.connectHwButtonLocator); }); } - // ::start trezor connect section - async selectCardanoNetwork() { - this.logger.info(`AddNewWallet::selectCardanoNetwork is called`); - await this.waitPresentedAndAct(this.cardanoNetworkButtonLocator, async () => { - await this.click(this.cardanoNetworkButtonLocator); + async selectCardanoNetwork(network = CardanoNetworks.MN) { + this.logger.info(`AddNewWallet::selectCardanoNetwork is called. Network to select: ${network}`); + let networkButtonLocator; + switch (network) { + case CardanoNetworks.PP: + networkButtonLocator = this.preprodNetworkButtonLocator; + break; + case CardanoNetworks.PV: + networkButtonLocator = this.previewNetworkButtonLocator; + break; + default: + networkButtonLocator = this.mainnetNetworkButtonLocator; + break; + } + await this.waitPresentedAndAct(networkButtonLocator, async () => { + await this.click(networkButtonLocator); }); } + // ::start HW connect section async selectTrezorHW() { this.logger.info(`AddNewWallet::selectTrezorHW is called`); await this.waitPresentedAndAct(this.trezorHWButtonLocator, async () => { await this.click(this.trezorHWButtonLocator); }); } + async selectLedgerHW() { + this.logger.info(`AddNewWallet::selectLedgerHW is called`); + await this.waitPresentedAndAct(this.ledgerHWButtonLocator, async () => { + await this.click(this.ledgerHWButtonLocator); + }); + } async confirmChecking() { this.logger.info(`AddNewWallet::confirmChecking is called`); await this.customWaitIsPresented(this.checkDialogLocator, fiveSeconds, quarterSecond); @@ -107,13 +146,13 @@ class AddNewWallet extends WalletCommonBase { } }); } - async connectTrezor() { - this.logger.info(`AddNewWallet::connectTrezor is called`); + async connectHardwareWallet() { + this.logger.info(`AddNewWallet::connectHardwareWallet is called`); await this.customWaitIsPresented(this.connectDialogLocator, fiveSeconds, quarterSecond); - await this.waitPresentedAndAct(this.nextButtonLocator, async () => { - const btnEnabled = await this.buttonIsEnabled(this.nextButtonLocator); + await this.waitPresentedAndAct(this.connectButtonLocator, async () => { + const btnEnabled = await this.buttonIsEnabled(this.connectButtonLocator); if (btnEnabled) { - await this.click(this.nextButtonLocator); + await this.click(this.connectButtonLocator); } else { throw new Error(`The button ${this.nextButtonLocator.locator} is disabled`); } @@ -121,13 +160,12 @@ class AddNewWallet extends WalletCommonBase { } async enterHWWalletName(walletName) { await this.customWaitIsPresented(this.hwWalletNameInputLocator, fiveSeconds, quarterSecond); - // the label "Emulator" is used in TrezorEmulatorController.emulatorSetup() - await this.clearInputUpdatingForm(this.hwWalletNameInputLocator, 'Emulator'.length); + await this.clearInputAll(this.hwWalletNameInputLocator); await this.input(this.hwWalletNameInputLocator, walletName); } async saveHWInfo() { - await this.waitPresentedAndAct(this.nextButtonLocator, async () => { - await this.click(this.nextButtonLocator); + await this.waitPresentedAndAct(this.saveButtonLocator, async () => { + await this.click(this.saveButtonLocator); }); } // ::end trezor connect section diff --git a/packages/e2e-tests/pages/ledgerConnect.page.js b/packages/e2e-tests/pages/ledgerConnect.page.js new file mode 100644 index 0000000000..d0b3169b30 --- /dev/null +++ b/packages/e2e-tests/pages/ledgerConnect.page.js @@ -0,0 +1,31 @@ +import { defaultWaitTimeout, halfSecond } from '../helpers/timeConstants.js'; +import BasePage from './basepage.js'; + +class LedgerConnect extends BasePage { + // locators + nanoSButtonLocator = { + locator: 'ledgerConnect-selectNanoS-button', + method: 'id', + }; + nanoXButtonLocator = { + locator: 'ledgerConnect-selectNanoX-button', + method: 'id', + }; + // functions + async selectNanoS() { + this.logger.info(`LedgerConnect::selectNanoS is called`); + await this.waitPresentedAndAct( + this.nanoSButtonLocator, + async () => await this.click(this.nanoSButtonLocator) + ); + } + async selectNanoX() { + this.logger.info(`LedgerConnect::selectNanoS is called`); + await this.waitPresentedAndAct( + this.nanoXButtonLocator, + async () => await this.click(this.nanoXButtonLocator) + ); + } +} + +export default LedgerConnect; diff --git a/packages/e2e-tests/test/hw/connectLedgerWallet.test.js b/packages/e2e-tests/test/hw/connectLedgerWallet.test.js new file mode 100644 index 0000000000..5ff1c95b5a --- /dev/null +++ b/packages/e2e-tests/test/hw/connectLedgerWallet.test.js @@ -0,0 +1,99 @@ +import { expect } from 'chai'; +import { getDriver } from '../../utils/driverBootstrap.js'; +import { customAfterEach } from '../../utils/customHooks.js'; +import { getTestLogger } from '../../utils/utils.js'; +import { + CARDANO_IS_READY, + LedgerEmulatorController, +} from '../../helpers/ledgerEmulatorController.js'; +import { + WindowManager, + extensionTabName, + ledgerConnectTabName, +} from '../../helpers/windowManager.js'; +import BasePage from '../../pages/basepage.js'; +import InitialStepsPage from '../../pages/initialSteps.page.js'; +import AddNewWallet from '../../pages/addNewWallet.page.js'; +import LedgerConnect from '../../pages/ledgerConnect.page.js'; +import TransactionsSubTab from '../../pages/wallet/walletTab/walletTransactions.page.js'; +import { oneMinute, quarterSecond, threeSeconds } from '../../helpers/timeConstants.js'; + +describe('Connect Ledger HW wallet', function () { + this.timeout(2 * oneMinute); + let webdriver = null; + let logger = null; + let ledgerLogger = null; + let ledgerController = null; + let wmLogger = null; + let windowManager = null; + + before(function (done) { + webdriver = getDriver(); + logger = getTestLogger(this.test.parent.title); + ledgerLogger = getTestLogger('ledger', this.test.parent.title); + ledgerController = new LedgerEmulatorController(ledgerLogger); + wmLogger = getTestLogger('windowManager', this.test.parent.title); + windowManager = new WindowManager(webdriver, wmLogger); + const basePage = new BasePage(webdriver, logger); + basePage.goToExtension(); + done(); + }); + + it('Initials steps', async function () { + await windowManager.init(); + const initialStepsPage = new InitialStepsPage(webdriver, logger); + await initialStepsPage.skipInitialSteps(); + }); + + it('Ledger is ready', async function () { + const ledgerState = await ledgerController.readScreen(); + expect(ledgerState, 'Cardano app is not ready').to.equal(CARDANO_IS_READY); + }); + + it('Selecting Connect HW wallet', async function () { + const addNewWalletPage = new AddNewWallet(webdriver, logger); + await addNewWalletPage.selectConnectHW(); + await addNewWalletPage.selectCardanoNetwork(); + await addNewWalletPage.selectLedgerHW(); + await addNewWalletPage.confirmChecking(); + await addNewWalletPage.connectHardwareWallet(); + }); + + it('Approve connection', async function () { + await windowManager.findNewWindowAndSwitchTo(ledgerConnectTabName); + const ledgerConnectPage = new LedgerConnect(webdriver, logger); + await ledgerConnectPage.selectNanoS(); + const ledgerIsReady = await ledgerController.isReadyForAction(threeSeconds, quarterSecond); + expect(ledgerIsReady, `Ledger isn't ready after ${threeSeconds / 1000} seconds`).to.be.true; + await ledgerController.clickBoth(); + await windowManager.waitForClosingAndSwitchTo(ledgerConnectTabName, extensionTabName); + }); + + it('Enter wallet details', async function () { + const addNewWalletPage = new AddNewWallet(webdriver, logger); + await addNewWalletPage.enterHWWalletName('Speculos'); + await addNewWalletPage.saveHWInfo(); + }); + + it('Check new wallet', async function () { + const transactionsPage = new TransactionsSubTab(webdriver, logger); + await transactionsPage.waitPrepareWalletBannerIsClosed(); + await transactionsPage.closeUpdatesModalWindow(); + const txPageIsDisplayed = await transactionsPage.isDisplayed(); + expect(txPageIsDisplayed, 'The transactions page is not displayed').to.be.true; + const walletInfo = await transactionsPage.getSelectedWalletInfo(); + expect(walletInfo.balance, 'The wallet balance is different').to.equal(0); + expect(walletInfo.name, `The wallet name should be Speculos.`).to.equal('Speculos'); + expect(walletInfo.plate, `The wallet plate should be PAXX-9560`).to.equal('PAXX-9560'); + }); + + afterEach(function (done) { + customAfterEach(this, webdriver, logger); + done(); + }); + + after(async function () { + const basePage = new BasePage(webdriver, logger); + basePage.closeBrowser(); + }); +}); diff --git a/packages/e2e-tests/test/hw/connectTrezorWallet.test.js b/packages/e2e-tests/test/hw/connectTrezorWallet.test.js index f0a8d53d77..869b77cecc 100644 --- a/packages/e2e-tests/test/hw/connectTrezorWallet.test.js +++ b/packages/e2e-tests/test/hw/connectTrezorWallet.test.js @@ -54,7 +54,7 @@ describe('Connect Trezor HW wallet', function () { await addNewWalletPage.selectCardanoNetwork(); await addNewWalletPage.selectTrezorHW(); await addNewWalletPage.confirmChecking(); - await addNewWalletPage.connectTrezor(); + await addNewWalletPage.connectHardwareWallet(); }); it('Approve connection', async function () { diff --git a/packages/yoroi-extension/app/api/ada/lib/storage/database/prepackaged/networks.js b/packages/yoroi-extension/app/api/ada/lib/storage/database/prepackaged/networks.js index 66d911fa7d..53a458b6e2 100644 --- a/packages/yoroi-extension/app/api/ada/lib/storage/database/prepackaged/networks.js +++ b/packages/yoroi-extension/app/api/ada/lib/storage/database/prepackaged/networks.js @@ -15,9 +15,7 @@ export const networks = Object.freeze({ NetworkName: 'Cardano Mainnet', NetworkFeatureName: 'mainnet', Backend: { - BackendService: environment.isTest() - ? 'http://localhost:21000' - : 'https://api.yoroiwallet.com', + BackendService: 'https://api.yoroiwallet.com', TokenInfoService: 'https://cdn.yoroiwallet.com', BackendServiceZero: 'https://zero.yoroiwallet.com', @@ -58,9 +56,7 @@ export const networks = Object.freeze({ NetworkName: 'Cardano Preprod Testnet', NetworkFeatureName: 'preprod', Backend: { - BackendService: environment.isTest() - ? 'http://localhost:21000' - : 'https://preprod-backend.yoroiwallet.com', + BackendService: 'https://preprod-backend.yoroiwallet.com', TokenInfoService: 'https://stage-cdn.yoroiwallet.com', BackendServiceZero: 'https://yoroi-backend-zero-preprod.emurgornd.com', @@ -100,9 +96,7 @@ export const networks = Object.freeze({ NetworkName: 'Cardano Preview Testnet', NetworkFeatureName: 'preview', Backend: { - BackendService: environment.isTest() - ? 'http://localhost:21000' - : 'https://preview-backend.emurgornd.com', + BackendService: 'https://preview-backend.emurgornd.com', TokenInfoService: 'https://stage-cdn.yoroiwallet.com', BackendServiceZero: 'https://yoroi-backend-zero-preview.emurgornd.com', }, diff --git a/packages/yoroi-extension/app/components/widgets/options/OptionBlock.js b/packages/yoroi-extension/app/components/widgets/options/OptionBlock.js index 75dc3ce1ca..538ef1fc1f 100644 --- a/packages/yoroi-extension/app/components/widgets/options/OptionBlock.js +++ b/packages/yoroi-extension/app/components/widgets/options/OptionBlock.js @@ -56,12 +56,22 @@ export default class OptionBlock extends Component { const learnMoreButtonClasses = classnames([styles.learnMoreButton, this.state.showLearnMore && styles.arrowUp]); + const getNetworkNameForId = () => { + const nameArr = title.split(' '); + return nameArr.length === 1 ? 'Mainnet' : nameArr[1] + }; + return (
  • {/* Submit button block */} -